mirror of
https://github.com/pgvector/pgvector.git
synced 2026-07-22 03:57:34 +08:00
Compare commits
544 Commits
v0.4.4
...
sparsevec-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5425dd2357 | ||
|
|
7ca7a64dbb | ||
|
|
aaa2d644ce | ||
|
|
253acbccf4 | ||
|
|
060d299e4b | ||
|
|
d7354a86a8 | ||
|
|
daba71694b | ||
|
|
506dd2b44a | ||
|
|
bcf41f5f66 | ||
|
|
abac7a3f77 | ||
|
|
32a502c838 | ||
|
|
1134e52762 | ||
|
|
3ef632e042 | ||
|
|
e2a527ffda | ||
|
|
835f010257 | ||
|
|
d6044dd423 | ||
|
|
c75634a03c | ||
|
|
ab7b2ed39e | ||
|
|
499b6bc2c9 | ||
|
|
741c6a8a7b | ||
|
|
1c82bdd932 | ||
|
|
94a444f029 | ||
|
|
7ee9074a9c | ||
|
|
2f2f3631a8 | ||
|
|
4b22851bbd | ||
|
|
3acdbf99e8 | ||
|
|
11ea3d8483 | ||
|
|
2c48e3edc2 | ||
|
|
7d63bb4b98 | ||
|
|
de410a2915 | ||
|
|
64aa99aa31 | ||
|
|
997fa167da | ||
|
|
67eec4edbf | ||
|
|
396090d8e0 | ||
|
|
ba18942fcf | ||
|
|
8e59455c3c | ||
|
|
bd50e3067d | ||
|
|
af9d4ad659 | ||
|
|
08abb63cbe | ||
|
|
06b8556a49 | ||
|
|
3f674c9994 | ||
|
|
31e41b3ba9 | ||
|
|
903a925662 | ||
|
|
96ff19be44 | ||
|
|
6c969bebad | ||
|
|
b64a1482d9 | ||
|
|
a5f2d70bc2 | ||
|
|
f3fcb5e005 | ||
|
|
3a6e0afb9c | ||
|
|
183d50bdbd | ||
|
|
bd776fee68 | ||
|
|
d30b113e4b | ||
|
|
fd3200f718 | ||
|
|
02c815d876 | ||
|
|
4b2a7cc49d | ||
|
|
da0ff998e9 | ||
|
|
cb36e24289 | ||
|
|
b1d0d4c7a3 | ||
|
|
1dc6514b66 | ||
|
|
6c53f7ca02 | ||
|
|
0d35a14198 | ||
|
|
3ea2ce89be | ||
|
|
62350b1589 | ||
|
|
dd57309281 | ||
|
|
c6ddf62a29 | ||
|
|
801be04d8b | ||
|
|
587e9ba97c | ||
|
|
d57047a935 | ||
|
|
f1db1f17e3 | ||
|
|
2f48c0fac4 | ||
|
|
84a8aa8176 | ||
|
|
f64ebbef50 | ||
|
|
ac8156509b | ||
|
|
82bf69b479 | ||
|
|
4be2f57916 | ||
|
|
91e3d2905f | ||
|
|
fe2406564f | ||
|
|
fa52511eaa | ||
|
|
447ef4d27a | ||
|
|
b36cd22ccc | ||
|
|
b447ae4989 | ||
|
|
efed873a3e | ||
|
|
14b278dec9 | ||
|
|
133a728e48 | ||
|
|
ca10cbaa7d | ||
|
|
eb29019a14 | ||
|
|
334c386a45 | ||
|
|
1cbd204f52 | ||
|
|
5ba62fca84 | ||
|
|
22cb2a3fe7 | ||
|
|
72b144906a | ||
|
|
a618c1bc78 | ||
|
|
f43cd0ed98 | ||
|
|
51df640961 | ||
|
|
2716a223a6 | ||
|
|
3697043898 | ||
|
|
c22740962c | ||
|
|
a55ecf3281 | ||
|
|
fecdd5794e | ||
|
|
281d4fcf60 | ||
|
|
5b43aaad5a | ||
|
|
797ce8034c | ||
|
|
cad9e22d9a | ||
|
|
a7d43904f7 | ||
|
|
ded649891b | ||
|
|
2d092016fc | ||
|
|
86b31fdf96 | ||
|
|
5023269f0d | ||
|
|
642ee1f423 | ||
|
|
270dd8189a | ||
|
|
ba2776850b | ||
|
|
0cc883b944 | ||
|
|
e8e69278eb | ||
|
|
a4893d9d5f | ||
|
|
10416b841f | ||
|
|
d0f13d4e7f | ||
|
|
705e71015f | ||
|
|
121f53b8ff | ||
|
|
8765e79ec2 | ||
|
|
7fff6cd138 | ||
|
|
571697fee7 | ||
|
|
f7eda7bd20 | ||
|
|
2260e13315 | ||
|
|
90e0a14bda | ||
|
|
c816b5d0d1 | ||
|
|
b7b11cd8d5 | ||
|
|
54c560c5cb | ||
|
|
8a6c52f649 | ||
|
|
c8be3a369b | ||
|
|
e5d1a6bdbb | ||
|
|
f31d708c2b | ||
|
|
a1b1c99ff7 | ||
|
|
3ace98add6 | ||
|
|
083008c21e | ||
|
|
a1e526ef82 | ||
|
|
8ffb3718a4 | ||
|
|
2d0f162bd7 | ||
|
|
4c6928bd3c | ||
|
|
6fd05dd6f6 | ||
|
|
70106f5413 | ||
|
|
44b90be452 | ||
|
|
31572a7b28 | ||
|
|
2427290ea9 | ||
|
|
ca71ef7a51 | ||
|
|
8bd01ff006 | ||
|
|
cdb1c9a6d3 | ||
|
|
bf34ceef7c | ||
|
|
61b1566ea2 | ||
|
|
91acc3c178 | ||
|
|
885dd5b665 | ||
|
|
2a7b38bf1f | ||
|
|
4bd4a0996b | ||
|
|
490522b883 | ||
|
|
a1a38156d7 | ||
|
|
042ddfdc8a | ||
|
|
56870ce04d | ||
|
|
4ab77f3d24 | ||
|
|
cc9e6a6778 | ||
|
|
8f1b669c4f | ||
|
|
1ff9ab5133 | ||
|
|
4894dc5da1 | ||
|
|
7390f31261 | ||
|
|
b7304a3a4a | ||
|
|
018ceb7a46 | ||
|
|
0b2be00622 | ||
|
|
0ce497a1b1 | ||
|
|
c7d60346d8 | ||
|
|
597bfdc76b | ||
|
|
cbf3eb4fa5 | ||
|
|
cacd389f6d | ||
|
|
423cc2b06c | ||
|
|
85c4ef6a14 | ||
|
|
c6160a783a | ||
|
|
1881b857f9 | ||
|
|
51bde5fb22 | ||
|
|
10e65ce349 | ||
|
|
61279f5a59 | ||
|
|
72b3889e26 | ||
|
|
bb21b2decf | ||
|
|
8a65c0e831 | ||
|
|
7d75d423e4 | ||
|
|
6cad1f5de0 | ||
|
|
67eeade63c | ||
|
|
108fb09d7b | ||
|
|
65d060ac86 | ||
|
|
62ee33bb92 | ||
|
|
520e274dde | ||
|
|
9e680884bd | ||
|
|
19a0e1b341 | ||
|
|
c7fe1571ee | ||
|
|
cb4c770df2 | ||
|
|
85fdecd79b | ||
|
|
6132428914 | ||
|
|
81d13bd40f | ||
|
|
8ee37b60a0 | ||
|
|
9b73b3d1a6 | ||
|
|
cae630784b | ||
|
|
d87bcd2deb | ||
|
|
736576220a | ||
|
|
a508b120c1 | ||
|
|
9a782d29f8 | ||
|
|
1e422cd62b | ||
|
|
569c69580a | ||
|
|
59509c3a17 | ||
|
|
61738846af | ||
|
|
e8c3bf0cef | ||
|
|
50d1aed3d8 | ||
|
|
66e14d2434 | ||
|
|
42cd4c6833 | ||
|
|
dcbe0b6f0d | ||
|
|
f61d4087b5 | ||
|
|
57554e5b46 | ||
|
|
6738fa0bd7 | ||
|
|
9ab10aa674 | ||
|
|
ec41dfa1d7 | ||
|
|
43e0b3d9d4 | ||
|
|
2bff7ccaa2 | ||
|
|
e88a425c9b | ||
|
|
921427ee03 | ||
|
|
a59aa02dd9 | ||
|
|
2fef497b7e | ||
|
|
0e19a984fb | ||
|
|
a156f6c7ae | ||
|
|
c653ac524f | ||
|
|
bf0d56e78e | ||
|
|
4d6739a7af | ||
|
|
ff744214d0 | ||
|
|
7ca9298163 | ||
|
|
ff3bffd9a8 | ||
|
|
014753eb9c | ||
|
|
6763661d3d | ||
|
|
d287921d15 | ||
|
|
5b12ae8225 | ||
|
|
4549e8aeb1 | ||
|
|
3263b350f5 | ||
|
|
dfee5d4045 | ||
|
|
69a2ce0d43 | ||
|
|
c5e8c46b80 | ||
|
|
94f7304ccd | ||
|
|
d078db3d25 | ||
|
|
fbb904ae2f | ||
|
|
3cf6f62900 | ||
|
|
2a69e22ca4 | ||
|
|
84e073888c | ||
|
|
81a62d55d1 | ||
|
|
3f3463bde5 | ||
|
|
a01a72d812 | ||
|
|
0c2fc18a80 | ||
|
|
e860042d3c | ||
|
|
5986862bd2 | ||
|
|
5d24f5d09a | ||
|
|
7c43b0d8ee | ||
|
|
7be40036f4 | ||
|
|
9b5a1a69db | ||
|
|
04b96506f5 | ||
|
|
35cd7b63cb | ||
|
|
b5416d6f10 | ||
|
|
f361bf2704 | ||
|
|
3d8c1921aa | ||
|
|
154207bc17 | ||
|
|
8e507f3bf5 | ||
|
|
e115773a55 | ||
|
|
9333bef046 | ||
|
|
4851e47d9f | ||
|
|
12aecfb4f5 | ||
|
|
800697fb14 | ||
|
|
de1f2b09dd | ||
|
|
bcccb7f5a5 | ||
|
|
bec3d30d68 | ||
|
|
588de60445 | ||
|
|
c599f92b52 | ||
|
|
2a17b335da | ||
|
|
6ede6ac301 | ||
|
|
3f49b95f01 | ||
|
|
ef1bea7163 | ||
|
|
e630efd195 | ||
|
|
b5b912906b | ||
|
|
4b5db94307 | ||
|
|
65e70326b8 | ||
|
|
71641ed84e | ||
|
|
f3dba25036 | ||
|
|
5588ba6410 | ||
|
|
ec9fac5456 | ||
|
|
8085d3e538 | ||
|
|
cae162ffc6 | ||
|
|
62482e3760 | ||
|
|
c81302b835 | ||
|
|
a713e2acaa | ||
|
|
6e1312ddbe | ||
|
|
4ef5bca275 | ||
|
|
1ecf6ada76 | ||
|
|
564a3d45fc | ||
|
|
8d7abb6590 | ||
|
|
b247b688a8 | ||
|
|
9672446a4c | ||
|
|
334614b7f7 | ||
|
|
643eacd9dc | ||
|
|
bca50a03fa | ||
|
|
d87833cacc | ||
|
|
4c01073ac1 | ||
|
|
6fed8f1e78 | ||
|
|
611f5b1047 | ||
|
|
e367155732 | ||
|
|
466c556b1a | ||
|
|
75e09265d6 | ||
|
|
77c5070fb6 | ||
|
|
1006fdf3f2 | ||
|
|
4209c9b3af | ||
|
|
ec0bb4e4ff | ||
|
|
b164833933 | ||
|
|
30fb4dd602 | ||
|
|
d032726976 | ||
|
|
8fa9001474 | ||
|
|
3431acef94 | ||
|
|
41bdf24cb7 | ||
|
|
3469a0e74c | ||
|
|
0fe43ca675 | ||
|
|
bbbe1db72b | ||
|
|
bab5fea9e7 | ||
|
|
b1f9519689 | ||
|
|
4571fccc60 | ||
|
|
db747e5aa0 | ||
|
|
2179414c05 | ||
|
|
8426ee61d2 | ||
|
|
c98c4e13aa | ||
|
|
04312f6638 | ||
|
|
72ea3c1210 | ||
|
|
b0801b8833 | ||
|
|
d05d6ee83d | ||
|
|
4022bb66a0 | ||
|
|
034d4acaea | ||
|
|
01f58e470a | ||
|
|
dbef8d1ad1 | ||
|
|
5c005cf57c | ||
|
|
5665a11a05 | ||
|
|
6b2e215447 | ||
|
|
0d86191eaf | ||
|
|
cf9f7aeea9 | ||
|
|
0b0e542ce6 | ||
|
|
a4590d2d9d | ||
|
|
9ebec1529b | ||
|
|
77ff4c18f0 | ||
|
|
88dabaa41c | ||
|
|
1809ffa52b | ||
|
|
024f283ee8 | ||
|
|
da3b2fab46 | ||
|
|
884026a23c | ||
|
|
4d352e6c30 | ||
|
|
a8e257e1f1 | ||
|
|
3913432303 | ||
|
|
baeebec244 | ||
|
|
d578a4cccc | ||
|
|
1e18e19496 | ||
|
|
39f973dab2 | ||
|
|
453fa15f28 | ||
|
|
e50a79108f | ||
|
|
cfaa2ecd7f | ||
|
|
bb1e5ed98f | ||
|
|
8801832946 | ||
|
|
552c64d492 | ||
|
|
86c29b3bf0 | ||
|
|
e406b7f5ea | ||
|
|
6d88a9e1d2 | ||
|
|
1e851c12c0 | ||
|
|
8ed3cc5f0b | ||
|
|
bace0891bd | ||
|
|
4600979504 | ||
|
|
69addf05d0 | ||
|
|
4a5ae8a8df | ||
|
|
ce7da66ca2 | ||
|
|
c6be1c5e13 | ||
|
|
9b3d1a32ff | ||
|
|
8420734350 | ||
|
|
8b03267267 | ||
|
|
641ddf5413 | ||
|
|
782a1051e3 | ||
|
|
ca2be5be6e | ||
|
|
0e1de45463 | ||
|
|
3f3b3ca8e3 | ||
|
|
d4fe67e8ee | ||
|
|
02f4e0ec8b | ||
|
|
1301706d30 | ||
|
|
0d58683011 | ||
|
|
90a042e5aa | ||
|
|
bbd57dfebf | ||
|
|
40a013a622 | ||
|
|
ef1209eaf4 | ||
|
|
687263ccd4 | ||
|
|
a62c045c93 | ||
|
|
651e4343c1 | ||
|
|
2ca2ef94e6 | ||
|
|
6ba95691d3 | ||
|
|
e9de248e4f | ||
|
|
fe79d435c0 | ||
|
|
b34525fbc2 | ||
|
|
206c42e170 | ||
|
|
4f8d824280 | ||
|
|
ca847d02b0 | ||
|
|
ca726052ae | ||
|
|
2b25127f3d | ||
|
|
fecb8c06c9 | ||
|
|
a03f6ae4bc | ||
|
|
ed20d98777 | ||
|
|
b72a22b3c0 | ||
|
|
8c5c0f791e | ||
|
|
f9f5ae61df | ||
|
|
2b138d6cb5 | ||
|
|
e8f36aee92 | ||
|
|
508a8a9ac7 | ||
|
|
d0b0298cda | ||
|
|
c3bafc76e8 | ||
|
|
86a062e504 | ||
|
|
b421e76f29 | ||
|
|
ef480ff0b9 | ||
|
|
e1d6654063 | ||
|
|
c5b2f3ac8b | ||
|
|
0f238b1fa9 | ||
|
|
6fc7d06313 | ||
|
|
304e262a41 | ||
|
|
23cb79c1dc | ||
|
|
dc8d467ba5 | ||
|
|
29a0a00731 | ||
|
|
eda07ccf7c | ||
|
|
c694dbb776 | ||
|
|
3a857ef9f9 | ||
|
|
b60e2483a9 | ||
|
|
4295ee3b3a | ||
|
|
8b1ea42d42 | ||
|
|
3c291fea41 | ||
|
|
3d568ef6a1 | ||
|
|
7db3b08ef4 | ||
|
|
4196c88dbd | ||
|
|
3e7ee6ea04 | ||
|
|
5616c1d7a7 | ||
|
|
0778a507de | ||
|
|
aabbeac30c | ||
|
|
746dce72ef | ||
|
|
098813c483 | ||
|
|
9d689afd7e | ||
|
|
03930e15a4 | ||
|
|
8a807c01f6 | ||
|
|
85ebb7714e | ||
|
|
34e99593ab | ||
|
|
fc45231ef0 | ||
|
|
0191a90f9f | ||
|
|
82e83bd3b1 | ||
|
|
d8a14c658a | ||
|
|
ae5de26893 | ||
|
|
daef83a112 | ||
|
|
c9d82c6bdc | ||
|
|
573a336f53 | ||
|
|
e7a913f361 | ||
|
|
33438c3cf9 | ||
|
|
8ec1821f1e | ||
|
|
b288de719d | ||
|
|
c13f713f29 | ||
|
|
6d15435003 | ||
|
|
12d15a9c41 | ||
|
|
c64288377b | ||
|
|
451e36cee7 | ||
|
|
3ff2e34d7f | ||
|
|
94a684c9e9 | ||
|
|
1a0d7bccc7 | ||
|
|
ded8bda72a | ||
|
|
9b967d592f | ||
|
|
ed513e62c1 | ||
|
|
27ccb5fa7a | ||
|
|
92e25e7733 | ||
|
|
a18bea24b8 | ||
|
|
483173460b | ||
|
|
da8a914106 | ||
|
|
4d6da72b08 | ||
|
|
443c7a5dab | ||
|
|
9287fe8bcc | ||
|
|
309fa94c05 | ||
|
|
6f15dd266c | ||
|
|
df68eb4570 | ||
|
|
7c0d94c99c | ||
|
|
d63d430af8 | ||
|
|
5d62e4d080 | ||
|
|
dab8f25d1c | ||
|
|
4b887a98ae | ||
|
|
d253bafee6 | ||
|
|
600ca5a797 | ||
|
|
c17d51588a | ||
|
|
51d292c93d | ||
|
|
19a6c81367 | ||
|
|
6f212d7cc1 | ||
|
|
25ecabf1ea | ||
|
|
03457f41ba | ||
|
|
e45c84fd46 | ||
|
|
37b49b0a37 | ||
|
|
4df01af8b6 | ||
|
|
237a6df51f | ||
|
|
bcc1366d86 | ||
|
|
047877f495 | ||
|
|
e36cf20bce | ||
|
|
47e5a86b63 | ||
|
|
f210791846 | ||
|
|
280ec74cbd | ||
|
|
67e6b9ee6d | ||
|
|
0160f8f2f1 | ||
|
|
b710dc68a0 | ||
|
|
8a05debda1 | ||
|
|
1a81b21029 | ||
|
|
cf1f151cff | ||
|
|
3950bc3dc6 | ||
|
|
f4c28b1c06 | ||
|
|
d13eb8563e | ||
|
|
f6fc033622 | ||
|
|
518a35784d | ||
|
|
c3394ace03 | ||
|
|
98ba795d83 | ||
|
|
6e8746277e | ||
|
|
ad8df27fbb | ||
|
|
b77451f19e | ||
|
|
f1d3aa2ba8 | ||
|
|
a722df9cac | ||
|
|
a0c7f618ff | ||
|
|
f9d9d64a4e | ||
|
|
1fd9794d19 | ||
|
|
13ecd271ba | ||
|
|
fd3c3ce83d | ||
|
|
5796f38ad2 | ||
|
|
1d47d7218d | ||
|
|
42007b41ea | ||
|
|
21f56374b5 | ||
|
|
890f189495 | ||
|
|
ed1bc3e856 | ||
|
|
08e7209810 | ||
|
|
cd4ac17f9f | ||
|
|
b6a822918f | ||
|
|
21d5d7e934 | ||
|
|
ea47342870 | ||
|
|
22e9be6528 | ||
|
|
b56971febe | ||
|
|
de6502ab6e | ||
|
|
7aaba14440 | ||
|
|
ee0bf10d7d | ||
|
|
88094fc39f | ||
|
|
209394faab | ||
|
|
673aca97fc | ||
|
|
0e2dc0e6d4 | ||
|
|
3c681b94fd |
64
.github/workflows/build.yml
vendored
64
.github/workflows/build.yml
vendored
@@ -8,6 +8,8 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- postgres: 17
|
||||
os: ubuntu-22.04
|
||||
- postgres: 16
|
||||
os: ubuntu-22.04
|
||||
- postgres: 15
|
||||
@@ -18,17 +20,15 @@ jobs:
|
||||
os: ubuntu-20.04
|
||||
- postgres: 12
|
||||
os: ubuntu-20.04
|
||||
- postgres: 11
|
||||
os: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ankane/setup-postgres@v1
|
||||
with:
|
||||
postgres-version: ${{ matrix.postgres }}
|
||||
dev-files: true
|
||||
- run: make
|
||||
env:
|
||||
PG_CFLAGS: -Wall -Wextra -Werror -Wno-unused-parameter -Wno-sign-compare
|
||||
PG_CFLAGS: -DUSE_ASSERT_CHECKING -Wall -Wextra -Werror -Wno-unused-parameter -Wno-sign-compare
|
||||
- run: |
|
||||
export PG_CONFIG=`which pg_config`
|
||||
sudo --preserve-env=PG_CONFIG make install
|
||||
@@ -40,37 +40,54 @@ jobs:
|
||||
sudo apt-get install libipc-run-perl
|
||||
- run: make prove_installcheck
|
||||
mac:
|
||||
runs-on: macos-latest
|
||||
runs-on: ${{ matrix.os }}
|
||||
if: ${{ !startsWith(github.ref_name, 'windows') }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- postgres: 16
|
||||
os: macos-14
|
||||
- postgres: 14
|
||||
os: macos-12
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ankane/setup-postgres@v1
|
||||
with:
|
||||
postgres-version: 14
|
||||
postgres-version: ${{ matrix.postgres }}
|
||||
- run: make
|
||||
env:
|
||||
PG_CFLAGS: -Wall -Wextra -Werror -Wno-unused-parameter
|
||||
PG_CFLAGS: -DUSE_ASSERT_CHECKING -Wall -Wextra -Werror -Wno-unused-parameter
|
||||
- run: make install
|
||||
- run: make installcheck
|
||||
- if: ${{ failure() }}
|
||||
run: cat regression.diffs
|
||||
# Homebrew Postgres does not enable TAP tests, so need to download
|
||||
- run: |
|
||||
brew install cpanm
|
||||
cpanm --notest IPC::Run
|
||||
wget -q https://github.com/postgres/postgres/archive/refs/tags/REL_14_5.tar.gz
|
||||
tar xf REL_14_5.tar.gz
|
||||
- run: make prove_installcheck PROVE_FLAGS="-I ./postgres-REL_14_5/src/test/perl" PERL5LIB="/Users/runner/perl5/lib/perl5"
|
||||
- run: make clean && /usr/local/opt/llvm@15/bin/scan-build --status-bugs make
|
||||
wget -q https://github.com/postgres/postgres/archive/refs/tags/$TAG.tar.gz
|
||||
tar xf $TAG.tar.gz
|
||||
mv postgres-$TAG postgres
|
||||
env:
|
||||
TAG: ${{ matrix.postgres == 16 && 'REL_16_2' || 'REL_14_11' }}
|
||||
- run: make prove_installcheck PROVE_FLAGS="-I ./postgres/src/test/perl -I ./test/perl"
|
||||
env:
|
||||
PERL5LIB: /Users/runner/perl5/lib/perl5
|
||||
- run: make clean && $(brew --prefix llvm@15)/bin/scan-build --status-bugs make
|
||||
env:
|
||||
PG_CFLAGS: -DUSE_ASSERT_CHECKING
|
||||
windows:
|
||||
runs-on: windows-latest
|
||||
if: ${{ !startsWith(github.ref_name, 'mac') }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ankane/setup-postgres@v1
|
||||
with:
|
||||
postgres-version: 14
|
||||
- run: |
|
||||
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat" && ^
|
||||
cd %TEMP% && ^
|
||||
nmake /NOLOGO /F Makefile.win && ^
|
||||
nmake /NOLOGO /F Makefile.win install && ^
|
||||
nmake /NOLOGO /F Makefile.win installcheck && ^
|
||||
@@ -78,21 +95,34 @@ jobs:
|
||||
nmake /NOLOGO /F Makefile.win uninstall
|
||||
shell: cmd
|
||||
i386:
|
||||
if: ${{ !startsWith(github.ref_name, 'mac') && !startsWith(github.ref_name, 'windows') }}
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: debian:11
|
||||
image: debian:12
|
||||
options: --platform linux/386
|
||||
steps:
|
||||
- run: apt-get update && apt-get install -y build-essential git libipc-run-perl postgresql-13 postgresql-server-dev-13 sudo
|
||||
- run: apt-get update && apt-get install -y build-essential git libipc-run-perl postgresql-15 postgresql-server-dev-15 sudo
|
||||
- run: service postgresql start
|
||||
- run: |
|
||||
git clone https://github.com/${{ github.repository }}.git pgvector
|
||||
cd pgvector
|
||||
git checkout ${{ github.ref }}
|
||||
git fetch origin ${{ github.ref }}
|
||||
git reset --hard FETCH_HEAD
|
||||
make
|
||||
make install
|
||||
chown -R postgres .
|
||||
sudo -u postgres make installcheck
|
||||
sudo -u postgres make prove_installcheck
|
||||
env:
|
||||
PG_CFLAGS: -Wall -Wextra -Werror -Wno-unused-parameter -Wno-sign-compare
|
||||
PG_CFLAGS: -DUSE_ASSERT_CHECKING -Wall -Wextra -Werror -Wno-unused-parameter -Wno-sign-compare
|
||||
valgrind:
|
||||
if: ${{ !startsWith(github.ref_name, 'mac') && !startsWith(github.ref_name, 'windows') }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ankane/setup-postgres-valgrind@v1
|
||||
with:
|
||||
postgres-version: 16
|
||||
- run: make
|
||||
- run: sudo --preserve-env=PG_CONFIG make install
|
||||
- run: make installcheck
|
||||
|
||||
57
CHANGELOG.md
57
CHANGELOG.md
@@ -1,3 +1,56 @@
|
||||
## 0.7.0 (unreleased)
|
||||
|
||||
- Added `halfvec` type
|
||||
- Added `sparsevec` type
|
||||
- Added support for `bit` vectors to HNSW
|
||||
- Added `hamming_distance` function
|
||||
- Added `jaccard_distance` function
|
||||
- Added `quantize_binary` function
|
||||
- Added `subvector` function
|
||||
- Updated comparison operators to support vectors with different dimensions
|
||||
|
||||
## 0.6.2 (2024-03-18)
|
||||
|
||||
- Reduced lock contention with parallel HNSW index builds
|
||||
|
||||
## 0.6.1 (2024-03-04)
|
||||
|
||||
- Fixed error with `ANALYZE` and vectors with different dimensions
|
||||
- Fixed segmentation fault with `shared_preload_libraries`
|
||||
- Fixed vector subtraction being marked as commutative
|
||||
|
||||
## 0.6.0 (2024-01-29)
|
||||
|
||||
If upgrading with Postgres 12 or Docker, see [these notes](https://github.com/pgvector/pgvector#060).
|
||||
|
||||
- Added support for parallel index builds for HNSW
|
||||
- Added validation for GUC parameters
|
||||
- Changed storage for vector from `extended` to `external`
|
||||
- Improved performance of HNSW
|
||||
- Reduced memory usage for HNSW index builds
|
||||
- Reduced WAL generation for HNSW index builds
|
||||
- Fixed error with logical replication
|
||||
- Fixed `invalid memory alloc request size` error with HNSW index builds
|
||||
- Moved Docker image to `pgvector` org
|
||||
- Added Docker tags for each supported version of Postgres
|
||||
- Dropped support for Postgres 11
|
||||
|
||||
## 0.5.1 (2023-10-10)
|
||||
|
||||
- Improved performance of HNSW index builds
|
||||
- Added check for MVCC-compliant snapshot for index scans
|
||||
|
||||
## 0.5.0 (2023-08-28)
|
||||
|
||||
- Added HNSW index type
|
||||
- Added support for parallel index builds for IVFFlat
|
||||
- Added `l1_distance` function
|
||||
- Added element-wise multiplication for vectors
|
||||
- Added `sum` aggregate
|
||||
- Improved performance of distance functions
|
||||
- Fixed out of range results for cosine distance
|
||||
- Fixed results for NULL and NaN distances for IVFFlat
|
||||
|
||||
## 0.4.4 (2023-06-12)
|
||||
|
||||
- Improved error message for malformed vector literal
|
||||
@@ -28,7 +81,7 @@
|
||||
|
||||
## 0.4.0 (2023-01-11)
|
||||
|
||||
If upgrading with Postgres < 13, see [this note](https://github.com/pgvector/pgvector#040).
|
||||
If upgrading with Postgres < 13, see [this note](https://github.com/pgvector/pgvector/blob/v0.4.0/README.md#040).
|
||||
|
||||
- Changed text representation for vector elements to match `real`
|
||||
- Changed storage for vector from `plain` to `extended`
|
||||
@@ -45,7 +98,7 @@ If upgrading with Postgres < 13, see [this note](https://github.com/pgvector/pgv
|
||||
|
||||
## 0.3.1 (2022-11-02)
|
||||
|
||||
If upgrading from 0.2.7 or 0.3.0, [recreate](https://github.com/pgvector/pgvector#031) all `ivfflat` indexes after upgrading to ensure all data is indexed.
|
||||
If upgrading from 0.2.7 or 0.3.0, [recreate](https://github.com/pgvector/pgvector/blob/v0.3.1/README.md#031) all `ivfflat` indexes after upgrading to ensure all data is indexed.
|
||||
|
||||
- Fixed issue with inserts silently corrupting `ivfflat` indexes (introduced in 0.2.7)
|
||||
- Fixed segmentation fault with index creation when lists > 6500
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
ARG PG_MAJOR=15
|
||||
ARG PG_MAJOR=16
|
||||
FROM postgres:$PG_MAJOR
|
||||
ARG PG_MAJOR
|
||||
|
||||
COPY . /tmp/pgvector
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-mark hold locales && \
|
||||
apt-get install -y --no-install-recommends build-essential postgresql-server-dev-$PG_MAJOR && \
|
||||
cd /tmp/pgvector && \
|
||||
make clean && \
|
||||
@@ -15,4 +16,5 @@ RUN apt-get update && \
|
||||
rm -r /tmp/pgvector && \
|
||||
apt-get remove -y build-essential postgresql-server-dev-$PG_MAJOR && \
|
||||
apt-get autoremove -y && \
|
||||
apt-mark unhold locales && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
2
LICENSE
2
LICENSE
@@ -1,4 +1,4 @@
|
||||
Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
|
||||
Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
|
||||
|
||||
Portions Copyright (c) 1994, The Regents of the University of California
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "vector",
|
||||
"abstract": "Open-source vector similarity search for Postgres",
|
||||
"description": "Supports L2 distance, inner product, and cosine distance",
|
||||
"version": "0.4.4",
|
||||
"version": "0.6.2",
|
||||
"maintainer": [
|
||||
"Andrew Kane <andrew@ankane.org>"
|
||||
],
|
||||
@@ -12,7 +12,7 @@
|
||||
"prereqs": {
|
||||
"runtime": {
|
||||
"requires": {
|
||||
"PostgreSQL": "11.0.0"
|
||||
"PostgreSQL": "12.0.0"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -20,7 +20,7 @@
|
||||
"vector": {
|
||||
"file": "sql/vector.sql",
|
||||
"docfile": "README.md",
|
||||
"version": "0.4.4",
|
||||
"version": "0.6.2",
|
||||
"abstract": "Open-source vector similarity search for Postgres"
|
||||
}
|
||||
},
|
||||
|
||||
17
Makefile
17
Makefile
@@ -1,17 +1,18 @@
|
||||
EXTENSION = vector
|
||||
EXTVERSION = 0.4.4
|
||||
EXTVERSION = 0.6.2
|
||||
|
||||
MODULE_big = vector
|
||||
DATA = $(wildcard sql/*--*.sql)
|
||||
OBJS = src/ivfbuild.o src/ivfflat.o src/ivfinsert.o src/ivfkmeans.o src/ivfscan.o src/ivfutils.o src/ivfvacuum.o src/vector.o
|
||||
OBJS = src/bitvector.o src/halfvec.o src/hnsw.o src/hnswbuild.o src/hnswinsert.o src/hnswscan.o src/hnswutils.o src/hnswvacuum.o src/ivfbuild.o src/ivfflat.o src/ivfinsert.o src/ivfkmeans.o src/ivfscan.o src/ivfutils.o src/ivfvacuum.o src/sparsevec.o src/vector.o
|
||||
HEADERS = src/halfvec.h src/sparsevec.h src/vector.h
|
||||
|
||||
TESTS = $(wildcard test/sql/*.sql)
|
||||
REGRESS = $(patsubst test/sql/%.sql,%,$(TESTS))
|
||||
REGRESS_OPTS = --inputdir=test --load-extension=vector
|
||||
REGRESS_OPTS = --inputdir=test --load-extension=$(EXTENSION)
|
||||
|
||||
OPTFLAGS = -march=native
|
||||
|
||||
# Mac ARM doesn't support -march=native
|
||||
# Mac ARM doesn't always support -march=native
|
||||
ifeq ($(shell uname -s), Darwin)
|
||||
ifeq ($(shell uname -p), arm)
|
||||
# no difference with -march=armv8.5-a
|
||||
@@ -64,13 +65,15 @@ dist:
|
||||
mkdir -p dist
|
||||
git archive --format zip --prefix=$(EXTENSION)-$(EXTVERSION)/ --output dist/$(EXTENSION)-$(EXTVERSION).zip master
|
||||
|
||||
# for Docker
|
||||
PG_MAJOR ?= 16
|
||||
|
||||
.PHONY: docker
|
||||
|
||||
docker:
|
||||
docker build --pull --no-cache --platform linux/amd64 -t ankane/pgvector:latest .
|
||||
docker build --pull --no-cache --build-arg PG_MAJOR=$(PG_MAJOR) -t pgvector/pgvector:pg$(PG_MAJOR) -t pgvector/pgvector:$(EXTVERSION)-pg$(PG_MAJOR) .
|
||||
|
||||
.PHONY: docker-release
|
||||
|
||||
docker-release:
|
||||
docker buildx build --push --pull --no-cache --platform linux/amd64,linux/arm64 -t ankane/pgvector:latest .
|
||||
docker buildx build --push --platform linux/amd64,linux/arm64 -t ankane/pgvector:v$(EXTVERSION) .
|
||||
docker buildx build --push --pull --no-cache --platform linux/amd64,linux/arm64 --build-arg PG_MAJOR=$(PG_MAJOR) -t pgvector/pgvector:pg$(PG_MAJOR) -t pgvector/pgvector:$(EXTVERSION)-pg$(PG_MAJOR) .
|
||||
|
||||
15
Makefile.win
15
Makefile.win
@@ -1,10 +1,11 @@
|
||||
EXTENSION = vector
|
||||
EXTVERSION = 0.4.4
|
||||
EXTVERSION = 0.6.2
|
||||
|
||||
OBJS = src\ivfbuild.obj src\ivfflat.obj src\ivfinsert.obj src\ivfkmeans.obj src\ivfscan.obj src\ivfutils.obj src\ivfvacuum.obj src\vector.obj
|
||||
OBJS = src\bitvector.obj src\halfvec.obj src\hnsw.obj src\hnswbuild.obj src\hnswinsert.obj src\hnswscan.obj src\hnswutils.obj src\hnswvacuum.obj src\ivfbuild.obj src\ivfflat.obj src\ivfinsert.obj src\ivfkmeans.obj src\ivfscan.obj src\ivfutils.obj src\ivfvacuum.obj src\sparsevec.obj src\vector.obj
|
||||
HEADERS = src\halfvec.h src\sparsevec.h src\vector.h
|
||||
|
||||
REGRESS = btree cast copy functions input ivfflat_cosine ivfflat_ip ivfflat_l2 ivfflat_options ivfflat_unlogged
|
||||
REGRESS_OPTS = --inputdir=test --load-extension=vector
|
||||
REGRESS = bit_functions btree cast copy halfvec_functions halfvec_input hnsw_bit_hamming hnsw_bit_jaccard hnsw_halfvec_cosine hnsw_halfvec_ip hnsw_halfvec_l2 hnsw_options hnsw_sparsevec_cosine hnsw_sparsevec_ip hnsw_sparsevec_l2 hnsw_unlogged hnsw_vector_cosine hnsw_vector_ip hnsw_vector_l2 ivfflat_options ivfflat_unlogged ivfflat_vector_cosine ivfflat_vector_ip ivfflat_vector_l2 sparsevec_functions sparsevec_input vector_functions vector_input
|
||||
REGRESS_OPTS = --inputdir=test --load-extension=$(EXTENSION)
|
||||
|
||||
# For /arch flags
|
||||
# https://learn.microsoft.com/en-us/cpp/build/reference/arch-minimum-cpu-architecture
|
||||
@@ -54,6 +55,8 @@ install:
|
||||
copy $(SHLIB) "$(PKGLIBDIR)"
|
||||
copy $(EXTENSION).control "$(SHAREDIR)\extension"
|
||||
copy sql\$(EXTENSION)--*.sql "$(SHAREDIR)\extension"
|
||||
mkdir "$(INCLUDEDIR_SERVER)\extension\$(EXTENSION)"
|
||||
for %f in ($(HEADERS)) do copy %f "$(INCLUDEDIR_SERVER)\extension\$(EXTENSION)"
|
||||
|
||||
installcheck:
|
||||
"$(BINDIR)\pg_regress" --bindir="$(BINDIR)" $(REGRESS_OPTS) $(REGRESS)
|
||||
@@ -61,7 +64,9 @@ installcheck:
|
||||
uninstall:
|
||||
del /f "$(PKGLIBDIR)\$(SHLIB)"
|
||||
del /f "$(SHAREDIR)\extension\$(EXTENSION).control"
|
||||
del /f "$(SHAREDIR)\extension\vector--*.sql"
|
||||
del /f "$(SHAREDIR)\extension\$(EXTENSION)--*.sql"
|
||||
del /f "$(INCLUDEDIR_SERVER)\extension\$(EXTENSION)\*.h"
|
||||
rmdir "$(INCLUDEDIR_SERVER)\extension\$(EXTENSION)"
|
||||
|
||||
clean:
|
||||
del /f $(SHLIB) $(EXTENSION).lib $(EXTENSION).exp
|
||||
|
||||
699
README.md
699
README.md
@@ -2,7 +2,7 @@
|
||||
|
||||
Open-source vector similarity search for Postgres
|
||||
|
||||
Supports
|
||||
Store your vectors with the rest of your data. Supports:
|
||||
|
||||
- exact and approximate nearest neighbor search
|
||||
- L2 distance, inner product, and cosine distance
|
||||
@@ -10,23 +10,50 @@ Supports
|
||||
|
||||
Plus [ACID](https://en.wikipedia.org/wiki/ACID) compliance, point-in-time recovery, JOINs, and all of the other [great features](https://www.postgresql.org/about/) of Postgres
|
||||
|
||||
[](https://github.com/pgvector/pgvector/actions)
|
||||
[](https://github.com/pgvector/pgvector/actions)
|
||||
|
||||
## Installation
|
||||
|
||||
Compile and install the extension (supports Postgres 11+)
|
||||
### Linux and Mac
|
||||
|
||||
Compile and install the extension (supports Postgres 12+)
|
||||
|
||||
```sh
|
||||
cd /tmp
|
||||
git clone --branch v0.4.4 https://github.com/pgvector/pgvector.git
|
||||
git clone --branch v0.6.2 https://github.com/pgvector/pgvector.git
|
||||
cd pgvector
|
||||
make
|
||||
make install # may need sudo
|
||||
```
|
||||
|
||||
See the [installation notes](#installation-notes) if you run into issues
|
||||
See the [installation notes](#installation-notes---linux-and-mac) if you run into issues
|
||||
|
||||
You can also install it with [Docker](#docker), [Homebrew](#homebrew), [PGXN](#pgxn), [APT](#apt), [Yum](#yum), or [conda-forge](#conda-forge), and it comes preinstalled with [Postgres.app](#postgresapp) and many [hosted providers](#hosted-postgres)
|
||||
You can also install it with [Docker](#docker), [Homebrew](#homebrew), [PGXN](#pgxn), [APT](#apt), [Yum](#yum), [pkg](#pkg), or [conda-forge](#conda-forge), and it comes preinstalled with [Postgres.app](#postgresapp) and many [hosted providers](#hosted-postgres). There are also instructions for [GitHub Actions](https://github.com/pgvector/setup-pgvector).
|
||||
|
||||
### Windows
|
||||
|
||||
Ensure [C++ support in Visual Studio](https://learn.microsoft.com/en-us/cpp/build/building-on-the-command-line?view=msvc-170#download-and-install-the-tools) is installed, and run:
|
||||
|
||||
```cmd
|
||||
call "C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvars64.bat"
|
||||
```
|
||||
|
||||
Note: The exact path will vary depending on your Visual Studio version and edition
|
||||
|
||||
Then use `nmake` to build:
|
||||
|
||||
```cmd
|
||||
set "PGROOT=C:\Program Files\PostgreSQL\16"
|
||||
cd %TEMP%
|
||||
git clone --branch v0.6.2 https://github.com/pgvector/pgvector.git
|
||||
cd pgvector
|
||||
nmake /F Makefile.win
|
||||
nmake /F Makefile.win install
|
||||
```
|
||||
|
||||
See the [installation notes](#installation-notes---windows) if you run into issues
|
||||
|
||||
You can also install it with [Docker](#docker) or [conda-forge](#conda-forge).
|
||||
|
||||
## Getting Started
|
||||
|
||||
@@ -78,6 +105,12 @@ Insert vectors
|
||||
INSERT INTO items (embedding) VALUES ('[1,2,3]'), ('[4,5,6]');
|
||||
```
|
||||
|
||||
Or load vectors in bulk using `COPY` ([example](https://github.com/pgvector/pgvector-python/blob/master/examples/bulk_loading.py))
|
||||
|
||||
```sql
|
||||
COPY items (embedding) FROM STDIN WITH (FORMAT BINARY);
|
||||
```
|
||||
|
||||
Upsert vectors
|
||||
|
||||
```sql
|
||||
@@ -157,7 +190,132 @@ SELECT category_id, AVG(embedding) FROM items GROUP BY category_id;
|
||||
|
||||
By default, pgvector performs exact nearest neighbor search, which provides perfect recall.
|
||||
|
||||
You can add an index to use approximate nearest neighbor search, which trades some recall for performance. Unlike typical indexes, you will see different results for queries after adding an approximate index.
|
||||
You can add an index to use approximate nearest neighbor search, which trades some recall for speed. Unlike typical indexes, you will see different results for queries after adding an approximate index.
|
||||
|
||||
Supported index types are:
|
||||
|
||||
- [HNSW](#hnsw) - added in 0.5.0
|
||||
- [IVFFlat](#ivfflat)
|
||||
|
||||
## HNSW
|
||||
|
||||
An HNSW index creates a multilayer graph. It has better query performance than IVFFlat (in terms of speed-recall tradeoff), but has slower build times and uses more memory. Also, an index can be created without any data in the table since there isn’t a training step like IVFFlat.
|
||||
|
||||
Add an index for each distance function you want to use.
|
||||
|
||||
L2 distance
|
||||
|
||||
```sql
|
||||
CREATE INDEX ON items USING hnsw (embedding vector_l2_ops);
|
||||
```
|
||||
|
||||
Inner product
|
||||
|
||||
```sql
|
||||
CREATE INDEX ON items USING hnsw (embedding vector_ip_ops);
|
||||
```
|
||||
|
||||
Cosine distance
|
||||
|
||||
```sql
|
||||
CREATE INDEX ON items USING hnsw (embedding vector_cosine_ops);
|
||||
```
|
||||
|
||||
Hamming distance - unreleased
|
||||
|
||||
```sql
|
||||
CREATE INDEX ON items USING hnsw (embedding bit_hamming_ops);
|
||||
```
|
||||
|
||||
Jaccard distance - unreleased
|
||||
|
||||
```sql
|
||||
CREATE INDEX ON items USING hnsw (embedding bit_jaccard_ops);
|
||||
```
|
||||
|
||||
Supported types are:
|
||||
|
||||
- `vector` - up to 2,000 dimensions
|
||||
- `halfvec` - up to 4,000 dimensions (unreleased)
|
||||
- `bit` - up to 64,000 dimensions (unreleased)
|
||||
- `sparsevec` - up to 1,000 non-zero elements (unreleased)
|
||||
|
||||
### Index Options
|
||||
|
||||
Specify HNSW parameters
|
||||
|
||||
- `m` - the max number of connections per layer (16 by default)
|
||||
- `ef_construction` - the size of the dynamic candidate list for constructing the graph (64 by default)
|
||||
|
||||
```sql
|
||||
CREATE INDEX ON items USING hnsw (embedding vector_l2_ops) WITH (m = 16, ef_construction = 64);
|
||||
```
|
||||
|
||||
A higher value of `ef_construction` provides better recall at the cost of index build time / insert speed.
|
||||
|
||||
### Query Options
|
||||
|
||||
Specify the size of the dynamic candidate list for search (40 by default)
|
||||
|
||||
```sql
|
||||
SET hnsw.ef_search = 100;
|
||||
```
|
||||
|
||||
A higher value provides better recall at the cost of speed.
|
||||
|
||||
Use `SET LOCAL` inside a transaction to set it for a single query
|
||||
|
||||
```sql
|
||||
BEGIN;
|
||||
SET LOCAL hnsw.ef_search = 100;
|
||||
SELECT ...
|
||||
COMMIT;
|
||||
```
|
||||
|
||||
### Index Build Time
|
||||
|
||||
Indexes build significantly faster when the graph fits into `maintenance_work_mem`
|
||||
|
||||
```sql
|
||||
SET maintenance_work_mem = '8GB';
|
||||
```
|
||||
|
||||
A notice is shown when the graph no longer fits
|
||||
|
||||
```text
|
||||
NOTICE: hnsw graph no longer fits into maintenance_work_mem after 100000 tuples
|
||||
DETAIL: Building will take significantly more time.
|
||||
HINT: Increase maintenance_work_mem to speed up builds.
|
||||
```
|
||||
|
||||
Note: Do not set `maintenance_work_mem` so high that it exhausts the memory on the server
|
||||
|
||||
Like other index types, it’s faster to create an index after loading your initial data
|
||||
|
||||
Starting with 0.6.0, you can also speed up index creation by increasing the number of parallel workers (2 by default)
|
||||
|
||||
```sql
|
||||
SET max_parallel_maintenance_workers = 7; -- plus leader
|
||||
```
|
||||
|
||||
For a large number of workers, you may also need to increase `max_parallel_workers` (8 by default)
|
||||
|
||||
### Indexing Progress
|
||||
|
||||
Check [indexing progress](https://www.postgresql.org/docs/current/progress-reporting.html#CREATE-INDEX-PROGRESS-REPORTING) with Postgres 12+
|
||||
|
||||
```sql
|
||||
SELECT phase, round(100.0 * blocks_done / nullif(blocks_total, 0), 1) AS "%" FROM pg_stat_progress_create_index;
|
||||
```
|
||||
|
||||
The phases for HNSW are:
|
||||
|
||||
1. `initializing`
|
||||
2. `loading tuples`
|
||||
|
||||
## IVFFlat
|
||||
|
||||
An IVFFlat index divides vectors into lists, and then searches a subset of those lists that are closest to the query vector. It has faster build times and uses less memory than HNSW, but has lower query performance (in terms of speed-recall tradeoff).
|
||||
|
||||
Three keys to achieving good recall are:
|
||||
|
||||
@@ -185,7 +343,9 @@ Cosine distance
|
||||
CREATE INDEX ON items USING ivfflat (embedding vector_cosine_ops) WITH (lists = 100);
|
||||
```
|
||||
|
||||
Vectors with up to 2,000 dimensions can be indexed.
|
||||
Supported types are:
|
||||
|
||||
- `vector` - up to 2,000 dimensions
|
||||
|
||||
### Query Options
|
||||
|
||||
@@ -206,24 +366,34 @@ SELECT ...
|
||||
COMMIT;
|
||||
```
|
||||
|
||||
### Index Build Time
|
||||
|
||||
Speed up index creation on large tables by increasing the number of parallel workers (2 by default)
|
||||
|
||||
```sql
|
||||
SET max_parallel_maintenance_workers = 7; -- plus leader
|
||||
```
|
||||
|
||||
For a large number of workers, you may also need to increase `max_parallel_workers` (8 by default)
|
||||
|
||||
### Indexing Progress
|
||||
|
||||
Check [indexing progress](https://www.postgresql.org/docs/current/progress-reporting.html#CREATE-INDEX-PROGRESS-REPORTING) with Postgres 12+
|
||||
|
||||
```sql
|
||||
SELECT phase, tuples_done, tuples_total FROM pg_stat_progress_create_index;
|
||||
SELECT phase, round(100.0 * tuples_done / nullif(tuples_total, 0), 1) AS "%" FROM pg_stat_progress_create_index;
|
||||
```
|
||||
|
||||
The phases are:
|
||||
The phases for IVFFlat are:
|
||||
|
||||
1. `initializing`
|
||||
2. `performing k-means`
|
||||
3. `sorting tuples`
|
||||
3. `assigning tuples`
|
||||
4. `loading tuples`
|
||||
|
||||
Note: `tuples_done` and `tuples_total` are only populated during the `loading tuples` phase
|
||||
Note: `%` is only populated during the `loading tuples` phase
|
||||
|
||||
### Filtering
|
||||
## Filtering
|
||||
|
||||
There are a few ways to index nearest neighbor queries with a `WHERE` clause
|
||||
|
||||
@@ -240,8 +410,7 @@ CREATE INDEX ON items (category_id);
|
||||
Or a [partial index](https://www.postgresql.org/docs/current/indexes-partial.html) on the vector column for approximate search
|
||||
|
||||
```sql
|
||||
CREATE INDEX ON items USING ivfflat (embedding vector_l2_ops) WITH (lists = 100)
|
||||
WHERE (category_id = 123);
|
||||
CREATE INDEX ON items USING hnsw (embedding vector_l2_ops) WHERE (category_id = 123);
|
||||
```
|
||||
|
||||
Use [partitioning](https://www.postgresql.org/docs/current/ddl-partitioning.html) for approximate search on many different values of the `WHERE` columns
|
||||
@@ -252,22 +421,62 @@ CREATE TABLE items (embedding vector(3), category_id int) PARTITION BY LIST(cate
|
||||
|
||||
## Hybrid Search
|
||||
|
||||
Use together with Postgres [full-text search](https://www.postgresql.org/docs/current/textsearch-intro.html) for hybrid search ([Python example](https://github.com/pgvector/pgvector-python/blob/master/examples/hybrid_search.py)).
|
||||
Use together with Postgres [full-text search](https://www.postgresql.org/docs/current/textsearch-intro.html) for hybrid search.
|
||||
|
||||
```sql
|
||||
SELECT id, content FROM items, to_tsquery('hello & search') query
|
||||
SELECT id, content FROM items, plainto_tsquery('hello search') query
|
||||
WHERE textsearch @@ query ORDER BY ts_rank_cd(textsearch, query) DESC LIMIT 5;
|
||||
```
|
||||
|
||||
You can use [Reciprocal Rank Fusion](https://github.com/pgvector/pgvector-python/blob/master/examples/hybrid_search_rrf.py) or a [cross-encoder](https://github.com/pgvector/pgvector-python/blob/master/examples/hybrid_search.py) to combine results.
|
||||
|
||||
## Performance
|
||||
|
||||
### Tuning
|
||||
|
||||
Use a tool like [PgTune](https://pgtune.leopard.in.ua/) to set initial values for Postgres server parameters. For instance, `shared_buffers` should typically be 25% of the server’s memory. You can find the config file with:
|
||||
|
||||
```sql
|
||||
SHOW config_file;
|
||||
```
|
||||
|
||||
And check individual settings with:
|
||||
|
||||
```sql
|
||||
SHOW shared_buffers;
|
||||
```
|
||||
|
||||
Be sure to restart Postgres for changes to take effect.
|
||||
|
||||
### Loading
|
||||
|
||||
Use `COPY` for bulk loading data ([example](https://github.com/pgvector/pgvector-python/blob/master/examples/bulk_loading.py)).
|
||||
|
||||
```sql
|
||||
COPY items (embedding) FROM STDIN WITH (FORMAT BINARY);
|
||||
```
|
||||
|
||||
Add any indexes *after* loading the initial data for best performance.
|
||||
|
||||
### Indexing
|
||||
|
||||
See index build time for [HNSW](#index-build-time) and [IVFFlat](#index-build-time-1).
|
||||
|
||||
In production environments, create indexes concurrently to avoid blocking writes.
|
||||
|
||||
```sql
|
||||
CREATE INDEX CONCURRENTLY ...
|
||||
```
|
||||
|
||||
### Querying
|
||||
|
||||
Use `EXPLAIN ANALYZE` to debug performance.
|
||||
|
||||
```sql
|
||||
EXPLAIN ANALYZE SELECT * FROM items ORDER BY embedding <-> '[3,1,2]' LIMIT 5;
|
||||
```
|
||||
|
||||
### Exact Search
|
||||
#### Exact Search
|
||||
|
||||
To speed up queries without an index, increase `max_parallel_workers_per_gather`.
|
||||
|
||||
@@ -281,30 +490,79 @@ If vectors are normalized to length 1 (like [OpenAI embeddings](https://platform
|
||||
SELECT * FROM items ORDER BY embedding <#> '[3,1,2]' LIMIT 5;
|
||||
```
|
||||
|
||||
### Approximate Search
|
||||
#### Approximate Search
|
||||
|
||||
To speed up queries with an index, increase the number of inverted lists (at the expense of recall).
|
||||
To speed up queries with an IVFFlat index, increase the number of inverted lists (at the expense of recall).
|
||||
|
||||
```sql
|
||||
CREATE INDEX ON items USING ivfflat (embedding vector_l2_ops) WITH (lists = 1000);
|
||||
```
|
||||
|
||||
### Vacuuming
|
||||
|
||||
Vacuuming can take a while for HNSW indexes. Speed it up by reindexing first.
|
||||
|
||||
```sql
|
||||
REINDEX INDEX CONCURRENTLY index_name;
|
||||
VACUUM table_name;
|
||||
```
|
||||
|
||||
## Monitoring
|
||||
|
||||
Monitor performance with [pg_stat_statements](https://www.postgresql.org/docs/current/pgstatstatements.html) (be sure to add it to `shared_preload_libraries`).
|
||||
|
||||
```sql
|
||||
CREATE EXTENSION pg_stat_statements;
|
||||
```
|
||||
|
||||
Get the most time-consuming queries with:
|
||||
|
||||
```sql
|
||||
SELECT query, calls, ROUND((total_plan_time + total_exec_time) / calls) AS avg_time_ms,
|
||||
ROUND((total_plan_time + total_exec_time) / 60000) AS total_time_min
|
||||
FROM pg_stat_statements ORDER BY total_plan_time + total_exec_time DESC LIMIT 20;
|
||||
```
|
||||
|
||||
Note: Replace `total_plan_time + total_exec_time` with `total_time` for Postgres < 13
|
||||
|
||||
Monitor recall by comparing results from approximate search with exact search.
|
||||
|
||||
```sql
|
||||
BEGIN;
|
||||
SET LOCAL enable_indexscan = off; -- use exact search
|
||||
SELECT ...
|
||||
COMMIT;
|
||||
```
|
||||
|
||||
## Scaling
|
||||
|
||||
Scale pgvector the same way you scale Postgres.
|
||||
|
||||
Scale vertically by increasing memory, CPU, and storage on a single instance. Use existing tools to [tune parameters](#tuning) and [monitor performance](#monitoring).
|
||||
|
||||
Scale horizontally with [replicas](https://www.postgresql.org/docs/current/hot-standby.html), or use [Citus](https://github.com/citusdata/citus) or another approach for sharding ([example](https://github.com/pgvector/pgvector-python/blob/master/examples/citus.py)).
|
||||
|
||||
## Languages
|
||||
|
||||
Use pgvector from any language with a Postgres client. You can even generate and store vectors in one language and query them in another.
|
||||
|
||||
Language | Libraries / Examples
|
||||
--- | ---
|
||||
C | [pgvector-c](https://github.com/pgvector/pgvector-c)
|
||||
C++ | [pgvector-cpp](https://github.com/pgvector/pgvector-cpp)
|
||||
C# | [pgvector-dotnet](https://github.com/pgvector/pgvector-dotnet)
|
||||
C#, F#, Visual Basic | [pgvector-dotnet](https://github.com/pgvector/pgvector-dotnet)
|
||||
Crystal | [pgvector-crystal](https://github.com/pgvector/pgvector-crystal)
|
||||
Dart | [pgvector-dart](https://github.com/pgvector/pgvector-dart)
|
||||
Elixir | [pgvector-elixir](https://github.com/pgvector/pgvector-elixir)
|
||||
Go | [pgvector-go](https://github.com/pgvector/pgvector-go)
|
||||
Haskell | [pgvector-haskell](https://github.com/pgvector/pgvector-haskell)
|
||||
Java, Scala | [pgvector-java](https://github.com/pgvector/pgvector-java)
|
||||
Java, Kotlin, Groovy, Scala | [pgvector-java](https://github.com/pgvector/pgvector-java)
|
||||
JavaScript, TypeScript | [pgvector-node](https://github.com/pgvector/pgvector-node)
|
||||
Julia | [pgvector-julia](https://github.com/pgvector/pgvector-julia)
|
||||
Lisp | [pgvector-lisp](https://github.com/pgvector/pgvector-lisp)
|
||||
Lua | [pgvector-lua](https://github.com/pgvector/pgvector-lua)
|
||||
Node.js | [pgvector-node](https://github.com/pgvector/pgvector-node)
|
||||
Nim | [pgvector-nim](https://github.com/pgvector/pgvector-nim)
|
||||
OCaml | [pgvector-ocaml](https://github.com/pgvector/pgvector-ocaml)
|
||||
Perl | [pgvector-perl](https://github.com/pgvector/pgvector-perl)
|
||||
PHP | [pgvector-php](https://github.com/pgvector/pgvector-php)
|
||||
Python | [pgvector-python](https://github.com/pgvector/pgvector-python)
|
||||
@@ -312,6 +570,7 @@ R | [pgvector-r](https://github.com/pgvector/pgvector-r)
|
||||
Ruby | [pgvector-ruby](https://github.com/pgvector/pgvector-ruby), [Neighbor](https://github.com/ankane/neighbor)
|
||||
Rust | [pgvector-rust](https://github.com/pgvector/pgvector-rust)
|
||||
Swift | [pgvector-swift](https://github.com/pgvector/pgvector-swift)
|
||||
Zig | [pgvector-zig](https://github.com/pgvector/pgvector-zig)
|
||||
|
||||
## Frequently Asked Questions
|
||||
|
||||
@@ -327,50 +586,248 @@ Yes, pgvector uses the write-ahead log (WAL), which allows for replication and p
|
||||
|
||||
You’ll need to use [dimensionality reduction](https://en.wikipedia.org/wiki/Dimensionality_reduction) at the moment.
|
||||
|
||||
#### Why am I seeing less results after adding an index?
|
||||
#### Can I store vectors with different dimensions in the same column?
|
||||
|
||||
You can use `vector` as the type (instead of `vector(3)`).
|
||||
|
||||
```sql
|
||||
CREATE TABLE embeddings (model_id bigint, item_id bigint, embedding vector, PRIMARY KEY (model_id, item_id));
|
||||
```
|
||||
|
||||
However, you can only create indexes on rows with the same number of dimensions (using [expression](https://www.postgresql.org/docs/current/indexes-expressional.html) and [partial](https://www.postgresql.org/docs/current/indexes-partial.html) indexing):
|
||||
|
||||
```sql
|
||||
CREATE INDEX ON embeddings USING hnsw ((embedding::vector(3)) vector_l2_ops) WHERE (model_id = 123);
|
||||
```
|
||||
|
||||
and query with:
|
||||
|
||||
```sql
|
||||
SELECT * FROM embeddings WHERE model_id = 123 ORDER BY embedding::vector(3) <-> '[3,1,2]' LIMIT 5;
|
||||
```
|
||||
|
||||
#### Can I store vectors with more precision?
|
||||
|
||||
You can use the `double precision[]` or `numeric[]` type to store vectors with more precision.
|
||||
|
||||
```sql
|
||||
CREATE TABLE items (id bigserial PRIMARY KEY, embedding double precision[]);
|
||||
|
||||
-- use {} instead of [] for Postgres arrays
|
||||
INSERT INTO items (embedding) VALUES ('{1,2,3}'), ('{4,5,6}');
|
||||
```
|
||||
|
||||
Optionally, add a [check constraint](https://www.postgresql.org/docs/current/ddl-constraints.html) to ensure data can be converted to the `vector` type and has the expected dimensions.
|
||||
|
||||
```sql
|
||||
ALTER TABLE items ADD CHECK (vector_dims(embedding::vector) = 3);
|
||||
```
|
||||
|
||||
Use [expression indexing](https://www.postgresql.org/docs/current/indexes-expressional.html) to index (at a lower precision):
|
||||
|
||||
```sql
|
||||
CREATE INDEX ON items USING hnsw ((embedding::vector(3)) vector_l2_ops);
|
||||
```
|
||||
|
||||
and query with:
|
||||
|
||||
```sql
|
||||
SELECT * FROM items ORDER BY embedding::vector(3) <-> '[3,1,2]' LIMIT 5;
|
||||
```
|
||||
|
||||
#### Are binary vectors supported?
|
||||
|
||||
You can store binary vectors and perform exact nearest neighbor search by Hamming distance in Postgres without an extension ([example](https://github.com/pgvector/pgvector-python/blob/master/examples/hash_image_search.py)).
|
||||
|
||||
```tsql
|
||||
CREATE TABLE items (id bigserial PRIMARY KEY, embedding bit(3));
|
||||
INSERT INTO items (embedding) VALUES (B'000'), (B'111');
|
||||
SELECT * FROM items ORDER BY bit_count(embedding # B'101') LIMIT 5;
|
||||
```
|
||||
|
||||
Indexing is not currently supported.
|
||||
|
||||
#### Do indexes need to fit into memory?
|
||||
|
||||
No, but like other index types, you’ll likely see better performance if they do. You can get the size of an index with:
|
||||
|
||||
```sql
|
||||
SELECT pg_size_pretty(pg_relation_size('index_name'));
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
#### Why isn’t a query using an index?
|
||||
|
||||
The query needs to have an `ORDER BY` and `LIMIT`, and the `ORDER BY` must be the result of a distance operator, not an expression.
|
||||
|
||||
```sql
|
||||
-- index
|
||||
ORDER BY embedding <=> '[3,1,2]' LIMIT 5;
|
||||
|
||||
-- no index
|
||||
ORDER BY 1 - (embedding <=> '[3,1,2]') DESC LIMIT 5;
|
||||
```
|
||||
|
||||
You can encourage the planner to use an index for a query with:
|
||||
|
||||
```sql
|
||||
BEGIN;
|
||||
SET LOCAL enable_seqscan = off;
|
||||
SELECT ...
|
||||
COMMIT;
|
||||
```
|
||||
|
||||
Also, if the table is small, a table scan may be faster.
|
||||
|
||||
#### Why isn’t a query using a parallel table scan?
|
||||
|
||||
The planner doesn’t consider [out-of-line storage](https://www.postgresql.org/docs/current/storage-toast.html) in cost estimates, which can make a serial scan look cheaper. You can reduce the cost of a parallel scan for a query with:
|
||||
|
||||
```sql
|
||||
BEGIN;
|
||||
SET LOCAL min_parallel_table_scan_size = 1;
|
||||
SET LOCAL parallel_setup_cost = 1;
|
||||
SELECT ...
|
||||
COMMIT;
|
||||
```
|
||||
|
||||
or choose to store vectors inline:
|
||||
|
||||
```sql
|
||||
ALTER TABLE items ALTER COLUMN embedding SET STORAGE PLAIN;
|
||||
```
|
||||
|
||||
#### Why are there less results for a query after adding an HNSW index?
|
||||
|
||||
Results are limited by the size of the dynamic candidate list (`hnsw.ef_search`). There may be even less results due to dead tuples or filtering conditions in the query. We recommend setting `hnsw.ef_search` to at least twice the `LIMIT` of the query. If you need more than 500 results, use an IVFFlat index instead.
|
||||
|
||||
Also, note that `NULL` vectors are not indexed (as well as zero vectors for cosine distance).
|
||||
|
||||
#### Why are there less results for a query after adding an IVFFlat index?
|
||||
|
||||
The index was likely created with too little data for the number of lists. Drop the index until the table has more data.
|
||||
|
||||
```sql
|
||||
DROP INDEX index_name;
|
||||
```
|
||||
|
||||
Results can also be limited by the number of probes (`ivfflat.probes`).
|
||||
|
||||
Also, note that `NULL` vectors are not indexed (as well as zero vectors for cosine distance).
|
||||
|
||||
## Reference
|
||||
|
||||
- [Vector](#vector-type)
|
||||
- [Halfvec](#halfvec-type)
|
||||
- [Bit](#bit-type)
|
||||
- [Sparsevec](#sparsevec-type)
|
||||
|
||||
### Vector Type
|
||||
|
||||
Each vector takes `4 * dimensions + 8` bytes of storage. Each element is a single precision floating-point number (like the `real` type in Postgres), and all elements must be finite (no `NaN`, `Infinity` or `-Infinity`). Vectors can have up to 16,000 dimensions.
|
||||
Each vector takes `4 * dimensions + 8` bytes of storage. Each element is a single-precision floating-point number (like the `real` type in Postgres), and all elements must be finite (no `NaN`, `Infinity` or `-Infinity`). Vectors can have up to 16,000 dimensions.
|
||||
|
||||
### Vector Operators
|
||||
|
||||
Operator | Description
|
||||
--- | ---
|
||||
\+ | element-wise addition
|
||||
\- | element-wise subtraction
|
||||
<-> | Euclidean distance
|
||||
<#> | negative inner product
|
||||
<=> | cosine distance
|
||||
Operator | Description | Added
|
||||
--- | --- | ---
|
||||
\+ | element-wise addition |
|
||||
\- | element-wise subtraction |
|
||||
\* | element-wise multiplication | 0.5.0
|
||||
<-> | Euclidean distance |
|
||||
<#> | negative inner product |
|
||||
<=> | cosine distance |
|
||||
|
||||
### Vector Functions
|
||||
|
||||
Function | Description
|
||||
--- | ---
|
||||
cosine_distance(vector, vector) → double precision | cosine distance
|
||||
inner_product(vector, vector) → double precision | inner product
|
||||
l2_distance(vector, vector) → double precision | Euclidean distance
|
||||
vector_dims(vector) → integer | number of dimensions
|
||||
vector_norm(vector) → double precision | Euclidean norm
|
||||
Function | Description | Added
|
||||
--- | --- | ---
|
||||
cosine_distance(vector, vector) → double precision | cosine distance |
|
||||
inner_product(vector, vector) → double precision | inner product |
|
||||
l2_distance(vector, vector) → double precision | Euclidean distance |
|
||||
l1_distance(vector, vector) → double precision | taxicab distance | 0.5.0
|
||||
quantize_binary(vector) → bit | quantize | unreleased
|
||||
subvector(vector, integer, integer) → vector | subvector | unreleased
|
||||
vector_dims(vector) → integer | number of dimensions |
|
||||
vector_norm(vector) → double precision | Euclidean norm |
|
||||
|
||||
### Aggregate Functions
|
||||
### Vector Aggregate Functions
|
||||
|
||||
Function | Description
|
||||
--- | ---
|
||||
avg(vector) → vector | arithmetic mean
|
||||
Function | Description | Added
|
||||
--- | --- | ---
|
||||
avg(vector) → vector | average |
|
||||
sum(vector) → vector | sum | 0.5.0
|
||||
|
||||
## Installation Notes
|
||||
### Halfvec Type
|
||||
|
||||
Each half vector takes `2 * dimensions + 8` bytes of storage. Each element is a half-precision floating-point number, and all elements must be finite (no `NaN`, `Infinity` or `-Infinity`). Half vectors can have up to 16,000 dimensions.
|
||||
|
||||
### Halfvec Operators
|
||||
|
||||
Operator | Description | Added
|
||||
--- | --- | ---
|
||||
<-> | Euclidean distance | unreleased
|
||||
<#> | negative inner product | unreleased
|
||||
<=> | cosine distance | unreleased
|
||||
|
||||
### Halfvec Functions
|
||||
|
||||
Function | Description | Added
|
||||
--- | --- | ---
|
||||
cosine_distance(halfvec, halfvec) → double precision | cosine distance | unreleased
|
||||
inner_product(halfvec, halfvec) → double precision | inner product | unreleased
|
||||
l2_distance(halfvec, halfvec) → double precision | Euclidean distance | unreleased
|
||||
l1_distance(halfvec, halfvec) → double precision | taxicab distance | unreleased
|
||||
quantize_binary(halfvec) → bit | quantize | unreleased
|
||||
subvector(halfvec, integer, integer) → halfvec | subvector | unreleased
|
||||
|
||||
### Bit Type
|
||||
|
||||
Each bit vector takes `dimensions / 8 + 8` bytes of storage. See the [Postgres docs](https://www.postgresql.org/docs/current/datatype-bit.html) for more info.
|
||||
|
||||
### Bit Operators
|
||||
|
||||
Operator | Description | Added
|
||||
--- | --- | ---
|
||||
<~> | Hamming distance | unreleased
|
||||
<%> | Jaccard distance | unreleased
|
||||
|
||||
### Bit Functions
|
||||
|
||||
Function | Description | Added
|
||||
--- | --- | ---
|
||||
hamming_distance(bit, bit) → double precision | Hamming distance | unreleased
|
||||
jaccard_distance(bit, bit) → double precision | Jaccard distance | unreleased
|
||||
|
||||
### Sparsevec Type
|
||||
|
||||
Each sparse vector takes `8 * non-zero elements + 16` bytes of storage. Each element is a single-precision floating-point number, and all elements must be finite (no `NaN`, `Infinity` or `-Infinity`). Sparse vectors can have up to 100,000 dimensions.
|
||||
|
||||
### Sparsevec Operators
|
||||
|
||||
Operator | Description | Added
|
||||
--- | --- | ---
|
||||
<-> | Euclidean distance | unreleased
|
||||
<#> | negative inner product | unreleased
|
||||
<=> | cosine distance | unreleased
|
||||
|
||||
### Sparsevec Functions
|
||||
|
||||
Function | Description | Added
|
||||
--- | --- | ---
|
||||
cosine_distance(sparsevec, sparsevec) → double precision | cosine distance | unreleased
|
||||
inner_product(sparsevec, sparsevec) → double precision | inner product | unreleased
|
||||
l2_distance(sparsevec, sparsevec) → double precision | Euclidean distance | unreleased
|
||||
l1_distance(sparsevec, sparsevec) → double precision | taxicab distance | unreleased
|
||||
|
||||
## Installation Notes - Linux and Mac
|
||||
|
||||
### Postgres Location
|
||||
|
||||
If your machine has multiple Postgres installations, specify the path to [pg_config](https://www.postgresql.org/docs/current/app-pgconfig.html) with:
|
||||
|
||||
```sh
|
||||
export PG_CONFIG=/Applications/Postgres.app/Contents/Versions/latest/bin/pg_config
|
||||
export PG_CONFIG=/Library/PostgreSQL/16/bin/pg_config
|
||||
```
|
||||
|
||||
Then re-run the installation instructions (run `make clean` before `make` if needed). If `sudo` is needed for `make install`, use:
|
||||
@@ -379,6 +836,14 @@ Then re-run the installation instructions (run `make clean` before `make` if nee
|
||||
sudo --preserve-env=PG_CONFIG make install
|
||||
```
|
||||
|
||||
A few common paths on Mac are:
|
||||
|
||||
- EDB installer - `/Library/PostgreSQL/16/bin/pg_config`
|
||||
- Homebrew (arm64) - `/opt/homebrew/opt/postgresql@16/bin/pg_config`
|
||||
- Homebrew (x86-64) - `/usr/local/opt/postgresql@16/bin/pg_config`
|
||||
|
||||
Note: Replace `16` with your Postgres server version
|
||||
|
||||
### Missing Header
|
||||
|
||||
If compilation fails with `fatal error: postgres.h: No such file or directory`, make sure Postgres development files are installed on the server.
|
||||
@@ -386,41 +851,53 @@ If compilation fails with `fatal error: postgres.h: No such file or directory`,
|
||||
For Ubuntu and Debian, use:
|
||||
|
||||
```sh
|
||||
sudo apt install postgresql-server-dev-15
|
||||
sudo apt install postgresql-server-dev-16
|
||||
```
|
||||
|
||||
Note: Replace `15` with your Postgres server version
|
||||
Note: Replace `16` with your Postgres server version
|
||||
|
||||
### Windows
|
||||
### Missing SDK
|
||||
|
||||
Support for Windows is currently experimental. Use `nmake` to build:
|
||||
If compilation fails and the output includes `warning: no such sysroot directory` on Mac, reinstall Xcode Command Line Tools.
|
||||
|
||||
```cmd
|
||||
set "PGROOT=C:\Program Files\PostgreSQL\15"
|
||||
git clone --branch v0.4.4 https://github.com/pgvector/pgvector.git
|
||||
cd pgvector
|
||||
nmake /F Makefile.win
|
||||
nmake /F Makefile.win install
|
||||
### Portability
|
||||
|
||||
By default, pgvector compiles with `-march=native` on some platforms for best performance. However, this can lead to `Illegal instruction` errors if trying to run the compiled extension on a different machine.
|
||||
|
||||
To compile for portability, use:
|
||||
|
||||
```sh
|
||||
make OPTFLAGS=""
|
||||
```
|
||||
|
||||
## Installation Notes - Windows
|
||||
|
||||
### Missing Header
|
||||
|
||||
If compilation fails with `Cannot open include file: 'postgres.h': No such file or directory`, make sure `PGROOT` is correct.
|
||||
|
||||
### Permissions
|
||||
|
||||
If installation fails with `Access is denied`, re-run the installation instructions as an administrator.
|
||||
|
||||
## Additional Installation Methods
|
||||
|
||||
### Docker
|
||||
|
||||
Get the [Docker image](https://hub.docker.com/r/ankane/pgvector) with:
|
||||
Get the [Docker image](https://hub.docker.com/r/pgvector/pgvector) with:
|
||||
|
||||
```sh
|
||||
docker pull ankane/pgvector
|
||||
docker pull pgvector/pgvector:pg16
|
||||
```
|
||||
|
||||
This adds pgvector to the [Postgres image](https://hub.docker.com/_/postgres) (run it the same way).
|
||||
This adds pgvector to the [Postgres image](https://hub.docker.com/_/postgres) (replace `16` with your Postgres server version, and run it the same way).
|
||||
|
||||
You can also build the image manually:
|
||||
|
||||
```sh
|
||||
git clone --branch v0.4.4 https://github.com/pgvector/pgvector.git
|
||||
git clone --branch v0.6.2 https://github.com/pgvector/pgvector.git
|
||||
cd pgvector
|
||||
docker build --build-arg PG_MAJOR=15 -t myuser/pgvector .
|
||||
docker build --build-arg PG_MAJOR=16 -t myuser/pgvector .
|
||||
```
|
||||
|
||||
### Homebrew
|
||||
@@ -446,22 +923,37 @@ pgxn install vector
|
||||
Debian and Ubuntu packages are available from the [PostgreSQL APT Repository](https://wiki.postgresql.org/wiki/Apt). Follow the [setup instructions](https://wiki.postgresql.org/wiki/Apt#Quickstart) and run:
|
||||
|
||||
```sh
|
||||
sudo apt install postgresql-15-pgvector
|
||||
sudo apt install postgresql-16-pgvector
|
||||
```
|
||||
|
||||
Note: Replace `15` with your Postgres server version
|
||||
Note: Replace `16` with your Postgres server version
|
||||
|
||||
### Yum
|
||||
|
||||
RPM packages are available from the [PostgreSQL Yum Repository](https://yum.postgresql.org/). Follow the [setup instructions](https://www.postgresql.org/download/linux/redhat/) for your distribution and run:
|
||||
|
||||
```sh
|
||||
sudo yum install pgvector_15
|
||||
sudo yum install pgvector_16
|
||||
# or
|
||||
sudo dnf install pgvector_15
|
||||
sudo dnf install pgvector_16
|
||||
```
|
||||
|
||||
Note: Replace `15` with your Postgres server version
|
||||
Note: Replace `16` with your Postgres server version
|
||||
|
||||
### pkg
|
||||
|
||||
Install the FreeBSD package with:
|
||||
|
||||
```sh
|
||||
pkg install postgresql15-pg_vector
|
||||
```
|
||||
|
||||
or the port with:
|
||||
|
||||
```sh
|
||||
cd /usr/ports/databases/pgvector
|
||||
make install
|
||||
```
|
||||
|
||||
### conda-forge
|
||||
|
||||
@@ -481,44 +973,48 @@ Download the [latest release](https://postgresapp.com/downloads.html) with Postg
|
||||
|
||||
pgvector is available on [these providers](https://github.com/pgvector/pgvector/issues/54).
|
||||
|
||||
To request a new extension on other providers:
|
||||
|
||||
- Google Cloud SQL - vote or comment on [this page](https://issuetracker.google.com/issues/265172065)
|
||||
- DigitalOcean Managed Databases - vote or comment on [this page](https://ideas.digitalocean.com/managed-database/p/pgvector-extension-for-postgresql)
|
||||
- Heroku Postgres - vote or comment on [this page](https://github.com/heroku/roadmap/issues/156)
|
||||
|
||||
## Upgrading
|
||||
|
||||
Install the latest version and run:
|
||||
[Install](#installation) the latest version (use the same method as the original installation). Then in each database you want to upgrade, run:
|
||||
|
||||
```sql
|
||||
ALTER EXTENSION vector UPDATE;
|
||||
```
|
||||
|
||||
## Upgrade Notes
|
||||
|
||||
### 0.4.0
|
||||
|
||||
If upgrading with Postgres < 13, remove this line from `sql/vector--0.3.2--0.4.0.sql`:
|
||||
You can check the version in the current database with:
|
||||
|
||||
```sql
|
||||
ALTER TYPE vector SET (STORAGE = extended);
|
||||
SELECT extversion FROM pg_extension WHERE extname = 'vector';
|
||||
```
|
||||
|
||||
## Upgrade Notes
|
||||
|
||||
### 0.6.0
|
||||
|
||||
#### Postgres 12
|
||||
|
||||
If upgrading with Postgres 12, remove this line from `sql/vector--0.5.1--0.6.0.sql`:
|
||||
|
||||
```sql
|
||||
ALTER TYPE vector SET (STORAGE = external);
|
||||
```
|
||||
|
||||
Then run `make install` and `ALTER EXTENSION vector UPDATE;`.
|
||||
|
||||
### 0.3.1
|
||||
#### Docker
|
||||
|
||||
If upgrading from 0.2.7 or 0.3.0, recreate all `ivfflat` indexes after upgrading to ensure all data is indexed.
|
||||
The Docker image is now published in the `pgvector` org, and there are tags for each supported version of Postgres (rather than a `latest` tag).
|
||||
|
||||
```sql
|
||||
-- Postgres 12+
|
||||
REINDEX INDEX CONCURRENTLY index_name;
|
||||
```sh
|
||||
docker pull pgvector/pgvector:pg16
|
||||
# or
|
||||
docker pull pgvector/pgvector:0.6.0-pg16
|
||||
```
|
||||
|
||||
-- Postgres < 12
|
||||
CREATE INDEX CONCURRENTLY temp_name ON table USING ivfflat (column opclass);
|
||||
DROP INDEX CONCURRENTLY index_name;
|
||||
ALTER INDEX temp_name RENAME TO index_name;
|
||||
Also, if you’ve increased `maintenance_work_mem`, make sure `--shm-size` is at least that size to avoid an error with parallel HNSW index builds.
|
||||
|
||||
```sh
|
||||
docker run --shm-size=1g ...
|
||||
```
|
||||
|
||||
## Thanks
|
||||
@@ -527,9 +1023,10 @@ Thanks to:
|
||||
|
||||
- [PASE: PostgreSQL Ultra-High-Dimensional Approximate Nearest Neighbor Search Extension](https://dl.acm.org/doi/pdf/10.1145/3318464.3386131)
|
||||
- [Faiss: A Library for Efficient Similarity Search and Clustering of Dense Vectors](https://github.com/facebookresearch/faiss)
|
||||
- [Using the Triangle Inequality to Accelerate k-means](https://www.aaai.org/Papers/ICML/2003/ICML03-022.pdf)
|
||||
- [Using the Triangle Inequality to Accelerate k-means](https://cdn.aaai.org/ICML/2003/ICML03-022.pdf)
|
||||
- [k-means++: The Advantage of Careful Seeding](https://theory.stanford.edu/~sergei/papers/kMeansPP-soda.pdf)
|
||||
- [Concept Decompositions for Large Sparse Text Data using Clustering](https://www.cs.utexas.edu/users/inderjit/public_papers/concept_mlj.pdf)
|
||||
- [Efficient and Robust Approximate Nearest Neighbor Search using Hierarchical Navigable Small World Graphs](https://arxiv.org/ftp/arxiv/papers/1603/1603.09320.pdf)
|
||||
|
||||
## History
|
||||
|
||||
@@ -563,18 +1060,36 @@ make prove_installcheck # TAP tests
|
||||
To run single tests:
|
||||
|
||||
```sh
|
||||
make installcheck REGRESS=functions # regression test
|
||||
make prove_installcheck PROVE_TESTS=test/t/001_wal.pl # TAP test
|
||||
make installcheck REGRESS=functions # regression test
|
||||
make prove_installcheck PROVE_TESTS=test/t/001_ivfflat_wal.pl # TAP test
|
||||
```
|
||||
|
||||
To enable assertions:
|
||||
|
||||
```sh
|
||||
make clean && PG_CFLAGS="-DUSE_ASSERT_CHECKING" make && make install
|
||||
```
|
||||
|
||||
To enable benchmarking:
|
||||
|
||||
```sh
|
||||
make clean && PG_CFLAGS=-DIVFFLAT_BENCH make && make install
|
||||
make clean && PG_CFLAGS="-DIVFFLAT_BENCH" make && make install
|
||||
```
|
||||
|
||||
To show memory usage:
|
||||
|
||||
```sh
|
||||
make clean && PG_CFLAGS="-DHNSW_MEMORY -DIVFFLAT_MEMORY" make && make install
|
||||
```
|
||||
|
||||
To get k-means metrics:
|
||||
|
||||
```sh
|
||||
make clean && PG_CFLAGS="-DIVFFLAT_KMEANS_DEBUG" make && make install
|
||||
```
|
||||
|
||||
Resources for contributors
|
||||
|
||||
- [Extension Building Infrastructure](https://www.postgresql.org/docs/current/extend-pgxs.html)
|
||||
- [Index Access Method Interface Definition](https://www.postgresql.org/docs/current/indexam.html)
|
||||
- [Generic WAL Records](https://www.postgresql.org/docs/13/generic-wal.html)
|
||||
- [Generic WAL Records](https://www.postgresql.org/docs/current/generic-wal.html)
|
||||
|
||||
43
sql/vector--0.4.4--0.5.0.sql
Normal file
43
sql/vector--0.4.4--0.5.0.sql
Normal file
@@ -0,0 +1,43 @@
|
||||
-- complain if script is sourced in psql, rather than via CREATE EXTENSION
|
||||
\echo Use "ALTER EXTENSION vector UPDATE TO '0.5.0'" to load this file. \quit
|
||||
|
||||
CREATE FUNCTION l1_distance(vector, vector) RETURNS float8
|
||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
CREATE FUNCTION vector_mul(vector, vector) RETURNS vector
|
||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
CREATE OPERATOR * (
|
||||
LEFTARG = vector, RIGHTARG = vector, PROCEDURE = vector_mul,
|
||||
COMMUTATOR = *
|
||||
);
|
||||
|
||||
CREATE AGGREGATE sum(vector) (
|
||||
SFUNC = vector_add,
|
||||
STYPE = vector,
|
||||
COMBINEFUNC = vector_add,
|
||||
PARALLEL = SAFE
|
||||
);
|
||||
|
||||
CREATE FUNCTION hnswhandler(internal) RETURNS index_am_handler
|
||||
AS 'MODULE_PATHNAME' LANGUAGE C;
|
||||
|
||||
CREATE ACCESS METHOD hnsw TYPE INDEX HANDLER hnswhandler;
|
||||
|
||||
COMMENT ON ACCESS METHOD hnsw IS 'hnsw index access method';
|
||||
|
||||
CREATE OPERATOR CLASS vector_l2_ops
|
||||
FOR TYPE vector USING hnsw AS
|
||||
OPERATOR 1 <-> (vector, vector) FOR ORDER BY float_ops,
|
||||
FUNCTION 1 vector_l2_squared_distance(vector, vector);
|
||||
|
||||
CREATE OPERATOR CLASS vector_ip_ops
|
||||
FOR TYPE vector USING hnsw AS
|
||||
OPERATOR 1 <#> (vector, vector) FOR ORDER BY float_ops,
|
||||
FUNCTION 1 vector_negative_inner_product(vector, vector);
|
||||
|
||||
CREATE OPERATOR CLASS vector_cosine_ops
|
||||
FOR TYPE vector USING hnsw AS
|
||||
OPERATOR 1 <=> (vector, vector) FOR ORDER BY float_ops,
|
||||
FUNCTION 1 vector_negative_inner_product(vector, vector),
|
||||
FUNCTION 2 vector_norm(vector);
|
||||
2
sql/vector--0.5.0--0.5.1.sql
Normal file
2
sql/vector--0.5.0--0.5.1.sql
Normal file
@@ -0,0 +1,2 @@
|
||||
-- complain if script is sourced in psql, rather than via CREATE EXTENSION
|
||||
\echo Use "ALTER EXTENSION vector UPDATE TO '0.5.1'" to load this file. \quit
|
||||
5
sql/vector--0.5.1--0.6.0.sql
Normal file
5
sql/vector--0.5.1--0.6.0.sql
Normal file
@@ -0,0 +1,5 @@
|
||||
-- complain if script is sourced in psql, rather than via CREATE EXTENSION
|
||||
\echo Use "ALTER EXTENSION vector UPDATE TO '0.6.0'" to load this file. \quit
|
||||
|
||||
-- remove this single line for Postgres < 13
|
||||
ALTER TYPE vector SET (STORAGE = external);
|
||||
16
sql/vector--0.6.0--0.6.1.sql
Normal file
16
sql/vector--0.6.0--0.6.1.sql
Normal file
@@ -0,0 +1,16 @@
|
||||
-- complain if script is sourced in psql, rather than via CREATE EXTENSION
|
||||
\echo Use "ALTER EXTENSION vector UPDATE TO '0.6.1'" to load this file. \quit
|
||||
|
||||
DROP OPERATOR - (vector, vector);
|
||||
|
||||
CREATE OPERATOR - (
|
||||
LEFTARG = vector, RIGHTARG = vector, PROCEDURE = vector_sub
|
||||
);
|
||||
|
||||
ALTER OPERATOR <= (vector, vector) SET (
|
||||
RESTRICT = scalarlesel, JOIN = scalarlejoinsel
|
||||
);
|
||||
|
||||
ALTER OPERATOR >= (vector, vector) SET (
|
||||
RESTRICT = scalargesel, JOIN = scalargejoinsel
|
||||
);
|
||||
2
sql/vector--0.6.1--0.6.2.sql
Normal file
2
sql/vector--0.6.1--0.6.2.sql
Normal file
@@ -0,0 +1,2 @@
|
||||
-- complain if script is sourced in psql, rather than via CREATE EXTENSION
|
||||
\echo Use "ALTER EXTENSION vector UPDATE TO '0.6.2'" to load this file. \quit
|
||||
262
sql/vector--0.6.2--0.7.0.sql
Normal file
262
sql/vector--0.6.2--0.7.0.sql
Normal file
@@ -0,0 +1,262 @@
|
||||
-- complain if script is sourced in psql, rather than via CREATE EXTENSION
|
||||
\echo Use "ALTER EXTENSION vector UPDATE TO '0.7.0'" to load this file. \quit
|
||||
|
||||
CREATE FUNCTION quantize_binary(vector) RETURNS bit
|
||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
CREATE FUNCTION subvector(vector, int, int) RETURNS vector
|
||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
CREATE FUNCTION hamming_distance(bit, bit) RETURNS float8
|
||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
CREATE FUNCTION jaccard_distance(bit, bit) RETURNS float8
|
||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
CREATE OPERATOR <~> (
|
||||
LEFTARG = bit, RIGHTARG = bit, PROCEDURE = hamming_distance,
|
||||
COMMUTATOR = '<~>'
|
||||
);
|
||||
|
||||
CREATE OPERATOR <%> (
|
||||
LEFTARG = bit, RIGHTARG = bit, PROCEDURE = jaccard_distance,
|
||||
COMMUTATOR = '<%>'
|
||||
);
|
||||
|
||||
CREATE OPERATOR CLASS bit_hamming_ops
|
||||
FOR TYPE bit USING hnsw AS
|
||||
OPERATOR 1 <~> (bit, bit) FOR ORDER BY float_ops,
|
||||
FUNCTION 1 hamming_distance(bit, bit);
|
||||
|
||||
CREATE OPERATOR CLASS bit_jaccard_ops
|
||||
FOR TYPE bit USING hnsw AS
|
||||
OPERATOR 1 <%> (bit, bit) FOR ORDER BY float_ops,
|
||||
FUNCTION 1 jaccard_distance(bit, bit);
|
||||
|
||||
CREATE TYPE halfvec;
|
||||
|
||||
CREATE FUNCTION halfvec_in(cstring, oid, integer) RETURNS halfvec
|
||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
CREATE FUNCTION halfvec_out(halfvec) RETURNS cstring
|
||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
CREATE FUNCTION halfvec_typmod_in(cstring[]) RETURNS integer
|
||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
CREATE FUNCTION halfvec_recv(internal, oid, integer) RETURNS halfvec
|
||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
CREATE FUNCTION halfvec_send(halfvec) RETURNS bytea
|
||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
CREATE TYPE halfvec (
|
||||
INPUT = halfvec_in,
|
||||
OUTPUT = halfvec_out,
|
||||
TYPMOD_IN = halfvec_typmod_in,
|
||||
RECEIVE = halfvec_recv,
|
||||
SEND = halfvec_send,
|
||||
STORAGE = external
|
||||
);
|
||||
|
||||
CREATE FUNCTION l2_distance(halfvec, halfvec) RETURNS float8
|
||||
AS 'MODULE_PATHNAME', 'halfvec_l2_distance' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
CREATE FUNCTION inner_product(halfvec, halfvec) RETURNS float8
|
||||
AS 'MODULE_PATHNAME', 'halfvec_inner_product' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
CREATE FUNCTION cosine_distance(halfvec, halfvec) RETURNS float8
|
||||
AS 'MODULE_PATHNAME', 'halfvec_cosine_distance' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
CREATE FUNCTION l1_distance(halfvec, halfvec) RETURNS float8
|
||||
AS 'MODULE_PATHNAME', 'halfvec_l1_distance' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
CREATE FUNCTION halfvec_norm(halfvec) RETURNS float8
|
||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
CREATE FUNCTION quantize_binary(halfvec) RETURNS bit
|
||||
AS 'MODULE_PATHNAME', 'halfvec_quantize_binary' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
CREATE FUNCTION subvector(halfvec, int, int) RETURNS halfvec
|
||||
AS 'MODULE_PATHNAME', 'halfvec_subvector' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
CREATE FUNCTION halfvec_l2_squared_distance(halfvec, halfvec) RETURNS float8
|
||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
CREATE FUNCTION halfvec_negative_inner_product(halfvec, halfvec) RETURNS float8
|
||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
CREATE FUNCTION halfvec(halfvec, integer, boolean) RETURNS halfvec
|
||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
CREATE FUNCTION array_to_halfvec(integer[], integer, boolean) RETURNS halfvec
|
||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
CREATE FUNCTION array_to_halfvec(real[], integer, boolean) RETURNS halfvec
|
||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
CREATE FUNCTION array_to_halfvec(double precision[], integer, boolean) RETURNS halfvec
|
||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
CREATE FUNCTION array_to_halfvec(numeric[], integer, boolean) RETURNS halfvec
|
||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
CREATE FUNCTION halfvec_to_float4(halfvec, integer, boolean) RETURNS real[]
|
||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
CREATE CAST (halfvec AS halfvec)
|
||||
WITH FUNCTION halfvec(halfvec, integer, boolean) AS IMPLICIT;
|
||||
|
||||
CREATE CAST (halfvec AS real[])
|
||||
WITH FUNCTION halfvec_to_float4(halfvec, integer, boolean) AS IMPLICIT;
|
||||
|
||||
CREATE CAST (integer[] AS halfvec)
|
||||
WITH FUNCTION array_to_halfvec(integer[], integer, boolean) AS ASSIGNMENT;
|
||||
|
||||
CREATE CAST (real[] AS halfvec)
|
||||
WITH FUNCTION array_to_halfvec(real[], integer, boolean) AS ASSIGNMENT;
|
||||
|
||||
CREATE CAST (double precision[] AS halfvec)
|
||||
WITH FUNCTION array_to_halfvec(double precision[], integer, boolean) AS ASSIGNMENT;
|
||||
|
||||
CREATE CAST (numeric[] AS halfvec)
|
||||
WITH FUNCTION array_to_halfvec(numeric[], integer, boolean) AS ASSIGNMENT;
|
||||
|
||||
CREATE OPERATOR <-> (
|
||||
LEFTARG = halfvec, RIGHTARG = halfvec, PROCEDURE = l2_distance,
|
||||
COMMUTATOR = '<->'
|
||||
);
|
||||
|
||||
CREATE OPERATOR <#> (
|
||||
LEFTARG = halfvec, RIGHTARG = halfvec, PROCEDURE = halfvec_negative_inner_product,
|
||||
COMMUTATOR = '<#>'
|
||||
);
|
||||
|
||||
CREATE OPERATOR <=> (
|
||||
LEFTARG = halfvec, RIGHTARG = halfvec, PROCEDURE = cosine_distance,
|
||||
COMMUTATOR = '<=>'
|
||||
);
|
||||
|
||||
CREATE OPERATOR CLASS halfvec_l2_ops
|
||||
FOR TYPE halfvec USING hnsw AS
|
||||
OPERATOR 1 <-> (halfvec, halfvec) FOR ORDER BY float_ops,
|
||||
FUNCTION 1 halfvec_l2_squared_distance(halfvec, halfvec);
|
||||
|
||||
CREATE OPERATOR CLASS halfvec_ip_ops
|
||||
FOR TYPE halfvec USING hnsw AS
|
||||
OPERATOR 1 <#> (halfvec, halfvec) FOR ORDER BY float_ops,
|
||||
FUNCTION 1 halfvec_negative_inner_product(halfvec, halfvec);
|
||||
|
||||
CREATE OPERATOR CLASS halfvec_cosine_ops
|
||||
FOR TYPE halfvec USING hnsw AS
|
||||
OPERATOR 1 <=> (halfvec, halfvec) FOR ORDER BY float_ops,
|
||||
FUNCTION 1 halfvec_negative_inner_product(halfvec, halfvec),
|
||||
FUNCTION 2 halfvec_norm(halfvec);
|
||||
|
||||
CREATE FUNCTION halfvec_to_vector(halfvec, integer, boolean) RETURNS vector
|
||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
CREATE FUNCTION vector_to_halfvec(vector, integer, boolean) RETURNS halfvec
|
||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
CREATE CAST (halfvec AS vector)
|
||||
WITH FUNCTION halfvec_to_vector(halfvec, integer, boolean) AS IMPLICIT;
|
||||
|
||||
CREATE CAST (vector AS halfvec)
|
||||
WITH FUNCTION vector_to_halfvec(vector, integer, boolean) AS IMPLICIT;
|
||||
|
||||
CREATE TYPE sparsevec;
|
||||
|
||||
CREATE FUNCTION sparsevec_in(cstring, oid, integer) RETURNS sparsevec
|
||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
CREATE FUNCTION sparsevec_out(sparsevec) RETURNS cstring
|
||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
CREATE FUNCTION sparsevec_typmod_in(cstring[]) RETURNS integer
|
||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
CREATE FUNCTION sparsevec_recv(internal, oid, integer) RETURNS sparsevec
|
||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
CREATE FUNCTION sparsevec_send(sparsevec) RETURNS bytea
|
||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
CREATE TYPE sparsevec (
|
||||
INPUT = sparsevec_in,
|
||||
OUTPUT = sparsevec_out,
|
||||
TYPMOD_IN = sparsevec_typmod_in,
|
||||
RECEIVE = sparsevec_recv,
|
||||
SEND = sparsevec_send,
|
||||
STORAGE = external
|
||||
);
|
||||
|
||||
CREATE FUNCTION l2_distance(sparsevec, sparsevec) RETURNS float8
|
||||
AS 'MODULE_PATHNAME', 'sparsevec_l2_distance' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
CREATE FUNCTION inner_product(sparsevec, sparsevec) RETURNS float8
|
||||
AS 'MODULE_PATHNAME', 'sparsevec_inner_product' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
CREATE FUNCTION cosine_distance(sparsevec, sparsevec) RETURNS float8
|
||||
AS 'MODULE_PATHNAME', 'sparsevec_cosine_distance' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
CREATE FUNCTION sparsevec_norm(sparsevec) RETURNS float8
|
||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
CREATE FUNCTION subvector(sparsevec, int, int) RETURNS sparsevec
|
||||
AS 'MODULE_PATHNAME', 'sparsevec_subvector' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
CREATE FUNCTION sparsevec_l2_squared_distance(sparsevec, sparsevec) RETURNS float8
|
||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
CREATE FUNCTION sparsevec_negative_inner_product(sparsevec, sparsevec) RETURNS float8
|
||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
CREATE FUNCTION sparsevec(sparsevec, integer, boolean) RETURNS sparsevec
|
||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
CREATE FUNCTION vector_to_sparsevec(vector, integer, boolean) RETURNS sparsevec
|
||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
CREATE FUNCTION sparsevec_to_vector(sparsevec, integer, boolean) RETURNS vector
|
||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
CREATE CAST (sparsevec AS sparsevec)
|
||||
WITH FUNCTION sparsevec(sparsevec, integer, boolean) AS IMPLICIT;
|
||||
|
||||
CREATE CAST (sparsevec AS vector)
|
||||
WITH FUNCTION sparsevec_to_vector(sparsevec, integer, boolean) AS IMPLICIT;
|
||||
|
||||
CREATE CAST (vector AS sparsevec)
|
||||
WITH FUNCTION vector_to_sparsevec(vector, integer, boolean) AS IMPLICIT;
|
||||
|
||||
CREATE OPERATOR <-> (
|
||||
LEFTARG = sparsevec, RIGHTARG = sparsevec, PROCEDURE = l2_distance,
|
||||
COMMUTATOR = '<->'
|
||||
);
|
||||
|
||||
CREATE OPERATOR <#> (
|
||||
LEFTARG = sparsevec, RIGHTARG = sparsevec, PROCEDURE = sparsevec_negative_inner_product,
|
||||
COMMUTATOR = '<#>'
|
||||
);
|
||||
|
||||
CREATE OPERATOR <=> (
|
||||
LEFTARG = sparsevec, RIGHTARG = sparsevec, PROCEDURE = cosine_distance,
|
||||
COMMUTATOR = '<=>'
|
||||
);
|
||||
|
||||
CREATE OPERATOR CLASS sparsevec_l2_ops
|
||||
FOR TYPE sparsevec USING hnsw AS
|
||||
OPERATOR 1 <-> (sparsevec, sparsevec) FOR ORDER BY float_ops,
|
||||
FUNCTION 1 sparsevec_l2_squared_distance(sparsevec, sparsevec);
|
||||
|
||||
CREATE OPERATOR CLASS sparsevec_ip_ops
|
||||
FOR TYPE sparsevec USING hnsw AS
|
||||
OPERATOR 1 <#> (sparsevec, sparsevec) FOR ORDER BY float_ops,
|
||||
FUNCTION 1 sparsevec_negative_inner_product(sparsevec, sparsevec);
|
||||
|
||||
CREATE OPERATOR CLASS sparsevec_cosine_ops
|
||||
FOR TYPE sparsevec USING hnsw AS
|
||||
OPERATOR 1 <=> (sparsevec, sparsevec) FOR ORDER BY float_ops,
|
||||
FUNCTION 1 sparsevec_negative_inner_product(sparsevec, sparsevec),
|
||||
FUNCTION 2 sparsevec_norm(sparsevec);
|
||||
362
sql/vector.sql
362
sql/vector.sql
@@ -1,7 +1,7 @@
|
||||
-- complain if script is sourced in psql, rather than via CREATE EXTENSION
|
||||
\echo Use "CREATE EXTENSION vector" to load this file. \quit
|
||||
|
||||
-- type
|
||||
-- vector type
|
||||
|
||||
CREATE TYPE vector;
|
||||
|
||||
@@ -26,10 +26,10 @@ CREATE TYPE vector (
|
||||
TYPMOD_IN = vector_typmod_in,
|
||||
RECEIVE = vector_recv,
|
||||
SEND = vector_send,
|
||||
STORAGE = extended
|
||||
STORAGE = external
|
||||
);
|
||||
|
||||
-- functions
|
||||
-- vector functions
|
||||
|
||||
CREATE FUNCTION l2_distance(vector, vector) RETURNS float8
|
||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
@@ -40,6 +40,9 @@ CREATE FUNCTION inner_product(vector, vector) RETURNS float8
|
||||
CREATE FUNCTION cosine_distance(vector, vector) RETURNS float8
|
||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
CREATE FUNCTION l1_distance(vector, vector) RETURNS float8
|
||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
CREATE FUNCTION vector_dims(vector) RETURNS integer
|
||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
@@ -52,7 +55,16 @@ CREATE FUNCTION vector_add(vector, vector) RETURNS vector
|
||||
CREATE FUNCTION vector_sub(vector, vector) RETURNS vector
|
||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
-- private functions
|
||||
CREATE FUNCTION vector_mul(vector, vector) RETURNS vector
|
||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
CREATE FUNCTION quantize_binary(vector) RETURNS bit
|
||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
CREATE FUNCTION subvector(vector, int, int) RETURNS vector
|
||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
-- vector private functions
|
||||
|
||||
CREATE FUNCTION vector_lt(vector, vector) RETURNS bool
|
||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
@@ -93,7 +105,7 @@ CREATE FUNCTION vector_avg(double precision[]) RETURNS vector
|
||||
CREATE FUNCTION vector_combine(double precision[], double precision[]) RETURNS double precision[]
|
||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
-- aggregates
|
||||
-- vector aggregates
|
||||
|
||||
CREATE AGGREGATE avg(vector) (
|
||||
SFUNC = vector_accum,
|
||||
@@ -104,7 +116,14 @@ CREATE AGGREGATE avg(vector) (
|
||||
PARALLEL = SAFE
|
||||
);
|
||||
|
||||
-- cast functions
|
||||
CREATE AGGREGATE sum(vector) (
|
||||
SFUNC = vector_add,
|
||||
STYPE = vector,
|
||||
COMBINEFUNC = vector_add,
|
||||
PARALLEL = SAFE
|
||||
);
|
||||
|
||||
-- vector cast functions
|
||||
|
||||
CREATE FUNCTION vector(vector, integer, boolean) RETURNS vector
|
||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
@@ -124,7 +143,7 @@ CREATE FUNCTION array_to_vector(numeric[], integer, boolean) RETURNS vector
|
||||
CREATE FUNCTION vector_to_float4(vector, integer, boolean) RETURNS real[]
|
||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
-- casts
|
||||
-- vector casts
|
||||
|
||||
CREATE CAST (vector AS vector)
|
||||
WITH FUNCTION vector(vector, integer, boolean) AS IMPLICIT;
|
||||
@@ -144,7 +163,7 @@ CREATE CAST (double precision[] AS vector)
|
||||
CREATE CAST (numeric[] AS vector)
|
||||
WITH FUNCTION array_to_vector(numeric[], integer, boolean) AS ASSIGNMENT;
|
||||
|
||||
-- operators
|
||||
-- vector operators
|
||||
|
||||
CREATE OPERATOR <-> (
|
||||
LEFTARG = vector, RIGHTARG = vector, PROCEDURE = l2_distance,
|
||||
@@ -167,8 +186,12 @@ CREATE OPERATOR + (
|
||||
);
|
||||
|
||||
CREATE OPERATOR - (
|
||||
LEFTARG = vector, RIGHTARG = vector, PROCEDURE = vector_sub,
|
||||
COMMUTATOR = -
|
||||
LEFTARG = vector, RIGHTARG = vector, PROCEDURE = vector_sub
|
||||
);
|
||||
|
||||
CREATE OPERATOR * (
|
||||
LEFTARG = vector, RIGHTARG = vector, PROCEDURE = vector_mul,
|
||||
COMMUTATOR = *
|
||||
);
|
||||
|
||||
CREATE OPERATOR < (
|
||||
@@ -177,11 +200,10 @@ CREATE OPERATOR < (
|
||||
RESTRICT = scalarltsel, JOIN = scalarltjoinsel
|
||||
);
|
||||
|
||||
-- should use scalarlesel and scalarlejoinsel, but not supported in Postgres < 11
|
||||
CREATE OPERATOR <= (
|
||||
LEFTARG = vector, RIGHTARG = vector, PROCEDURE = vector_le,
|
||||
COMMUTATOR = >= , NEGATOR = > ,
|
||||
RESTRICT = scalarltsel, JOIN = scalarltjoinsel
|
||||
RESTRICT = scalarlesel, JOIN = scalarlejoinsel
|
||||
);
|
||||
|
||||
CREATE OPERATOR = (
|
||||
@@ -196,11 +218,10 @@ CREATE OPERATOR <> (
|
||||
RESTRICT = eqsel, JOIN = eqjoinsel
|
||||
);
|
||||
|
||||
-- should use scalargesel and scalargejoinsel, but not supported in Postgres < 11
|
||||
CREATE OPERATOR >= (
|
||||
LEFTARG = vector, RIGHTARG = vector, PROCEDURE = vector_ge,
|
||||
COMMUTATOR = <= , NEGATOR = < ,
|
||||
RESTRICT = scalargtsel, JOIN = scalargtjoinsel
|
||||
RESTRICT = scalargesel, JOIN = scalargejoinsel
|
||||
);
|
||||
|
||||
CREATE OPERATOR > (
|
||||
@@ -209,7 +230,7 @@ CREATE OPERATOR > (
|
||||
RESTRICT = scalargtsel, JOIN = scalargtjoinsel
|
||||
);
|
||||
|
||||
-- access method
|
||||
-- access methods
|
||||
|
||||
CREATE FUNCTION ivfflathandler(internal) RETURNS index_am_handler
|
||||
AS 'MODULE_PATHNAME' LANGUAGE C;
|
||||
@@ -218,7 +239,14 @@ CREATE ACCESS METHOD ivfflat TYPE INDEX HANDLER ivfflathandler;
|
||||
|
||||
COMMENT ON ACCESS METHOD ivfflat IS 'ivfflat index access method';
|
||||
|
||||
-- opclasses
|
||||
CREATE FUNCTION hnswhandler(internal) RETURNS index_am_handler
|
||||
AS 'MODULE_PATHNAME' LANGUAGE C;
|
||||
|
||||
CREATE ACCESS METHOD hnsw TYPE INDEX HANDLER hnswhandler;
|
||||
|
||||
COMMENT ON ACCESS METHOD hnsw IS 'hnsw index access method';
|
||||
|
||||
-- vector opclasses
|
||||
|
||||
CREATE OPERATOR CLASS vector_ops
|
||||
DEFAULT FOR TYPE vector USING btree AS
|
||||
@@ -249,3 +277,305 @@ CREATE OPERATOR CLASS vector_cosine_ops
|
||||
FUNCTION 2 vector_norm(vector),
|
||||
FUNCTION 3 vector_spherical_distance(vector, vector),
|
||||
FUNCTION 4 vector_norm(vector);
|
||||
|
||||
CREATE OPERATOR CLASS vector_l2_ops
|
||||
FOR TYPE vector USING hnsw AS
|
||||
OPERATOR 1 <-> (vector, vector) FOR ORDER BY float_ops,
|
||||
FUNCTION 1 vector_l2_squared_distance(vector, vector);
|
||||
|
||||
CREATE OPERATOR CLASS vector_ip_ops
|
||||
FOR TYPE vector USING hnsw AS
|
||||
OPERATOR 1 <#> (vector, vector) FOR ORDER BY float_ops,
|
||||
FUNCTION 1 vector_negative_inner_product(vector, vector);
|
||||
|
||||
CREATE OPERATOR CLASS vector_cosine_ops
|
||||
FOR TYPE vector USING hnsw AS
|
||||
OPERATOR 1 <=> (vector, vector) FOR ORDER BY float_ops,
|
||||
FUNCTION 1 vector_negative_inner_product(vector, vector),
|
||||
FUNCTION 2 vector_norm(vector);
|
||||
|
||||
-- bit functions
|
||||
|
||||
CREATE FUNCTION hamming_distance(bit, bit) RETURNS float8
|
||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
CREATE FUNCTION jaccard_distance(bit, bit) RETURNS float8
|
||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
CREATE OPERATOR <~> (
|
||||
LEFTARG = bit, RIGHTARG = bit, PROCEDURE = hamming_distance,
|
||||
COMMUTATOR = '<~>'
|
||||
);
|
||||
|
||||
CREATE OPERATOR <%> (
|
||||
LEFTARG = bit, RIGHTARG = bit, PROCEDURE = jaccard_distance,
|
||||
COMMUTATOR = '<%>'
|
||||
);
|
||||
|
||||
CREATE OPERATOR CLASS bit_hamming_ops
|
||||
FOR TYPE bit USING hnsw AS
|
||||
OPERATOR 1 <~> (bit, bit) FOR ORDER BY float_ops,
|
||||
FUNCTION 1 hamming_distance(bit, bit);
|
||||
|
||||
CREATE OPERATOR CLASS bit_jaccard_ops
|
||||
FOR TYPE bit USING hnsw AS
|
||||
OPERATOR 1 <%> (bit, bit) FOR ORDER BY float_ops,
|
||||
FUNCTION 1 jaccard_distance(bit, bit);
|
||||
|
||||
-- halfvec type
|
||||
|
||||
CREATE TYPE halfvec;
|
||||
|
||||
CREATE FUNCTION halfvec_in(cstring, oid, integer) RETURNS halfvec
|
||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
CREATE FUNCTION halfvec_out(halfvec) RETURNS cstring
|
||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
CREATE FUNCTION halfvec_typmod_in(cstring[]) RETURNS integer
|
||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
CREATE FUNCTION halfvec_recv(internal, oid, integer) RETURNS halfvec
|
||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
CREATE FUNCTION halfvec_send(halfvec) RETURNS bytea
|
||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
CREATE TYPE halfvec (
|
||||
INPUT = halfvec_in,
|
||||
OUTPUT = halfvec_out,
|
||||
TYPMOD_IN = halfvec_typmod_in,
|
||||
RECEIVE = halfvec_recv,
|
||||
SEND = halfvec_send,
|
||||
STORAGE = external
|
||||
);
|
||||
|
||||
-- halfvec functions
|
||||
|
||||
CREATE FUNCTION l2_distance(halfvec, halfvec) RETURNS float8
|
||||
AS 'MODULE_PATHNAME', 'halfvec_l2_distance' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
CREATE FUNCTION inner_product(halfvec, halfvec) RETURNS float8
|
||||
AS 'MODULE_PATHNAME', 'halfvec_inner_product' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
CREATE FUNCTION cosine_distance(halfvec, halfvec) RETURNS float8
|
||||
AS 'MODULE_PATHNAME', 'halfvec_cosine_distance' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
CREATE FUNCTION l1_distance(halfvec, halfvec) RETURNS float8
|
||||
AS 'MODULE_PATHNAME', 'halfvec_l1_distance' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
CREATE FUNCTION halfvec_norm(halfvec) RETURNS float8
|
||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
CREATE FUNCTION quantize_binary(halfvec) RETURNS bit
|
||||
AS 'MODULE_PATHNAME', 'halfvec_quantize_binary' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
CREATE FUNCTION subvector(halfvec, int, int) RETURNS halfvec
|
||||
AS 'MODULE_PATHNAME', 'halfvec_subvector' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
-- halfvec private functions
|
||||
|
||||
CREATE FUNCTION halfvec_l2_squared_distance(halfvec, halfvec) RETURNS float8
|
||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
CREATE FUNCTION halfvec_negative_inner_product(halfvec, halfvec) RETURNS float8
|
||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
-- halfvec cast functions
|
||||
|
||||
CREATE FUNCTION halfvec(halfvec, integer, boolean) RETURNS halfvec
|
||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
CREATE FUNCTION array_to_halfvec(integer[], integer, boolean) RETURNS halfvec
|
||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
CREATE FUNCTION array_to_halfvec(real[], integer, boolean) RETURNS halfvec
|
||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
CREATE FUNCTION array_to_halfvec(double precision[], integer, boolean) RETURNS halfvec
|
||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
CREATE FUNCTION array_to_halfvec(numeric[], integer, boolean) RETURNS halfvec
|
||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
CREATE FUNCTION halfvec_to_float4(halfvec, integer, boolean) RETURNS real[]
|
||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
-- halfvec casts
|
||||
|
||||
CREATE CAST (halfvec AS halfvec)
|
||||
WITH FUNCTION halfvec(halfvec, integer, boolean) AS IMPLICIT;
|
||||
|
||||
CREATE CAST (halfvec AS real[])
|
||||
WITH FUNCTION halfvec_to_float4(halfvec, integer, boolean) AS IMPLICIT;
|
||||
|
||||
CREATE CAST (integer[] AS halfvec)
|
||||
WITH FUNCTION array_to_halfvec(integer[], integer, boolean) AS ASSIGNMENT;
|
||||
|
||||
CREATE CAST (real[] AS halfvec)
|
||||
WITH FUNCTION array_to_halfvec(real[], integer, boolean) AS ASSIGNMENT;
|
||||
|
||||
CREATE CAST (double precision[] AS halfvec)
|
||||
WITH FUNCTION array_to_halfvec(double precision[], integer, boolean) AS ASSIGNMENT;
|
||||
|
||||
CREATE CAST (numeric[] AS halfvec)
|
||||
WITH FUNCTION array_to_halfvec(numeric[], integer, boolean) AS ASSIGNMENT;
|
||||
|
||||
-- halfvec operators
|
||||
|
||||
CREATE OPERATOR <-> (
|
||||
LEFTARG = halfvec, RIGHTARG = halfvec, PROCEDURE = l2_distance,
|
||||
COMMUTATOR = '<->'
|
||||
);
|
||||
|
||||
CREATE OPERATOR <#> (
|
||||
LEFTARG = halfvec, RIGHTARG = halfvec, PROCEDURE = halfvec_negative_inner_product,
|
||||
COMMUTATOR = '<#>'
|
||||
);
|
||||
|
||||
CREATE OPERATOR <=> (
|
||||
LEFTARG = halfvec, RIGHTARG = halfvec, PROCEDURE = cosine_distance,
|
||||
COMMUTATOR = '<=>'
|
||||
);
|
||||
|
||||
-- halfvec opclasses
|
||||
|
||||
CREATE OPERATOR CLASS halfvec_l2_ops
|
||||
FOR TYPE halfvec USING hnsw AS
|
||||
OPERATOR 1 <-> (halfvec, halfvec) FOR ORDER BY float_ops,
|
||||
FUNCTION 1 halfvec_l2_squared_distance(halfvec, halfvec);
|
||||
|
||||
CREATE OPERATOR CLASS halfvec_ip_ops
|
||||
FOR TYPE halfvec USING hnsw AS
|
||||
OPERATOR 1 <#> (halfvec, halfvec) FOR ORDER BY float_ops,
|
||||
FUNCTION 1 halfvec_negative_inner_product(halfvec, halfvec);
|
||||
|
||||
CREATE OPERATOR CLASS halfvec_cosine_ops
|
||||
FOR TYPE halfvec USING hnsw AS
|
||||
OPERATOR 1 <=> (halfvec, halfvec) FOR ORDER BY float_ops,
|
||||
FUNCTION 1 halfvec_negative_inner_product(halfvec, halfvec),
|
||||
FUNCTION 2 halfvec_norm(halfvec);
|
||||
|
||||
-- extension casts
|
||||
|
||||
CREATE FUNCTION halfvec_to_vector(halfvec, integer, boolean) RETURNS vector
|
||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
CREATE FUNCTION vector_to_halfvec(vector, integer, boolean) RETURNS halfvec
|
||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
CREATE CAST (halfvec AS vector)
|
||||
WITH FUNCTION halfvec_to_vector(halfvec, integer, boolean) AS IMPLICIT;
|
||||
|
||||
CREATE CAST (vector AS halfvec)
|
||||
WITH FUNCTION vector_to_halfvec(vector, integer, boolean) AS IMPLICIT;
|
||||
|
||||
--- sparsevec type
|
||||
|
||||
CREATE TYPE sparsevec;
|
||||
|
||||
CREATE FUNCTION sparsevec_in(cstring, oid, integer) RETURNS sparsevec
|
||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
CREATE FUNCTION sparsevec_out(sparsevec) RETURNS cstring
|
||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
CREATE FUNCTION sparsevec_typmod_in(cstring[]) RETURNS integer
|
||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
CREATE FUNCTION sparsevec_recv(internal, oid, integer) RETURNS sparsevec
|
||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
CREATE FUNCTION sparsevec_send(sparsevec) RETURNS bytea
|
||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
CREATE TYPE sparsevec (
|
||||
INPUT = sparsevec_in,
|
||||
OUTPUT = sparsevec_out,
|
||||
TYPMOD_IN = sparsevec_typmod_in,
|
||||
RECEIVE = sparsevec_recv,
|
||||
SEND = sparsevec_send,
|
||||
STORAGE = external
|
||||
);
|
||||
|
||||
-- sparsevec functions
|
||||
|
||||
CREATE FUNCTION l2_distance(sparsevec, sparsevec) RETURNS float8
|
||||
AS 'MODULE_PATHNAME', 'sparsevec_l2_distance' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
CREATE FUNCTION inner_product(sparsevec, sparsevec) RETURNS float8
|
||||
AS 'MODULE_PATHNAME', 'sparsevec_inner_product' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
CREATE FUNCTION cosine_distance(sparsevec, sparsevec) RETURNS float8
|
||||
AS 'MODULE_PATHNAME', 'sparsevec_cosine_distance' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
CREATE FUNCTION sparsevec_norm(sparsevec) RETURNS float8
|
||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
CREATE FUNCTION subvector(sparsevec, int, int) RETURNS sparsevec
|
||||
AS 'MODULE_PATHNAME', 'sparsevec_subvector' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
-- sparsevec private functions
|
||||
|
||||
CREATE FUNCTION sparsevec_l2_squared_distance(sparsevec, sparsevec) RETURNS float8
|
||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
CREATE FUNCTION sparsevec_negative_inner_product(sparsevec, sparsevec) RETURNS float8
|
||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
-- sparsevec cast functions
|
||||
|
||||
CREATE FUNCTION sparsevec(sparsevec, integer, boolean) RETURNS sparsevec
|
||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
CREATE FUNCTION vector_to_sparsevec(vector, integer, boolean) RETURNS sparsevec
|
||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
CREATE FUNCTION sparsevec_to_vector(sparsevec, integer, boolean) RETURNS vector
|
||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
-- sparsevec casts
|
||||
|
||||
CREATE CAST (sparsevec AS sparsevec)
|
||||
WITH FUNCTION sparsevec(sparsevec, integer, boolean) AS IMPLICIT;
|
||||
|
||||
CREATE CAST (sparsevec AS vector)
|
||||
WITH FUNCTION sparsevec_to_vector(sparsevec, integer, boolean) AS IMPLICIT;
|
||||
|
||||
CREATE CAST (vector AS sparsevec)
|
||||
WITH FUNCTION vector_to_sparsevec(vector, integer, boolean) AS IMPLICIT;
|
||||
|
||||
-- sparsevec operators
|
||||
|
||||
CREATE OPERATOR <-> (
|
||||
LEFTARG = sparsevec, RIGHTARG = sparsevec, PROCEDURE = l2_distance,
|
||||
COMMUTATOR = '<->'
|
||||
);
|
||||
|
||||
CREATE OPERATOR <#> (
|
||||
LEFTARG = sparsevec, RIGHTARG = sparsevec, PROCEDURE = sparsevec_negative_inner_product,
|
||||
COMMUTATOR = '<#>'
|
||||
);
|
||||
|
||||
CREATE OPERATOR <=> (
|
||||
LEFTARG = sparsevec, RIGHTARG = sparsevec, PROCEDURE = cosine_distance,
|
||||
COMMUTATOR = '<=>'
|
||||
);
|
||||
|
||||
-- sparsevec opclasses
|
||||
|
||||
CREATE OPERATOR CLASS sparsevec_l2_ops
|
||||
FOR TYPE sparsevec USING hnsw AS
|
||||
OPERATOR 1 <-> (sparsevec, sparsevec) FOR ORDER BY float_ops,
|
||||
FUNCTION 1 sparsevec_l2_squared_distance(sparsevec, sparsevec);
|
||||
|
||||
CREATE OPERATOR CLASS sparsevec_ip_ops
|
||||
FOR TYPE sparsevec USING hnsw AS
|
||||
OPERATOR 1 <#> (sparsevec, sparsevec) FOR ORDER BY float_ops,
|
||||
FUNCTION 1 sparsevec_negative_inner_product(sparsevec, sparsevec);
|
||||
|
||||
CREATE OPERATOR CLASS sparsevec_cosine_ops
|
||||
FOR TYPE sparsevec USING hnsw AS
|
||||
OPERATOR 1 <=> (sparsevec, sparsevec) FOR ORDER BY float_ops,
|
||||
FUNCTION 1 sparsevec_negative_inner_product(sparsevec, sparsevec),
|
||||
FUNCTION 2 sparsevec_norm(sparsevec);
|
||||
|
||||
90
src/bitvector.c
Normal file
90
src/bitvector.c
Normal file
@@ -0,0 +1,90 @@
|
||||
#include "postgres.h"
|
||||
|
||||
#include "bitvector.h"
|
||||
#include "port/pg_bitutils.h"
|
||||
#include "utils/varbit.h"
|
||||
|
||||
#if PG_VERSION_NUM >= 160000
|
||||
#include "varatt.h"
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Allocate and initialize a new bit vector
|
||||
*/
|
||||
VarBit *
|
||||
InitBitVector(int dim)
|
||||
{
|
||||
VarBit *result;
|
||||
int size;
|
||||
|
||||
size = VARBITTOTALLEN(dim);
|
||||
result = (VarBit *) palloc0(size);
|
||||
SET_VARSIZE(result, size);
|
||||
VARBITLEN(result) = dim;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* Ensure same dimensions
|
||||
*/
|
||||
static inline void
|
||||
CheckDims(VarBit *a, VarBit *b)
|
||||
{
|
||||
if (VARBITLEN(a) != VARBITLEN(b))
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_DATA_EXCEPTION),
|
||||
errmsg("different bit lengths %u and %u", VARBITLEN(a), VARBITLEN(b))));
|
||||
}
|
||||
|
||||
/*
|
||||
* Get the Hamming distance between two bit vectors
|
||||
*/
|
||||
PGDLLEXPORT PG_FUNCTION_INFO_V1(hamming_distance);
|
||||
Datum
|
||||
hamming_distance(PG_FUNCTION_ARGS)
|
||||
{
|
||||
VarBit *a = PG_GETARG_VARBIT_P(0);
|
||||
VarBit *b = PG_GETARG_VARBIT_P(1);
|
||||
unsigned char *ax = VARBITS(a);
|
||||
unsigned char *bx = VARBITS(b);
|
||||
uint64 distance = 0;
|
||||
|
||||
CheckDims(a, b);
|
||||
|
||||
/* TODO Improve performance */
|
||||
for (uint32 i = 0; i < VARBITBYTES(a); i++)
|
||||
distance += pg_number_of_ones[ax[i] ^ bx[i]];
|
||||
|
||||
PG_RETURN_FLOAT8((double) distance);
|
||||
}
|
||||
|
||||
/*
|
||||
* Get the Jaccard distance between two bit vectors
|
||||
*/
|
||||
PGDLLEXPORT PG_FUNCTION_INFO_V1(jaccard_distance);
|
||||
Datum
|
||||
jaccard_distance(PG_FUNCTION_ARGS)
|
||||
{
|
||||
VarBit *a = PG_GETARG_VARBIT_P(0);
|
||||
VarBit *b = PG_GETARG_VARBIT_P(1);
|
||||
unsigned char *ax = VARBITS(a);
|
||||
unsigned char *bx = VARBITS(b);
|
||||
uint64 ab = 0;
|
||||
uint64 aa;
|
||||
uint64 bb;
|
||||
|
||||
CheckDims(a, b);
|
||||
|
||||
/* TODO Improve performance */
|
||||
for (uint32 i = 0; i < VARBITBYTES(a); i++)
|
||||
ab += pg_number_of_ones[ax[i] & bx[i]];
|
||||
|
||||
if (ab == 0)
|
||||
PG_RETURN_FLOAT8(1);
|
||||
|
||||
aa = pg_popcount((char *) ax, VARBITBYTES(a));
|
||||
bb = pg_popcount((char *) bx, VARBITBYTES(b));
|
||||
|
||||
PG_RETURN_FLOAT8(1 - (ab / ((double) (aa + bb - ab))));
|
||||
}
|
||||
8
src/bitvector.h
Normal file
8
src/bitvector.h
Normal file
@@ -0,0 +1,8 @@
|
||||
#ifndef BITVECTOR_H
|
||||
#define BITVECTOR_H
|
||||
|
||||
#include "utils/varbit.h"
|
||||
|
||||
VarBit *InitBitVector(int dim);
|
||||
|
||||
#endif
|
||||
1020
src/halfvec.c
Normal file
1020
src/halfvec.c
Normal file
File diff suppressed because it is too large
Load Diff
43
src/halfvec.h
Normal file
43
src/halfvec.h
Normal file
@@ -0,0 +1,43 @@
|
||||
#ifndef HALFVEC_H
|
||||
#define HALFVEC_H
|
||||
|
||||
#define __STDC_WANT_IEC_60559_TYPES_EXT__
|
||||
|
||||
#include <float.h>
|
||||
|
||||
#include "vector.h"
|
||||
|
||||
#ifdef __FLT16_MAX__
|
||||
#define FLT16_SUPPORT
|
||||
#endif
|
||||
|
||||
#ifdef FLT16_SUPPORT
|
||||
#define half _Float16
|
||||
#define HALF_MAX FLT16_MAX
|
||||
#else
|
||||
/* TODO #pragma message("")? */
|
||||
#define half uint16
|
||||
#define HALF_MAX 65504
|
||||
#endif
|
||||
|
||||
#define HALFVEC_MAX_DIM VECTOR_MAX_DIM
|
||||
|
||||
#define HALFVEC_SIZE(_dim) (offsetof(HalfVector, x) + sizeof(half)*(_dim))
|
||||
#define DatumGetHalfVector(x) ((HalfVector *) PG_DETOAST_DATUM(x))
|
||||
#define PG_GETARG_HALFVEC_P(x) DatumGetHalfVector(PG_GETARG_DATUM(x))
|
||||
#define PG_RETURN_HALFVEC_P(x) PG_RETURN_POINTER(x)
|
||||
|
||||
typedef struct HalfVector
|
||||
{
|
||||
int32 vl_len_; /* varlena header (do not touch directly!) */
|
||||
int16 dim; /* number of dimensions */
|
||||
int16 unused;
|
||||
half x[FLEXIBLE_ARRAY_MEMBER];
|
||||
} HalfVector;
|
||||
|
||||
HalfVector *InitHalfVector(int dim);
|
||||
float HalfToFloat4(half num);
|
||||
half Float4ToHalf(float num);
|
||||
half Float4ToHalfUnchecked(float num);
|
||||
|
||||
#endif
|
||||
249
src/hnsw.c
Normal file
249
src/hnsw.c
Normal file
@@ -0,0 +1,249 @@
|
||||
#include "postgres.h"
|
||||
|
||||
#include <float.h>
|
||||
#include <math.h>
|
||||
|
||||
#include "access/amapi.h"
|
||||
#include "access/reloptions.h"
|
||||
#include "commands/progress.h"
|
||||
#include "commands/vacuum.h"
|
||||
#include "hnsw.h"
|
||||
#include "miscadmin.h"
|
||||
#include "utils/guc.h"
|
||||
#include "utils/selfuncs.h"
|
||||
|
||||
#if PG_VERSION_NUM < 150000
|
||||
#define MarkGUCPrefixReserved(x) EmitWarningsOnPlaceholders(x)
|
||||
#endif
|
||||
|
||||
int hnsw_ef_search;
|
||||
int hnsw_lock_tranche_id;
|
||||
static relopt_kind hnsw_relopt_kind;
|
||||
|
||||
/*
|
||||
* Assign a tranche ID for our LWLocks. This only needs to be done by one
|
||||
* backend, as the tranche ID is remembered in shared memory.
|
||||
*
|
||||
* This shared memory area is very small, so we just allocate it from the
|
||||
* "slop" that PostgreSQL reserves for small allocations like this. If
|
||||
* this grows bigger, we should use a shmem_request_hook and
|
||||
* RequestAddinShmemSpace() to pre-reserve space for this.
|
||||
*/
|
||||
void
|
||||
HnswInitLockTranche(void)
|
||||
{
|
||||
int *tranche_ids;
|
||||
bool found;
|
||||
|
||||
LWLockAcquire(AddinShmemInitLock, LW_EXCLUSIVE);
|
||||
tranche_ids = ShmemInitStruct("hnsw LWLock ids",
|
||||
sizeof(int) * 1,
|
||||
&found);
|
||||
if (!found)
|
||||
tranche_ids[0] = LWLockNewTrancheId();
|
||||
hnsw_lock_tranche_id = tranche_ids[0];
|
||||
LWLockRelease(AddinShmemInitLock);
|
||||
|
||||
/* Per-backend registration of the tranche ID */
|
||||
LWLockRegisterTranche(hnsw_lock_tranche_id, "HnswBuild");
|
||||
}
|
||||
|
||||
/*
|
||||
* Initialize index options and variables
|
||||
*/
|
||||
void
|
||||
HnswInit(void)
|
||||
{
|
||||
if (!process_shared_preload_libraries_in_progress)
|
||||
HnswInitLockTranche();
|
||||
|
||||
hnsw_relopt_kind = add_reloption_kind();
|
||||
add_int_reloption(hnsw_relopt_kind, "m", "Max number of connections",
|
||||
HNSW_DEFAULT_M, HNSW_MIN_M, HNSW_MAX_M
|
||||
#if PG_VERSION_NUM >= 130000
|
||||
,AccessExclusiveLock
|
||||
#endif
|
||||
);
|
||||
add_int_reloption(hnsw_relopt_kind, "ef_construction", "Size of the dynamic candidate list for construction",
|
||||
HNSW_DEFAULT_EF_CONSTRUCTION, HNSW_MIN_EF_CONSTRUCTION, HNSW_MAX_EF_CONSTRUCTION
|
||||
#if PG_VERSION_NUM >= 130000
|
||||
,AccessExclusiveLock
|
||||
#endif
|
||||
);
|
||||
|
||||
DefineCustomIntVariable("hnsw.ef_search", "Sets the size of the dynamic candidate list for search",
|
||||
"Valid range is 1..1000.", &hnsw_ef_search,
|
||||
HNSW_DEFAULT_EF_SEARCH, HNSW_MIN_EF_SEARCH, HNSW_MAX_EF_SEARCH, PGC_USERSET, 0, NULL, NULL, NULL);
|
||||
|
||||
MarkGUCPrefixReserved("hnsw");
|
||||
}
|
||||
|
||||
/*
|
||||
* Get the name of index build phase
|
||||
*/
|
||||
static char *
|
||||
hnswbuildphasename(int64 phasenum)
|
||||
{
|
||||
switch (phasenum)
|
||||
{
|
||||
case PROGRESS_CREATEIDX_SUBPHASE_INITIALIZE:
|
||||
return "initializing";
|
||||
case PROGRESS_HNSW_PHASE_LOAD:
|
||||
return "loading tuples";
|
||||
default:
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Estimate the cost of an index scan
|
||||
*/
|
||||
static void
|
||||
hnswcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
|
||||
Cost *indexStartupCost, Cost *indexTotalCost,
|
||||
Selectivity *indexSelectivity, double *indexCorrelation,
|
||||
double *indexPages)
|
||||
{
|
||||
GenericCosts costs;
|
||||
int m;
|
||||
int entryLevel;
|
||||
Relation index;
|
||||
|
||||
/* Never use index without order */
|
||||
if (path->indexorderbys == NULL)
|
||||
{
|
||||
*indexStartupCost = DBL_MAX;
|
||||
*indexTotalCost = DBL_MAX;
|
||||
*indexSelectivity = 0;
|
||||
*indexCorrelation = 0;
|
||||
*indexPages = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
MemSet(&costs, 0, sizeof(costs));
|
||||
|
||||
index = index_open(path->indexinfo->indexoid, NoLock);
|
||||
HnswGetMetaPageInfo(index, &m, NULL);
|
||||
index_close(index, NoLock);
|
||||
|
||||
/* Approximate entry level */
|
||||
entryLevel = (int) -log(1.0 / path->indexinfo->tuples) * HnswGetMl(m);
|
||||
|
||||
/* TODO Improve estimate of visited tuples (currently underestimates) */
|
||||
/* Account for number of tuples (or entry level), m, and ef_search */
|
||||
costs.numIndexTuples = (entryLevel + 2) * m;
|
||||
|
||||
genericcostestimate(root, path, loop_count, &costs);
|
||||
|
||||
/* Use total cost since most work happens before first tuple is returned */
|
||||
*indexStartupCost = costs.indexTotalCost;
|
||||
*indexTotalCost = costs.indexTotalCost;
|
||||
*indexSelectivity = costs.indexSelectivity;
|
||||
*indexCorrelation = costs.indexCorrelation;
|
||||
*indexPages = costs.numIndexPages;
|
||||
}
|
||||
|
||||
/*
|
||||
* Parse and validate the reloptions
|
||||
*/
|
||||
static bytea *
|
||||
hnswoptions(Datum reloptions, bool validate)
|
||||
{
|
||||
static const relopt_parse_elt tab[] = {
|
||||
{"m", RELOPT_TYPE_INT, offsetof(HnswOptions, m)},
|
||||
{"ef_construction", RELOPT_TYPE_INT, offsetof(HnswOptions, efConstruction)},
|
||||
};
|
||||
|
||||
#if PG_VERSION_NUM >= 130000
|
||||
return (bytea *) build_reloptions(reloptions, validate,
|
||||
hnsw_relopt_kind,
|
||||
sizeof(HnswOptions),
|
||||
tab, lengthof(tab));
|
||||
#else
|
||||
relopt_value *options;
|
||||
int numoptions;
|
||||
HnswOptions *rdopts;
|
||||
|
||||
options = parseRelOptions(reloptions, validate, hnsw_relopt_kind, &numoptions);
|
||||
rdopts = allocateReloptStruct(sizeof(HnswOptions), options, numoptions);
|
||||
fillRelOptions((void *) rdopts, sizeof(HnswOptions), options, numoptions,
|
||||
validate, tab, lengthof(tab));
|
||||
|
||||
return (bytea *) rdopts;
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
* Validate catalog entries for the specified operator class
|
||||
*/
|
||||
static bool
|
||||
hnswvalidate(Oid opclassoid)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Define index handler
|
||||
*
|
||||
* See https://www.postgresql.org/docs/current/index-api.html
|
||||
*/
|
||||
PGDLLEXPORT PG_FUNCTION_INFO_V1(hnswhandler);
|
||||
Datum
|
||||
hnswhandler(PG_FUNCTION_ARGS)
|
||||
{
|
||||
IndexAmRoutine *amroutine = makeNode(IndexAmRoutine);
|
||||
|
||||
amroutine->amstrategies = 0;
|
||||
amroutine->amsupport = 2;
|
||||
#if PG_VERSION_NUM >= 130000
|
||||
amroutine->amoptsprocnum = 0;
|
||||
#endif
|
||||
amroutine->amcanorder = false;
|
||||
amroutine->amcanorderbyop = true;
|
||||
amroutine->amcanbackward = false; /* can change direction mid-scan */
|
||||
amroutine->amcanunique = false;
|
||||
amroutine->amcanmulticol = false;
|
||||
amroutine->amoptionalkey = true;
|
||||
amroutine->amsearcharray = false;
|
||||
amroutine->amsearchnulls = false;
|
||||
amroutine->amstorage = false;
|
||||
amroutine->amclusterable = false;
|
||||
amroutine->ampredlocks = false;
|
||||
amroutine->amcanparallel = false;
|
||||
amroutine->amcaninclude = false;
|
||||
#if PG_VERSION_NUM >= 130000
|
||||
amroutine->amusemaintenanceworkmem = false; /* not used during VACUUM */
|
||||
amroutine->amparallelvacuumoptions = VACUUM_OPTION_PARALLEL_BULKDEL;
|
||||
#endif
|
||||
amroutine->amkeytype = InvalidOid;
|
||||
|
||||
/* Interface functions */
|
||||
amroutine->ambuild = hnswbuild;
|
||||
amroutine->ambuildempty = hnswbuildempty;
|
||||
amroutine->aminsert = hnswinsert;
|
||||
amroutine->ambulkdelete = hnswbulkdelete;
|
||||
amroutine->amvacuumcleanup = hnswvacuumcleanup;
|
||||
amroutine->amcanreturn = NULL;
|
||||
amroutine->amcostestimate = hnswcostestimate;
|
||||
amroutine->amoptions = hnswoptions;
|
||||
amroutine->amproperty = NULL; /* TODO AMPROP_DISTANCE_ORDERABLE */
|
||||
amroutine->ambuildphasename = hnswbuildphasename;
|
||||
amroutine->amvalidate = hnswvalidate;
|
||||
#if PG_VERSION_NUM >= 140000
|
||||
amroutine->amadjustmembers = NULL;
|
||||
#endif
|
||||
amroutine->ambeginscan = hnswbeginscan;
|
||||
amroutine->amrescan = hnswrescan;
|
||||
amroutine->amgettuple = hnswgettuple;
|
||||
amroutine->amgetbitmap = NULL;
|
||||
amroutine->amendscan = hnswendscan;
|
||||
amroutine->ammarkpos = NULL;
|
||||
amroutine->amrestrpos = NULL;
|
||||
|
||||
/* Interface functions to support parallel index scans */
|
||||
amroutine->amestimateparallelscan = NULL;
|
||||
amroutine->aminitparallelscan = NULL;
|
||||
amroutine->amparallelrescan = NULL;
|
||||
|
||||
PG_RETURN_POINTER(amroutine);
|
||||
}
|
||||
473
src/hnsw.h
Normal file
473
src/hnsw.h
Normal file
@@ -0,0 +1,473 @@
|
||||
#ifndef HNSW_H
|
||||
#define HNSW_H
|
||||
|
||||
#include "postgres.h"
|
||||
|
||||
#include "access/genam.h"
|
||||
#include "access/parallel.h"
|
||||
#include "lib/pairingheap.h"
|
||||
#include "nodes/execnodes.h"
|
||||
#include "port.h" /* for random() */
|
||||
#include "utils/relptr.h"
|
||||
#include "utils/sampling.h"
|
||||
#include "vector.h"
|
||||
|
||||
#if PG_VERSION_NUM < 120000
|
||||
#error "Requires PostgreSQL 12+"
|
||||
#endif
|
||||
|
||||
#define HNSW_MAX_DIM 2000
|
||||
#define HNSW_MAX_NNZ 1000
|
||||
|
||||
/* Support functions */
|
||||
#define HNSW_DISTANCE_PROC 1
|
||||
#define HNSW_NORM_PROC 2
|
||||
|
||||
#define HNSW_VERSION 1
|
||||
#define HNSW_MAGIC_NUMBER 0xA953A953
|
||||
#define HNSW_PAGE_ID 0xFF90
|
||||
|
||||
/* Preserved page numbers */
|
||||
#define HNSW_METAPAGE_BLKNO 0
|
||||
#define HNSW_HEAD_BLKNO 1 /* first element page */
|
||||
|
||||
/* Must correspond to page numbers since page lock is used */
|
||||
#define HNSW_UPDATE_LOCK 0
|
||||
#define HNSW_SCAN_LOCK 1
|
||||
|
||||
/* HNSW parameters */
|
||||
#define HNSW_DEFAULT_M 16
|
||||
#define HNSW_MIN_M 2
|
||||
#define HNSW_MAX_M 100
|
||||
#define HNSW_DEFAULT_EF_CONSTRUCTION 64
|
||||
#define HNSW_MIN_EF_CONSTRUCTION 4
|
||||
#define HNSW_MAX_EF_CONSTRUCTION 1000
|
||||
#define HNSW_DEFAULT_EF_SEARCH 40
|
||||
#define HNSW_MIN_EF_SEARCH 1
|
||||
#define HNSW_MAX_EF_SEARCH 1000
|
||||
|
||||
/* Tuple types */
|
||||
#define HNSW_ELEMENT_TUPLE_TYPE 1
|
||||
#define HNSW_NEIGHBOR_TUPLE_TYPE 2
|
||||
|
||||
/* Make graph robust against non-HOT updates */
|
||||
#define HNSW_HEAPTIDS 10
|
||||
|
||||
#define HNSW_UPDATE_ENTRY_GREATER 1
|
||||
#define HNSW_UPDATE_ENTRY_ALWAYS 2
|
||||
|
||||
typedef enum HnswType
|
||||
{
|
||||
HNSW_TYPE_VECTOR,
|
||||
HNSW_TYPE_HALFVEC,
|
||||
HNSW_TYPE_BIT,
|
||||
HNSW_TYPE_SPARSEVEC
|
||||
} HnswType;
|
||||
|
||||
/* Build phases */
|
||||
/* PROGRESS_CREATEIDX_SUBPHASE_INITIALIZE is 1 */
|
||||
#define PROGRESS_HNSW_PHASE_LOAD 2
|
||||
|
||||
#define HNSW_MAX_SIZE (BLCKSZ - MAXALIGN(SizeOfPageHeaderData) - MAXALIGN(sizeof(HnswPageOpaqueData)) - sizeof(ItemIdData))
|
||||
#define HNSW_TUPLE_ALLOC_SIZE BLCKSZ
|
||||
|
||||
#define HNSW_ELEMENT_TUPLE_SIZE(size) MAXALIGN(offsetof(HnswElementTupleData, data) + (size))
|
||||
#define HNSW_NEIGHBOR_TUPLE_SIZE(level, m) MAXALIGN(offsetof(HnswNeighborTupleData, indextids) + ((level) + 2) * (m) * sizeof(ItemPointerData))
|
||||
|
||||
#define HNSW_NEIGHBOR_ARRAY_SIZE(lm) (offsetof(HnswNeighborArray, items) + sizeof(HnswCandidate) * (lm))
|
||||
|
||||
#define HnswPageGetOpaque(page) ((HnswPageOpaque) PageGetSpecialPointer(page))
|
||||
#define HnswPageGetMeta(page) ((HnswMetaPageData *) PageGetContents(page))
|
||||
|
||||
#if PG_VERSION_NUM >= 150000
|
||||
#define RandomDouble() pg_prng_double(&pg_global_prng_state)
|
||||
#define SeedRandom(seed) pg_prng_seed(&pg_global_prng_state, seed)
|
||||
#else
|
||||
#define RandomDouble() (((double) random()) / MAX_RANDOM_VALUE)
|
||||
#define SeedRandom(seed) srandom(seed)
|
||||
#endif
|
||||
|
||||
#if PG_VERSION_NUM < 130000
|
||||
#define list_delete_last(list) list_truncate(list, list_length(list) - 1)
|
||||
#define list_sort(list, cmp) ((list) = list_qsort(list, cmp))
|
||||
#endif
|
||||
|
||||
#define HnswIsElementTuple(tup) ((tup)->type == HNSW_ELEMENT_TUPLE_TYPE)
|
||||
#define HnswIsNeighborTuple(tup) ((tup)->type == HNSW_NEIGHBOR_TUPLE_TYPE)
|
||||
|
||||
/* 2 * M connections for ground layer */
|
||||
#define HnswGetLayerM(m, layer) (layer == 0 ? (m) * 2 : (m))
|
||||
|
||||
/* Optimal ML from paper */
|
||||
#define HnswGetMl(m) (1 / log(m))
|
||||
|
||||
/* Ensure fits on page and in uint8 */
|
||||
#define HnswGetMaxLevel(m) Min(((BLCKSZ - MAXALIGN(SizeOfPageHeaderData) - MAXALIGN(sizeof(HnswPageOpaqueData)) - offsetof(HnswNeighborTupleData, indextids) - sizeof(ItemIdData)) / (sizeof(ItemPointerData)) / (m)) - 2, 255)
|
||||
|
||||
#define HnswGetValue(base, element) PointerGetDatum(HnswPtrAccess(base, (element)->value))
|
||||
|
||||
#if PG_VERSION_NUM < 140005
|
||||
#define relptr_offset(rp) ((rp).relptr_off - 1)
|
||||
#endif
|
||||
|
||||
/* Pointer macros */
|
||||
#define HnswPtrAccess(base, hp) ((base) == NULL ? (hp).ptr : relptr_access(base, (hp).relptr))
|
||||
#define HnswPtrStore(base, hp, value) ((base) == NULL ? (void) ((hp).ptr = (value)) : (void) relptr_store(base, (hp).relptr, value))
|
||||
#define HnswPtrIsNull(base, hp) ((base) == NULL ? (hp).ptr == NULL : relptr_is_null((hp).relptr))
|
||||
#define HnswPtrEqual(base, hp1, hp2) ((base) == NULL ? (hp1).ptr == (hp2).ptr : relptr_offset((hp1).relptr) == relptr_offset((hp2).relptr))
|
||||
|
||||
/* For code paths dedicated to each type */
|
||||
#define HnswPtrPointer(hp) (hp).ptr
|
||||
#define HnswPtrOffset(hp) relptr_offset((hp).relptr)
|
||||
|
||||
/* Variables */
|
||||
extern int hnsw_ef_search;
|
||||
extern int hnsw_lock_tranche_id;
|
||||
|
||||
typedef struct HnswElementData HnswElementData;
|
||||
typedef struct HnswNeighborArray HnswNeighborArray;
|
||||
|
||||
#define HnswPtrDeclare(type, relptrtype, ptrtype) \
|
||||
relptr_declare(type, relptrtype); \
|
||||
typedef union { type *ptr; relptrtype relptr; } ptrtype;
|
||||
|
||||
/* Pointers that can be absolute or relative */
|
||||
/* Use char for DatumPtr so works with Pointer */
|
||||
HnswPtrDeclare(HnswElementData, HnswElementRelptr, HnswElementPtr);
|
||||
HnswPtrDeclare(HnswNeighborArray, HnswNeighborArrayRelptr, HnswNeighborArrayPtr);
|
||||
HnswPtrDeclare(HnswNeighborArrayPtr, HnswNeighborsRelptr, HnswNeighborsPtr);
|
||||
HnswPtrDeclare(char, DatumRelptr, DatumPtr);
|
||||
|
||||
struct HnswElementData
|
||||
{
|
||||
HnswElementPtr next;
|
||||
ItemPointerData heaptids[HNSW_HEAPTIDS];
|
||||
uint8 heaptidsLength;
|
||||
uint8 level;
|
||||
uint8 deleted;
|
||||
uint32 hash;
|
||||
HnswNeighborsPtr neighbors;
|
||||
BlockNumber blkno;
|
||||
OffsetNumber offno;
|
||||
OffsetNumber neighborOffno;
|
||||
BlockNumber neighborPage;
|
||||
DatumPtr value;
|
||||
LWLock lock;
|
||||
};
|
||||
|
||||
typedef HnswElementData * HnswElement;
|
||||
|
||||
typedef struct HnswCandidate
|
||||
{
|
||||
HnswElementPtr element;
|
||||
float distance;
|
||||
bool closer;
|
||||
} HnswCandidate;
|
||||
|
||||
struct HnswNeighborArray
|
||||
{
|
||||
int length;
|
||||
bool closerSet;
|
||||
HnswCandidate items[FLEXIBLE_ARRAY_MEMBER];
|
||||
};
|
||||
|
||||
typedef struct HnswPairingHeapNode
|
||||
{
|
||||
pairingheap_node ph_node;
|
||||
HnswCandidate *inner;
|
||||
} HnswPairingHeapNode;
|
||||
|
||||
/* HNSW index options */
|
||||
typedef struct HnswOptions
|
||||
{
|
||||
int32 vl_len_; /* varlena header (do not touch directly!) */
|
||||
int m; /* number of connections */
|
||||
int efConstruction; /* size of dynamic candidate list */
|
||||
} HnswOptions;
|
||||
|
||||
typedef struct HnswGraph
|
||||
{
|
||||
/* Graph state */
|
||||
slock_t lock;
|
||||
HnswElementPtr head;
|
||||
double indtuples;
|
||||
|
||||
/* Entry state */
|
||||
LWLock entryLock;
|
||||
LWLock entryWaitLock;
|
||||
HnswElementPtr entryPoint;
|
||||
|
||||
/* Allocations state */
|
||||
LWLock allocatorLock;
|
||||
long memoryUsed;
|
||||
long memoryTotal;
|
||||
|
||||
/* Flushed state */
|
||||
LWLock flushLock;
|
||||
bool flushed;
|
||||
} HnswGraph;
|
||||
|
||||
typedef struct HnswShared
|
||||
{
|
||||
/* Immutable state */
|
||||
Oid heaprelid;
|
||||
Oid indexrelid;
|
||||
bool isconcurrent;
|
||||
|
||||
/* Worker progress */
|
||||
ConditionVariable workersdonecv;
|
||||
|
||||
/* Mutex for mutable state */
|
||||
slock_t mutex;
|
||||
|
||||
/* Mutable state */
|
||||
int nparticipantsdone;
|
||||
double reltuples;
|
||||
HnswGraph graphData;
|
||||
} HnswShared;
|
||||
|
||||
#define ParallelTableScanFromHnswShared(shared) \
|
||||
(ParallelTableScanDesc) ((char *) (shared) + BUFFERALIGN(sizeof(HnswShared)))
|
||||
|
||||
typedef struct HnswLeader
|
||||
{
|
||||
ParallelContext *pcxt;
|
||||
int nparticipanttuplesorts;
|
||||
HnswShared *hnswshared;
|
||||
Snapshot snapshot;
|
||||
char *hnswarea;
|
||||
} HnswLeader;
|
||||
|
||||
typedef struct HnswAllocator
|
||||
{
|
||||
void *(*alloc) (Size size, void *state);
|
||||
void *state;
|
||||
} HnswAllocator;
|
||||
|
||||
typedef struct HnswBuildState
|
||||
{
|
||||
/* Info */
|
||||
Relation heap;
|
||||
Relation index;
|
||||
IndexInfo *indexInfo;
|
||||
ForkNumber forkNum;
|
||||
HnswType type;
|
||||
|
||||
/* Settings */
|
||||
int dimensions;
|
||||
int m;
|
||||
int efConstruction;
|
||||
|
||||
/* Statistics */
|
||||
double indtuples;
|
||||
double reltuples;
|
||||
|
||||
/* Support functions */
|
||||
FmgrInfo *procinfo;
|
||||
FmgrInfo *normprocinfo;
|
||||
Oid collation;
|
||||
|
||||
/* Variables */
|
||||
HnswGraph graphData;
|
||||
HnswGraph *graph;
|
||||
double ml;
|
||||
int maxLevel;
|
||||
|
||||
/* Memory */
|
||||
MemoryContext graphCtx;
|
||||
MemoryContext tmpCtx;
|
||||
HnswAllocator allocator;
|
||||
|
||||
/* Parallel builds */
|
||||
HnswLeader *hnswleader;
|
||||
HnswShared *hnswshared;
|
||||
char *hnswarea;
|
||||
} HnswBuildState;
|
||||
|
||||
typedef struct HnswMetaPageData
|
||||
{
|
||||
uint32 magicNumber;
|
||||
uint32 version;
|
||||
uint32 dimensions;
|
||||
uint16 m;
|
||||
uint16 efConstruction;
|
||||
BlockNumber entryBlkno;
|
||||
OffsetNumber entryOffno;
|
||||
int16 entryLevel;
|
||||
BlockNumber insertPage;
|
||||
} HnswMetaPageData;
|
||||
|
||||
typedef HnswMetaPageData * HnswMetaPage;
|
||||
|
||||
typedef struct HnswPageOpaqueData
|
||||
{
|
||||
BlockNumber nextblkno;
|
||||
uint16 unused;
|
||||
uint16 page_id; /* for identification of HNSW indexes */
|
||||
} HnswPageOpaqueData;
|
||||
|
||||
typedef HnswPageOpaqueData * HnswPageOpaque;
|
||||
|
||||
typedef struct HnswElementTupleData
|
||||
{
|
||||
uint8 type;
|
||||
uint8 level;
|
||||
uint8 deleted;
|
||||
uint8 unused;
|
||||
ItemPointerData heaptids[HNSW_HEAPTIDS];
|
||||
ItemPointerData neighbortid;
|
||||
uint16 unused2;
|
||||
Vector data;
|
||||
} HnswElementTupleData;
|
||||
|
||||
typedef HnswElementTupleData * HnswElementTuple;
|
||||
|
||||
typedef struct HnswNeighborTupleData
|
||||
{
|
||||
uint8 type;
|
||||
uint8 unused;
|
||||
uint16 count;
|
||||
ItemPointerData indextids[FLEXIBLE_ARRAY_MEMBER];
|
||||
} HnswNeighborTupleData;
|
||||
|
||||
typedef HnswNeighborTupleData * HnswNeighborTuple;
|
||||
|
||||
typedef struct HnswScanOpaqueData
|
||||
{
|
||||
bool first;
|
||||
List *w;
|
||||
MemoryContext tmpCtx;
|
||||
|
||||
/* Support functions */
|
||||
FmgrInfo *procinfo;
|
||||
FmgrInfo *normprocinfo;
|
||||
Oid collation;
|
||||
} HnswScanOpaqueData;
|
||||
|
||||
typedef HnswScanOpaqueData * HnswScanOpaque;
|
||||
|
||||
typedef struct HnswVacuumState
|
||||
{
|
||||
/* Info */
|
||||
Relation index;
|
||||
IndexBulkDeleteResult *stats;
|
||||
IndexBulkDeleteCallback callback;
|
||||
void *callback_state;
|
||||
|
||||
/* Settings */
|
||||
int m;
|
||||
int efConstruction;
|
||||
|
||||
/* Support functions */
|
||||
FmgrInfo *procinfo;
|
||||
Oid collation;
|
||||
|
||||
/* Variables */
|
||||
struct tidhash_hash *deleted;
|
||||
BufferAccessStrategy bas;
|
||||
HnswNeighborTuple ntup;
|
||||
HnswElementData highestPoint;
|
||||
|
||||
/* Memory */
|
||||
MemoryContext tmpCtx;
|
||||
} HnswVacuumState;
|
||||
|
||||
/* Methods */
|
||||
int HnswGetM(Relation index);
|
||||
int HnswGetEfConstruction(Relation index);
|
||||
FmgrInfo *HnswOptionalProcInfo(Relation index, uint16 procnum);
|
||||
HnswType HnswGetType(Relation index);
|
||||
bool HnswNormValue(FmgrInfo *procinfo, Oid collation, Datum *value, HnswType type);
|
||||
void HnswCheckValue(Datum value, HnswType type);
|
||||
Buffer HnswNewBuffer(Relation index, ForkNumber forkNum);
|
||||
void HnswInitPage(Buffer buf, Page page);
|
||||
void HnswInit(void);
|
||||
List *HnswSearchLayer(char *base, Datum q, List *ep, int ef, int lc, Relation index, FmgrInfo *procinfo, Oid collation, int m, bool inserting, HnswElement skipElement);
|
||||
HnswElement HnswGetEntryPoint(Relation index);
|
||||
void HnswGetMetaPageInfo(Relation index, int *m, HnswElement * entryPoint);
|
||||
void *HnswAlloc(HnswAllocator * allocator, Size size);
|
||||
HnswElement HnswInitElement(char *base, ItemPointer tid, int m, double ml, int maxLevel, HnswAllocator * alloc);
|
||||
HnswElement HnswInitElementFromBlock(BlockNumber blkno, OffsetNumber offno);
|
||||
void HnswFindElementNeighbors(char *base, HnswElement element, HnswElement entryPoint, Relation index, FmgrInfo *procinfo, Oid collation, int m, int efConstruction, bool existing);
|
||||
HnswCandidate *HnswEntryCandidate(char *base, HnswElement em, Datum q, Relation rel, FmgrInfo *procinfo, Oid collation, bool loadVec);
|
||||
void HnswUpdateMetaPage(Relation index, int updateEntry, HnswElement entryPoint, BlockNumber insertPage, ForkNumber forkNum, bool building);
|
||||
void HnswSetNeighborTuple(char *base, HnswNeighborTuple ntup, HnswElement e, int m);
|
||||
void HnswAddHeapTid(HnswElement element, ItemPointer heaptid);
|
||||
void HnswInitNeighbors(char *base, HnswElement element, int m, HnswAllocator * alloc);
|
||||
bool HnswInsertTupleOnDisk(Relation index, Datum value, Datum *values, bool *isnull, ItemPointer heap_tid, bool building);
|
||||
void HnswUpdateNeighborsOnDisk(Relation index, FmgrInfo *procinfo, Oid collation, HnswElement e, int m, bool checkExisting, bool building);
|
||||
void HnswLoadElementFromTuple(HnswElement element, HnswElementTuple etup, bool loadHeaptids, bool loadVec);
|
||||
void HnswLoadElement(HnswElement element, float *distance, Datum *q, Relation index, FmgrInfo *procinfo, Oid collation, bool loadVec);
|
||||
void HnswSetElementTuple(char *base, HnswElementTuple etup, HnswElement element);
|
||||
void HnswUpdateConnection(char *base, HnswElement element, HnswCandidate * hc, int lm, int lc, int *updateIdx, Relation index, FmgrInfo *procinfo, Oid collation);
|
||||
void HnswLoadNeighbors(HnswElement element, Relation index, int m);
|
||||
void HnswInitLockTranche(void);
|
||||
PGDLLEXPORT void HnswParallelBuildMain(dsm_segment *seg, shm_toc *toc);
|
||||
|
||||
/* Index access methods */
|
||||
IndexBuildResult *hnswbuild(Relation heap, Relation index, IndexInfo *indexInfo);
|
||||
void hnswbuildempty(Relation index);
|
||||
bool hnswinsert(Relation index, Datum *values, bool *isnull, ItemPointer heap_tid, Relation heap, IndexUniqueCheck checkUnique
|
||||
#if PG_VERSION_NUM >= 140000
|
||||
,bool indexUnchanged
|
||||
#endif
|
||||
,IndexInfo *indexInfo
|
||||
);
|
||||
IndexBulkDeleteResult *hnswbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats, IndexBulkDeleteCallback callback, void *callback_state);
|
||||
IndexBulkDeleteResult *hnswvacuumcleanup(IndexVacuumInfo *info, IndexBulkDeleteResult *stats);
|
||||
IndexScanDesc hnswbeginscan(Relation index, int nkeys, int norderbys);
|
||||
void hnswrescan(IndexScanDesc scan, ScanKey keys, int nkeys, ScanKey orderbys, int norderbys);
|
||||
bool hnswgettuple(IndexScanDesc scan, ScanDirection dir);
|
||||
void hnswendscan(IndexScanDesc scan);
|
||||
|
||||
static inline HnswNeighborArray *
|
||||
HnswGetNeighbors(char *base, HnswElement element, int lc)
|
||||
{
|
||||
HnswNeighborArrayPtr *neighborList = HnswPtrAccess(base, element->neighbors);
|
||||
|
||||
Assert(element->level >= lc);
|
||||
|
||||
return HnswPtrAccess(base, neighborList[lc]);
|
||||
}
|
||||
|
||||
/* Hash tables */
|
||||
typedef struct TidHashEntry
|
||||
{
|
||||
ItemPointerData tid;
|
||||
char status;
|
||||
} TidHashEntry;
|
||||
|
||||
#define SH_PREFIX tidhash
|
||||
#define SH_ELEMENT_TYPE TidHashEntry
|
||||
#define SH_KEY_TYPE ItemPointerData
|
||||
#define SH_SCOPE extern
|
||||
#define SH_DECLARE
|
||||
#include "lib/simplehash.h"
|
||||
|
||||
typedef struct PointerHashEntry
|
||||
{
|
||||
uintptr_t ptr;
|
||||
char status;
|
||||
} PointerHashEntry;
|
||||
|
||||
#define SH_PREFIX pointerhash
|
||||
#define SH_ELEMENT_TYPE PointerHashEntry
|
||||
#define SH_KEY_TYPE uintptr_t
|
||||
#define SH_SCOPE extern
|
||||
#define SH_DECLARE
|
||||
#include "lib/simplehash.h"
|
||||
|
||||
typedef struct OffsetHashEntry
|
||||
{
|
||||
Size offset;
|
||||
char status;
|
||||
} OffsetHashEntry;
|
||||
|
||||
#define SH_PREFIX offsethash
|
||||
#define SH_ELEMENT_TYPE OffsetHashEntry
|
||||
#define SH_KEY_TYPE Size
|
||||
#define SH_SCOPE extern
|
||||
#define SH_DECLARE
|
||||
#include "lib/simplehash.h"
|
||||
|
||||
#endif
|
||||
1172
src/hnswbuild.c
Normal file
1172
src/hnswbuild.c
Normal file
File diff suppressed because it is too large
Load Diff
669
src/hnswinsert.c
Normal file
669
src/hnswinsert.c
Normal file
@@ -0,0 +1,669 @@
|
||||
#include "postgres.h"
|
||||
|
||||
#include <math.h>
|
||||
|
||||
#include "access/generic_xlog.h"
|
||||
#include "hnsw.h"
|
||||
#include "storage/bufmgr.h"
|
||||
#include "storage/lmgr.h"
|
||||
#include "utils/datum.h"
|
||||
#include "utils/memutils.h"
|
||||
|
||||
/*
|
||||
* Get the insert page
|
||||
*/
|
||||
static BlockNumber
|
||||
GetInsertPage(Relation index)
|
||||
{
|
||||
Buffer buf;
|
||||
Page page;
|
||||
HnswMetaPage metap;
|
||||
BlockNumber insertPage;
|
||||
|
||||
buf = ReadBuffer(index, HNSW_METAPAGE_BLKNO);
|
||||
LockBuffer(buf, BUFFER_LOCK_SHARE);
|
||||
page = BufferGetPage(buf);
|
||||
metap = HnswPageGetMeta(page);
|
||||
|
||||
insertPage = metap->insertPage;
|
||||
|
||||
UnlockReleaseBuffer(buf);
|
||||
|
||||
return insertPage;
|
||||
}
|
||||
|
||||
/*
|
||||
* Check for a free offset
|
||||
*/
|
||||
static bool
|
||||
HnswFreeOffset(Relation index, Buffer buf, Page page, HnswElement element, Size ntupSize, Buffer *nbuf, Page *npage, OffsetNumber *freeOffno, OffsetNumber *freeNeighborOffno, BlockNumber *newInsertPage)
|
||||
{
|
||||
OffsetNumber offno;
|
||||
OffsetNumber maxoffno = PageGetMaxOffsetNumber(page);
|
||||
|
||||
for (offno = FirstOffsetNumber; offno <= maxoffno; offno = OffsetNumberNext(offno))
|
||||
{
|
||||
HnswElementTuple etup = (HnswElementTuple) PageGetItem(page, PageGetItemId(page, offno));
|
||||
|
||||
/* Skip neighbor tuples */
|
||||
if (!HnswIsElementTuple(etup))
|
||||
continue;
|
||||
|
||||
if (etup->deleted)
|
||||
{
|
||||
BlockNumber elementPage = BufferGetBlockNumber(buf);
|
||||
BlockNumber neighborPage = ItemPointerGetBlockNumber(&etup->neighbortid);
|
||||
OffsetNumber neighborOffno = ItemPointerGetOffsetNumber(&etup->neighbortid);
|
||||
ItemId itemid;
|
||||
|
||||
if (!BlockNumberIsValid(*newInsertPage))
|
||||
*newInsertPage = elementPage;
|
||||
|
||||
if (neighborPage == elementPage)
|
||||
{
|
||||
*nbuf = buf;
|
||||
*npage = page;
|
||||
}
|
||||
else
|
||||
{
|
||||
*nbuf = ReadBuffer(index, neighborPage);
|
||||
LockBuffer(*nbuf, BUFFER_LOCK_EXCLUSIVE);
|
||||
|
||||
/* Skip WAL for now */
|
||||
*npage = BufferGetPage(*nbuf);
|
||||
}
|
||||
|
||||
itemid = PageGetItemId(*npage, neighborOffno);
|
||||
|
||||
/* Check for space on neighbor tuple page */
|
||||
if (PageGetFreeSpace(*npage) + ItemIdGetLength(itemid) - sizeof(ItemIdData) >= ntupSize)
|
||||
{
|
||||
*freeOffno = offno;
|
||||
*freeNeighborOffno = neighborOffno;
|
||||
return true;
|
||||
}
|
||||
else if (*nbuf != buf)
|
||||
UnlockReleaseBuffer(*nbuf);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Add a new page
|
||||
*/
|
||||
static void
|
||||
HnswInsertAppendPage(Relation index, Buffer *nbuf, Page *npage, GenericXLogState *state, Page page, bool building)
|
||||
{
|
||||
/* Add a new page */
|
||||
LockRelationForExtension(index, ExclusiveLock);
|
||||
*nbuf = HnswNewBuffer(index, MAIN_FORKNUM);
|
||||
UnlockRelationForExtension(index, ExclusiveLock);
|
||||
|
||||
/* Init new page */
|
||||
if (building)
|
||||
*npage = BufferGetPage(*nbuf);
|
||||
else
|
||||
*npage = GenericXLogRegisterBuffer(state, *nbuf, GENERIC_XLOG_FULL_IMAGE);
|
||||
|
||||
HnswInitPage(*nbuf, *npage);
|
||||
|
||||
/* Update previous buffer */
|
||||
HnswPageGetOpaque(page)->nextblkno = BufferGetBlockNumber(*nbuf);
|
||||
}
|
||||
|
||||
/*
|
||||
* Add to element and neighbor pages
|
||||
*/
|
||||
static void
|
||||
AddElementOnDisk(Relation index, HnswElement e, int m, BlockNumber insertPage, BlockNumber *updatedInsertPage, bool building)
|
||||
{
|
||||
Buffer buf;
|
||||
Page page;
|
||||
GenericXLogState *state;
|
||||
Size etupSize;
|
||||
Size ntupSize;
|
||||
Size combinedSize;
|
||||
Size maxSize;
|
||||
Size minCombinedSize;
|
||||
HnswElementTuple etup;
|
||||
BlockNumber currentPage = insertPage;
|
||||
HnswNeighborTuple ntup;
|
||||
Buffer nbuf;
|
||||
Page npage;
|
||||
OffsetNumber freeOffno = InvalidOffsetNumber;
|
||||
OffsetNumber freeNeighborOffno = InvalidOffsetNumber;
|
||||
BlockNumber newInsertPage = InvalidBlockNumber;
|
||||
char *base = NULL;
|
||||
|
||||
/* Calculate sizes */
|
||||
etupSize = HNSW_ELEMENT_TUPLE_SIZE(VARSIZE_ANY(HnswPtrAccess(base, e->value)));
|
||||
ntupSize = HNSW_NEIGHBOR_TUPLE_SIZE(e->level, m);
|
||||
combinedSize = etupSize + ntupSize + sizeof(ItemIdData);
|
||||
maxSize = HNSW_MAX_SIZE;
|
||||
minCombinedSize = etupSize + HNSW_NEIGHBOR_TUPLE_SIZE(0, m) + sizeof(ItemIdData);
|
||||
|
||||
/* Prepare element tuple */
|
||||
etup = palloc0(etupSize);
|
||||
HnswSetElementTuple(base, etup, e);
|
||||
|
||||
/* Prepare neighbor tuple */
|
||||
ntup = palloc0(ntupSize);
|
||||
HnswSetNeighborTuple(base, ntup, e, m);
|
||||
|
||||
/* Find a page (or two if needed) to insert the tuples */
|
||||
for (;;)
|
||||
{
|
||||
buf = ReadBuffer(index, currentPage);
|
||||
LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
|
||||
|
||||
if (building)
|
||||
{
|
||||
state = NULL;
|
||||
page = BufferGetPage(buf);
|
||||
}
|
||||
else
|
||||
{
|
||||
state = GenericXLogStart(index);
|
||||
page = GenericXLogRegisterBuffer(state, buf, 0);
|
||||
}
|
||||
|
||||
/* Keep track of first page where element at level 0 can fit */
|
||||
if (!BlockNumberIsValid(newInsertPage) && PageGetFreeSpace(page) >= minCombinedSize)
|
||||
newInsertPage = currentPage;
|
||||
|
||||
/* First, try the fastest path */
|
||||
/* Space for both tuples on the current page */
|
||||
/* This can split existing tuples in rare cases */
|
||||
if (PageGetFreeSpace(page) >= combinedSize)
|
||||
{
|
||||
nbuf = buf;
|
||||
npage = page;
|
||||
break;
|
||||
}
|
||||
|
||||
/* Next, try space from a deleted element */
|
||||
if (HnswFreeOffset(index, buf, page, e, ntupSize, &nbuf, &npage, &freeOffno, &freeNeighborOffno, &newInsertPage))
|
||||
{
|
||||
if (nbuf != buf)
|
||||
{
|
||||
if (building)
|
||||
npage = BufferGetPage(nbuf);
|
||||
else
|
||||
npage = GenericXLogRegisterBuffer(state, nbuf, 0);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
/* Finally, try space for element only if last page */
|
||||
/* Skip if both tuples can fit on the same page */
|
||||
if (combinedSize > maxSize && PageGetFreeSpace(page) >= etupSize && !BlockNumberIsValid(HnswPageGetOpaque(page)->nextblkno))
|
||||
{
|
||||
HnswInsertAppendPage(index, &nbuf, &npage, state, page, building);
|
||||
break;
|
||||
}
|
||||
|
||||
currentPage = HnswPageGetOpaque(page)->nextblkno;
|
||||
|
||||
if (BlockNumberIsValid(currentPage))
|
||||
{
|
||||
/* Move to next page */
|
||||
if (!building)
|
||||
GenericXLogAbort(state);
|
||||
UnlockReleaseBuffer(buf);
|
||||
}
|
||||
else
|
||||
{
|
||||
Buffer newbuf;
|
||||
Page newpage;
|
||||
|
||||
HnswInsertAppendPage(index, &newbuf, &newpage, state, page, building);
|
||||
|
||||
/* Commit */
|
||||
if (building)
|
||||
MarkBufferDirty(buf);
|
||||
else
|
||||
GenericXLogFinish(state);
|
||||
|
||||
/* Unlock previous buffer */
|
||||
UnlockReleaseBuffer(buf);
|
||||
|
||||
/* Prepare new buffer */
|
||||
buf = newbuf;
|
||||
if (building)
|
||||
{
|
||||
state = NULL;
|
||||
page = BufferGetPage(buf);
|
||||
}
|
||||
else
|
||||
{
|
||||
state = GenericXLogStart(index);
|
||||
page = GenericXLogRegisterBuffer(state, buf, 0);
|
||||
}
|
||||
|
||||
/* Create new page for neighbors if needed */
|
||||
if (PageGetFreeSpace(page) < combinedSize)
|
||||
HnswInsertAppendPage(index, &nbuf, &npage, state, page, building);
|
||||
else
|
||||
{
|
||||
nbuf = buf;
|
||||
npage = page;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
e->blkno = BufferGetBlockNumber(buf);
|
||||
e->neighborPage = BufferGetBlockNumber(nbuf);
|
||||
|
||||
/* Added tuple to new page if newInsertPage is not set */
|
||||
/* So can set to neighbor page instead of element page */
|
||||
if (!BlockNumberIsValid(newInsertPage))
|
||||
newInsertPage = e->neighborPage;
|
||||
|
||||
if (OffsetNumberIsValid(freeOffno))
|
||||
{
|
||||
e->offno = freeOffno;
|
||||
e->neighborOffno = freeNeighborOffno;
|
||||
}
|
||||
else
|
||||
{
|
||||
e->offno = OffsetNumberNext(PageGetMaxOffsetNumber(page));
|
||||
if (nbuf == buf)
|
||||
e->neighborOffno = OffsetNumberNext(e->offno);
|
||||
else
|
||||
e->neighborOffno = FirstOffsetNumber;
|
||||
}
|
||||
|
||||
ItemPointerSet(&etup->neighbortid, e->neighborPage, e->neighborOffno);
|
||||
|
||||
/* Add element and neighbors */
|
||||
if (OffsetNumberIsValid(freeOffno))
|
||||
{
|
||||
if (!PageIndexTupleOverwrite(page, e->offno, (Item) etup, etupSize))
|
||||
elog(ERROR, "failed to add index item to \"%s\"", RelationGetRelationName(index));
|
||||
|
||||
if (!PageIndexTupleOverwrite(npage, e->neighborOffno, (Item) ntup, ntupSize))
|
||||
elog(ERROR, "failed to add index item to \"%s\"", RelationGetRelationName(index));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (PageAddItem(page, (Item) etup, etupSize, InvalidOffsetNumber, false, false) != e->offno)
|
||||
elog(ERROR, "failed to add index item to \"%s\"", RelationGetRelationName(index));
|
||||
|
||||
if (PageAddItem(npage, (Item) ntup, ntupSize, InvalidOffsetNumber, false, false) != e->neighborOffno)
|
||||
elog(ERROR, "failed to add index item to \"%s\"", RelationGetRelationName(index));
|
||||
}
|
||||
|
||||
/* Commit */
|
||||
if (building)
|
||||
{
|
||||
MarkBufferDirty(buf);
|
||||
if (nbuf != buf)
|
||||
MarkBufferDirty(nbuf);
|
||||
}
|
||||
else
|
||||
GenericXLogFinish(state);
|
||||
UnlockReleaseBuffer(buf);
|
||||
if (nbuf != buf)
|
||||
UnlockReleaseBuffer(nbuf);
|
||||
|
||||
/* Update the insert page */
|
||||
if (BlockNumberIsValid(newInsertPage) && newInsertPage != insertPage)
|
||||
*updatedInsertPage = newInsertPage;
|
||||
}
|
||||
|
||||
/*
|
||||
* Check if connection already exists
|
||||
*/
|
||||
static bool
|
||||
ConnectionExists(HnswElement e, HnswNeighborTuple ntup, int startIdx, int lm)
|
||||
{
|
||||
for (int i = 0; i < lm; i++)
|
||||
{
|
||||
ItemPointer indextid = &ntup->indextids[startIdx + i];
|
||||
|
||||
if (!ItemPointerIsValid(indextid))
|
||||
break;
|
||||
|
||||
if (ItemPointerGetBlockNumber(indextid) == e->blkno && ItemPointerGetOffsetNumber(indextid) == e->offno)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Update neighbors
|
||||
*/
|
||||
void
|
||||
HnswUpdateNeighborsOnDisk(Relation index, FmgrInfo *procinfo, Oid collation, HnswElement e, int m, bool checkExisting, bool building)
|
||||
{
|
||||
char *base = NULL;
|
||||
|
||||
for (int lc = e->level; lc >= 0; lc--)
|
||||
{
|
||||
int lm = HnswGetLayerM(m, lc);
|
||||
HnswNeighborArray *neighbors = HnswGetNeighbors(base, e, lc);
|
||||
|
||||
for (int i = 0; i < neighbors->length; i++)
|
||||
{
|
||||
HnswCandidate *hc = &neighbors->items[i];
|
||||
Buffer buf;
|
||||
Page page;
|
||||
GenericXLogState *state;
|
||||
HnswNeighborTuple ntup;
|
||||
int idx = -1;
|
||||
int startIdx;
|
||||
HnswElement neighborElement = HnswPtrAccess(base, hc->element);
|
||||
OffsetNumber offno = neighborElement->neighborOffno;
|
||||
|
||||
/* Get latest neighbors since they may have changed */
|
||||
/* Do not lock yet since selecting neighbors can take time */
|
||||
HnswLoadNeighbors(neighborElement, index, m);
|
||||
|
||||
/*
|
||||
* Could improve performance for vacuuming by checking neighbors
|
||||
* against list of elements being deleted to find index. It's
|
||||
* important to exclude already deleted elements for this since
|
||||
* they can be replaced at any time.
|
||||
*/
|
||||
|
||||
/* Select neighbors */
|
||||
HnswUpdateConnection(NULL, e, hc, lm, lc, &idx, index, procinfo, collation);
|
||||
|
||||
/* New element was not selected as a neighbor */
|
||||
if (idx == -1)
|
||||
continue;
|
||||
|
||||
/* Register page */
|
||||
buf = ReadBuffer(index, neighborElement->neighborPage);
|
||||
LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
|
||||
if (building)
|
||||
{
|
||||
state = NULL;
|
||||
page = BufferGetPage(buf);
|
||||
}
|
||||
else
|
||||
{
|
||||
state = GenericXLogStart(index);
|
||||
page = GenericXLogRegisterBuffer(state, buf, 0);
|
||||
}
|
||||
|
||||
/* Get tuple */
|
||||
ntup = (HnswNeighborTuple) PageGetItem(page, PageGetItemId(page, offno));
|
||||
|
||||
/* Calculate index for update */
|
||||
startIdx = (neighborElement->level - lc) * m;
|
||||
|
||||
/* Check for existing connection */
|
||||
if (checkExisting && ConnectionExists(e, ntup, startIdx, lm))
|
||||
idx = -1;
|
||||
else if (idx == -2)
|
||||
{
|
||||
/* Find free offset if still exists */
|
||||
/* TODO Retry updating connections if not */
|
||||
for (int j = 0; j < lm; j++)
|
||||
{
|
||||
if (!ItemPointerIsValid(&ntup->indextids[startIdx + j]))
|
||||
{
|
||||
idx = startIdx + j;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
idx += startIdx;
|
||||
|
||||
/* Make robust to issues */
|
||||
if (idx >= 0 && idx < ntup->count)
|
||||
{
|
||||
ItemPointer indextid = &ntup->indextids[idx];
|
||||
|
||||
/* Update neighbor on the buffer */
|
||||
ItemPointerSet(indextid, e->blkno, e->offno);
|
||||
|
||||
/* Commit */
|
||||
if (building)
|
||||
MarkBufferDirty(buf);
|
||||
else
|
||||
GenericXLogFinish(state);
|
||||
}
|
||||
else if (!building)
|
||||
GenericXLogAbort(state);
|
||||
|
||||
UnlockReleaseBuffer(buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Add a heap TID to an existing element
|
||||
*/
|
||||
static bool
|
||||
AddDuplicateOnDisk(Relation index, HnswElement element, HnswElement dup, bool building)
|
||||
{
|
||||
Buffer buf;
|
||||
Page page;
|
||||
GenericXLogState *state;
|
||||
HnswElementTuple etup;
|
||||
int i;
|
||||
|
||||
/* Read page */
|
||||
buf = ReadBuffer(index, dup->blkno);
|
||||
LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
|
||||
if (building)
|
||||
{
|
||||
state = NULL;
|
||||
page = BufferGetPage(buf);
|
||||
}
|
||||
else
|
||||
{
|
||||
state = GenericXLogStart(index);
|
||||
page = GenericXLogRegisterBuffer(state, buf, 0);
|
||||
}
|
||||
|
||||
/* Find space */
|
||||
etup = (HnswElementTuple) PageGetItem(page, PageGetItemId(page, dup->offno));
|
||||
for (i = 0; i < HNSW_HEAPTIDS; i++)
|
||||
{
|
||||
if (!ItemPointerIsValid(&etup->heaptids[i]))
|
||||
break;
|
||||
}
|
||||
|
||||
/* Either being deleted or we lost our chance to another backend */
|
||||
if (i == 0 || i == HNSW_HEAPTIDS)
|
||||
{
|
||||
if (!building)
|
||||
GenericXLogAbort(state);
|
||||
UnlockReleaseBuffer(buf);
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Add heap TID, modifying the tuple on the page directly */
|
||||
etup->heaptids[i] = element->heaptids[0];
|
||||
|
||||
/* Commit */
|
||||
if (building)
|
||||
MarkBufferDirty(buf);
|
||||
else
|
||||
GenericXLogFinish(state);
|
||||
UnlockReleaseBuffer(buf);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Find duplicate element
|
||||
*/
|
||||
static bool
|
||||
FindDuplicateOnDisk(Relation index, HnswElement element, bool building)
|
||||
{
|
||||
char *base = NULL;
|
||||
HnswNeighborArray *neighbors = HnswGetNeighbors(base, element, 0);
|
||||
Datum value = HnswGetValue(base, element);
|
||||
|
||||
for (int i = 0; i < neighbors->length; i++)
|
||||
{
|
||||
HnswCandidate *neighbor = &neighbors->items[i];
|
||||
HnswElement neighborElement = HnswPtrAccess(base, neighbor->element);
|
||||
Datum neighborValue = HnswGetValue(base, neighborElement);
|
||||
|
||||
/* Exit early since ordered by distance */
|
||||
if (!datumIsEqual(value, neighborValue, false, -1))
|
||||
return false;
|
||||
|
||||
if (AddDuplicateOnDisk(index, element, neighborElement, building))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Update graph on disk
|
||||
*/
|
||||
static void
|
||||
UpdateGraphOnDisk(Relation index, FmgrInfo *procinfo, Oid collation, HnswElement element, int m, int efConstruction, HnswElement entryPoint, bool building)
|
||||
{
|
||||
BlockNumber newInsertPage = InvalidBlockNumber;
|
||||
|
||||
/* Look for duplicate */
|
||||
if (FindDuplicateOnDisk(index, element, building))
|
||||
return;
|
||||
|
||||
/* Add element */
|
||||
AddElementOnDisk(index, element, m, GetInsertPage(index), &newInsertPage, building);
|
||||
|
||||
/* Update insert page if needed */
|
||||
if (BlockNumberIsValid(newInsertPage))
|
||||
HnswUpdateMetaPage(index, 0, NULL, newInsertPage, MAIN_FORKNUM, building);
|
||||
|
||||
/* Update neighbors */
|
||||
HnswUpdateNeighborsOnDisk(index, procinfo, collation, element, m, false, building);
|
||||
|
||||
/* Update entry point if needed */
|
||||
if (entryPoint == NULL || element->level > entryPoint->level)
|
||||
HnswUpdateMetaPage(index, HNSW_UPDATE_ENTRY_GREATER, element, InvalidBlockNumber, MAIN_FORKNUM, building);
|
||||
}
|
||||
|
||||
/*
|
||||
* Insert a tuple into the index
|
||||
*/
|
||||
bool
|
||||
HnswInsertTupleOnDisk(Relation index, Datum value, Datum *values, bool *isnull, ItemPointer heap_tid, bool building)
|
||||
{
|
||||
HnswElement entryPoint;
|
||||
HnswElement element;
|
||||
int m;
|
||||
int efConstruction = HnswGetEfConstruction(index);
|
||||
FmgrInfo *procinfo = index_getprocinfo(index, 1, HNSW_DISTANCE_PROC);
|
||||
Oid collation = index->rd_indcollation[0];
|
||||
LOCKMODE lockmode = ShareLock;
|
||||
char *base = NULL;
|
||||
|
||||
/*
|
||||
* Get a shared lock. This allows vacuum to ensure no in-flight inserts
|
||||
* before repairing graph. Use a page lock so it does not interfere with
|
||||
* buffer lock (or reads when vacuuming).
|
||||
*/
|
||||
LockPage(index, HNSW_UPDATE_LOCK, lockmode);
|
||||
|
||||
/* Get m and entry point */
|
||||
HnswGetMetaPageInfo(index, &m, &entryPoint);
|
||||
|
||||
/* Create an element */
|
||||
element = HnswInitElement(base, heap_tid, m, HnswGetMl(m), HnswGetMaxLevel(m), NULL);
|
||||
HnswPtrStore(base, element->value, DatumGetPointer(value));
|
||||
|
||||
/* Prevent concurrent inserts when likely updating entry point */
|
||||
if (entryPoint == NULL || element->level > entryPoint->level)
|
||||
{
|
||||
/* Release shared lock */
|
||||
UnlockPage(index, HNSW_UPDATE_LOCK, lockmode);
|
||||
|
||||
/* Get exclusive lock */
|
||||
lockmode = ExclusiveLock;
|
||||
LockPage(index, HNSW_UPDATE_LOCK, lockmode);
|
||||
|
||||
/* Get latest entry point after lock is acquired */
|
||||
entryPoint = HnswGetEntryPoint(index);
|
||||
}
|
||||
|
||||
/* Find neighbors for element */
|
||||
HnswFindElementNeighbors(base, element, entryPoint, index, procinfo, collation, m, efConstruction, false);
|
||||
|
||||
/* Update graph on disk */
|
||||
UpdateGraphOnDisk(index, procinfo, collation, element, m, efConstruction, entryPoint, building);
|
||||
|
||||
/* Release lock */
|
||||
UnlockPage(index, HNSW_UPDATE_LOCK, lockmode);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Insert a tuple into the index
|
||||
*/
|
||||
static void
|
||||
HnswInsertTuple(Relation index, Datum *values, bool *isnull, ItemPointer heap_tid)
|
||||
{
|
||||
Datum value;
|
||||
FmgrInfo *normprocinfo;
|
||||
Oid collation = index->rd_indcollation[0];
|
||||
HnswType type = HnswGetType(index);
|
||||
|
||||
/* Detoast once for all calls */
|
||||
value = PointerGetDatum(PG_DETOAST_DATUM(values[0]));
|
||||
|
||||
/* Check value */
|
||||
HnswCheckValue(value, type);
|
||||
|
||||
/* Normalize if needed */
|
||||
normprocinfo = HnswOptionalProcInfo(index, HNSW_NORM_PROC);
|
||||
if (normprocinfo != NULL)
|
||||
{
|
||||
if (!HnswNormValue(normprocinfo, collation, &value, type))
|
||||
return;
|
||||
}
|
||||
|
||||
HnswInsertTupleOnDisk(index, value, values, isnull, heap_tid, false);
|
||||
}
|
||||
|
||||
/*
|
||||
* Insert a tuple into the index
|
||||
*/
|
||||
bool
|
||||
hnswinsert(Relation index, Datum *values, bool *isnull, ItemPointer heap_tid,
|
||||
Relation heap, IndexUniqueCheck checkUnique
|
||||
#if PG_VERSION_NUM >= 140000
|
||||
,bool indexUnchanged
|
||||
#endif
|
||||
,IndexInfo *indexInfo
|
||||
)
|
||||
{
|
||||
MemoryContext oldCtx;
|
||||
MemoryContext insertCtx;
|
||||
|
||||
/* Skip nulls */
|
||||
if (isnull[0])
|
||||
return false;
|
||||
|
||||
/* Create memory context */
|
||||
insertCtx = AllocSetContextCreate(CurrentMemoryContext,
|
||||
"Hnsw insert temporary context",
|
||||
ALLOCSET_DEFAULT_SIZES);
|
||||
oldCtx = MemoryContextSwitchTo(insertCtx);
|
||||
|
||||
/* Insert tuple */
|
||||
HnswInsertTuple(index, values, isnull, heap_tid);
|
||||
|
||||
/* Delete memory context */
|
||||
MemoryContextSwitchTo(oldCtx);
|
||||
MemoryContextDelete(insertCtx);
|
||||
|
||||
return false;
|
||||
}
|
||||
203
src/hnswscan.c
Normal file
203
src/hnswscan.c
Normal file
@@ -0,0 +1,203 @@
|
||||
#include "postgres.h"
|
||||
|
||||
#include "access/relscan.h"
|
||||
#include "hnsw.h"
|
||||
#include "pgstat.h"
|
||||
#include "storage/bufmgr.h"
|
||||
#include "storage/lmgr.h"
|
||||
#include "utils/memutils.h"
|
||||
|
||||
/*
|
||||
* Algorithm 5 from paper
|
||||
*/
|
||||
static List *
|
||||
GetScanItems(IndexScanDesc scan, Datum q)
|
||||
{
|
||||
HnswScanOpaque so = (HnswScanOpaque) scan->opaque;
|
||||
Relation index = scan->indexRelation;
|
||||
FmgrInfo *procinfo = so->procinfo;
|
||||
Oid collation = so->collation;
|
||||
List *ep;
|
||||
List *w;
|
||||
int m;
|
||||
HnswElement entryPoint;
|
||||
char *base = NULL;
|
||||
|
||||
/* Get m and entry point */
|
||||
HnswGetMetaPageInfo(index, &m, &entryPoint);
|
||||
|
||||
if (entryPoint == NULL)
|
||||
return NIL;
|
||||
|
||||
ep = list_make1(HnswEntryCandidate(base, entryPoint, q, index, procinfo, collation, false));
|
||||
|
||||
for (int lc = entryPoint->level; lc >= 1; lc--)
|
||||
{
|
||||
w = HnswSearchLayer(base, q, ep, 1, lc, index, procinfo, collation, m, false, NULL);
|
||||
ep = w;
|
||||
}
|
||||
|
||||
return HnswSearchLayer(base, q, ep, hnsw_ef_search, 0, index, procinfo, collation, m, false, NULL);
|
||||
}
|
||||
|
||||
/*
|
||||
* Get scan value
|
||||
*/
|
||||
static Datum
|
||||
GetScanValue(IndexScanDesc scan)
|
||||
{
|
||||
HnswScanOpaque so = (HnswScanOpaque) scan->opaque;
|
||||
Datum value;
|
||||
|
||||
if (scan->orderByData->sk_flags & SK_ISNULL)
|
||||
value = PointerGetDatum(NULL);
|
||||
else
|
||||
{
|
||||
value = scan->orderByData->sk_argument;
|
||||
|
||||
/* Value should not be compressed or toasted */
|
||||
Assert(!VARATT_IS_COMPRESSED(DatumGetPointer(value)));
|
||||
Assert(!VARATT_IS_EXTENDED(DatumGetPointer(value)));
|
||||
|
||||
/* Fine if normalization fails */
|
||||
if (so->normprocinfo != NULL)
|
||||
HnswNormValue(so->normprocinfo, so->collation, &value, HnswGetType(scan->indexRelation));
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/*
|
||||
* Prepare for an index scan
|
||||
*/
|
||||
IndexScanDesc
|
||||
hnswbeginscan(Relation index, int nkeys, int norderbys)
|
||||
{
|
||||
IndexScanDesc scan;
|
||||
HnswScanOpaque so;
|
||||
|
||||
scan = RelationGetIndexScan(index, nkeys, norderbys);
|
||||
|
||||
so = (HnswScanOpaque) palloc(sizeof(HnswScanOpaqueData));
|
||||
so->first = true;
|
||||
so->tmpCtx = AllocSetContextCreate(CurrentMemoryContext,
|
||||
"Hnsw scan temporary context",
|
||||
ALLOCSET_DEFAULT_SIZES);
|
||||
|
||||
/* Set support functions */
|
||||
so->procinfo = index_getprocinfo(index, 1, HNSW_DISTANCE_PROC);
|
||||
so->normprocinfo = HnswOptionalProcInfo(index, HNSW_NORM_PROC);
|
||||
so->collation = index->rd_indcollation[0];
|
||||
|
||||
scan->opaque = so;
|
||||
|
||||
return scan;
|
||||
}
|
||||
|
||||
/*
|
||||
* Start or restart an index scan
|
||||
*/
|
||||
void
|
||||
hnswrescan(IndexScanDesc scan, ScanKey keys, int nkeys, ScanKey orderbys, int norderbys)
|
||||
{
|
||||
HnswScanOpaque so = (HnswScanOpaque) scan->opaque;
|
||||
|
||||
so->first = true;
|
||||
MemoryContextReset(so->tmpCtx);
|
||||
|
||||
if (keys && scan->numberOfKeys > 0)
|
||||
memmove(scan->keyData, keys, scan->numberOfKeys * sizeof(ScanKeyData));
|
||||
|
||||
if (orderbys && scan->numberOfOrderBys > 0)
|
||||
memmove(scan->orderByData, orderbys, scan->numberOfOrderBys * sizeof(ScanKeyData));
|
||||
}
|
||||
|
||||
/*
|
||||
* Fetch the next tuple in the given scan
|
||||
*/
|
||||
bool
|
||||
hnswgettuple(IndexScanDesc scan, ScanDirection dir)
|
||||
{
|
||||
HnswScanOpaque so = (HnswScanOpaque) scan->opaque;
|
||||
MemoryContext oldCtx = MemoryContextSwitchTo(so->tmpCtx);
|
||||
|
||||
/*
|
||||
* Index can be used to scan backward, but Postgres doesn't support
|
||||
* backward scan on operators
|
||||
*/
|
||||
Assert(ScanDirectionIsForward(dir));
|
||||
|
||||
if (so->first)
|
||||
{
|
||||
Datum value;
|
||||
|
||||
/* Count index scan for stats */
|
||||
pgstat_count_index_scan(scan->indexRelation);
|
||||
|
||||
/* Safety check */
|
||||
if (scan->orderByData == NULL)
|
||||
elog(ERROR, "cannot scan hnsw index without order");
|
||||
|
||||
/* Requires MVCC-compliant snapshot as not able to maintain a pin */
|
||||
/* https://www.postgresql.org/docs/current/index-locking.html */
|
||||
if (!IsMVCCSnapshot(scan->xs_snapshot))
|
||||
elog(ERROR, "non-MVCC snapshots are not supported with hnsw");
|
||||
|
||||
/* Get scan value */
|
||||
value = GetScanValue(scan);
|
||||
|
||||
/*
|
||||
* Get a shared lock. This allows vacuum to ensure no in-flight scans
|
||||
* before marking tuples as deleted.
|
||||
*/
|
||||
LockPage(scan->indexRelation, HNSW_SCAN_LOCK, ShareLock);
|
||||
|
||||
so->w = GetScanItems(scan, value);
|
||||
|
||||
/* Release shared lock */
|
||||
UnlockPage(scan->indexRelation, HNSW_SCAN_LOCK, ShareLock);
|
||||
|
||||
so->first = false;
|
||||
}
|
||||
|
||||
while (list_length(so->w) > 0)
|
||||
{
|
||||
char *base = NULL;
|
||||
HnswCandidate *hc = llast(so->w);
|
||||
HnswElement element = HnswPtrAccess(base, hc->element);
|
||||
ItemPointer heaptid;
|
||||
|
||||
/* Move to next element if no valid heap TIDs */
|
||||
if (element->heaptidsLength == 0)
|
||||
{
|
||||
so->w = list_delete_last(so->w);
|
||||
continue;
|
||||
}
|
||||
|
||||
heaptid = &element->heaptids[--element->heaptidsLength];
|
||||
|
||||
MemoryContextSwitchTo(oldCtx);
|
||||
|
||||
scan->xs_heaptid = *heaptid;
|
||||
scan->xs_recheck = false;
|
||||
scan->xs_recheckorderby = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
MemoryContextSwitchTo(oldCtx);
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* End a scan and release resources
|
||||
*/
|
||||
void
|
||||
hnswendscan(IndexScanDesc scan)
|
||||
{
|
||||
HnswScanOpaque so = (HnswScanOpaque) scan->opaque;
|
||||
|
||||
MemoryContextDelete(so->tmpCtx);
|
||||
|
||||
pfree(so);
|
||||
scan->opaque = NULL;
|
||||
}
|
||||
1360
src/hnswutils.c
Normal file
1360
src/hnswutils.c
Normal file
File diff suppressed because it is too large
Load Diff
646
src/hnswvacuum.c
Normal file
646
src/hnswvacuum.c
Normal file
@@ -0,0 +1,646 @@
|
||||
#include "postgres.h"
|
||||
|
||||
#include <math.h>
|
||||
|
||||
#include "access/generic_xlog.h"
|
||||
#include "commands/vacuum.h"
|
||||
#include "hnsw.h"
|
||||
#include "storage/bufmgr.h"
|
||||
#include "storage/lmgr.h"
|
||||
#include "utils/memutils.h"
|
||||
|
||||
/*
|
||||
* Check if deleted list contains an index TID
|
||||
*/
|
||||
static bool
|
||||
DeletedContains(tidhash_hash * deleted, ItemPointer indextid)
|
||||
{
|
||||
return tidhash_lookup(deleted, *indextid) != NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Remove deleted heap TIDs
|
||||
*
|
||||
* OK to remove for entry point, since always considered for searches and inserts
|
||||
*/
|
||||
static void
|
||||
RemoveHeapTids(HnswVacuumState * vacuumstate)
|
||||
{
|
||||
BlockNumber blkno = HNSW_HEAD_BLKNO;
|
||||
HnswElement highestPoint = &vacuumstate->highestPoint;
|
||||
Relation index = vacuumstate->index;
|
||||
BufferAccessStrategy bas = vacuumstate->bas;
|
||||
HnswElement entryPoint = HnswGetEntryPoint(vacuumstate->index);
|
||||
IndexBulkDeleteResult *stats = vacuumstate->stats;
|
||||
|
||||
/* Store separately since highestPoint.level is uint8 */
|
||||
int highestLevel = -1;
|
||||
|
||||
/* Initialize highest point */
|
||||
highestPoint->blkno = InvalidBlockNumber;
|
||||
highestPoint->offno = InvalidOffsetNumber;
|
||||
|
||||
while (BlockNumberIsValid(blkno))
|
||||
{
|
||||
Buffer buf;
|
||||
Page page;
|
||||
GenericXLogState *state;
|
||||
OffsetNumber offno;
|
||||
OffsetNumber maxoffno;
|
||||
bool updated = false;
|
||||
|
||||
vacuum_delay_point();
|
||||
|
||||
buf = ReadBufferExtended(index, MAIN_FORKNUM, blkno, RBM_NORMAL, bas);
|
||||
LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
|
||||
state = GenericXLogStart(index);
|
||||
page = GenericXLogRegisterBuffer(state, buf, 0);
|
||||
maxoffno = PageGetMaxOffsetNumber(page);
|
||||
|
||||
/* Iterate over nodes */
|
||||
for (offno = FirstOffsetNumber; offno <= maxoffno; offno = OffsetNumberNext(offno))
|
||||
{
|
||||
HnswElementTuple etup = (HnswElementTuple) PageGetItem(page, PageGetItemId(page, offno));
|
||||
int idx = 0;
|
||||
bool itemUpdated = false;
|
||||
|
||||
/* Skip neighbor tuples */
|
||||
if (!HnswIsElementTuple(etup))
|
||||
continue;
|
||||
|
||||
if (ItemPointerIsValid(&etup->heaptids[0]))
|
||||
{
|
||||
for (int i = 0; i < HNSW_HEAPTIDS; i++)
|
||||
{
|
||||
/* Stop at first unused */
|
||||
if (!ItemPointerIsValid(&etup->heaptids[i]))
|
||||
break;
|
||||
|
||||
if (vacuumstate->callback(&etup->heaptids[i], vacuumstate->callback_state))
|
||||
{
|
||||
itemUpdated = true;
|
||||
stats->tuples_removed++;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Move to front of list */
|
||||
etup->heaptids[idx++] = etup->heaptids[i];
|
||||
stats->num_index_tuples++;
|
||||
}
|
||||
}
|
||||
|
||||
if (itemUpdated)
|
||||
{
|
||||
/* Mark rest as invalid */
|
||||
for (int i = idx; i < HNSW_HEAPTIDS; i++)
|
||||
ItemPointerSetInvalid(&etup->heaptids[i]);
|
||||
|
||||
updated = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!ItemPointerIsValid(&etup->heaptids[0]))
|
||||
{
|
||||
ItemPointerData ip;
|
||||
bool found;
|
||||
|
||||
/* Add to deleted list */
|
||||
ItemPointerSet(&ip, blkno, offno);
|
||||
|
||||
tidhash_insert(vacuumstate->deleted, ip, &found);
|
||||
Assert(!found);
|
||||
}
|
||||
else if (etup->level > highestLevel && !(entryPoint != NULL && blkno == entryPoint->blkno && offno == entryPoint->offno))
|
||||
{
|
||||
/* Keep track of highest non-entry point */
|
||||
highestPoint->blkno = blkno;
|
||||
highestPoint->offno = offno;
|
||||
highestPoint->level = etup->level;
|
||||
highestLevel = etup->level;
|
||||
}
|
||||
}
|
||||
|
||||
blkno = HnswPageGetOpaque(page)->nextblkno;
|
||||
|
||||
if (updated)
|
||||
GenericXLogFinish(state);
|
||||
else
|
||||
GenericXLogAbort(state);
|
||||
|
||||
UnlockReleaseBuffer(buf);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Check for deleted neighbors
|
||||
*/
|
||||
static bool
|
||||
NeedsUpdated(HnswVacuumState * vacuumstate, HnswElement element)
|
||||
{
|
||||
Relation index = vacuumstate->index;
|
||||
BufferAccessStrategy bas = vacuumstate->bas;
|
||||
Buffer buf;
|
||||
Page page;
|
||||
HnswNeighborTuple ntup;
|
||||
bool needsUpdated = false;
|
||||
|
||||
buf = ReadBufferExtended(index, MAIN_FORKNUM, element->neighborPage, RBM_NORMAL, bas);
|
||||
LockBuffer(buf, BUFFER_LOCK_SHARE);
|
||||
page = BufferGetPage(buf);
|
||||
ntup = (HnswNeighborTuple) PageGetItem(page, PageGetItemId(page, element->neighborOffno));
|
||||
|
||||
Assert(HnswIsNeighborTuple(ntup));
|
||||
|
||||
/* Check neighbors */
|
||||
for (int i = 0; i < ntup->count; i++)
|
||||
{
|
||||
ItemPointer indextid = &ntup->indextids[i];
|
||||
|
||||
if (!ItemPointerIsValid(indextid))
|
||||
continue;
|
||||
|
||||
/* Check if in deleted list */
|
||||
if (DeletedContains(vacuumstate->deleted, indextid))
|
||||
{
|
||||
needsUpdated = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Also update if layer 0 is not full */
|
||||
/* This could indicate too many candidates being deleted during insert */
|
||||
if (!needsUpdated)
|
||||
needsUpdated = !ItemPointerIsValid(&ntup->indextids[ntup->count - 1]);
|
||||
|
||||
UnlockReleaseBuffer(buf);
|
||||
|
||||
return needsUpdated;
|
||||
}
|
||||
|
||||
/*
|
||||
* Repair graph for a single element
|
||||
*/
|
||||
static void
|
||||
RepairGraphElement(HnswVacuumState * vacuumstate, HnswElement element, HnswElement entryPoint)
|
||||
{
|
||||
Relation index = vacuumstate->index;
|
||||
Buffer buf;
|
||||
Page page;
|
||||
GenericXLogState *state;
|
||||
int m = vacuumstate->m;
|
||||
int efConstruction = vacuumstate->efConstruction;
|
||||
FmgrInfo *procinfo = vacuumstate->procinfo;
|
||||
Oid collation = vacuumstate->collation;
|
||||
BufferAccessStrategy bas = vacuumstate->bas;
|
||||
HnswNeighborTuple ntup = vacuumstate->ntup;
|
||||
Size ntupSize = HNSW_NEIGHBOR_TUPLE_SIZE(element->level, m);
|
||||
char *base = NULL;
|
||||
|
||||
/* Skip if element is entry point */
|
||||
if (entryPoint != NULL && element->blkno == entryPoint->blkno && element->offno == entryPoint->offno)
|
||||
return;
|
||||
|
||||
/* Init fields */
|
||||
HnswInitNeighbors(base, element, m, NULL);
|
||||
element->heaptidsLength = 0;
|
||||
|
||||
/* Find neighbors for element, skipping itself */
|
||||
HnswFindElementNeighbors(base, element, entryPoint, index, procinfo, collation, m, efConstruction, true);
|
||||
|
||||
/* Zero memory for each element */
|
||||
MemSet(ntup, 0, HNSW_TUPLE_ALLOC_SIZE);
|
||||
|
||||
/* Update neighbor tuple */
|
||||
/* Do this before getting page to minimize locking */
|
||||
HnswSetNeighborTuple(base, ntup, element, m);
|
||||
|
||||
/* Get neighbor page */
|
||||
buf = ReadBufferExtended(index, MAIN_FORKNUM, element->neighborPage, RBM_NORMAL, bas);
|
||||
LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
|
||||
state = GenericXLogStart(index);
|
||||
page = GenericXLogRegisterBuffer(state, buf, 0);
|
||||
|
||||
/* Overwrite tuple */
|
||||
if (!PageIndexTupleOverwrite(page, element->neighborOffno, (Item) ntup, ntupSize))
|
||||
elog(ERROR, "failed to add index item to \"%s\"", RelationGetRelationName(index));
|
||||
|
||||
/* Commit */
|
||||
GenericXLogFinish(state);
|
||||
UnlockReleaseBuffer(buf);
|
||||
|
||||
/* Update neighbors */
|
||||
HnswUpdateNeighborsOnDisk(index, procinfo, collation, element, m, true, false);
|
||||
}
|
||||
|
||||
/*
|
||||
* Repair graph entry point
|
||||
*/
|
||||
static void
|
||||
RepairGraphEntryPoint(HnswVacuumState * vacuumstate)
|
||||
{
|
||||
Relation index = vacuumstate->index;
|
||||
HnswElement highestPoint = &vacuumstate->highestPoint;
|
||||
HnswElement entryPoint;
|
||||
MemoryContext oldCtx = MemoryContextSwitchTo(vacuumstate->tmpCtx);
|
||||
|
||||
if (!BlockNumberIsValid(highestPoint->blkno))
|
||||
highestPoint = NULL;
|
||||
|
||||
/*
|
||||
* Repair graph for highest non-entry point. Highest point may be outdated
|
||||
* due to inserts that happen during and after RemoveHeapTids.
|
||||
*/
|
||||
if (highestPoint != NULL)
|
||||
{
|
||||
/* Get a shared lock */
|
||||
LockPage(index, HNSW_UPDATE_LOCK, ShareLock);
|
||||
|
||||
/* Load element */
|
||||
HnswLoadElement(highestPoint, NULL, NULL, index, vacuumstate->procinfo, vacuumstate->collation, true);
|
||||
|
||||
/* Repair if needed */
|
||||
if (NeedsUpdated(vacuumstate, highestPoint))
|
||||
RepairGraphElement(vacuumstate, highestPoint, HnswGetEntryPoint(index));
|
||||
|
||||
/* Release lock */
|
||||
UnlockPage(index, HNSW_UPDATE_LOCK, ShareLock);
|
||||
}
|
||||
|
||||
/* Prevent concurrent inserts when possibly updating entry point */
|
||||
LockPage(index, HNSW_UPDATE_LOCK, ExclusiveLock);
|
||||
|
||||
/* Get latest entry point */
|
||||
entryPoint = HnswGetEntryPoint(index);
|
||||
|
||||
if (entryPoint != NULL)
|
||||
{
|
||||
ItemPointerData epData;
|
||||
|
||||
ItemPointerSet(&epData, entryPoint->blkno, entryPoint->offno);
|
||||
|
||||
if (DeletedContains(vacuumstate->deleted, &epData))
|
||||
{
|
||||
/*
|
||||
* Replace the entry point with the highest point. If highest
|
||||
* point is outdated and empty, the entry point will be empty
|
||||
* until an element is repaired.
|
||||
*/
|
||||
HnswUpdateMetaPage(index, HNSW_UPDATE_ENTRY_ALWAYS, highestPoint, InvalidBlockNumber, MAIN_FORKNUM, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
/*
|
||||
* Repair the entry point with the highest point. If highest point
|
||||
* is outdated, this can remove connections at higher levels in
|
||||
* the graph until they are repaired, but this should be fine.
|
||||
*/
|
||||
HnswLoadElement(entryPoint, NULL, NULL, index, vacuumstate->procinfo, vacuumstate->collation, true);
|
||||
|
||||
if (NeedsUpdated(vacuumstate, entryPoint))
|
||||
{
|
||||
/* Reset neighbors from previous update */
|
||||
if (highestPoint != NULL)
|
||||
HnswPtrStore((char *) NULL, highestPoint->neighbors, (HnswNeighborArrayPtr *) NULL);
|
||||
|
||||
RepairGraphElement(vacuumstate, entryPoint, highestPoint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Release lock */
|
||||
UnlockPage(index, HNSW_UPDATE_LOCK, ExclusiveLock);
|
||||
|
||||
/* Reset memory context */
|
||||
MemoryContextSwitchTo(oldCtx);
|
||||
MemoryContextReset(vacuumstate->tmpCtx);
|
||||
}
|
||||
|
||||
/*
|
||||
* Repair graph for all elements
|
||||
*/
|
||||
static void
|
||||
RepairGraph(HnswVacuumState * vacuumstate)
|
||||
{
|
||||
Relation index = vacuumstate->index;
|
||||
BufferAccessStrategy bas = vacuumstate->bas;
|
||||
BlockNumber blkno = HNSW_HEAD_BLKNO;
|
||||
|
||||
/*
|
||||
* Wait for inserts to complete. Inserts before this point may have
|
||||
* neighbors about to be deleted. Inserts after this point will not.
|
||||
*/
|
||||
LockPage(index, HNSW_UPDATE_LOCK, ExclusiveLock);
|
||||
UnlockPage(index, HNSW_UPDATE_LOCK, ExclusiveLock);
|
||||
|
||||
/* Repair entry point first */
|
||||
RepairGraphEntryPoint(vacuumstate);
|
||||
|
||||
while (BlockNumberIsValid(blkno))
|
||||
{
|
||||
Buffer buf;
|
||||
Page page;
|
||||
OffsetNumber offno;
|
||||
OffsetNumber maxoffno;
|
||||
List *elements = NIL;
|
||||
ListCell *lc2;
|
||||
MemoryContext oldCtx;
|
||||
|
||||
vacuum_delay_point();
|
||||
|
||||
oldCtx = MemoryContextSwitchTo(vacuumstate->tmpCtx);
|
||||
|
||||
buf = ReadBufferExtended(index, MAIN_FORKNUM, blkno, RBM_NORMAL, bas);
|
||||
LockBuffer(buf, BUFFER_LOCK_SHARE);
|
||||
page = BufferGetPage(buf);
|
||||
maxoffno = PageGetMaxOffsetNumber(page);
|
||||
|
||||
/* Load items into memory to minimize locking */
|
||||
for (offno = FirstOffsetNumber; offno <= maxoffno; offno = OffsetNumberNext(offno))
|
||||
{
|
||||
HnswElementTuple etup = (HnswElementTuple) PageGetItem(page, PageGetItemId(page, offno));
|
||||
HnswElement element;
|
||||
|
||||
/* Skip neighbor tuples */
|
||||
if (!HnswIsElementTuple(etup))
|
||||
continue;
|
||||
|
||||
/* Skip updating neighbors if being deleted */
|
||||
if (!ItemPointerIsValid(&etup->heaptids[0]))
|
||||
continue;
|
||||
|
||||
/* Create an element */
|
||||
element = HnswInitElementFromBlock(blkno, offno);
|
||||
HnswLoadElementFromTuple(element, etup, false, true);
|
||||
|
||||
elements = lappend(elements, element);
|
||||
}
|
||||
|
||||
blkno = HnswPageGetOpaque(page)->nextblkno;
|
||||
|
||||
UnlockReleaseBuffer(buf);
|
||||
|
||||
/* Update neighbor pages */
|
||||
foreach(lc2, elements)
|
||||
{
|
||||
HnswElement element = (HnswElement) lfirst(lc2);
|
||||
HnswElement entryPoint;
|
||||
LOCKMODE lockmode = ShareLock;
|
||||
|
||||
/* Check if any neighbors point to deleted values */
|
||||
if (!NeedsUpdated(vacuumstate, element))
|
||||
continue;
|
||||
|
||||
/* Get a shared lock */
|
||||
LockPage(index, HNSW_UPDATE_LOCK, lockmode);
|
||||
|
||||
/* Refresh entry point for each element */
|
||||
entryPoint = HnswGetEntryPoint(index);
|
||||
|
||||
/* Prevent concurrent inserts when likely updating entry point */
|
||||
if (entryPoint == NULL || element->level > entryPoint->level)
|
||||
{
|
||||
/* Release shared lock */
|
||||
UnlockPage(index, HNSW_UPDATE_LOCK, lockmode);
|
||||
|
||||
/* Get exclusive lock */
|
||||
lockmode = ExclusiveLock;
|
||||
LockPage(index, HNSW_UPDATE_LOCK, lockmode);
|
||||
|
||||
/* Get latest entry point after lock is acquired */
|
||||
entryPoint = HnswGetEntryPoint(index);
|
||||
}
|
||||
|
||||
/* Repair connections */
|
||||
RepairGraphElement(vacuumstate, element, entryPoint);
|
||||
|
||||
/*
|
||||
* Update metapage if needed. Should only happen if entry point
|
||||
* was replaced and highest point was outdated.
|
||||
*/
|
||||
if (entryPoint == NULL || element->level > entryPoint->level)
|
||||
HnswUpdateMetaPage(index, HNSW_UPDATE_ENTRY_GREATER, element, InvalidBlockNumber, MAIN_FORKNUM, false);
|
||||
|
||||
/* Release lock */
|
||||
UnlockPage(index, HNSW_UPDATE_LOCK, lockmode);
|
||||
}
|
||||
|
||||
/* Reset memory context */
|
||||
MemoryContextSwitchTo(oldCtx);
|
||||
MemoryContextReset(vacuumstate->tmpCtx);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Mark items as deleted
|
||||
*/
|
||||
static void
|
||||
MarkDeleted(HnswVacuumState * vacuumstate)
|
||||
{
|
||||
BlockNumber blkno = HNSW_HEAD_BLKNO;
|
||||
BlockNumber insertPage = InvalidBlockNumber;
|
||||
Relation index = vacuumstate->index;
|
||||
BufferAccessStrategy bas = vacuumstate->bas;
|
||||
|
||||
/*
|
||||
* Wait for index scans to complete. Scans before this point may contain
|
||||
* tuples about to be deleted. Scans after this point will not, since the
|
||||
* graph has been repaired.
|
||||
*/
|
||||
LockPage(index, HNSW_SCAN_LOCK, ExclusiveLock);
|
||||
UnlockPage(index, HNSW_SCAN_LOCK, ExclusiveLock);
|
||||
|
||||
while (BlockNumberIsValid(blkno))
|
||||
{
|
||||
Buffer buf;
|
||||
Page page;
|
||||
GenericXLogState *state;
|
||||
OffsetNumber offno;
|
||||
OffsetNumber maxoffno;
|
||||
|
||||
vacuum_delay_point();
|
||||
|
||||
buf = ReadBufferExtended(index, MAIN_FORKNUM, blkno, RBM_NORMAL, bas);
|
||||
|
||||
/*
|
||||
* ambulkdelete cannot delete entries from pages that are pinned by
|
||||
* other backends
|
||||
*
|
||||
* https://www.postgresql.org/docs/current/index-locking.html
|
||||
*/
|
||||
LockBufferForCleanup(buf);
|
||||
|
||||
state = GenericXLogStart(index);
|
||||
page = GenericXLogRegisterBuffer(state, buf, 0);
|
||||
maxoffno = PageGetMaxOffsetNumber(page);
|
||||
|
||||
/* Update element and neighbors together */
|
||||
for (offno = FirstOffsetNumber; offno <= maxoffno; offno = OffsetNumberNext(offno))
|
||||
{
|
||||
HnswElementTuple etup = (HnswElementTuple) PageGetItem(page, PageGetItemId(page, offno));
|
||||
HnswNeighborTuple ntup;
|
||||
Buffer nbuf;
|
||||
Page npage;
|
||||
BlockNumber neighborPage;
|
||||
OffsetNumber neighborOffno;
|
||||
|
||||
/* Skip neighbor tuples */
|
||||
if (!HnswIsElementTuple(etup))
|
||||
continue;
|
||||
|
||||
/* Skip deleted tuples */
|
||||
if (etup->deleted)
|
||||
{
|
||||
/* Set to first free page */
|
||||
if (!BlockNumberIsValid(insertPage))
|
||||
insertPage = blkno;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Skip live tuples */
|
||||
if (ItemPointerIsValid(&etup->heaptids[0]))
|
||||
continue;
|
||||
|
||||
/* Get neighbor page */
|
||||
neighborPage = ItemPointerGetBlockNumber(&etup->neighbortid);
|
||||
neighborOffno = ItemPointerGetOffsetNumber(&etup->neighbortid);
|
||||
|
||||
if (neighborPage == blkno)
|
||||
{
|
||||
nbuf = buf;
|
||||
npage = page;
|
||||
}
|
||||
else
|
||||
{
|
||||
nbuf = ReadBufferExtended(index, MAIN_FORKNUM, neighborPage, RBM_NORMAL, bas);
|
||||
LockBuffer(nbuf, BUFFER_LOCK_EXCLUSIVE);
|
||||
npage = GenericXLogRegisterBuffer(state, nbuf, 0);
|
||||
}
|
||||
|
||||
ntup = (HnswNeighborTuple) PageGetItem(npage, PageGetItemId(npage, neighborOffno));
|
||||
|
||||
/* Overwrite element */
|
||||
etup->deleted = 1;
|
||||
MemSet(&etup->data, 0, VARSIZE_ANY(&etup->data));
|
||||
|
||||
/* Overwrite neighbors */
|
||||
for (int i = 0; i < ntup->count; i++)
|
||||
ItemPointerSetInvalid(&ntup->indextids[i]);
|
||||
|
||||
/*
|
||||
* We modified the tuples in place, no need to call
|
||||
* PageIndexTupleOverwrite
|
||||
*/
|
||||
|
||||
/* Commit */
|
||||
GenericXLogFinish(state);
|
||||
if (nbuf != buf)
|
||||
UnlockReleaseBuffer(nbuf);
|
||||
|
||||
/* Set to first free page */
|
||||
if (!BlockNumberIsValid(insertPage))
|
||||
insertPage = blkno;
|
||||
|
||||
/* Prepare new xlog */
|
||||
state = GenericXLogStart(index);
|
||||
page = GenericXLogRegisterBuffer(state, buf, 0);
|
||||
}
|
||||
|
||||
blkno = HnswPageGetOpaque(page)->nextblkno;
|
||||
|
||||
GenericXLogAbort(state);
|
||||
UnlockReleaseBuffer(buf);
|
||||
}
|
||||
|
||||
/* Update insert page last, after everything has been marked as deleted */
|
||||
HnswUpdateMetaPage(index, 0, NULL, insertPage, MAIN_FORKNUM, false);
|
||||
}
|
||||
|
||||
/*
|
||||
* Initialize the vacuum state
|
||||
*/
|
||||
static void
|
||||
InitVacuumState(HnswVacuumState * vacuumstate, IndexVacuumInfo *info, IndexBulkDeleteResult *stats, IndexBulkDeleteCallback callback, void *callback_state)
|
||||
{
|
||||
Relation index = info->index;
|
||||
|
||||
if (stats == NULL)
|
||||
stats = (IndexBulkDeleteResult *) palloc0(sizeof(IndexBulkDeleteResult));
|
||||
|
||||
vacuumstate->index = index;
|
||||
vacuumstate->stats = stats;
|
||||
vacuumstate->callback = callback;
|
||||
vacuumstate->callback_state = callback_state;
|
||||
vacuumstate->efConstruction = HnswGetEfConstruction(index);
|
||||
vacuumstate->bas = GetAccessStrategy(BAS_BULKREAD);
|
||||
vacuumstate->procinfo = index_getprocinfo(index, 1, HNSW_DISTANCE_PROC);
|
||||
vacuumstate->collation = index->rd_indcollation[0];
|
||||
vacuumstate->ntup = palloc0(HNSW_TUPLE_ALLOC_SIZE);
|
||||
vacuumstate->tmpCtx = AllocSetContextCreate(CurrentMemoryContext,
|
||||
"Hnsw vacuum temporary context",
|
||||
ALLOCSET_DEFAULT_SIZES);
|
||||
|
||||
/* Get m from metapage */
|
||||
HnswGetMetaPageInfo(index, &vacuumstate->m, NULL);
|
||||
|
||||
/* Create hash table */
|
||||
vacuumstate->deleted = tidhash_create(CurrentMemoryContext, 256, NULL);
|
||||
}
|
||||
|
||||
/*
|
||||
* Free resources
|
||||
*/
|
||||
static void
|
||||
FreeVacuumState(HnswVacuumState * vacuumstate)
|
||||
{
|
||||
tidhash_destroy(vacuumstate->deleted);
|
||||
FreeAccessStrategy(vacuumstate->bas);
|
||||
pfree(vacuumstate->ntup);
|
||||
MemoryContextDelete(vacuumstate->tmpCtx);
|
||||
}
|
||||
|
||||
/*
|
||||
* Bulk delete tuples from the index
|
||||
*/
|
||||
IndexBulkDeleteResult *
|
||||
hnswbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
|
||||
IndexBulkDeleteCallback callback, void *callback_state)
|
||||
{
|
||||
HnswVacuumState vacuumstate;
|
||||
|
||||
InitVacuumState(&vacuumstate, info, stats, callback, callback_state);
|
||||
|
||||
/* Pass 1: Remove heap TIDs */
|
||||
RemoveHeapTids(&vacuumstate);
|
||||
|
||||
/* Pass 2: Repair graph */
|
||||
RepairGraph(&vacuumstate);
|
||||
|
||||
/* Pass 3: Mark as deleted */
|
||||
MarkDeleted(&vacuumstate);
|
||||
|
||||
FreeVacuumState(&vacuumstate);
|
||||
|
||||
return vacuumstate.stats;
|
||||
}
|
||||
|
||||
/*
|
||||
* Clean up after a VACUUM operation
|
||||
*/
|
||||
IndexBulkDeleteResult *
|
||||
hnswvacuumcleanup(IndexVacuumInfo *info, IndexBulkDeleteResult *stats)
|
||||
{
|
||||
Relation rel = info->index;
|
||||
|
||||
if (info->analyze_only)
|
||||
return stats;
|
||||
|
||||
/* stats is NULL if ambulkdelete not called */
|
||||
/* OK to return NULL if index not changed */
|
||||
if (stats == NULL)
|
||||
return NULL;
|
||||
|
||||
stats->num_pages = RelationGetNumberOfBlocks(rel);
|
||||
|
||||
return stats;
|
||||
}
|
||||
549
src/ivfbuild.c
549
src/ivfbuild.c
@@ -2,42 +2,43 @@
|
||||
|
||||
#include <float.h>
|
||||
|
||||
#include "access/table.h"
|
||||
#include "access/tableam.h"
|
||||
#include "access/parallel.h"
|
||||
#include "access/xact.h"
|
||||
#include "catalog/index.h"
|
||||
#include "catalog/pg_operator_d.h"
|
||||
#include "catalog/pg_type_d.h"
|
||||
#include "commands/progress.h"
|
||||
#include "ivfflat.h"
|
||||
#include "miscadmin.h"
|
||||
#include "optimizer/optimizer.h"
|
||||
#include "storage/bufmgr.h"
|
||||
#include "tcop/tcopprot.h"
|
||||
#include "utils/memutils.h"
|
||||
|
||||
#if PG_VERSION_NUM >= 140000
|
||||
#include "utils/backend_progress.h"
|
||||
#elif PG_VERSION_NUM >= 120000
|
||||
#else
|
||||
#include "pgstat.h"
|
||||
#endif
|
||||
|
||||
#if PG_VERSION_NUM >= 120000
|
||||
#include "access/tableam.h"
|
||||
#include "commands/progress.h"
|
||||
#else
|
||||
#define PROGRESS_CREATEIDX_SUBPHASE 0
|
||||
#define PROGRESS_CREATEIDX_TUPLES_TOTAL 0
|
||||
#define PROGRESS_CREATEIDX_TUPLES_DONE 0
|
||||
#endif
|
||||
|
||||
#include "catalog/pg_operator_d.h"
|
||||
#include "catalog/pg_type_d.h"
|
||||
|
||||
#if PG_VERSION_NUM >= 130000
|
||||
#define CALLBACK_ITEM_POINTER ItemPointer tid
|
||||
#else
|
||||
#define CALLBACK_ITEM_POINTER HeapTuple hup
|
||||
#endif
|
||||
|
||||
#if PG_VERSION_NUM >= 120000
|
||||
#define UpdateProgress(index, val) pgstat_progress_update_param(index, val)
|
||||
#else
|
||||
#define UpdateProgress(index, val) ((void)val)
|
||||
#if PG_VERSION_NUM >= 140000
|
||||
#include "utils/backend_status.h"
|
||||
#include "utils/wait_event.h"
|
||||
#endif
|
||||
|
||||
#define PARALLEL_KEY_IVFFLAT_SHARED UINT64CONST(0xA000000000000001)
|
||||
#define PARALLEL_KEY_TUPLESORT UINT64CONST(0xA000000000000002)
|
||||
#define PARALLEL_KEY_IVFFLAT_CENTERS UINT64CONST(0xA000000000000003)
|
||||
#define PARALLEL_KEY_QUERY_TEXT UINT64CONST(0xA000000000000004)
|
||||
|
||||
/*
|
||||
* Add sample
|
||||
*/
|
||||
@@ -56,7 +57,7 @@ AddSample(Datum *values, IvfflatBuildState * buildstate)
|
||||
*/
|
||||
if (buildstate->kmeansnormprocinfo != NULL)
|
||||
{
|
||||
if (!IvfflatNormValue(buildstate->kmeansnormprocinfo, buildstate->collation, &value, buildstate->normvec))
|
||||
if (!IvfflatNormValue(buildstate->kmeansnormprocinfo, buildstate->collation, &value))
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -104,7 +105,7 @@ SampleCallback(Relation index, CALLBACK_ITEM_POINTER, Datum *values,
|
||||
oldCtx = MemoryContextSwitchTo(buildstate->tmpCtx);
|
||||
|
||||
/* Add sample */
|
||||
AddSample(values, state);
|
||||
AddSample(values, buildstate);
|
||||
|
||||
/* Reset memory context */
|
||||
MemoryContextSwitchTo(oldCtx);
|
||||
@@ -129,13 +130,8 @@ SampleRows(IvfflatBuildState * buildstate)
|
||||
{
|
||||
BlockNumber targblock = BlockSampler_Next(&buildstate->bs);
|
||||
|
||||
#if PG_VERSION_NUM >= 120000
|
||||
table_index_build_range_scan(buildstate->heap, buildstate->index, buildstate->indexInfo,
|
||||
false, true, false, targblock, 1, SampleCallback, (void *) buildstate, NULL);
|
||||
#else
|
||||
IndexBuildHeapRangeScan(buildstate->heap, buildstate->index, buildstate->indexInfo,
|
||||
false, true, targblock, 1, SampleCallback, (void *) buildstate, NULL);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,7 +146,6 @@ AddTupleToSort(Relation index, ItemPointer tid, Datum *values, IvfflatBuildState
|
||||
int closestCenter = 0;
|
||||
VectorArray centers = buildstate->centers;
|
||||
TupleTableSlot *slot = buildstate->slot;
|
||||
int i;
|
||||
|
||||
/* Detoast once for all calls */
|
||||
Datum value = PointerGetDatum(PG_DETOAST_DATUM(values[0]));
|
||||
@@ -158,12 +153,12 @@ AddTupleToSort(Relation index, ItemPointer tid, Datum *values, IvfflatBuildState
|
||||
/* Normalize if needed */
|
||||
if (buildstate->normprocinfo != NULL)
|
||||
{
|
||||
if (!IvfflatNormValue(buildstate->normprocinfo, buildstate->collation, &value, buildstate->normvec))
|
||||
if (!IvfflatNormValue(buildstate->normprocinfo, buildstate->collation, &value))
|
||||
return;
|
||||
}
|
||||
|
||||
/* Find the list that minimizes the distance */
|
||||
for (i = 0; i < centers->length; i++)
|
||||
for (int i = 0; i < centers->length; i++)
|
||||
{
|
||||
distance = DatumGetFloat8(FunctionCall2Coll(buildstate->procinfo, buildstate->collation, value, PointerGetDatum(VectorArrayGet(centers, i))));
|
||||
|
||||
@@ -258,32 +253,27 @@ GetNextTuple(Tuplesortstate *sortstate, TupleDesc tupdesc, TupleTableSlot *slot,
|
||||
static void
|
||||
InsertTuples(Relation index, IvfflatBuildState * buildstate, ForkNumber forkNum)
|
||||
{
|
||||
Buffer buf;
|
||||
Page page;
|
||||
GenericXLogState *state;
|
||||
int list;
|
||||
IndexTuple itup = NULL; /* silence compiler warning */
|
||||
BlockNumber startPage;
|
||||
BlockNumber insertPage;
|
||||
Size itemsz;
|
||||
int i;
|
||||
int64 inserted = 0;
|
||||
|
||||
#if PG_VERSION_NUM >= 120000
|
||||
TupleTableSlot *slot = MakeSingleTupleTableSlot(buildstate->tupdesc, &TTSOpsMinimalTuple);
|
||||
#else
|
||||
TupleTableSlot *slot = MakeSingleTupleTableSlot(buildstate->tupdesc);
|
||||
#endif
|
||||
TupleDesc tupdesc = RelationGetDescr(index);
|
||||
|
||||
UpdateProgress(PROGRESS_CREATEIDX_SUBPHASE, PROGRESS_IVFFLAT_PHASE_LOAD);
|
||||
pgstat_progress_update_param(PROGRESS_CREATEIDX_SUBPHASE, PROGRESS_IVFFLAT_PHASE_LOAD);
|
||||
|
||||
UpdateProgress(PROGRESS_CREATEIDX_TUPLES_TOTAL, buildstate->indtuples);
|
||||
pgstat_progress_update_param(PROGRESS_CREATEIDX_TUPLES_TOTAL, buildstate->indtuples);
|
||||
|
||||
GetNextTuple(buildstate->sortstate, tupdesc, slot, &itup, &list);
|
||||
|
||||
for (i = 0; i < buildstate->centers->length; i++)
|
||||
for (int i = 0; i < buildstate->centers->length; i++)
|
||||
{
|
||||
Buffer buf;
|
||||
Page page;
|
||||
GenericXLogState *state;
|
||||
BlockNumber startPage;
|
||||
BlockNumber insertPage;
|
||||
|
||||
/* Can take a while, so ensure we can interrupt */
|
||||
/* Needs to be called when no buffer locks are held */
|
||||
CHECK_FOR_INTERRUPTS();
|
||||
@@ -297,7 +287,8 @@ InsertTuples(Relation index, IvfflatBuildState * buildstate, ForkNumber forkNum)
|
||||
while (list == i)
|
||||
{
|
||||
/* Check for free space */
|
||||
itemsz = MAXALIGN(IndexTupleSize(itup));
|
||||
Size itemsz = MAXALIGN(IndexTupleSize(itup));
|
||||
|
||||
if (PageGetFreeSpace(page) < itemsz)
|
||||
IvfflatAppendPage(index, &buf, &page, &state, forkNum);
|
||||
|
||||
@@ -307,7 +298,7 @@ InsertTuples(Relation index, IvfflatBuildState * buildstate, ForkNumber forkNum)
|
||||
|
||||
pfree(itup);
|
||||
|
||||
UpdateProgress(PROGRESS_CREATEIDX_TUPLES_DONE, ++inserted);
|
||||
pgstat_progress_update_param(PROGRESS_CREATEIDX_TUPLES_DONE, ++inserted);
|
||||
|
||||
GetNextTuple(buildstate->sortstate, tupdesc, slot, &itup, &list);
|
||||
}
|
||||
@@ -317,7 +308,7 @@ InsertTuples(Relation index, IvfflatBuildState * buildstate, ForkNumber forkNum)
|
||||
IvfflatCommitBuffer(buf, state);
|
||||
|
||||
/* Set the start and insert pages */
|
||||
IvfflatUpdateList(index, state, buildstate->listInfo[i], insertPage, InvalidBlockNumber, startPage, forkNum);
|
||||
IvfflatUpdateList(index, buildstate->listInfo[i], insertPage, InvalidBlockNumber, startPage, forkNum);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -351,33 +342,20 @@ InitBuildState(IvfflatBuildState * buildstate, Relation heap, Relation index, In
|
||||
buildstate->collation = index->rd_indcollation[0];
|
||||
|
||||
/* Require more than one dimension for spherical k-means */
|
||||
/* Lists check for backwards compatibility */
|
||||
/* TODO Remove lists check in 0.3.0 */
|
||||
if (buildstate->kmeansnormprocinfo != NULL && buildstate->dimensions == 1 && buildstate->lists > 1)
|
||||
if (buildstate->kmeansnormprocinfo != NULL && buildstate->dimensions == 1)
|
||||
elog(ERROR, "dimensions must be greater than one for this opclass");
|
||||
|
||||
/* Create tuple description for sorting */
|
||||
#if PG_VERSION_NUM >= 120000
|
||||
buildstate->tupdesc = CreateTemplateTupleDesc(3);
|
||||
#else
|
||||
buildstate->tupdesc = CreateTemplateTupleDesc(3, false);
|
||||
#endif
|
||||
TupleDescInitEntry(buildstate->tupdesc, (AttrNumber) 1, "list", INT4OID, -1, 0);
|
||||
TupleDescInitEntry(buildstate->tupdesc, (AttrNumber) 2, "tid", TIDOID, -1, 0);
|
||||
TupleDescInitEntry(buildstate->tupdesc, (AttrNumber) 3, "vector", RelationGetDescr(index)->attrs[0].atttypid, -1, 0);
|
||||
|
||||
#if PG_VERSION_NUM >= 120000
|
||||
buildstate->slot = MakeSingleTupleTableSlot(buildstate->tupdesc, &TTSOpsVirtual);
|
||||
#else
|
||||
buildstate->slot = MakeSingleTupleTableSlot(buildstate->tupdesc);
|
||||
#endif
|
||||
|
||||
buildstate->centers = VectorArrayInit(buildstate->lists, buildstate->dimensions);
|
||||
buildstate->listInfo = palloc(sizeof(ListInfo) * buildstate->lists);
|
||||
|
||||
/* Reuse for each tuple */
|
||||
buildstate->normvec = InitVector(buildstate->dimensions);
|
||||
|
||||
buildstate->tmpCtx = AllocSetContextCreate(CurrentMemoryContext,
|
||||
"Ivfflat build temporary context",
|
||||
ALLOCSET_DEFAULT_SIZES);
|
||||
@@ -387,6 +365,8 @@ InitBuildState(IvfflatBuildState * buildstate, Relation heap, Relation index, In
|
||||
buildstate->listSums = palloc0(sizeof(double) * buildstate->lists);
|
||||
buildstate->listCounts = palloc0(sizeof(int) * buildstate->lists);
|
||||
#endif
|
||||
|
||||
buildstate->ivfleader = NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -397,7 +377,6 @@ FreeBuildState(IvfflatBuildState * buildstate)
|
||||
{
|
||||
VectorArrayFree(buildstate->centers);
|
||||
pfree(buildstate->listInfo);
|
||||
pfree(buildstate->normvec);
|
||||
|
||||
#ifdef IVFFLAT_KMEANS_DEBUG
|
||||
pfree(buildstate->listSums);
|
||||
@@ -415,7 +394,7 @@ ComputeCenters(IvfflatBuildState * buildstate)
|
||||
{
|
||||
int numSamples;
|
||||
|
||||
UpdateProgress(PROGRESS_CREATEIDX_SUBPHASE, PROGRESS_IVFFLAT_PHASE_KMEANS);
|
||||
pgstat_progress_update_param(PROGRESS_CREATEIDX_SUBPHASE, PROGRESS_IVFFLAT_PHASE_KMEANS);
|
||||
|
||||
/* Target 50 samples per list, with at least 10000 samples */
|
||||
/* The number of samples has a large effect on index build time */
|
||||
@@ -483,33 +462,33 @@ static void
|
||||
CreateListPages(Relation index, VectorArray centers, int dimensions,
|
||||
int lists, ForkNumber forkNum, ListInfo * *listInfo)
|
||||
{
|
||||
int i;
|
||||
Buffer buf;
|
||||
Page page;
|
||||
GenericXLogState *state;
|
||||
OffsetNumber offno;
|
||||
Size itemsz;
|
||||
Size listSize;
|
||||
IvfflatList list;
|
||||
|
||||
itemsz = MAXALIGN(IVFFLAT_LIST_SIZE(dimensions));
|
||||
list = palloc(itemsz);
|
||||
listSize = MAXALIGN(IVFFLAT_LIST_SIZE(dimensions));
|
||||
list = palloc0(listSize);
|
||||
|
||||
buf = IvfflatNewBuffer(index, forkNum);
|
||||
IvfflatInitRegisterPage(index, &buf, &page, &state);
|
||||
|
||||
for (i = 0; i < lists; i++)
|
||||
for (int i = 0; i < lists; i++)
|
||||
{
|
||||
OffsetNumber offno;
|
||||
|
||||
/* Load list */
|
||||
list->startPage = InvalidBlockNumber;
|
||||
list->insertPage = InvalidBlockNumber;
|
||||
memcpy(&list->center, VectorArrayGet(centers, i), VECTOR_SIZE(dimensions));
|
||||
|
||||
/* Ensure free space */
|
||||
if (PageGetFreeSpace(page) < itemsz)
|
||||
if (PageGetFreeSpace(page) < listSize)
|
||||
IvfflatAppendPage(index, &buf, &page, &state, forkNum);
|
||||
|
||||
/* Add the item */
|
||||
offno = PageAddItem(page, (Item) list, itemsz, InvalidOffsetNumber, false, false);
|
||||
offno = PageAddItem(page, (Item) list, listSize, InvalidOffsetNumber, false, false);
|
||||
if (offno == InvalidOffsetNumber)
|
||||
elog(ERROR, "failed to add index item to \"%s\"", RelationGetRelationName(index));
|
||||
|
||||
@@ -523,17 +502,17 @@ CreateListPages(Relation index, VectorArray centers, int dimensions,
|
||||
pfree(list);
|
||||
}
|
||||
|
||||
#ifdef IVFFLAT_KMEANS_DEBUG
|
||||
/*
|
||||
* Print k-means metrics
|
||||
*/
|
||||
#ifdef IVFFLAT_KMEANS_DEBUG
|
||||
static void
|
||||
PrintKmeansMetrics(IvfflatBuildState * buildstate)
|
||||
{
|
||||
elog(INFO, "inertia: %.3e", buildstate->inertia);
|
||||
|
||||
/* Calculate Davies-Bouldin index */
|
||||
if (buildstate->lists > 1)
|
||||
if (buildstate->lists > 1 && !buildstate->ivfleader)
|
||||
{
|
||||
double db = 0.0;
|
||||
|
||||
@@ -568,19 +547,409 @@ PrintKmeansMetrics(IvfflatBuildState * buildstate)
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Within leader, wait for end of heap scan
|
||||
*/
|
||||
static double
|
||||
ParallelHeapScan(IvfflatBuildState * buildstate)
|
||||
{
|
||||
IvfflatShared *ivfshared = buildstate->ivfleader->ivfshared;
|
||||
int nparticipanttuplesorts;
|
||||
double reltuples;
|
||||
|
||||
nparticipanttuplesorts = buildstate->ivfleader->nparticipanttuplesorts;
|
||||
for (;;)
|
||||
{
|
||||
SpinLockAcquire(&ivfshared->mutex);
|
||||
if (ivfshared->nparticipantsdone == nparticipanttuplesorts)
|
||||
{
|
||||
buildstate->indtuples = ivfshared->indtuples;
|
||||
reltuples = ivfshared->reltuples;
|
||||
#ifdef IVFFLAT_KMEANS_DEBUG
|
||||
buildstate->inertia = ivfshared->inertia;
|
||||
#endif
|
||||
SpinLockRelease(&ivfshared->mutex);
|
||||
break;
|
||||
}
|
||||
SpinLockRelease(&ivfshared->mutex);
|
||||
|
||||
ConditionVariableSleep(&ivfshared->workersdonecv,
|
||||
WAIT_EVENT_PARALLEL_CREATE_INDEX_SCAN);
|
||||
}
|
||||
|
||||
ConditionVariableCancelSleep();
|
||||
|
||||
return reltuples;
|
||||
}
|
||||
|
||||
/*
|
||||
* Perform a worker's portion of a parallel sort
|
||||
*/
|
||||
static void
|
||||
IvfflatParallelScanAndSort(IvfflatSpool * ivfspool, IvfflatShared * ivfshared, Sharedsort *sharedsort, Vector * ivfcenters, int sortmem, bool progress)
|
||||
{
|
||||
SortCoordinate coordinate;
|
||||
IvfflatBuildState buildstate;
|
||||
TableScanDesc scan;
|
||||
double reltuples;
|
||||
IndexInfo *indexInfo;
|
||||
|
||||
/* Sort options, which must match AssignTuples */
|
||||
AttrNumber attNums[] = {1};
|
||||
Oid sortOperators[] = {Int4LessOperator};
|
||||
Oid sortCollations[] = {InvalidOid};
|
||||
bool nullsFirstFlags[] = {false};
|
||||
|
||||
/* Initialize local tuplesort coordination state */
|
||||
coordinate = palloc0(sizeof(SortCoordinateData));
|
||||
coordinate->isWorker = true;
|
||||
coordinate->nParticipants = -1;
|
||||
coordinate->sharedsort = sharedsort;
|
||||
|
||||
/* Join parallel scan */
|
||||
indexInfo = BuildIndexInfo(ivfspool->index);
|
||||
indexInfo->ii_Concurrent = ivfshared->isconcurrent;
|
||||
InitBuildState(&buildstate, ivfspool->heap, ivfspool->index, indexInfo);
|
||||
memcpy(buildstate.centers->items, ivfcenters, VECTOR_SIZE(buildstate.centers->dim) * buildstate.centers->maxlen);
|
||||
buildstate.centers->length = buildstate.centers->maxlen;
|
||||
ivfspool->sortstate = tuplesort_begin_heap(buildstate.tupdesc, 1, attNums, sortOperators, sortCollations, nullsFirstFlags, sortmem, coordinate, false);
|
||||
buildstate.sortstate = ivfspool->sortstate;
|
||||
scan = table_beginscan_parallel(ivfspool->heap,
|
||||
ParallelTableScanFromIvfflatShared(ivfshared));
|
||||
reltuples = table_index_build_scan(ivfspool->heap, ivfspool->index, indexInfo,
|
||||
true, progress, BuildCallback,
|
||||
(void *) &buildstate, scan);
|
||||
|
||||
/* Execute this worker's part of the sort */
|
||||
tuplesort_performsort(ivfspool->sortstate);
|
||||
|
||||
/* Record statistics */
|
||||
SpinLockAcquire(&ivfshared->mutex);
|
||||
ivfshared->nparticipantsdone++;
|
||||
ivfshared->reltuples += reltuples;
|
||||
ivfshared->indtuples += buildstate.indtuples;
|
||||
#ifdef IVFFLAT_KMEANS_DEBUG
|
||||
ivfshared->inertia += buildstate.inertia;
|
||||
#endif
|
||||
SpinLockRelease(&ivfshared->mutex);
|
||||
|
||||
/* Log statistics */
|
||||
if (progress)
|
||||
ereport(DEBUG1, (errmsg("leader processed " INT64_FORMAT " tuples", (int64) reltuples)));
|
||||
else
|
||||
ereport(DEBUG1, (errmsg("worker processed " INT64_FORMAT " tuples", (int64) reltuples)));
|
||||
|
||||
/* Notify leader */
|
||||
ConditionVariableSignal(&ivfshared->workersdonecv);
|
||||
|
||||
/* We can end tuplesorts immediately */
|
||||
tuplesort_end(ivfspool->sortstate);
|
||||
|
||||
FreeBuildState(&buildstate);
|
||||
}
|
||||
|
||||
/*
|
||||
* Perform work within a launched parallel process
|
||||
*/
|
||||
void
|
||||
IvfflatParallelBuildMain(dsm_segment *seg, shm_toc *toc)
|
||||
{
|
||||
char *sharedquery;
|
||||
IvfflatSpool *ivfspool;
|
||||
IvfflatShared *ivfshared;
|
||||
Sharedsort *sharedsort;
|
||||
Vector *ivfcenters;
|
||||
Relation heapRel;
|
||||
Relation indexRel;
|
||||
LOCKMODE heapLockmode;
|
||||
LOCKMODE indexLockmode;
|
||||
int sortmem;
|
||||
|
||||
/* Set debug_query_string for individual workers first */
|
||||
sharedquery = shm_toc_lookup(toc, PARALLEL_KEY_QUERY_TEXT, true);
|
||||
debug_query_string = sharedquery;
|
||||
|
||||
/* Report the query string from leader */
|
||||
pgstat_report_activity(STATE_RUNNING, debug_query_string);
|
||||
|
||||
/* Look up shared state */
|
||||
ivfshared = shm_toc_lookup(toc, PARALLEL_KEY_IVFFLAT_SHARED, false);
|
||||
|
||||
/* Open relations using lock modes known to be obtained by index.c */
|
||||
if (!ivfshared->isconcurrent)
|
||||
{
|
||||
heapLockmode = ShareLock;
|
||||
indexLockmode = AccessExclusiveLock;
|
||||
}
|
||||
else
|
||||
{
|
||||
heapLockmode = ShareUpdateExclusiveLock;
|
||||
indexLockmode = RowExclusiveLock;
|
||||
}
|
||||
|
||||
/* Open relations within worker */
|
||||
heapRel = table_open(ivfshared->heaprelid, heapLockmode);
|
||||
indexRel = index_open(ivfshared->indexrelid, indexLockmode);
|
||||
|
||||
/* Initialize worker's own spool */
|
||||
ivfspool = (IvfflatSpool *) palloc0(sizeof(IvfflatSpool));
|
||||
ivfspool->heap = heapRel;
|
||||
ivfspool->index = indexRel;
|
||||
|
||||
/* Look up shared state private to tuplesort.c */
|
||||
sharedsort = shm_toc_lookup(toc, PARALLEL_KEY_TUPLESORT, false);
|
||||
tuplesort_attach_shared(sharedsort, seg);
|
||||
|
||||
ivfcenters = shm_toc_lookup(toc, PARALLEL_KEY_IVFFLAT_CENTERS, false);
|
||||
|
||||
/* Perform sorting */
|
||||
sortmem = maintenance_work_mem / ivfshared->scantuplesortstates;
|
||||
IvfflatParallelScanAndSort(ivfspool, ivfshared, sharedsort, ivfcenters, sortmem, false);
|
||||
|
||||
/* Close relations within worker */
|
||||
index_close(indexRel, indexLockmode);
|
||||
table_close(heapRel, heapLockmode);
|
||||
}
|
||||
|
||||
/*
|
||||
* End parallel build
|
||||
*/
|
||||
static void
|
||||
IvfflatEndParallel(IvfflatLeader * ivfleader)
|
||||
{
|
||||
/* Shutdown worker processes */
|
||||
WaitForParallelWorkersToFinish(ivfleader->pcxt);
|
||||
|
||||
/* Free last reference to MVCC snapshot, if one was used */
|
||||
if (IsMVCCSnapshot(ivfleader->snapshot))
|
||||
UnregisterSnapshot(ivfleader->snapshot);
|
||||
DestroyParallelContext(ivfleader->pcxt);
|
||||
ExitParallelMode();
|
||||
}
|
||||
|
||||
/*
|
||||
* Return size of shared memory required for parallel index build
|
||||
*/
|
||||
static Size
|
||||
ParallelEstimateShared(Relation heap, Snapshot snapshot)
|
||||
{
|
||||
return add_size(BUFFERALIGN(sizeof(IvfflatShared)), table_parallelscan_estimate(heap, snapshot));
|
||||
}
|
||||
|
||||
/*
|
||||
* Within leader, participate as a parallel worker
|
||||
*/
|
||||
static void
|
||||
IvfflatLeaderParticipateAsWorker(IvfflatBuildState * buildstate)
|
||||
{
|
||||
IvfflatLeader *ivfleader = buildstate->ivfleader;
|
||||
IvfflatSpool *leaderworker;
|
||||
int sortmem;
|
||||
|
||||
/* Allocate memory and initialize private spool */
|
||||
leaderworker = (IvfflatSpool *) palloc0(sizeof(IvfflatSpool));
|
||||
leaderworker->heap = buildstate->heap;
|
||||
leaderworker->index = buildstate->index;
|
||||
|
||||
/* Perform work common to all participants */
|
||||
sortmem = maintenance_work_mem / ivfleader->nparticipanttuplesorts;
|
||||
IvfflatParallelScanAndSort(leaderworker, ivfleader->ivfshared,
|
||||
ivfleader->sharedsort, ivfleader->ivfcenters,
|
||||
sortmem, true);
|
||||
}
|
||||
|
||||
/*
|
||||
* Begin parallel build
|
||||
*/
|
||||
static void
|
||||
IvfflatBeginParallel(IvfflatBuildState * buildstate, bool isconcurrent, int request)
|
||||
{
|
||||
ParallelContext *pcxt;
|
||||
int scantuplesortstates;
|
||||
Snapshot snapshot;
|
||||
Size estivfshared;
|
||||
Size estsort;
|
||||
Size estcenters;
|
||||
IvfflatShared *ivfshared;
|
||||
Sharedsort *sharedsort;
|
||||
Vector *ivfcenters;
|
||||
IvfflatLeader *ivfleader = (IvfflatLeader *) palloc0(sizeof(IvfflatLeader));
|
||||
bool leaderparticipates = true;
|
||||
int querylen;
|
||||
|
||||
#ifdef DISABLE_LEADER_PARTICIPATION
|
||||
leaderparticipates = false;
|
||||
#endif
|
||||
|
||||
/* Enter parallel mode and create context */
|
||||
EnterParallelMode();
|
||||
Assert(request > 0);
|
||||
pcxt = CreateParallelContext("vector", "IvfflatParallelBuildMain", request);
|
||||
|
||||
scantuplesortstates = leaderparticipates ? request + 1 : request;
|
||||
|
||||
/* Get snapshot for table scan */
|
||||
if (!isconcurrent)
|
||||
snapshot = SnapshotAny;
|
||||
else
|
||||
snapshot = RegisterSnapshot(GetTransactionSnapshot());
|
||||
|
||||
/* Estimate size of workspaces */
|
||||
estivfshared = ParallelEstimateShared(buildstate->heap, snapshot);
|
||||
shm_toc_estimate_chunk(&pcxt->estimator, estivfshared);
|
||||
estsort = tuplesort_estimate_shared(scantuplesortstates);
|
||||
shm_toc_estimate_chunk(&pcxt->estimator, estsort);
|
||||
estcenters = VECTOR_SIZE(buildstate->dimensions) * buildstate->lists;
|
||||
shm_toc_estimate_chunk(&pcxt->estimator, estcenters);
|
||||
shm_toc_estimate_keys(&pcxt->estimator, 3);
|
||||
|
||||
/* Finally, estimate PARALLEL_KEY_QUERY_TEXT space */
|
||||
if (debug_query_string)
|
||||
{
|
||||
querylen = strlen(debug_query_string);
|
||||
shm_toc_estimate_chunk(&pcxt->estimator, querylen + 1);
|
||||
shm_toc_estimate_keys(&pcxt->estimator, 1);
|
||||
}
|
||||
else
|
||||
querylen = 0; /* keep compiler quiet */
|
||||
|
||||
/* Everyone's had a chance to ask for space, so now create the DSM */
|
||||
InitializeParallelDSM(pcxt);
|
||||
|
||||
/* If no DSM segment was available, back out (do serial build) */
|
||||
if (pcxt->seg == NULL)
|
||||
{
|
||||
if (IsMVCCSnapshot(snapshot))
|
||||
UnregisterSnapshot(snapshot);
|
||||
DestroyParallelContext(pcxt);
|
||||
ExitParallelMode();
|
||||
return;
|
||||
}
|
||||
|
||||
/* Store shared build state, for which we reserved space */
|
||||
ivfshared = (IvfflatShared *) shm_toc_allocate(pcxt->toc, estivfshared);
|
||||
/* Initialize immutable state */
|
||||
ivfshared->heaprelid = RelationGetRelid(buildstate->heap);
|
||||
ivfshared->indexrelid = RelationGetRelid(buildstate->index);
|
||||
ivfshared->isconcurrent = isconcurrent;
|
||||
ivfshared->scantuplesortstates = scantuplesortstates;
|
||||
ConditionVariableInit(&ivfshared->workersdonecv);
|
||||
SpinLockInit(&ivfshared->mutex);
|
||||
/* Initialize mutable state */
|
||||
ivfshared->nparticipantsdone = 0;
|
||||
ivfshared->reltuples = 0;
|
||||
ivfshared->indtuples = 0;
|
||||
#ifdef IVFFLAT_KMEANS_DEBUG
|
||||
ivfshared->inertia = 0;
|
||||
#endif
|
||||
table_parallelscan_initialize(buildstate->heap,
|
||||
ParallelTableScanFromIvfflatShared(ivfshared),
|
||||
snapshot);
|
||||
|
||||
/* Store shared tuplesort-private state, for which we reserved space */
|
||||
sharedsort = (Sharedsort *) shm_toc_allocate(pcxt->toc, estsort);
|
||||
tuplesort_initialize_shared(sharedsort, scantuplesortstates,
|
||||
pcxt->seg);
|
||||
|
||||
ivfcenters = (Vector *) shm_toc_allocate(pcxt->toc, estcenters);
|
||||
memcpy(ivfcenters, buildstate->centers->items, estcenters);
|
||||
|
||||
shm_toc_insert(pcxt->toc, PARALLEL_KEY_IVFFLAT_SHARED, ivfshared);
|
||||
shm_toc_insert(pcxt->toc, PARALLEL_KEY_TUPLESORT, sharedsort);
|
||||
shm_toc_insert(pcxt->toc, PARALLEL_KEY_IVFFLAT_CENTERS, ivfcenters);
|
||||
|
||||
/* Store query string for workers */
|
||||
if (debug_query_string)
|
||||
{
|
||||
char *sharedquery;
|
||||
|
||||
sharedquery = (char *) shm_toc_allocate(pcxt->toc, querylen + 1);
|
||||
memcpy(sharedquery, debug_query_string, querylen + 1);
|
||||
shm_toc_insert(pcxt->toc, PARALLEL_KEY_QUERY_TEXT, sharedquery);
|
||||
}
|
||||
|
||||
/* Launch workers, saving status for leader/caller */
|
||||
LaunchParallelWorkers(pcxt);
|
||||
ivfleader->pcxt = pcxt;
|
||||
ivfleader->nparticipanttuplesorts = pcxt->nworkers_launched;
|
||||
if (leaderparticipates)
|
||||
ivfleader->nparticipanttuplesorts++;
|
||||
ivfleader->ivfshared = ivfshared;
|
||||
ivfleader->sharedsort = sharedsort;
|
||||
ivfleader->snapshot = snapshot;
|
||||
ivfleader->ivfcenters = ivfcenters;
|
||||
|
||||
/* If no workers were successfully launched, back out (do serial build) */
|
||||
if (pcxt->nworkers_launched == 0)
|
||||
{
|
||||
IvfflatEndParallel(ivfleader);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Log participants */
|
||||
ereport(DEBUG1, (errmsg("using %d parallel workers", pcxt->nworkers_launched)));
|
||||
|
||||
/* Save leader state now that it's clear build will be parallel */
|
||||
buildstate->ivfleader = ivfleader;
|
||||
|
||||
/* Join heap scan ourselves */
|
||||
if (leaderparticipates)
|
||||
IvfflatLeaderParticipateAsWorker(buildstate);
|
||||
|
||||
/* Wait for all launched workers */
|
||||
WaitForParallelWorkersToAttach(pcxt);
|
||||
}
|
||||
|
||||
/*
|
||||
* Scan table for tuples to index
|
||||
*/
|
||||
static void
|
||||
ScanTable(IvfflatBuildState * buildstate)
|
||||
AssignTuples(IvfflatBuildState * buildstate)
|
||||
{
|
||||
#if PG_VERSION_NUM >= 120000
|
||||
buildstate->reltuples = table_index_build_scan(buildstate->heap, buildstate->index, buildstate->indexInfo,
|
||||
true, true, BuildCallback, (void *) buildstate, NULL);
|
||||
#else
|
||||
buildstate->reltuples = IndexBuildHeapScan(buildstate->heap, buildstate->index, buildstate->indexInfo,
|
||||
true, BuildCallback, (void *) buildstate, NULL);
|
||||
int parallel_workers = 0;
|
||||
SortCoordinate coordinate = NULL;
|
||||
|
||||
/* Sort options, which must match IvfflatParallelScanAndSort */
|
||||
AttrNumber attNums[] = {1};
|
||||
Oid sortOperators[] = {Int4LessOperator};
|
||||
Oid sortCollations[] = {InvalidOid};
|
||||
bool nullsFirstFlags[] = {false};
|
||||
|
||||
pgstat_progress_update_param(PROGRESS_CREATEIDX_SUBPHASE, PROGRESS_IVFFLAT_PHASE_ASSIGN);
|
||||
|
||||
/* Calculate parallel workers */
|
||||
if (buildstate->heap != NULL)
|
||||
parallel_workers = plan_create_index_workers(RelationGetRelid(buildstate->heap), RelationGetRelid(buildstate->index));
|
||||
|
||||
/* Attempt to launch parallel worker scan when required */
|
||||
if (parallel_workers > 0)
|
||||
IvfflatBeginParallel(buildstate, buildstate->indexInfo->ii_Concurrent, parallel_workers);
|
||||
|
||||
/* Set up coordination state if at least one worker launched */
|
||||
if (buildstate->ivfleader)
|
||||
{
|
||||
coordinate = (SortCoordinate) palloc0(sizeof(SortCoordinateData));
|
||||
coordinate->isWorker = false;
|
||||
coordinate->nParticipants = buildstate->ivfleader->nparticipanttuplesorts;
|
||||
coordinate->sharedsort = buildstate->ivfleader->sharedsort;
|
||||
}
|
||||
|
||||
/* Begin serial/leader tuplesort */
|
||||
buildstate->sortstate = tuplesort_begin_heap(buildstate->tupdesc, 1, attNums, sortOperators, sortCollations, nullsFirstFlags, maintenance_work_mem, coordinate, false);
|
||||
|
||||
/* Add tuples to sort */
|
||||
if (buildstate->heap != NULL)
|
||||
{
|
||||
if (buildstate->ivfleader)
|
||||
buildstate->reltuples = ParallelHeapScan(buildstate);
|
||||
else
|
||||
buildstate->reltuples = table_index_build_scan(buildstate->heap, buildstate->index, buildstate->indexInfo,
|
||||
true, true, BuildCallback, (void *) buildstate, NULL);
|
||||
|
||||
#ifdef IVFFLAT_KMEANS_DEBUG
|
||||
PrintKmeansMetrics(buildstate);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -589,29 +958,21 @@ ScanTable(IvfflatBuildState * buildstate)
|
||||
static void
|
||||
CreateEntryPages(IvfflatBuildState * buildstate, ForkNumber forkNum)
|
||||
{
|
||||
AttrNumber attNums[] = {1};
|
||||
Oid sortOperators[] = {Int4LessOperator};
|
||||
Oid sortCollations[] = {InvalidOid};
|
||||
bool nullsFirstFlags[] = {false};
|
||||
|
||||
UpdateProgress(PROGRESS_CREATEIDX_SUBPHASE, PROGRESS_IVFFLAT_PHASE_SORT);
|
||||
|
||||
buildstate->sortstate = tuplesort_begin_heap(buildstate->tupdesc, 1, attNums, sortOperators, sortCollations, nullsFirstFlags, maintenance_work_mem, NULL, false);
|
||||
|
||||
/* Add tuples to sort */
|
||||
if (buildstate->heap != NULL)
|
||||
IvfflatBench("assign tuples", ScanTable(buildstate));
|
||||
/* Assign */
|
||||
IvfflatBench("assign tuples", AssignTuples(buildstate));
|
||||
|
||||
/* Sort */
|
||||
IvfflatBench("sort tuples", tuplesort_performsort(buildstate->sortstate));
|
||||
|
||||
#ifdef IVFFLAT_KMEANS_DEBUG
|
||||
PrintKmeansMetrics(buildstate);
|
||||
#endif
|
||||
|
||||
/* Insert */
|
||||
/* Load */
|
||||
IvfflatBench("load tuples", InsertTuples(buildstate->index, buildstate, forkNum));
|
||||
|
||||
/* End sort */
|
||||
tuplesort_end(buildstate->sortstate);
|
||||
|
||||
/* End parallel build */
|
||||
if (buildstate->ivfleader)
|
||||
IvfflatEndParallel(buildstate->ivfleader);
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -3,14 +3,16 @@
|
||||
#include <float.h>
|
||||
|
||||
#include "access/amapi.h"
|
||||
#include "access/reloptions.h"
|
||||
#include "commands/progress.h"
|
||||
#include "commands/vacuum.h"
|
||||
#include "ivfflat.h"
|
||||
#include "utils/guc.h"
|
||||
#include "utils/selfuncs.h"
|
||||
#include "utils/spccache.h"
|
||||
|
||||
#if PG_VERSION_NUM >= 120000
|
||||
#include "commands/progress.h"
|
||||
#if PG_VERSION_NUM < 150000
|
||||
#define MarkGUCPrefixReserved(x) EmitWarningsOnPlaceholders(x)
|
||||
#endif
|
||||
|
||||
int ivfflat_probes;
|
||||
@@ -20,11 +22,11 @@ static relopt_kind ivfflat_relopt_kind;
|
||||
* Initialize index options and variables
|
||||
*/
|
||||
void
|
||||
_PG_init(void)
|
||||
IvfflatInit(void)
|
||||
{
|
||||
ivfflat_relopt_kind = add_reloption_kind();
|
||||
add_int_reloption(ivfflat_relopt_kind, "lists", "Number of inverted lists",
|
||||
IVFFLAT_DEFAULT_LISTS, 1, IVFFLAT_MAX_LISTS
|
||||
IVFFLAT_DEFAULT_LISTS, IVFFLAT_MIN_LISTS, IVFFLAT_MAX_LISTS
|
||||
#if PG_VERSION_NUM >= 130000
|
||||
,AccessExclusiveLock
|
||||
#endif
|
||||
@@ -32,13 +34,14 @@ _PG_init(void)
|
||||
|
||||
DefineCustomIntVariable("ivfflat.probes", "Sets the number of probes",
|
||||
"Valid range is 1..lists.", &ivfflat_probes,
|
||||
1, 1, IVFFLAT_MAX_LISTS, PGC_USERSET, 0, NULL, NULL, NULL);
|
||||
IVFFLAT_DEFAULT_PROBES, IVFFLAT_MIN_LISTS, IVFFLAT_MAX_LISTS, PGC_USERSET, 0, NULL, NULL, NULL);
|
||||
|
||||
MarkGUCPrefixReserved("ivfflat");
|
||||
}
|
||||
|
||||
/*
|
||||
* Get the name of index build phase
|
||||
*/
|
||||
#if PG_VERSION_NUM >= 120000
|
||||
static char *
|
||||
ivfflatbuildphasename(int64 phasenum)
|
||||
{
|
||||
@@ -48,15 +51,14 @@ ivfflatbuildphasename(int64 phasenum)
|
||||
return "initializing";
|
||||
case PROGRESS_IVFFLAT_PHASE_KMEANS:
|
||||
return "performing k-means";
|
||||
case PROGRESS_IVFFLAT_PHASE_SORT:
|
||||
return "sorting tuples";
|
||||
case PROGRESS_IVFFLAT_PHASE_ASSIGN:
|
||||
return "assigning tuples";
|
||||
case PROGRESS_IVFFLAT_PHASE_LOAD:
|
||||
return "loading tuples";
|
||||
default:
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Estimate the cost of an index scan
|
||||
@@ -71,10 +73,7 @@ ivfflatcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
|
||||
int lists;
|
||||
double ratio;
|
||||
double spc_seq_page_cost;
|
||||
Relation indexRel;
|
||||
#if PG_VERSION_NUM < 120000
|
||||
List *qinfos;
|
||||
#endif
|
||||
Relation index;
|
||||
|
||||
/* Never use index without order */
|
||||
if (path->indexorderbys == NULL)
|
||||
@@ -89,9 +88,9 @@ ivfflatcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
|
||||
|
||||
MemSet(&costs, 0, sizeof(costs));
|
||||
|
||||
indexRel = index_open(path->indexinfo->indexoid, NoLock);
|
||||
lists = IvfflatGetLists(indexRel);
|
||||
index_close(indexRel, NoLock);
|
||||
index = index_open(path->indexinfo->indexoid, NoLock);
|
||||
IvfflatGetMetaPageInfo(index, &lists, NULL);
|
||||
index_close(index, NoLock);
|
||||
|
||||
/* Get the ratio of lists that we need to visit */
|
||||
ratio = ((double) ivfflat_probes) / lists;
|
||||
@@ -105,12 +104,7 @@ ivfflatcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
|
||||
*/
|
||||
costs.numIndexTuples = path->indexinfo->tuples * ratio;
|
||||
|
||||
#if PG_VERSION_NUM >= 120000
|
||||
genericcostestimate(root, path, loop_count, &costs);
|
||||
#else
|
||||
qinfos = deconstruct_indexquals(path);
|
||||
genericcostestimate(root, path, loop_count, qinfos, &costs);
|
||||
#endif
|
||||
|
||||
get_tablespace_page_costs(path->indexinfo->reltablespace, NULL, &spc_seq_page_cost);
|
||||
|
||||
@@ -227,9 +221,7 @@ ivfflathandler(PG_FUNCTION_ARGS)
|
||||
amroutine->amcostestimate = ivfflatcostestimate;
|
||||
amroutine->amoptions = ivfflatoptions;
|
||||
amroutine->amproperty = NULL; /* TODO AMPROP_DISTANCE_ORDERABLE */
|
||||
#if PG_VERSION_NUM >= 120000
|
||||
amroutine->ambuildphasename = ivfflatbuildphasename;
|
||||
#endif
|
||||
amroutine->amvalidate = ivfflatvalidate;
|
||||
#if PG_VERSION_NUM >= 140000
|
||||
amroutine->amadjustmembers = NULL;
|
||||
|
||||
@@ -3,14 +3,12 @@
|
||||
|
||||
#include "postgres.h"
|
||||
|
||||
#if PG_VERSION_NUM < 110000
|
||||
#error "Requires PostgreSQL 11+"
|
||||
#endif
|
||||
|
||||
#include "access/genam.h"
|
||||
#include "access/generic_xlog.h"
|
||||
#include "access/reloptions.h"
|
||||
#include "access/parallel.h"
|
||||
#include "lib/pairingheap.h"
|
||||
#include "nodes/execnodes.h"
|
||||
#include "port.h" /* for strtof() and random() */
|
||||
#include "port.h" /* for random() */
|
||||
#include "utils/sampling.h"
|
||||
#include "utils/tuplesort.h"
|
||||
#include "vector.h"
|
||||
@@ -39,13 +37,16 @@
|
||||
#define IVFFLAT_METAPAGE_BLKNO 0
|
||||
#define IVFFLAT_HEAD_BLKNO 1 /* first list page */
|
||||
|
||||
/* IVFFlat parameters */
|
||||
#define IVFFLAT_DEFAULT_LISTS 100
|
||||
#define IVFFLAT_MIN_LISTS 1
|
||||
#define IVFFLAT_MAX_LISTS 32768
|
||||
#define IVFFLAT_DEFAULT_PROBES 1
|
||||
|
||||
/* Build phases */
|
||||
/* PROGRESS_CREATEIDX_SUBPHASE_INITIALIZE is 1 */
|
||||
#define PROGRESS_IVFFLAT_PHASE_KMEANS 2
|
||||
#define PROGRESS_IVFFLAT_PHASE_SORT 3
|
||||
#define PROGRESS_IVFFLAT_PHASE_ASSIGN 3
|
||||
#define PROGRESS_IVFFLAT_PHASE_LOAD 4
|
||||
|
||||
#define IVFFLAT_LIST_SIZE(_dim) (offsetof(IvfflatListData, center) + VECTOR_SIZE(_dim))
|
||||
@@ -79,9 +80,6 @@
|
||||
/* Variables */
|
||||
extern int ivfflat_probes;
|
||||
|
||||
/* Exported functions */
|
||||
PGDLLEXPORT void _PG_init(void);
|
||||
|
||||
typedef struct VectorArrayData
|
||||
{
|
||||
int length;
|
||||
@@ -105,6 +103,50 @@ typedef struct IvfflatOptions
|
||||
int lists; /* number of lists */
|
||||
} IvfflatOptions;
|
||||
|
||||
typedef struct IvfflatSpool
|
||||
{
|
||||
Tuplesortstate *sortstate;
|
||||
Relation heap;
|
||||
Relation index;
|
||||
} IvfflatSpool;
|
||||
|
||||
typedef struct IvfflatShared
|
||||
{
|
||||
/* Immutable state */
|
||||
Oid heaprelid;
|
||||
Oid indexrelid;
|
||||
bool isconcurrent;
|
||||
int scantuplesortstates;
|
||||
|
||||
/* Worker progress */
|
||||
ConditionVariable workersdonecv;
|
||||
|
||||
/* Mutex for mutable state */
|
||||
slock_t mutex;
|
||||
|
||||
/* Mutable state */
|
||||
int nparticipantsdone;
|
||||
double reltuples;
|
||||
double indtuples;
|
||||
|
||||
#ifdef IVFFLAT_KMEANS_DEBUG
|
||||
double inertia;
|
||||
#endif
|
||||
} IvfflatShared;
|
||||
|
||||
#define ParallelTableScanFromIvfflatShared(shared) \
|
||||
(ParallelTableScanDesc) ((char *) (shared) + BUFFERALIGN(sizeof(IvfflatShared)))
|
||||
|
||||
typedef struct IvfflatLeader
|
||||
{
|
||||
ParallelContext *pcxt;
|
||||
int nparticipanttuplesorts;
|
||||
IvfflatShared *ivfshared;
|
||||
Sharedsort *sharedsort;
|
||||
Snapshot snapshot;
|
||||
Vector *ivfcenters;
|
||||
} IvfflatLeader;
|
||||
|
||||
typedef struct IvfflatBuildState
|
||||
{
|
||||
/* Info */
|
||||
@@ -130,7 +172,6 @@ typedef struct IvfflatBuildState
|
||||
VectorArray samples;
|
||||
VectorArray centers;
|
||||
ListInfo *listInfo;
|
||||
Vector *normvec;
|
||||
|
||||
#ifdef IVFFLAT_KMEANS_DEBUG
|
||||
double inertia;
|
||||
@@ -150,6 +191,9 @@ typedef struct IvfflatBuildState
|
||||
|
||||
/* Memory */
|
||||
MemoryContext tmpCtx;
|
||||
|
||||
/* Parallel builds */
|
||||
IvfflatLeader *ivfleader;
|
||||
} IvfflatBuildState;
|
||||
|
||||
typedef struct IvfflatMetaPageData
|
||||
@@ -190,8 +234,8 @@ typedef struct IvfflatScanList
|
||||
typedef struct IvfflatScanOpaqueData
|
||||
{
|
||||
int probes;
|
||||
int dimensions;
|
||||
bool first;
|
||||
Buffer buf;
|
||||
|
||||
/* Sorting */
|
||||
Tuplesortstate *sortstate;
|
||||
@@ -221,15 +265,18 @@ VectorArray VectorArrayInit(int maxlen, int dimensions);
|
||||
void VectorArrayFree(VectorArray arr);
|
||||
void PrintVectorArray(char *msg, VectorArray arr);
|
||||
void IvfflatKmeans(Relation index, VectorArray samples, VectorArray centers);
|
||||
FmgrInfo *IvfflatOptionalProcInfo(Relation rel, uint16 procnum);
|
||||
bool IvfflatNormValue(FmgrInfo *procinfo, Oid collation, Datum *value, Vector * result);
|
||||
FmgrInfo *IvfflatOptionalProcInfo(Relation index, uint16 procnum);
|
||||
bool IvfflatNormValue(FmgrInfo *procinfo, Oid collation, Datum *value);
|
||||
int IvfflatGetLists(Relation index);
|
||||
void IvfflatUpdateList(Relation index, GenericXLogState *state, ListInfo listInfo, BlockNumber insertPage, BlockNumber originalInsertPage, BlockNumber startPage, ForkNumber forkNum);
|
||||
void IvfflatGetMetaPageInfo(Relation index, int *lists, int *dimensions);
|
||||
void IvfflatUpdateList(Relation index, ListInfo listInfo, BlockNumber insertPage, BlockNumber originalInsertPage, BlockNumber startPage, ForkNumber forkNum);
|
||||
void IvfflatCommitBuffer(Buffer buf, GenericXLogState *state);
|
||||
void IvfflatAppendPage(Relation index, Buffer *buf, Page *page, GenericXLogState **state, ForkNumber forkNum);
|
||||
Buffer IvfflatNewBuffer(Relation index, ForkNumber forkNum);
|
||||
void IvfflatInitPage(Buffer buf, Page page);
|
||||
void IvfflatInitRegisterPage(Relation index, Buffer *buf, Page *page, GenericXLogState **state);
|
||||
void IvfflatInit(void);
|
||||
PGDLLEXPORT void IvfflatParallelBuildMain(dsm_segment *seg, shm_toc *toc);
|
||||
|
||||
/* Index access methods */
|
||||
IndexBuildResult *ivfflatbuild(Relation heap, Relation index, IndexInfo *indexInfo);
|
||||
|
||||
@@ -2,44 +2,47 @@
|
||||
|
||||
#include <float.h>
|
||||
|
||||
#include "access/generic_xlog.h"
|
||||
#include "ivfflat.h"
|
||||
#include "storage/bufmgr.h"
|
||||
#include "storage/lmgr.h"
|
||||
#include "utils/memutils.h"
|
||||
|
||||
/*
|
||||
* Find the list that minimizes the distance function
|
||||
*/
|
||||
static void
|
||||
FindInsertPage(Relation rel, Datum *values, BlockNumber *insertPage, ListInfo * listInfo)
|
||||
FindInsertPage(Relation index, Datum *values, BlockNumber *insertPage, ListInfo * listInfo)
|
||||
{
|
||||
Buffer cbuf;
|
||||
Page cpage;
|
||||
IvfflatList list;
|
||||
double distance;
|
||||
double minDistance = DBL_MAX;
|
||||
BlockNumber nextblkno = IVFFLAT_HEAD_BLKNO;
|
||||
FmgrInfo *procinfo;
|
||||
Oid collation;
|
||||
OffsetNumber offno;
|
||||
OffsetNumber maxoffno;
|
||||
|
||||
/* Avoid compiler warning */
|
||||
listInfo->blkno = nextblkno;
|
||||
listInfo->offno = FirstOffsetNumber;
|
||||
|
||||
procinfo = index_getprocinfo(rel, 1, IVFFLAT_DISTANCE_PROC);
|
||||
collation = rel->rd_indcollation[0];
|
||||
procinfo = index_getprocinfo(index, 1, IVFFLAT_DISTANCE_PROC);
|
||||
collation = index->rd_indcollation[0];
|
||||
|
||||
/* Search all list pages */
|
||||
while (BlockNumberIsValid(nextblkno))
|
||||
{
|
||||
cbuf = ReadBuffer(rel, nextblkno);
|
||||
Buffer cbuf;
|
||||
Page cpage;
|
||||
OffsetNumber maxoffno;
|
||||
|
||||
cbuf = ReadBuffer(index, nextblkno);
|
||||
LockBuffer(cbuf, BUFFER_LOCK_SHARE);
|
||||
cpage = BufferGetPage(cbuf);
|
||||
maxoffno = PageGetMaxOffsetNumber(cpage);
|
||||
|
||||
for (offno = FirstOffsetNumber; offno <= maxoffno; offno = OffsetNumberNext(offno))
|
||||
for (OffsetNumber offno = FirstOffsetNumber; offno <= maxoffno; offno = OffsetNumberNext(offno))
|
||||
{
|
||||
IvfflatList list;
|
||||
double distance;
|
||||
|
||||
list = (IvfflatList) PageGetItem(cpage, PageGetItemId(cpage, offno));
|
||||
distance = DatumGetFloat8(FunctionCall2Coll(procinfo, collation, values[0], PointerGetDatum(&list->center)));
|
||||
|
||||
@@ -62,7 +65,7 @@ FindInsertPage(Relation rel, Datum *values, BlockNumber *insertPage, ListInfo *
|
||||
* Insert a tuple into the index
|
||||
*/
|
||||
static void
|
||||
InsertTuple(Relation rel, Datum *values, bool *isnull, ItemPointer heap_tid, Relation heapRel)
|
||||
InsertTuple(Relation index, Datum *values, bool *isnull, ItemPointer heap_tid, Relation heapRel)
|
||||
{
|
||||
IndexTuple itup;
|
||||
Datum value;
|
||||
@@ -79,33 +82,33 @@ InsertTuple(Relation rel, Datum *values, bool *isnull, ItemPointer heap_tid, Rel
|
||||
value = PointerGetDatum(PG_DETOAST_DATUM(values[0]));
|
||||
|
||||
/* Normalize if needed */
|
||||
normprocinfo = IvfflatOptionalProcInfo(rel, IVFFLAT_NORM_PROC);
|
||||
normprocinfo = IvfflatOptionalProcInfo(index, IVFFLAT_NORM_PROC);
|
||||
if (normprocinfo != NULL)
|
||||
{
|
||||
if (!IvfflatNormValue(normprocinfo, rel->rd_indcollation[0], &value, NULL))
|
||||
if (!IvfflatNormValue(normprocinfo, index->rd_indcollation[0], &value))
|
||||
return;
|
||||
}
|
||||
|
||||
/* Find the insert page - sets the page and list info */
|
||||
FindInsertPage(rel, values, &insertPage, &listInfo);
|
||||
FindInsertPage(index, values, &insertPage, &listInfo);
|
||||
Assert(BlockNumberIsValid(insertPage));
|
||||
originalInsertPage = insertPage;
|
||||
|
||||
/* Form tuple */
|
||||
itup = index_form_tuple(RelationGetDescr(rel), &value, isnull);
|
||||
itup = index_form_tuple(RelationGetDescr(index), &value, isnull);
|
||||
itup->t_tid = *heap_tid;
|
||||
|
||||
/* Get tuple size */
|
||||
itemsz = MAXALIGN(IndexTupleSize(itup));
|
||||
Assert(itemsz <= BLCKSZ - MAXALIGN(SizeOfPageHeaderData) - MAXALIGN(sizeof(IvfflatPageOpaqueData)));
|
||||
Assert(itemsz <= BLCKSZ - MAXALIGN(SizeOfPageHeaderData) - MAXALIGN(sizeof(IvfflatPageOpaqueData)) - sizeof(ItemIdData));
|
||||
|
||||
/* Find a page to insert the item */
|
||||
for (;;)
|
||||
{
|
||||
buf = ReadBuffer(rel, insertPage);
|
||||
buf = ReadBuffer(index, insertPage);
|
||||
LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
|
||||
|
||||
state = GenericXLogStart(rel);
|
||||
state = GenericXLogStart(index);
|
||||
page = GenericXLogRegisterBuffer(state, buf, 0);
|
||||
|
||||
if (PageGetFreeSpace(page) >= itemsz)
|
||||
@@ -121,23 +124,16 @@ InsertTuple(Relation rel, Datum *values, bool *isnull, ItemPointer heap_tid, Rel
|
||||
}
|
||||
else
|
||||
{
|
||||
Buffer metabuf;
|
||||
Buffer newbuf;
|
||||
Page newpage;
|
||||
|
||||
/*
|
||||
* From ReadBufferExtended: Caller is responsible for ensuring
|
||||
* that only one backend tries to extend a relation at the same
|
||||
* time!
|
||||
*/
|
||||
metabuf = ReadBuffer(rel, IVFFLAT_METAPAGE_BLKNO);
|
||||
LockBuffer(metabuf, BUFFER_LOCK_EXCLUSIVE);
|
||||
|
||||
/* Add a new page */
|
||||
newbuf = IvfflatNewBuffer(rel, MAIN_FORKNUM);
|
||||
newpage = GenericXLogRegisterBuffer(state, newbuf, GENERIC_XLOG_FULL_IMAGE);
|
||||
LockRelationForExtension(index, ExclusiveLock);
|
||||
newbuf = IvfflatNewBuffer(index, MAIN_FORKNUM);
|
||||
UnlockRelationForExtension(index, ExclusiveLock);
|
||||
|
||||
/* Init new page */
|
||||
newpage = GenericXLogRegisterBuffer(state, newbuf, GENERIC_XLOG_FULL_IMAGE);
|
||||
IvfflatInitPage(newbuf, newpage);
|
||||
|
||||
/* Update insert page */
|
||||
@@ -147,18 +143,13 @@ InsertTuple(Relation rel, Datum *values, bool *isnull, ItemPointer heap_tid, Rel
|
||||
IvfflatPageGetOpaque(page)->nextblkno = insertPage;
|
||||
|
||||
/* Commit */
|
||||
MarkBufferDirty(newbuf);
|
||||
MarkBufferDirty(buf);
|
||||
GenericXLogFinish(state);
|
||||
|
||||
/* Unlock extend relation lock as early as possible */
|
||||
UnlockReleaseBuffer(metabuf);
|
||||
|
||||
/* Unlock previous buffer */
|
||||
UnlockReleaseBuffer(buf);
|
||||
|
||||
/* Prepare new buffer */
|
||||
state = GenericXLogStart(rel);
|
||||
state = GenericXLogStart(index);
|
||||
buf = newbuf;
|
||||
page = GenericXLogRegisterBuffer(state, buf, 0);
|
||||
break;
|
||||
@@ -167,13 +158,13 @@ InsertTuple(Relation rel, Datum *values, bool *isnull, ItemPointer heap_tid, Rel
|
||||
|
||||
/* Add to next offset */
|
||||
if (PageAddItem(page, (Item) itup, itemsz, InvalidOffsetNumber, false, false) == InvalidOffsetNumber)
|
||||
elog(ERROR, "failed to add index item to \"%s\"", RelationGetRelationName(rel));
|
||||
elog(ERROR, "failed to add index item to \"%s\"", RelationGetRelationName(index));
|
||||
|
||||
IvfflatCommitBuffer(buf, state);
|
||||
|
||||
/* Update the insert page */
|
||||
if (insertPage != originalInsertPage)
|
||||
IvfflatUpdateList(rel, state, listInfo, insertPage, originalInsertPage, InvalidBlockNumber, MAIN_FORKNUM);
|
||||
IvfflatUpdateList(index, listInfo, insertPage, originalInsertPage, InvalidBlockNumber, MAIN_FORKNUM);
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
116
src/ivfkmeans.c
116
src/ivfkmeans.c
@@ -6,6 +6,10 @@
|
||||
#include "ivfflat.h"
|
||||
#include "miscadmin.h"
|
||||
|
||||
#ifdef IVFFLAT_MEMORY
|
||||
#include "utils/memutils.h"
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Initialize with kmeans++
|
||||
*
|
||||
@@ -16,12 +20,7 @@ InitCenters(Relation index, VectorArray samples, VectorArray centers, float *low
|
||||
{
|
||||
FmgrInfo *procinfo;
|
||||
Oid collation;
|
||||
int i;
|
||||
int64 j;
|
||||
double distance;
|
||||
double sum;
|
||||
double choice;
|
||||
Vector *vec;
|
||||
float *weight = palloc(samples->length * sizeof(float));
|
||||
int numCenters = centers->maxlen;
|
||||
int numSamples = samples->length;
|
||||
@@ -34,17 +33,21 @@ InitCenters(Relation index, VectorArray samples, VectorArray centers, float *low
|
||||
centers->length++;
|
||||
|
||||
for (j = 0; j < numSamples; j++)
|
||||
weight[j] = DBL_MAX;
|
||||
weight[j] = FLT_MAX;
|
||||
|
||||
for (i = 0; i < numCenters; i++)
|
||||
for (int i = 0; i < numCenters; i++)
|
||||
{
|
||||
double sum;
|
||||
double choice;
|
||||
|
||||
CHECK_FOR_INTERRUPTS();
|
||||
|
||||
sum = 0.0;
|
||||
|
||||
for (j = 0; j < numSamples; j++)
|
||||
{
|
||||
vec = VectorArrayGet(samples, j);
|
||||
Vector *vec = VectorArrayGet(samples, j);
|
||||
double distance;
|
||||
|
||||
/* Only need to compute distance for new center */
|
||||
/* TODO Use triangle inequality to reduce distance calculations */
|
||||
@@ -88,13 +91,12 @@ InitCenters(Relation index, VectorArray samples, VectorArray centers, float *low
|
||||
static inline void
|
||||
ApplyNorm(FmgrInfo *normprocinfo, Oid collation, Vector * vec)
|
||||
{
|
||||
int i;
|
||||
double norm = DatumGetFloat8(FunctionCall1Coll(normprocinfo, collation, PointerGetDatum(vec)));
|
||||
|
||||
/* TODO Handle zero norm */
|
||||
if (norm > 0)
|
||||
{
|
||||
for (i = 0; i < vec->dim; i++)
|
||||
for (int i = 0; i < vec->dim; i++)
|
||||
vec->x[i] /= norm;
|
||||
}
|
||||
}
|
||||
@@ -114,9 +116,6 @@ CompareVectors(const void *a, const void *b)
|
||||
static void
|
||||
QuickCenters(Relation index, VectorArray samples, VectorArray centers)
|
||||
{
|
||||
int i;
|
||||
int j;
|
||||
Vector *vec;
|
||||
int dimensions = centers->dim;
|
||||
Oid collation = index->rd_indcollation[0];
|
||||
FmgrInfo *normprocinfo = IvfflatOptionalProcInfo(index, IVFFLAT_KMEANS_NORM_PROC);
|
||||
@@ -125,9 +124,9 @@ QuickCenters(Relation index, VectorArray samples, VectorArray centers)
|
||||
if (samples->length > 0)
|
||||
{
|
||||
qsort(samples->items, samples->length, VECTOR_SIZE(samples->dim), CompareVectors);
|
||||
for (i = 0; i < samples->length; i++)
|
||||
for (int i = 0; i < samples->length; i++)
|
||||
{
|
||||
vec = VectorArrayGet(samples, i);
|
||||
Vector *vec = VectorArrayGet(samples, i);
|
||||
|
||||
if (i == 0 || CompareVectors(vec, VectorArrayGet(samples, i - 1)) != 0)
|
||||
{
|
||||
@@ -140,12 +139,12 @@ QuickCenters(Relation index, VectorArray samples, VectorArray centers)
|
||||
/* Fill remaining with random data */
|
||||
while (centers->length < centers->maxlen)
|
||||
{
|
||||
vec = VectorArrayGet(centers, centers->length);
|
||||
Vector *vec = VectorArrayGet(centers, centers->length);
|
||||
|
||||
SET_VARSIZE(vec, VECTOR_SIZE(dimensions));
|
||||
vec->dim = dimensions;
|
||||
|
||||
for (j = 0; j < dimensions; j++)
|
||||
for (int j = 0; j < dimensions; j++)
|
||||
vec->x[j] = RandomDouble();
|
||||
|
||||
/* Normalize if needed (only needed for random centers) */
|
||||
@@ -156,6 +155,23 @@ QuickCenters(Relation index, VectorArray samples, VectorArray centers)
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef IVFFLAT_MEMORY
|
||||
/*
|
||||
* Show memory usage
|
||||
*/
|
||||
static void
|
||||
ShowMemoryUsage(Size estimatedSize)
|
||||
{
|
||||
#if PG_VERSION_NUM >= 130000
|
||||
elog(INFO, "total memory: %zu MB",
|
||||
MemoryContextMemAllocated(CurrentMemoryContext, true) / (1024 * 1024));
|
||||
#else
|
||||
MemoryContextStats(CurrentMemoryContext);
|
||||
#endif
|
||||
elog(INFO, "estimated memory: %zu MB", estimatedSize / (1024 * 1024));
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Use Elkan for performance. This requires distance function to satisfy triangle inequality.
|
||||
*
|
||||
@@ -172,7 +188,6 @@ ElkanKmeans(Relation index, VectorArray samples, VectorArray centers)
|
||||
Oid collation;
|
||||
Vector *vec;
|
||||
Vector *newCenter;
|
||||
int iteration;
|
||||
int64 j;
|
||||
int64 k;
|
||||
int dimensions = centers->dim;
|
||||
@@ -186,14 +201,6 @@ ElkanKmeans(Relation index, VectorArray samples, VectorArray centers)
|
||||
float *s;
|
||||
float *halfcdist;
|
||||
float *newcdist;
|
||||
int changes;
|
||||
double minDistance;
|
||||
int closestCenter;
|
||||
double distance;
|
||||
bool rj;
|
||||
bool rjreset;
|
||||
double dxcx;
|
||||
double dxc;
|
||||
|
||||
/* Calculate allocation sizes */
|
||||
Size samplesSize = VECTOR_ARRAY_SIZE(samples->maxlen, samples->dim);
|
||||
@@ -245,20 +252,24 @@ ElkanKmeans(Relation index, VectorArray samples, VectorArray centers)
|
||||
vec->dim = dimensions;
|
||||
}
|
||||
|
||||
#ifdef IVFFLAT_MEMORY
|
||||
ShowMemoryUsage(totalSize);
|
||||
#endif
|
||||
|
||||
/* Pick initial centers */
|
||||
InitCenters(index, samples, centers, lowerBound);
|
||||
|
||||
/* Assign each x to its closest initial center c(x) = argmin d(x,c) */
|
||||
for (j = 0; j < numSamples; j++)
|
||||
{
|
||||
minDistance = DBL_MAX;
|
||||
closestCenter = 0;
|
||||
float minDistance = FLT_MAX;
|
||||
int closestCenter = 0;
|
||||
|
||||
/* Find closest center */
|
||||
for (k = 0; k < numCenters; k++)
|
||||
{
|
||||
/* TODO Use Lemma 1 in k-means++ initialization */
|
||||
distance = lowerBound[j * numCenters + k];
|
||||
float distance = lowerBound[j * numCenters + k];
|
||||
|
||||
if (distance < minDistance)
|
||||
{
|
||||
@@ -272,13 +283,14 @@ ElkanKmeans(Relation index, VectorArray samples, VectorArray centers)
|
||||
}
|
||||
|
||||
/* Give 500 iterations to converge */
|
||||
for (iteration = 0; iteration < 500; iteration++)
|
||||
for (int iteration = 0; iteration < 500; iteration++)
|
||||
{
|
||||
int changes = 0;
|
||||
bool rjreset;
|
||||
|
||||
/* Can take a while, so ensure we can interrupt */
|
||||
CHECK_FOR_INTERRUPTS();
|
||||
|
||||
changes = 0;
|
||||
|
||||
/* Step 1: For all centers, compute distance */
|
||||
for (j = 0; j < numCenters; j++)
|
||||
{
|
||||
@@ -286,7 +298,8 @@ ElkanKmeans(Relation index, VectorArray samples, VectorArray centers)
|
||||
|
||||
for (k = j + 1; k < numCenters; k++)
|
||||
{
|
||||
distance = 0.5 * DatumGetFloat8(FunctionCall2Coll(procinfo, collation, PointerGetDatum(vec), PointerGetDatum(VectorArrayGet(centers, k))));
|
||||
float distance = 0.5 * DatumGetFloat8(FunctionCall2Coll(procinfo, collation, PointerGetDatum(vec), PointerGetDatum(VectorArrayGet(centers, k))));
|
||||
|
||||
halfcdist[j * numCenters + k] = distance;
|
||||
halfcdist[k * numCenters + j] = distance;
|
||||
}
|
||||
@@ -295,10 +308,12 @@ ElkanKmeans(Relation index, VectorArray samples, VectorArray centers)
|
||||
/* For all centers c, compute s(c) */
|
||||
for (j = 0; j < numCenters; j++)
|
||||
{
|
||||
minDistance = DBL_MAX;
|
||||
float minDistance = FLT_MAX;
|
||||
|
||||
for (k = 0; k < numCenters; k++)
|
||||
{
|
||||
float distance;
|
||||
|
||||
if (j == k)
|
||||
continue;
|
||||
|
||||
@@ -314,6 +329,8 @@ ElkanKmeans(Relation index, VectorArray samples, VectorArray centers)
|
||||
|
||||
for (j = 0; j < numSamples; j++)
|
||||
{
|
||||
bool rj;
|
||||
|
||||
/* Step 2: Identify all points x such that u(x) <= s(c(x)) */
|
||||
if (upperBound[j] <= s[closestCenters[j]])
|
||||
continue;
|
||||
@@ -322,6 +339,8 @@ ElkanKmeans(Relation index, VectorArray samples, VectorArray centers)
|
||||
|
||||
for (k = 0; k < numCenters; k++)
|
||||
{
|
||||
float dxcx;
|
||||
|
||||
/* Step 3: For all remaining points x and centers c */
|
||||
if (k == closestCenters[j])
|
||||
continue;
|
||||
@@ -351,7 +370,7 @@ ElkanKmeans(Relation index, VectorArray samples, VectorArray centers)
|
||||
/* Step 3b */
|
||||
if (dxcx > lowerBound[j * numCenters + k] || dxcx > halfcdist[closestCenters[j] * numCenters + k])
|
||||
{
|
||||
dxc = DatumGetFloat8(FunctionCall2Coll(procinfo, collation, PointerGetDatum(vec), PointerGetDatum(VectorArrayGet(centers, k))));
|
||||
float dxc = DatumGetFloat8(FunctionCall2Coll(procinfo, collation, PointerGetDatum(vec), PointerGetDatum(VectorArrayGet(centers, k))));
|
||||
|
||||
/* d(x,c) calculated */
|
||||
lowerBound[j * numCenters + k] = dxc;
|
||||
@@ -365,7 +384,6 @@ ElkanKmeans(Relation index, VectorArray samples, VectorArray centers)
|
||||
|
||||
changes++;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -382,6 +400,8 @@ ElkanKmeans(Relation index, VectorArray samples, VectorArray centers)
|
||||
|
||||
for (j = 0; j < numSamples; j++)
|
||||
{
|
||||
int closestCenter;
|
||||
|
||||
vec = VectorArrayGet(samples, j);
|
||||
closestCenter = closestCenters[j];
|
||||
|
||||
@@ -430,7 +450,7 @@ ElkanKmeans(Relation index, VectorArray samples, VectorArray centers)
|
||||
{
|
||||
for (k = 0; k < numCenters; k++)
|
||||
{
|
||||
distance = lowerBound[j * numCenters + k] - newcdist[k];
|
||||
float distance = lowerBound[j * numCenters + k] - newcdist[k];
|
||||
|
||||
if (distance < 0)
|
||||
distance = 0;
|
||||
@@ -446,7 +466,7 @@ ElkanKmeans(Relation index, VectorArray samples, VectorArray centers)
|
||||
|
||||
/* Step 7 */
|
||||
for (j = 0; j < numCenters; j++)
|
||||
memcpy(VectorArrayGet(centers, j), VectorArrayGet(newCenters, j), VECTOR_SIZE(dimensions));
|
||||
VectorArraySet(centers, j, VectorArrayGet(newCenters, j));
|
||||
|
||||
if (changes == 0 && iteration != 0)
|
||||
break;
|
||||
@@ -469,21 +489,16 @@ static void
|
||||
CheckCenters(Relation index, VectorArray centers)
|
||||
{
|
||||
FmgrInfo *normprocinfo;
|
||||
Oid collation;
|
||||
Vector *vec;
|
||||
int i;
|
||||
int j;
|
||||
double norm;
|
||||
|
||||
if (centers->length != centers->maxlen)
|
||||
elog(ERROR, "Not enough centers. Please report a bug.");
|
||||
|
||||
/* Ensure no NaN or infinite values */
|
||||
for (i = 0; i < centers->length; i++)
|
||||
for (int i = 0; i < centers->length; i++)
|
||||
{
|
||||
vec = VectorArrayGet(centers, i);
|
||||
Vector *vec = VectorArrayGet(centers, i);
|
||||
|
||||
for (j = 0; j < vec->dim; j++)
|
||||
for (int j = 0; j < vec->dim; j++)
|
||||
{
|
||||
if (isnan(vec->x[j]))
|
||||
elog(ERROR, "NaN detected. Please report a bug.");
|
||||
@@ -496,7 +511,7 @@ CheckCenters(Relation index, VectorArray centers)
|
||||
/* Ensure no duplicate centers */
|
||||
/* Fine to sort in-place */
|
||||
qsort(centers->items, centers->length, VECTOR_SIZE(centers->dim), CompareVectors);
|
||||
for (i = 1; i < centers->length; i++)
|
||||
for (int i = 1; i < centers->length; i++)
|
||||
{
|
||||
if (CompareVectors(VectorArrayGet(centers, i), VectorArrayGet(centers, i - 1)) == 0)
|
||||
elog(ERROR, "Duplicate centers detected. Please report a bug.");
|
||||
@@ -507,11 +522,12 @@ CheckCenters(Relation index, VectorArray centers)
|
||||
normprocinfo = IvfflatOptionalProcInfo(index, IVFFLAT_NORM_PROC);
|
||||
if (normprocinfo != NULL)
|
||||
{
|
||||
collation = index->rd_indcollation[0];
|
||||
Oid collation = index->rd_indcollation[0];
|
||||
|
||||
for (i = 0; i < centers->length; i++)
|
||||
for (int i = 0; i < centers->length; i++)
|
||||
{
|
||||
norm = DatumGetFloat8(FunctionCall1Coll(normprocinfo, collation, PointerGetDatum(VectorArrayGet(centers, i))));
|
||||
double norm = DatumGetFloat8(FunctionCall1Coll(normprocinfo, collation, PointerGetDatum(VectorArrayGet(centers, i))));
|
||||
|
||||
if (norm == 0)
|
||||
elog(ERROR, "Zero norm detected. Please report a bug.");
|
||||
}
|
||||
|
||||
134
src/ivfscan.c
134
src/ivfscan.c
@@ -3,14 +3,14 @@
|
||||
#include <float.h>
|
||||
|
||||
#include "access/relscan.h"
|
||||
#include "catalog/pg_operator_d.h"
|
||||
#include "catalog/pg_type_d.h"
|
||||
#include "lib/pairingheap.h"
|
||||
#include "ivfflat.h"
|
||||
#include "miscadmin.h"
|
||||
#include "pgstat.h"
|
||||
#include "storage/bufmgr.h"
|
||||
|
||||
#include "catalog/pg_operator_d.h"
|
||||
#include "catalog/pg_type_d.h"
|
||||
|
||||
/*
|
||||
* Compare list distances
|
||||
*/
|
||||
@@ -32,36 +32,36 @@ CompareLists(const pairingheap_node *a, const pairingheap_node *b, void *arg)
|
||||
static void
|
||||
GetScanLists(IndexScanDesc scan, Datum value)
|
||||
{
|
||||
Buffer cbuf;
|
||||
Page cpage;
|
||||
IvfflatList list;
|
||||
OffsetNumber offno;
|
||||
OffsetNumber maxoffno;
|
||||
IvfflatScanOpaque so = (IvfflatScanOpaque) scan->opaque;
|
||||
BlockNumber nextblkno = IVFFLAT_HEAD_BLKNO;
|
||||
int listCount = 0;
|
||||
IvfflatScanOpaque so = (IvfflatScanOpaque) scan->opaque;
|
||||
double distance;
|
||||
IvfflatScanList *scanlist;
|
||||
double maxDistance = DBL_MAX;
|
||||
|
||||
/* Search all list pages */
|
||||
while (BlockNumberIsValid(nextblkno))
|
||||
{
|
||||
Buffer cbuf;
|
||||
Page cpage;
|
||||
OffsetNumber maxoffno;
|
||||
|
||||
cbuf = ReadBuffer(scan->indexRelation, nextblkno);
|
||||
LockBuffer(cbuf, BUFFER_LOCK_SHARE);
|
||||
cpage = BufferGetPage(cbuf);
|
||||
|
||||
maxoffno = PageGetMaxOffsetNumber(cpage);
|
||||
|
||||
for (offno = FirstOffsetNumber; offno <= maxoffno; offno = OffsetNumberNext(offno))
|
||||
for (OffsetNumber offno = FirstOffsetNumber; offno <= maxoffno; offno = OffsetNumberNext(offno))
|
||||
{
|
||||
list = (IvfflatList) PageGetItem(cpage, PageGetItemId(cpage, offno));
|
||||
IvfflatList list = (IvfflatList) PageGetItem(cpage, PageGetItemId(cpage, offno));
|
||||
double distance;
|
||||
|
||||
/* Use procinfo from the index instead of scan key for performance */
|
||||
distance = DatumGetFloat8(FunctionCall2Coll(so->procinfo, so->collation, PointerGetDatum(&list->center), value));
|
||||
|
||||
if (listCount < so->probes)
|
||||
{
|
||||
IvfflatScanList *scanlist;
|
||||
|
||||
scanlist = &so->lists[listCount];
|
||||
scanlist->startPage = list->startPage;
|
||||
scanlist->distance = distance;
|
||||
@@ -76,6 +76,8 @@ GetScanLists(IndexScanDesc scan, Datum value)
|
||||
}
|
||||
else if (distance < maxDistance)
|
||||
{
|
||||
IvfflatScanList *scanlist;
|
||||
|
||||
/* Remove */
|
||||
scanlist = (IvfflatScanList *) pairingheap_remove_first(so->listQueue);
|
||||
|
||||
@@ -102,22 +104,9 @@ static void
|
||||
GetScanItems(IndexScanDesc scan, Datum value)
|
||||
{
|
||||
IvfflatScanOpaque so = (IvfflatScanOpaque) scan->opaque;
|
||||
Buffer buf;
|
||||
Page page;
|
||||
IndexTuple itup;
|
||||
BlockNumber searchPage;
|
||||
OffsetNumber offno;
|
||||
OffsetNumber maxoffno;
|
||||
Datum datum;
|
||||
bool isnull;
|
||||
TupleDesc tupdesc = RelationGetDescr(scan->indexRelation);
|
||||
double tuples = 0;
|
||||
|
||||
#if PG_VERSION_NUM >= 120000
|
||||
TupleTableSlot *slot = MakeSingleTupleTableSlot(so->tupdesc, &TTSOpsVirtual);
|
||||
#else
|
||||
TupleTableSlot *slot = MakeSingleTupleTableSlot(so->tupdesc);
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Reuse same set of shared buffers for scan
|
||||
@@ -129,19 +118,28 @@ GetScanItems(IndexScanDesc scan, Datum value)
|
||||
/* Search closest probes lists */
|
||||
while (!pairingheap_is_empty(so->listQueue))
|
||||
{
|
||||
searchPage = ((IvfflatScanList *) pairingheap_remove_first(so->listQueue))->startPage;
|
||||
BlockNumber searchPage = ((IvfflatScanList *) pairingheap_remove_first(so->listQueue))->startPage;
|
||||
|
||||
/* Search all entry pages for list */
|
||||
while (BlockNumberIsValid(searchPage))
|
||||
{
|
||||
Buffer buf;
|
||||
Page page;
|
||||
OffsetNumber maxoffno;
|
||||
|
||||
buf = ReadBufferExtended(scan->indexRelation, MAIN_FORKNUM, searchPage, RBM_NORMAL, bas);
|
||||
LockBuffer(buf, BUFFER_LOCK_SHARE);
|
||||
page = BufferGetPage(buf);
|
||||
maxoffno = PageGetMaxOffsetNumber(page);
|
||||
|
||||
for (offno = FirstOffsetNumber; offno <= maxoffno; offno = OffsetNumberNext(offno))
|
||||
for (OffsetNumber offno = FirstOffsetNumber; offno <= maxoffno; offno = OffsetNumberNext(offno))
|
||||
{
|
||||
itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, offno));
|
||||
IndexTuple itup;
|
||||
Datum datum;
|
||||
bool isnull;
|
||||
ItemId itemid = PageGetItemId(page, offno);
|
||||
|
||||
itup = (IndexTuple) PageGetItem(page, itemid);
|
||||
datum = index_getattr(itup, 1, tupdesc, &isnull);
|
||||
|
||||
/*
|
||||
@@ -155,8 +153,6 @@ GetScanItems(IndexScanDesc scan, Datum value)
|
||||
slot->tts_isnull[0] = false;
|
||||
slot->tts_values[1] = PointerGetDatum(&itup->t_tid);
|
||||
slot->tts_isnull[1] = false;
|
||||
slot->tts_values[2] = Int32GetDatum((int) searchPage);
|
||||
slot->tts_isnull[2] = false;
|
||||
ExecStoreVirtualTuple(slot);
|
||||
|
||||
tuplesort_puttupleslot(so->sortstate, slot);
|
||||
@@ -172,7 +168,6 @@ GetScanItems(IndexScanDesc scan, Datum value)
|
||||
|
||||
FreeAccessStrategy(bas);
|
||||
|
||||
/* TODO Scan more lists */
|
||||
if (tuples < 100)
|
||||
ereport(DEBUG1,
|
||||
(errmsg("index scan found few tuples"),
|
||||
@@ -191,6 +186,7 @@ ivfflatbeginscan(Relation index, int nkeys, int norderbys)
|
||||
IndexScanDesc scan;
|
||||
IvfflatScanOpaque so;
|
||||
int lists;
|
||||
int dimensions;
|
||||
AttrNumber attNums[] = {1};
|
||||
Oid sortOperators[] = {Float8LessOperator};
|
||||
Oid sortCollations[] = {InvalidOid};
|
||||
@@ -198,15 +194,17 @@ ivfflatbeginscan(Relation index, int nkeys, int norderbys)
|
||||
int probes = ivfflat_probes;
|
||||
|
||||
scan = RelationGetIndexScan(index, nkeys, norderbys);
|
||||
lists = IvfflatGetLists(scan->indexRelation);
|
||||
|
||||
/* Get lists and dimensions from metapage */
|
||||
IvfflatGetMetaPageInfo(index, &lists, &dimensions);
|
||||
|
||||
if (probes > lists)
|
||||
probes = lists;
|
||||
|
||||
so = (IvfflatScanOpaque) palloc(offsetof(IvfflatScanOpaqueData, lists) + probes * sizeof(IvfflatScanList));
|
||||
so->buf = InvalidBuffer;
|
||||
so->first = true;
|
||||
so->probes = probes;
|
||||
so->dimensions = dimensions;
|
||||
|
||||
/* Set support functions */
|
||||
so->procinfo = index_getprocinfo(index, 1, IVFFLAT_DISTANCE_PROC);
|
||||
@@ -214,23 +212,14 @@ ivfflatbeginscan(Relation index, int nkeys, int norderbys)
|
||||
so->collation = index->rd_indcollation[0];
|
||||
|
||||
/* Create tuple description for sorting */
|
||||
#if PG_VERSION_NUM >= 120000
|
||||
so->tupdesc = CreateTemplateTupleDesc(3);
|
||||
#else
|
||||
so->tupdesc = CreateTemplateTupleDesc(3, false);
|
||||
#endif
|
||||
so->tupdesc = CreateTemplateTupleDesc(2);
|
||||
TupleDescInitEntry(so->tupdesc, (AttrNumber) 1, "distance", FLOAT8OID, -1, 0);
|
||||
TupleDescInitEntry(so->tupdesc, (AttrNumber) 2, "tid", TIDOID, -1, 0);
|
||||
TupleDescInitEntry(so->tupdesc, (AttrNumber) 3, "indexblkno", INT4OID, -1, 0);
|
||||
TupleDescInitEntry(so->tupdesc, (AttrNumber) 2, "heaptid", TIDOID, -1, 0);
|
||||
|
||||
/* Prep sort */
|
||||
so->sortstate = tuplesort_begin_heap(so->tupdesc, 1, attNums, sortOperators, sortCollations, nullsFirstFlags, work_mem, NULL, false);
|
||||
|
||||
#if PG_VERSION_NUM >= 120000
|
||||
so->slot = MakeSingleTupleTableSlot(so->tupdesc, &TTSOpsMinimalTuple);
|
||||
#else
|
||||
so->slot = MakeSingleTupleTableSlot(so->tupdesc);
|
||||
#endif
|
||||
|
||||
so->listQueue = pairingheap_allocate(CompareLists, scan);
|
||||
|
||||
@@ -287,21 +276,24 @@ ivfflatgettuple(IndexScanDesc scan, ScanDirection dir)
|
||||
if (scan->orderByData == NULL)
|
||||
elog(ERROR, "cannot scan ivfflat index without order");
|
||||
|
||||
/* No items will match if null */
|
||||
/* Requires MVCC-compliant snapshot as not able to pin during sorting */
|
||||
/* https://www.postgresql.org/docs/current/index-locking.html */
|
||||
if (!IsMVCCSnapshot(scan->xs_snapshot))
|
||||
elog(ERROR, "non-MVCC snapshots are not supported with ivfflat");
|
||||
|
||||
if (scan->orderByData->sk_flags & SK_ISNULL)
|
||||
return false;
|
||||
|
||||
value = scan->orderByData->sk_argument;
|
||||
|
||||
/* Value should not be compressed or toasted */
|
||||
Assert(!VARATT_IS_COMPRESSED(DatumGetPointer(value)));
|
||||
Assert(!VARATT_IS_EXTENDED(DatumGetPointer(value)));
|
||||
|
||||
if (so->normprocinfo != NULL)
|
||||
value = PointerGetDatum(InitVector(so->dimensions));
|
||||
else
|
||||
{
|
||||
/* No items will match if normalization fails */
|
||||
if (!IvfflatNormValue(so->normprocinfo, so->collation, &value, NULL))
|
||||
return false;
|
||||
value = scan->orderByData->sk_argument;
|
||||
|
||||
/* Value should not be compressed or toasted */
|
||||
Assert(!VARATT_IS_COMPRESSED(DatumGetPointer(value)));
|
||||
Assert(!VARATT_IS_EXTENDED(DatumGetPointer(value)));
|
||||
|
||||
/* Fine if normalization fails */
|
||||
if (so->normprocinfo != NULL)
|
||||
IvfflatNormValue(so->normprocinfo, so->collation, &value);
|
||||
}
|
||||
|
||||
IvfflatBench("GetScanLists", GetScanLists(scan, value));
|
||||
@@ -315,26 +307,10 @@ ivfflatgettuple(IndexScanDesc scan, ScanDirection dir)
|
||||
|
||||
if (tuplesort_gettupleslot(so->sortstate, true, false, so->slot, NULL))
|
||||
{
|
||||
ItemPointer tid = (ItemPointer) DatumGetPointer(slot_getattr(so->slot, 2, &so->isnull));
|
||||
BlockNumber indexblkno = DatumGetInt32(slot_getattr(so->slot, 3, &so->isnull));
|
||||
|
||||
#if PG_VERSION_NUM >= 120000
|
||||
scan->xs_heaptid = *tid;
|
||||
#else
|
||||
scan->xs_ctup.t_self = *tid;
|
||||
#endif
|
||||
|
||||
if (BufferIsValid(so->buf))
|
||||
ReleaseBuffer(so->buf);
|
||||
|
||||
/*
|
||||
* An index scan must maintain a pin on the index page holding the
|
||||
* item last returned by amgettuple
|
||||
*
|
||||
* https://www.postgresql.org/docs/current/index-locking.html
|
||||
*/
|
||||
so->buf = ReadBuffer(scan->indexRelation, indexblkno);
|
||||
ItemPointer heaptid = (ItemPointer) DatumGetPointer(slot_getattr(so->slot, 2, &so->isnull));
|
||||
|
||||
scan->xs_heaptid = *heaptid;
|
||||
scan->xs_recheck = false;
|
||||
scan->xs_recheckorderby = false;
|
||||
return true;
|
||||
}
|
||||
@@ -350,10 +326,6 @@ ivfflatendscan(IndexScanDesc scan)
|
||||
{
|
||||
IvfflatScanOpaque so = (IvfflatScanOpaque) scan->opaque;
|
||||
|
||||
/* Release pin */
|
||||
if (BufferIsValid(so->buf))
|
||||
ReleaseBuffer(so->buf);
|
||||
|
||||
pairingheap_free(so->listQueue);
|
||||
tuplesort_end(so->sortstate);
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "postgres.h"
|
||||
|
||||
#include "access/generic_xlog.h"
|
||||
#include "ivfflat.h"
|
||||
#include "storage/bufmgr.h"
|
||||
#include "vector.h"
|
||||
@@ -35,9 +36,7 @@ VectorArrayFree(VectorArray arr)
|
||||
void
|
||||
PrintVectorArray(char *msg, VectorArray arr)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < arr->length; i++)
|
||||
for (int i = 0; i < arr->length; i++)
|
||||
PrintVector(msg, VectorArrayGet(arr, i));
|
||||
}
|
||||
|
||||
@@ -59,12 +58,12 @@ IvfflatGetLists(Relation index)
|
||||
* Get proc
|
||||
*/
|
||||
FmgrInfo *
|
||||
IvfflatOptionalProcInfo(Relation rel, uint16 procnum)
|
||||
IvfflatOptionalProcInfo(Relation index, uint16 procnum)
|
||||
{
|
||||
if (!OidIsValid(index_getprocid(rel, 1, procnum)))
|
||||
if (!OidIsValid(index_getprocid(index, 1, procnum)))
|
||||
return NULL;
|
||||
|
||||
return index_getprocinfo(rel, 1, procnum);
|
||||
return index_getprocinfo(index, 1, procnum);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -76,22 +75,16 @@ IvfflatOptionalProcInfo(Relation rel, uint16 procnum)
|
||||
* if it's different than the original value
|
||||
*/
|
||||
bool
|
||||
IvfflatNormValue(FmgrInfo *procinfo, Oid collation, Datum *value, Vector * result)
|
||||
IvfflatNormValue(FmgrInfo *procinfo, Oid collation, Datum *value)
|
||||
{
|
||||
Vector *v;
|
||||
int i;
|
||||
double norm;
|
||||
|
||||
norm = DatumGetFloat8(FunctionCall1Coll(procinfo, collation, *value));
|
||||
double norm = DatumGetFloat8(FunctionCall1Coll(procinfo, collation, *value));
|
||||
|
||||
if (norm > 0)
|
||||
{
|
||||
v = DatumGetVector(*value);
|
||||
Vector *v = DatumGetVector(*value);
|
||||
Vector *result = InitVector(v->dim);
|
||||
|
||||
if (result == NULL)
|
||||
result = InitVector(v->dim);
|
||||
|
||||
for (i = 0; i < v->dim; i++)
|
||||
for (int i = 0; i < v->dim; i++)
|
||||
result->x[i] = v->x[i] / norm;
|
||||
|
||||
*value = PointerGetDatum(result);
|
||||
@@ -142,7 +135,6 @@ IvfflatInitRegisterPage(Relation index, Buffer *buf, Page *page, GenericXLogStat
|
||||
void
|
||||
IvfflatCommitBuffer(Buffer buf, GenericXLogState *state)
|
||||
{
|
||||
MarkBufferDirty(buf);
|
||||
GenericXLogFinish(state);
|
||||
UnlockReleaseBuffer(buf);
|
||||
}
|
||||
@@ -166,8 +158,6 @@ IvfflatAppendPage(Relation index, Buffer *buf, Page *page, GenericXLogState **st
|
||||
IvfflatInitPage(newbuf, newpage);
|
||||
|
||||
/* Commit */
|
||||
MarkBufferDirty(*buf);
|
||||
MarkBufferDirty(newbuf);
|
||||
GenericXLogFinish(*state);
|
||||
|
||||
/* Unlock */
|
||||
@@ -178,16 +168,40 @@ IvfflatAppendPage(Relation index, Buffer *buf, Page *page, GenericXLogState **st
|
||||
*buf = newbuf;
|
||||
}
|
||||
|
||||
/*
|
||||
* Get the metapage info
|
||||
*/
|
||||
void
|
||||
IvfflatGetMetaPageInfo(Relation index, int *lists, int *dimensions)
|
||||
{
|
||||
Buffer buf;
|
||||
Page page;
|
||||
IvfflatMetaPage metap;
|
||||
|
||||
buf = ReadBuffer(index, IVFFLAT_METAPAGE_BLKNO);
|
||||
LockBuffer(buf, BUFFER_LOCK_SHARE);
|
||||
page = BufferGetPage(buf);
|
||||
metap = IvfflatPageGetMeta(page);
|
||||
|
||||
*lists = metap->lists;
|
||||
|
||||
if (dimensions != NULL)
|
||||
*dimensions = metap->dimensions;
|
||||
|
||||
UnlockReleaseBuffer(buf);
|
||||
}
|
||||
|
||||
/*
|
||||
* Update the start or insert page of a list
|
||||
*/
|
||||
void
|
||||
IvfflatUpdateList(Relation index, GenericXLogState *state, ListInfo listInfo,
|
||||
IvfflatUpdateList(Relation index, ListInfo listInfo,
|
||||
BlockNumber insertPage, BlockNumber originalInsertPage,
|
||||
BlockNumber startPage, ForkNumber forkNum)
|
||||
{
|
||||
Buffer buf;
|
||||
Page page;
|
||||
GenericXLogState *state;
|
||||
IvfflatList list;
|
||||
bool changed = false;
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "postgres.h"
|
||||
|
||||
#include "access/generic_xlog.h"
|
||||
#include "commands/vacuum.h"
|
||||
#include "ivfflat.h"
|
||||
#include "storage/bufmgr.h"
|
||||
@@ -12,34 +13,23 @@ ivfflatbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
|
||||
IndexBulkDeleteCallback callback, void *callback_state)
|
||||
{
|
||||
Relation index = info->index;
|
||||
Buffer cbuf;
|
||||
Page cpage;
|
||||
Buffer buf;
|
||||
Page page;
|
||||
IvfflatList list;
|
||||
IndexTuple itup;
|
||||
ItemPointer htup;
|
||||
OffsetNumber deletable[MaxOffsetNumber];
|
||||
int ndeletable;
|
||||
BlockNumber startPages[MaxOffsetNumber];
|
||||
BlockNumber nextblkno = IVFFLAT_HEAD_BLKNO;
|
||||
BlockNumber searchPage;
|
||||
BlockNumber insertPage;
|
||||
GenericXLogState *state;
|
||||
OffsetNumber coffno;
|
||||
OffsetNumber cmaxoffno;
|
||||
OffsetNumber offno;
|
||||
OffsetNumber maxoffno;
|
||||
ListInfo listInfo;
|
||||
BlockNumber blkno = IVFFLAT_HEAD_BLKNO;
|
||||
BufferAccessStrategy bas = GetAccessStrategy(BAS_BULKREAD);
|
||||
|
||||
if (stats == NULL)
|
||||
stats = (IndexBulkDeleteResult *) palloc0(sizeof(IndexBulkDeleteResult));
|
||||
|
||||
/* Iterate over list pages */
|
||||
while (BlockNumberIsValid(nextblkno))
|
||||
while (BlockNumberIsValid(blkno))
|
||||
{
|
||||
cbuf = ReadBuffer(index, nextblkno);
|
||||
Buffer cbuf;
|
||||
Page cpage;
|
||||
OffsetNumber coffno;
|
||||
OffsetNumber cmaxoffno;
|
||||
BlockNumber startPages[MaxOffsetNumber];
|
||||
ListInfo listInfo;
|
||||
|
||||
cbuf = ReadBuffer(index, blkno);
|
||||
LockBuffer(cbuf, BUFFER_LOCK_SHARE);
|
||||
cpage = BufferGetPage(cbuf);
|
||||
|
||||
@@ -48,23 +38,32 @@ ivfflatbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
|
||||
/* Iterate over lists */
|
||||
for (coffno = FirstOffsetNumber; coffno <= cmaxoffno; coffno = OffsetNumberNext(coffno))
|
||||
{
|
||||
list = (IvfflatList) PageGetItem(cpage, PageGetItemId(cpage, coffno));
|
||||
IvfflatList list = (IvfflatList) PageGetItem(cpage, PageGetItemId(cpage, coffno));
|
||||
|
||||
startPages[coffno - FirstOffsetNumber] = list->startPage;
|
||||
}
|
||||
|
||||
listInfo.blkno = nextblkno;
|
||||
nextblkno = IvfflatPageGetOpaque(cpage)->nextblkno;
|
||||
listInfo.blkno = blkno;
|
||||
blkno = IvfflatPageGetOpaque(cpage)->nextblkno;
|
||||
|
||||
UnlockReleaseBuffer(cbuf);
|
||||
|
||||
for (coffno = FirstOffsetNumber; coffno <= cmaxoffno; coffno = OffsetNumberNext(coffno))
|
||||
{
|
||||
searchPage = startPages[coffno - FirstOffsetNumber];
|
||||
insertPage = InvalidBlockNumber;
|
||||
BlockNumber searchPage = startPages[coffno - FirstOffsetNumber];
|
||||
BlockNumber insertPage = InvalidBlockNumber;
|
||||
|
||||
/* Iterate over entry pages */
|
||||
while (BlockNumberIsValid(searchPage))
|
||||
{
|
||||
Buffer buf;
|
||||
Page page;
|
||||
GenericXLogState *state;
|
||||
OffsetNumber offno;
|
||||
OffsetNumber maxoffno;
|
||||
OffsetNumber deletable[MaxOffsetNumber];
|
||||
int ndeletable;
|
||||
|
||||
vacuum_delay_point();
|
||||
|
||||
buf = ReadBufferExtended(index, MAIN_FORKNUM, searchPage, RBM_NORMAL, bas);
|
||||
@@ -86,8 +85,8 @@ ivfflatbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
|
||||
/* Find deleted tuples */
|
||||
for (offno = FirstOffsetNumber; offno <= maxoffno; offno = OffsetNumberNext(offno))
|
||||
{
|
||||
itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, offno));
|
||||
htup = &(itup->t_tid);
|
||||
IndexTuple itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, offno));
|
||||
ItemPointer htup = &(itup->t_tid);
|
||||
|
||||
if (callback(htup, callback_state))
|
||||
{
|
||||
@@ -109,7 +108,6 @@ ivfflatbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
|
||||
{
|
||||
/* Delete tuples */
|
||||
PageIndexMultiDelete(page, deletable, ndeletable);
|
||||
MarkBufferDirty(buf);
|
||||
GenericXLogFinish(state);
|
||||
}
|
||||
else
|
||||
@@ -127,7 +125,7 @@ ivfflatbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
|
||||
if (BlockNumberIsValid(insertPage))
|
||||
{
|
||||
listInfo.offno = coffno;
|
||||
IvfflatUpdateList(index, state, listInfo, insertPage, InvalidBlockNumber, InvalidBlockNumber, MAIN_FORKNUM);
|
||||
IvfflatUpdateList(index, listInfo, insertPage, InvalidBlockNumber, InvalidBlockNumber, MAIN_FORKNUM);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
830
src/sparsevec.c
Normal file
830
src/sparsevec.c
Normal file
@@ -0,0 +1,830 @@
|
||||
#include "postgres.h"
|
||||
|
||||
#include <limits.h>
|
||||
#include <math.h>
|
||||
|
||||
#include "fmgr.h"
|
||||
#include "libpq/pqformat.h"
|
||||
#include "sparsevec.h"
|
||||
#include "utils/array.h"
|
||||
#include "utils/builtins.h"
|
||||
#include "vector.h"
|
||||
|
||||
#if PG_VERSION_NUM >= 120000
|
||||
#include "common/shortest_dec.h"
|
||||
#include "utils/float.h"
|
||||
#else
|
||||
#include <float.h>
|
||||
#include "utils/builtins.h"
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Ensure same dimensions
|
||||
*/
|
||||
static inline void
|
||||
CheckDims(SparseVector * a, SparseVector * b)
|
||||
{
|
||||
if (a->dim != b->dim)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_DATA_EXCEPTION),
|
||||
errmsg("different sparsevec dimensions %d and %d", a->dim, b->dim)));
|
||||
}
|
||||
|
||||
/*
|
||||
* Ensure expected dimensions
|
||||
*/
|
||||
static inline void
|
||||
CheckExpectedDim(int32 typmod, int dim)
|
||||
{
|
||||
if (typmod != -1 && typmod != dim)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_DATA_EXCEPTION),
|
||||
errmsg("expected %d dimensions, not %d", typmod, dim)));
|
||||
}
|
||||
|
||||
/*
|
||||
* Ensure valid dimensions
|
||||
*/
|
||||
static inline void
|
||||
CheckDim(int dim)
|
||||
{
|
||||
if (dim < 1)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_DATA_EXCEPTION),
|
||||
errmsg("sparsevec must have at least 1 dimension")));
|
||||
|
||||
if (dim > SPARSEVEC_MAX_DIM)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
|
||||
errmsg("sparsevec cannot have more than %d dimensions", SPARSEVEC_MAX_DIM)));
|
||||
}
|
||||
|
||||
/*
|
||||
* Ensure valid nnz
|
||||
*/
|
||||
static inline void
|
||||
CheckNnz(int nnz, int dim)
|
||||
{
|
||||
if (nnz < 0)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_DATA_EXCEPTION),
|
||||
errmsg("sparsevec must have at least one element")));
|
||||
|
||||
if (nnz > dim)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
|
||||
errmsg("sparsevec cannot have more elements than dimensions")));
|
||||
}
|
||||
|
||||
/*
|
||||
* Ensure valid index
|
||||
*/
|
||||
static inline void
|
||||
CheckIndex(int32 *indices, int i, int dim)
|
||||
{
|
||||
int32 index = indices[i];
|
||||
|
||||
if (index < 0)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_DATA_EXCEPTION),
|
||||
errmsg("index must not be negative")));
|
||||
|
||||
if (index >= dim)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_DATA_EXCEPTION),
|
||||
errmsg("index must be less than dimensions")));
|
||||
|
||||
if (i > 0)
|
||||
{
|
||||
if (index < indices[i - 1])
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_DATA_EXCEPTION),
|
||||
errmsg("indexes must be in ascending order")));
|
||||
|
||||
if (index == indices[i - 1])
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_DATA_EXCEPTION),
|
||||
errmsg("indexes must not contain duplicates")));
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Ensure finite element
|
||||
*/
|
||||
static inline void
|
||||
CheckElement(float value)
|
||||
{
|
||||
if (isnan(value))
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_DATA_EXCEPTION),
|
||||
errmsg("NaN not allowed in sparsevec")));
|
||||
|
||||
if (isinf(value))
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_DATA_EXCEPTION),
|
||||
errmsg("infinite value not allowed in sparsevec")));
|
||||
}
|
||||
|
||||
/*
|
||||
* Allocate and initialize a new sparse vector
|
||||
*/
|
||||
SparseVector *
|
||||
InitSparseVector(int dim, int nnz)
|
||||
{
|
||||
SparseVector *result;
|
||||
int size;
|
||||
|
||||
size = SPARSEVEC_SIZE(nnz);
|
||||
result = (SparseVector *) palloc0(size);
|
||||
SET_VARSIZE(result, size);
|
||||
result->dim = dim;
|
||||
result->nnz = nnz;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* Check for whitespace, since array_isspace() is static
|
||||
*/
|
||||
static inline bool
|
||||
sparsevec_isspace(char ch)
|
||||
{
|
||||
if (ch == ' ' ||
|
||||
ch == '\t' ||
|
||||
ch == '\n' ||
|
||||
ch == '\r' ||
|
||||
ch == '\v' ||
|
||||
ch == '\f')
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Convert textual representation to internal representation
|
||||
*/
|
||||
PGDLLEXPORT PG_FUNCTION_INFO_V1(sparsevec_in);
|
||||
Datum
|
||||
sparsevec_in(PG_FUNCTION_ARGS)
|
||||
{
|
||||
char *lit = PG_GETARG_CSTRING(0);
|
||||
int32 typmod = PG_GETARG_INT32(2);
|
||||
int dim;
|
||||
char *pt;
|
||||
char *stringEnd;
|
||||
SparseVector *result;
|
||||
float *rvalues;
|
||||
char *litcopy = pstrdup(lit);
|
||||
char *str = litcopy;
|
||||
int32 *indices;
|
||||
float *values;
|
||||
int maxNnz;
|
||||
int nnz = 0;
|
||||
|
||||
maxNnz = 1;
|
||||
pt = str;
|
||||
while (*pt != '\0')
|
||||
{
|
||||
if (*pt == ',')
|
||||
maxNnz++;
|
||||
|
||||
pt++;
|
||||
}
|
||||
|
||||
indices = palloc(maxNnz * sizeof(int32));
|
||||
values = palloc(maxNnz * sizeof(float));
|
||||
|
||||
while (sparsevec_isspace(*str))
|
||||
str++;
|
||||
|
||||
if (*str != '{')
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
|
||||
errmsg("malformed sparsevec literal: \"%s\"", lit),
|
||||
errdetail("Vector contents must start with \"{\".")));
|
||||
|
||||
str++;
|
||||
pt = strtok(str, ",");
|
||||
stringEnd = pt;
|
||||
|
||||
while (pt != NULL && *stringEnd != '}')
|
||||
{
|
||||
long index;
|
||||
float value;
|
||||
|
||||
/* TODO Better error */
|
||||
if (nnz == maxNnz)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
|
||||
errmsg("ran out of buffer: \"%s\"", lit)));
|
||||
|
||||
while (sparsevec_isspace(*pt))
|
||||
pt++;
|
||||
|
||||
/* Check for empty string like float4in */
|
||||
if (*pt == '\0')
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
|
||||
errmsg("invalid input syntax for type sparsevec: \"%s\"", lit)));
|
||||
|
||||
/* Use similar logic as int2vectorin */
|
||||
errno = 0;
|
||||
index = strtol(pt, &stringEnd, 10);
|
||||
|
||||
if (stringEnd == pt)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
|
||||
errmsg("invalid input syntax for type sparsevec: \"%s\"", lit)));
|
||||
|
||||
if (errno == ERANGE || index < 0 || index > INT_MAX)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
|
||||
errmsg("index \"%ld\" is out of range for type sparsevec", index)));
|
||||
|
||||
if (stringEnd == pt)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
|
||||
errmsg("invalid input syntax for type sparsevec: \"%s\"", lit)));
|
||||
|
||||
while (sparsevec_isspace(*stringEnd))
|
||||
stringEnd++;
|
||||
|
||||
if (*stringEnd != ':')
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
|
||||
errmsg("invalid input syntax for type sparsevec: \"%s\"", lit)));
|
||||
|
||||
stringEnd++;
|
||||
|
||||
while (sparsevec_isspace(*stringEnd))
|
||||
stringEnd++;
|
||||
|
||||
errno = 0;
|
||||
pt = stringEnd;
|
||||
value = strtof(pt, &stringEnd);
|
||||
|
||||
if (stringEnd == pt)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
|
||||
errmsg("invalid input syntax for type sparsevec: \"%s\"", lit)));
|
||||
|
||||
/* Check for range error like float4in */
|
||||
if (errno == ERANGE && (value == 0 || isinf(value)))
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
|
||||
errmsg("\"%s\" is out of range for type sparsevec", pt)));
|
||||
|
||||
/* TODO Decide whether to store zero values */
|
||||
if (value != 0)
|
||||
{
|
||||
indices[nnz] = index;
|
||||
values[nnz] = value;
|
||||
nnz++;
|
||||
}
|
||||
|
||||
if (*stringEnd != '\0' && *stringEnd != '}')
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
|
||||
errmsg("invalid input syntax for type sparsevec: \"%s\"", lit)));
|
||||
|
||||
pt = strtok(NULL, ",");
|
||||
}
|
||||
|
||||
if (stringEnd == NULL || *stringEnd != '}')
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
|
||||
errmsg("malformed sparsevec literal: \"%s\"", lit),
|
||||
errdetail("Unexpected end of input.")));
|
||||
|
||||
stringEnd++;
|
||||
|
||||
if (*stringEnd != '/')
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
|
||||
errmsg("malformed sparsevec literal: \"%s\"", lit),
|
||||
errdetail("Unexpected end of input.")));
|
||||
|
||||
stringEnd++;
|
||||
|
||||
/* Use similar logic as int2vectorin */
|
||||
errno = 0;
|
||||
pt = stringEnd;
|
||||
dim = strtol(pt, &stringEnd, 10);
|
||||
|
||||
if (stringEnd == pt)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
|
||||
errmsg("invalid input syntax for type sparsevec: \"%s\"", lit)));
|
||||
|
||||
/* Only whitespace is allowed after the closing brace */
|
||||
while (sparsevec_isspace(*stringEnd))
|
||||
stringEnd++;
|
||||
|
||||
if (*stringEnd != '\0')
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
|
||||
errmsg("malformed sparsevec literal: \"%s\"", lit),
|
||||
errdetail("Junk after closing.")));
|
||||
|
||||
pfree(litcopy);
|
||||
|
||||
CheckDim(dim);
|
||||
CheckExpectedDim(typmod, dim);
|
||||
|
||||
result = InitSparseVector(dim, nnz);
|
||||
rvalues = SPARSEVEC_VALUES(result);
|
||||
for (int i = 0; i < nnz; i++)
|
||||
{
|
||||
result->indices[i] = indices[i];
|
||||
rvalues[i] = values[i];
|
||||
|
||||
CheckIndex(result->indices, i, dim);
|
||||
CheckElement(rvalues[i]);
|
||||
}
|
||||
|
||||
PG_RETURN_POINTER(result);
|
||||
}
|
||||
|
||||
#define AppendChar(ptr, c) (*(ptr)++ = (c))
|
||||
#define AppendFloat(ptr, f) ((ptr) += float_to_shortest_decimal_bufn((f), (ptr)))
|
||||
|
||||
#if PG_VERSION_NUM >= 140000
|
||||
#define AppendInt(ptr, i) ((ptr) += pg_ltoa((i), (ptr)))
|
||||
#else
|
||||
#define AppendInt(ptr, i) \
|
||||
do { \
|
||||
pg_ltoa(i, ptr); \
|
||||
while (*ptr != '\0') \
|
||||
ptr++; \
|
||||
} while (0)
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Convert internal representation to textual representation
|
||||
*/
|
||||
PGDLLEXPORT PG_FUNCTION_INFO_V1(sparsevec_out);
|
||||
Datum
|
||||
sparsevec_out(PG_FUNCTION_ARGS)
|
||||
{
|
||||
SparseVector *sparsevec = PG_GETARG_SPARSEVEC_P(0);
|
||||
float *values = SPARSEVEC_VALUES(sparsevec);
|
||||
char *buf;
|
||||
char *ptr;
|
||||
|
||||
/*
|
||||
* Need:
|
||||
*
|
||||
* nnz * 10 bytes for index (positive integer)
|
||||
*
|
||||
* nnz bytes for :
|
||||
*
|
||||
* nnz * (FLOAT_SHORTEST_DECIMAL_LEN - 1) bytes for
|
||||
* float_to_shortest_decimal_bufn
|
||||
*
|
||||
* nnz - 1 bytes for ,
|
||||
*
|
||||
* 10 bytes for dimensions
|
||||
*
|
||||
* 4 bytes for {, }, /, and \0
|
||||
*/
|
||||
buf = (char *) palloc((11 + FLOAT_SHORTEST_DECIMAL_LEN) * sparsevec->nnz + 13);
|
||||
ptr = buf;
|
||||
|
||||
AppendChar(ptr, '{');
|
||||
|
||||
for (int i = 0; i < sparsevec->nnz; i++)
|
||||
{
|
||||
if (i > 0)
|
||||
AppendChar(ptr, ',');
|
||||
|
||||
AppendInt(ptr, sparsevec->indices[i]);
|
||||
AppendChar(ptr, ':');
|
||||
AppendFloat(ptr, values[i]);
|
||||
}
|
||||
|
||||
AppendChar(ptr, '}');
|
||||
AppendChar(ptr, '/');
|
||||
AppendInt(ptr, sparsevec->dim);
|
||||
*ptr = '\0';
|
||||
|
||||
PG_FREE_IF_COPY(sparsevec, 0);
|
||||
PG_RETURN_CSTRING(buf);
|
||||
}
|
||||
|
||||
/*
|
||||
* Convert type modifier
|
||||
*/
|
||||
PGDLLEXPORT PG_FUNCTION_INFO_V1(sparsevec_typmod_in);
|
||||
Datum
|
||||
sparsevec_typmod_in(PG_FUNCTION_ARGS)
|
||||
{
|
||||
ArrayType *ta = PG_GETARG_ARRAYTYPE_P(0);
|
||||
int32 *tl;
|
||||
int n;
|
||||
|
||||
tl = ArrayGetIntegerTypmods(ta, &n);
|
||||
|
||||
if (n != 1)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
||||
errmsg("invalid type modifier")));
|
||||
|
||||
if (*tl < 1)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
||||
errmsg("dimensions for type sparsevec must be at least 1")));
|
||||
|
||||
if (*tl > SPARSEVEC_MAX_DIM)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
||||
errmsg("dimensions for type sparsevec cannot exceed %d", SPARSEVEC_MAX_DIM)));
|
||||
|
||||
PG_RETURN_INT32(*tl);
|
||||
}
|
||||
|
||||
/*
|
||||
* Convert external binary representation to internal representation
|
||||
*/
|
||||
PGDLLEXPORT PG_FUNCTION_INFO_V1(sparsevec_recv);
|
||||
Datum
|
||||
sparsevec_recv(PG_FUNCTION_ARGS)
|
||||
{
|
||||
StringInfo buf = (StringInfo) PG_GETARG_POINTER(0);
|
||||
int32 typmod = PG_GETARG_INT32(2);
|
||||
SparseVector *result;
|
||||
int32 dim;
|
||||
int32 nnz;
|
||||
int32 unused;
|
||||
float *values;
|
||||
|
||||
dim = pq_getmsgint(buf, sizeof(int32));
|
||||
nnz = pq_getmsgint(buf, sizeof(int32));
|
||||
unused = pq_getmsgint(buf, sizeof(int32));
|
||||
|
||||
CheckDim(dim);
|
||||
CheckNnz(nnz, dim);
|
||||
CheckExpectedDim(typmod, dim);
|
||||
|
||||
if (unused != 0)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_DATA_EXCEPTION),
|
||||
errmsg("expected unused to be 0, not %d", unused)));
|
||||
|
||||
result = InitSparseVector(dim, nnz);
|
||||
values = SPARSEVEC_VALUES(result);
|
||||
|
||||
for (int i = 0; i < nnz; i++)
|
||||
{
|
||||
result->indices[i] = pq_getmsgint(buf, sizeof(int32));
|
||||
CheckIndex(result->indices, i, dim);
|
||||
}
|
||||
|
||||
for (int i = 0; i < nnz; i++)
|
||||
{
|
||||
values[i] = pq_getmsgfloat4(buf);
|
||||
CheckElement(values[i]);
|
||||
}
|
||||
|
||||
PG_RETURN_POINTER(result);
|
||||
}
|
||||
|
||||
/*
|
||||
* Convert internal representation to the external binary representation
|
||||
*/
|
||||
PGDLLEXPORT PG_FUNCTION_INFO_V1(sparsevec_send);
|
||||
Datum
|
||||
sparsevec_send(PG_FUNCTION_ARGS)
|
||||
{
|
||||
SparseVector *svec = PG_GETARG_SPARSEVEC_P(0);
|
||||
float *values = SPARSEVEC_VALUES(svec);
|
||||
StringInfoData buf;
|
||||
|
||||
pq_begintypsend(&buf);
|
||||
pq_sendint(&buf, svec->dim, sizeof(int32));
|
||||
pq_sendint(&buf, svec->nnz, sizeof(int32));
|
||||
pq_sendint(&buf, svec->unused, sizeof(int32));
|
||||
for (int i = 0; i < svec->nnz; i++)
|
||||
pq_sendint(&buf, svec->indices[i], sizeof(int32));
|
||||
for (int i = 0; i < svec->nnz; i++)
|
||||
pq_sendfloat4(&buf, values[i]);
|
||||
|
||||
PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
|
||||
}
|
||||
|
||||
/*
|
||||
* Convert sparse vector to sparse vector
|
||||
* This is needed to check the type modifier
|
||||
*/
|
||||
PGDLLEXPORT PG_FUNCTION_INFO_V1(sparsevec);
|
||||
Datum
|
||||
sparsevec(PG_FUNCTION_ARGS)
|
||||
{
|
||||
SparseVector *svec = PG_GETARG_SPARSEVEC_P(0);
|
||||
int32 typmod = PG_GETARG_INT32(1);
|
||||
|
||||
CheckExpectedDim(typmod, svec->dim);
|
||||
|
||||
PG_RETURN_POINTER(svec);
|
||||
}
|
||||
|
||||
/*
|
||||
* Convert dense vector to sparse vector
|
||||
*/
|
||||
PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_to_sparsevec);
|
||||
Datum
|
||||
vector_to_sparsevec(PG_FUNCTION_ARGS)
|
||||
{
|
||||
Vector *vec = PG_GETARG_VECTOR_P(0);
|
||||
int32 typmod = PG_GETARG_INT32(1);
|
||||
SparseVector *result;
|
||||
int dim = vec->dim;
|
||||
int nnz = 0;
|
||||
float *values;
|
||||
int j = 0;
|
||||
|
||||
CheckDim(dim);
|
||||
CheckExpectedDim(typmod, dim);
|
||||
|
||||
for (int i = 0; i < dim; i++)
|
||||
{
|
||||
if (vec->x[i] != 0)
|
||||
nnz++;
|
||||
}
|
||||
|
||||
result = InitSparseVector(dim, nnz);
|
||||
values = SPARSEVEC_VALUES(result);
|
||||
for (int i = 0; i < dim; i++)
|
||||
{
|
||||
if (vec->x[i] != 0)
|
||||
{
|
||||
/* Safety check */
|
||||
if (j == nnz)
|
||||
elog(ERROR, "safety check failed");
|
||||
|
||||
result->indices[j] = i;
|
||||
values[j] = vec->x[i];
|
||||
j++;
|
||||
}
|
||||
}
|
||||
|
||||
PG_RETURN_POINTER(result);
|
||||
}
|
||||
|
||||
/*
|
||||
* Get the L2 squared distance between sparse vectors
|
||||
*/
|
||||
static double
|
||||
l2_distance_squared_internal(SparseVector * a, SparseVector * b)
|
||||
{
|
||||
float *ax = SPARSEVEC_VALUES(a);
|
||||
float *bx = SPARSEVEC_VALUES(b);
|
||||
double distance = 0.0;
|
||||
int bpos = 0;
|
||||
|
||||
for (int i = 0; i < a->nnz; i++)
|
||||
{
|
||||
int ai = a->indices[i];
|
||||
int bi = -1;
|
||||
|
||||
for (int j = bpos; j < b->nnz; j++)
|
||||
{
|
||||
bi = b->indices[j];
|
||||
|
||||
if (ai == bi)
|
||||
{
|
||||
double diff = ax[i] - bx[j];
|
||||
|
||||
distance += diff * diff;
|
||||
}
|
||||
else if (ai > bi)
|
||||
distance += bx[j] * bx[j];
|
||||
|
||||
/* Update start for next iteration */
|
||||
if (ai >= bi)
|
||||
bpos = j + 1;
|
||||
|
||||
/* Found or passed it */
|
||||
if (bi >= ai)
|
||||
break;
|
||||
}
|
||||
|
||||
if (ai != bi)
|
||||
distance += ax[i] * ax[i];
|
||||
}
|
||||
|
||||
for (int j = bpos; j < b->nnz; j++)
|
||||
distance += bx[j] * bx[j];
|
||||
|
||||
return distance;
|
||||
}
|
||||
|
||||
/*
|
||||
* Get the L2 distance between sparse vectors
|
||||
*/
|
||||
PGDLLEXPORT PG_FUNCTION_INFO_V1(sparsevec_l2_distance);
|
||||
Datum
|
||||
sparsevec_l2_distance(PG_FUNCTION_ARGS)
|
||||
{
|
||||
SparseVector *a = PG_GETARG_SPARSEVEC_P(0);
|
||||
SparseVector *b = PG_GETARG_SPARSEVEC_P(1);
|
||||
|
||||
CheckDims(a, b);
|
||||
|
||||
PG_RETURN_FLOAT8(sqrt(l2_distance_squared_internal(a, b)));
|
||||
}
|
||||
|
||||
/*
|
||||
* Get the L2 squared distance between sparse vectors
|
||||
* This saves a sqrt calculation
|
||||
*/
|
||||
PGDLLEXPORT PG_FUNCTION_INFO_V1(sparsevec_l2_squared_distance);
|
||||
Datum
|
||||
sparsevec_l2_squared_distance(PG_FUNCTION_ARGS)
|
||||
{
|
||||
SparseVector *a = PG_GETARG_SPARSEVEC_P(0);
|
||||
SparseVector *b = PG_GETARG_SPARSEVEC_P(1);
|
||||
|
||||
CheckDims(a, b);
|
||||
|
||||
PG_RETURN_FLOAT8(l2_distance_squared_internal(a, b));
|
||||
}
|
||||
|
||||
/*
|
||||
* Get the inner product of two sparse vectors
|
||||
*/
|
||||
static double
|
||||
inner_product_internal(SparseVector * a, SparseVector * b)
|
||||
{
|
||||
float *ax = SPARSEVEC_VALUES(a);
|
||||
float *bx = SPARSEVEC_VALUES(b);
|
||||
double distance = 0.0;
|
||||
int bpos = 0;
|
||||
|
||||
for (int i = 0; i < a->nnz; i++)
|
||||
{
|
||||
int ai = a->indices[i];
|
||||
|
||||
for (int j = bpos; j < b->nnz; j++)
|
||||
{
|
||||
int bi = b->indices[j];
|
||||
|
||||
/* Only update when the same index */
|
||||
if (ai == bi)
|
||||
distance += ax[i] * bx[j];
|
||||
|
||||
/* Update start for next iteration */
|
||||
if (ai >= bi)
|
||||
bpos = j + 1;
|
||||
|
||||
/* Found or passed it */
|
||||
if (bi >= ai)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return distance;
|
||||
}
|
||||
|
||||
/*
|
||||
* Get the inner product of two sparse vectors
|
||||
*/
|
||||
PGDLLEXPORT PG_FUNCTION_INFO_V1(sparsevec_inner_product);
|
||||
Datum
|
||||
sparsevec_inner_product(PG_FUNCTION_ARGS)
|
||||
{
|
||||
SparseVector *a = PG_GETARG_SPARSEVEC_P(0);
|
||||
SparseVector *b = PG_GETARG_SPARSEVEC_P(1);
|
||||
|
||||
CheckDims(a, b);
|
||||
|
||||
PG_RETURN_FLOAT8(inner_product_internal(a, b));
|
||||
}
|
||||
|
||||
/*
|
||||
* Get the negative inner product of two sparse vectors
|
||||
*/
|
||||
PGDLLEXPORT PG_FUNCTION_INFO_V1(sparsevec_negative_inner_product);
|
||||
Datum
|
||||
sparsevec_negative_inner_product(PG_FUNCTION_ARGS)
|
||||
{
|
||||
SparseVector *a = PG_GETARG_SPARSEVEC_P(0);
|
||||
SparseVector *b = PG_GETARG_SPARSEVEC_P(1);
|
||||
|
||||
CheckDims(a, b);
|
||||
|
||||
PG_RETURN_FLOAT8(-inner_product_internal(a, b));
|
||||
}
|
||||
|
||||
/*
|
||||
* Get the cosine distance between two sparse vectors
|
||||
*/
|
||||
PGDLLEXPORT PG_FUNCTION_INFO_V1(sparsevec_cosine_distance);
|
||||
Datum
|
||||
sparsevec_cosine_distance(PG_FUNCTION_ARGS)
|
||||
{
|
||||
SparseVector *a = PG_GETARG_SPARSEVEC_P(0);
|
||||
SparseVector *b = PG_GETARG_SPARSEVEC_P(1);
|
||||
float *ax = SPARSEVEC_VALUES(a);
|
||||
float *bx = SPARSEVEC_VALUES(b);
|
||||
float norma = 0.0;
|
||||
float normb = 0.0;
|
||||
double similarity;
|
||||
|
||||
CheckDims(a, b);
|
||||
|
||||
similarity = inner_product_internal(a, b);
|
||||
|
||||
/* Auto-vectorized */
|
||||
for (int i = 0; i < a->nnz; i++)
|
||||
norma += ax[i] * ax[i];
|
||||
|
||||
/* Auto-vectorized */
|
||||
for (int i = 0; i < b->nnz; i++)
|
||||
normb += bx[i] * bx[i];
|
||||
|
||||
/* Use sqrt(a * b) over sqrt(a) * sqrt(b) */
|
||||
similarity /= sqrt((double) norma * (double) normb);
|
||||
|
||||
#ifdef _MSC_VER
|
||||
/* /fp:fast may not propagate NaN */
|
||||
if (isnan(similarity))
|
||||
PG_RETURN_FLOAT8(NAN);
|
||||
#endif
|
||||
|
||||
/* Keep in range */
|
||||
if (similarity > 1)
|
||||
similarity = 1.0;
|
||||
else if (similarity < -1)
|
||||
similarity = -1.0;
|
||||
|
||||
PG_RETURN_FLOAT8(1.0 - similarity);
|
||||
}
|
||||
|
||||
/*
|
||||
* Get the L2 norm of a sparse vector
|
||||
*/
|
||||
PGDLLEXPORT PG_FUNCTION_INFO_V1(sparsevec_norm);
|
||||
Datum
|
||||
sparsevec_norm(PG_FUNCTION_ARGS)
|
||||
{
|
||||
SparseVector *a = PG_GETARG_SPARSEVEC_P(0);
|
||||
float *ax = SPARSEVEC_VALUES(a);
|
||||
double norm = 0.0;
|
||||
|
||||
/* Auto-vectorized */
|
||||
for (int i = 0; i < a->nnz; i++)
|
||||
norm += (double) ax[i] * (double) ax[i];
|
||||
|
||||
PG_RETURN_FLOAT8(sqrt(norm));
|
||||
}
|
||||
|
||||
/*
|
||||
* Get a subvector
|
||||
*/
|
||||
PGDLLEXPORT PG_FUNCTION_INFO_V1(sparsevec_subvector);
|
||||
Datum
|
||||
sparsevec_subvector(PG_FUNCTION_ARGS)
|
||||
{
|
||||
SparseVector *a = PG_GETARG_SPARSEVEC_P(0);
|
||||
int32 start = PG_GETARG_INT32(1);
|
||||
int32 count = PG_GETARG_INT32(2);
|
||||
int32 end = start + count;
|
||||
float *ax = SPARSEVEC_VALUES(a);
|
||||
SparseVector *result;
|
||||
float *rx;
|
||||
int dim;
|
||||
int nnz = 0;
|
||||
int startIndex;
|
||||
|
||||
/* Indexing starts at 1, like substring */
|
||||
if (start < 1)
|
||||
start = 1;
|
||||
|
||||
if (end > a->dim)
|
||||
end = a->dim + 1;
|
||||
|
||||
dim = end - start;
|
||||
CheckDim(dim);
|
||||
|
||||
for (startIndex = 0; startIndex < a->nnz; startIndex++)
|
||||
{
|
||||
if (a->indices[startIndex] >= start - 1)
|
||||
break;
|
||||
}
|
||||
|
||||
for (int i = startIndex; i < a->nnz; i++)
|
||||
{
|
||||
if (a->indices[i] < end - 1)
|
||||
nnz++;
|
||||
}
|
||||
|
||||
result = InitSparseVector(dim, nnz);
|
||||
rx = SPARSEVEC_VALUES(result);
|
||||
|
||||
for (int i = 0; i < nnz; i++)
|
||||
{
|
||||
result->indices[i] = a->indices[startIndex + i];
|
||||
rx[i] = ax[startIndex + i];
|
||||
}
|
||||
|
||||
PG_RETURN_POINTER(result);
|
||||
}
|
||||
24
src/sparsevec.h
Normal file
24
src/sparsevec.h
Normal file
@@ -0,0 +1,24 @@
|
||||
#ifndef SPARSEVEC_H
|
||||
#define SPARSEVEC_H
|
||||
|
||||
#define SPARSEVEC_MAX_DIM 100000
|
||||
|
||||
/* Ensure values are aligned */
|
||||
#define SPARSEVEC_SIZE(_nnz) (offsetof(SparseVector, indices) + MAXALIGN((_nnz) * sizeof(int32)) + (_nnz * sizeof(float)))
|
||||
#define SPARSEVEC_VALUES(x) ((float *) (((char *) (x)) + offsetof(SparseVector, indices) + MAXALIGN((x)->nnz * sizeof(int32))))
|
||||
#define DatumGetSparseVector(x) ((SparseVector *) PG_DETOAST_DATUM(x))
|
||||
#define PG_GETARG_SPARSEVEC_P(x) DatumGetSparseVector(PG_GETARG_DATUM(x))
|
||||
#define PG_RETURN_SPARSEVEC_P(x) PG_RETURN_POINTER(x)
|
||||
|
||||
typedef struct SparseVector
|
||||
{
|
||||
int32 vl_len_; /* varlena header (do not touch directly!) */
|
||||
int32 dim; /* number of dimensions */
|
||||
int32 nnz;
|
||||
int32 unused;
|
||||
int32 indices[FLEXIBLE_ARRAY_MEMBER];
|
||||
} SparseVector;
|
||||
|
||||
SparseVector *InitSparseVector(int dim, int nnz);
|
||||
|
||||
#endif
|
||||
447
src/vector.c
447
src/vector.c
@@ -2,21 +2,26 @@
|
||||
|
||||
#include <math.h>
|
||||
|
||||
#include "vector.h"
|
||||
#include "fmgr.h"
|
||||
#include "bitvector.h"
|
||||
#include "catalog/pg_type.h"
|
||||
#include "common/shortest_dec.h"
|
||||
#include "fmgr.h"
|
||||
#include "halfvec.h"
|
||||
#include "hnsw.h"
|
||||
#include "ivfflat.h"
|
||||
#include "lib/stringinfo.h"
|
||||
#include "libpq/pqformat.h"
|
||||
#include "port.h" /* for strtof() */
|
||||
#include "sparsevec.h"
|
||||
#include "utils/array.h"
|
||||
#include "utils/builtins.h"
|
||||
#include "utils/float.h"
|
||||
#include "utils/lsyscache.h"
|
||||
#include "utils/numeric.h"
|
||||
#include "vector.h"
|
||||
|
||||
#if PG_VERSION_NUM >= 120000
|
||||
#include "common/shortest_dec.h"
|
||||
#include "utils/float.h"
|
||||
#else
|
||||
#include <float.h>
|
||||
#if PG_VERSION_NUM >= 160000
|
||||
#include "varatt.h"
|
||||
#endif
|
||||
|
||||
#if PG_VERSION_NUM < 130000
|
||||
@@ -29,6 +34,17 @@
|
||||
|
||||
PG_MODULE_MAGIC;
|
||||
|
||||
/*
|
||||
* Initialize index options and variables
|
||||
*/
|
||||
PGDLLEXPORT void _PG_init(void);
|
||||
void
|
||||
_PG_init(void)
|
||||
{
|
||||
HnswInit();
|
||||
IvfflatInit();
|
||||
}
|
||||
|
||||
/*
|
||||
* Ensure same dimensions
|
||||
*/
|
||||
@@ -71,7 +87,7 @@ CheckDim(int dim)
|
||||
}
|
||||
|
||||
/*
|
||||
* Ensure finite elements
|
||||
* Ensure finite element
|
||||
*/
|
||||
static inline void
|
||||
CheckElement(float value)
|
||||
@@ -87,6 +103,23 @@ CheckElement(float value)
|
||||
errmsg("infinite value not allowed in vector")));
|
||||
}
|
||||
|
||||
/*
|
||||
* Allocate and initialize a new vector
|
||||
*/
|
||||
Vector *
|
||||
InitVector(int dim)
|
||||
{
|
||||
Vector *result;
|
||||
int size;
|
||||
|
||||
size = VECTOR_SIZE(dim);
|
||||
result = (Vector *) palloc0(size);
|
||||
SET_VARSIZE(result, size);
|
||||
result->dim = dim;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* Check for whitespace, since array_isspace() is static
|
||||
*/
|
||||
@@ -125,6 +158,14 @@ float_overflow_error(void)
|
||||
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
|
||||
errmsg("value out of range: overflow")));
|
||||
}
|
||||
|
||||
static pg_noinline void
|
||||
float_underflow_error(void)
|
||||
{
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
|
||||
errmsg("value out of range: underflow")));
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
@@ -134,15 +175,15 @@ PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_in);
|
||||
Datum
|
||||
vector_in(PG_FUNCTION_ARGS)
|
||||
{
|
||||
char *str = PG_GETARG_CSTRING(0);
|
||||
char *lit = PG_GETARG_CSTRING(0);
|
||||
int32 typmod = PG_GETARG_INT32(2);
|
||||
int i;
|
||||
float x[VECTOR_MAX_DIM];
|
||||
int dim = 0;
|
||||
char *pt;
|
||||
char *stringEnd;
|
||||
Vector *result;
|
||||
char *lit = pstrdup(str);
|
||||
char *litcopy = pstrdup(lit);
|
||||
char *str = litcopy;
|
||||
|
||||
while (vector_isspace(*str))
|
||||
str++;
|
||||
@@ -159,6 +200,8 @@ vector_in(PG_FUNCTION_ARGS)
|
||||
|
||||
while (pt != NULL && *stringEnd != ']')
|
||||
{
|
||||
float val;
|
||||
|
||||
if (dim == VECTOR_MAX_DIM)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
|
||||
@@ -174,15 +217,23 @@ vector_in(PG_FUNCTION_ARGS)
|
||||
errmsg("invalid input syntax for type vector: \"%s\"", lit)));
|
||||
|
||||
/* Use strtof like float4in to avoid a double-rounding problem */
|
||||
x[dim] = strtof(pt, &stringEnd);
|
||||
CheckElement(x[dim]);
|
||||
dim++;
|
||||
errno = 0;
|
||||
val = strtof(pt, &stringEnd);
|
||||
|
||||
if (stringEnd == pt)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
|
||||
errmsg("invalid input syntax for type vector: \"%s\"", lit)));
|
||||
|
||||
/* Check for range error like float4in */
|
||||
if (errno == ERANGE && isinf(val))
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
|
||||
errmsg("\"%s\" is out of range for type vector", pt)));
|
||||
|
||||
CheckElement(val);
|
||||
x[dim++] = val;
|
||||
|
||||
while (vector_isspace(*stringEnd))
|
||||
stringEnd++;
|
||||
|
||||
@@ -226,17 +277,20 @@ vector_in(PG_FUNCTION_ARGS)
|
||||
(errcode(ERRCODE_DATA_EXCEPTION),
|
||||
errmsg("vector must have at least 1 dimension")));
|
||||
|
||||
pfree(lit);
|
||||
pfree(litcopy);
|
||||
|
||||
CheckExpectedDim(typmod, dim);
|
||||
|
||||
result = InitVector(dim);
|
||||
for (i = 0; i < dim; i++)
|
||||
for (int i = 0; i < dim; i++)
|
||||
result->x[i] = x[i];
|
||||
|
||||
PG_RETURN_POINTER(result);
|
||||
}
|
||||
|
||||
#define AppendChar(ptr, c) (*(ptr)++ = (c))
|
||||
#define AppendFloat(ptr, f) ((ptr) += float_to_shortest_decimal_bufn((f), (ptr)))
|
||||
|
||||
/*
|
||||
* Convert internal representation to textual representation
|
||||
*/
|
||||
@@ -248,17 +302,6 @@ vector_out(PG_FUNCTION_ARGS)
|
||||
int dim = vector->dim;
|
||||
char *buf;
|
||||
char *ptr;
|
||||
int i;
|
||||
int n;
|
||||
|
||||
#if PG_VERSION_NUM < 120000
|
||||
int ndig = FLT_DIG + extra_float_digits;
|
||||
|
||||
if (ndig < 1)
|
||||
ndig = 1;
|
||||
|
||||
#define FLOAT_SHORTEST_DECIMAL_LEN (ndig + 10)
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Need:
|
||||
@@ -273,25 +316,17 @@ vector_out(PG_FUNCTION_ARGS)
|
||||
buf = (char *) palloc(FLOAT_SHORTEST_DECIMAL_LEN * dim + 2);
|
||||
ptr = buf;
|
||||
|
||||
*ptr = '[';
|
||||
ptr++;
|
||||
for (i = 0; i < dim; i++)
|
||||
AppendChar(ptr, '[');
|
||||
|
||||
for (int i = 0; i < dim; i++)
|
||||
{
|
||||
if (i > 0)
|
||||
{
|
||||
*ptr = ',';
|
||||
ptr++;
|
||||
}
|
||||
AppendChar(ptr, ',');
|
||||
|
||||
#if PG_VERSION_NUM >= 120000
|
||||
n = float_to_shortest_decimal_bufn(vector->x[i], ptr);
|
||||
#else
|
||||
n = sprintf(ptr, "%.*g", ndig, vector->x[i]);
|
||||
#endif
|
||||
ptr += n;
|
||||
AppendFloat(ptr, vector->x[i]);
|
||||
}
|
||||
*ptr = ']';
|
||||
ptr++;
|
||||
|
||||
AppendChar(ptr, ']');
|
||||
*ptr = '\0';
|
||||
|
||||
PG_FREE_IF_COPY(vector, 0);
|
||||
@@ -353,7 +388,6 @@ vector_recv(PG_FUNCTION_ARGS)
|
||||
Vector *result;
|
||||
int16 dim;
|
||||
int16 unused;
|
||||
int i;
|
||||
|
||||
dim = pq_getmsgint(buf, sizeof(int16));
|
||||
unused = pq_getmsgint(buf, sizeof(int16));
|
||||
@@ -367,7 +401,7 @@ vector_recv(PG_FUNCTION_ARGS)
|
||||
errmsg("expected unused to be 0, not %d", unused)));
|
||||
|
||||
result = InitVector(dim);
|
||||
for (i = 0; i < dim; i++)
|
||||
for (int i = 0; i < dim; i++)
|
||||
{
|
||||
result->x[i] = pq_getmsgfloat4(buf);
|
||||
CheckElement(result->x[i]);
|
||||
@@ -385,12 +419,11 @@ vector_send(PG_FUNCTION_ARGS)
|
||||
{
|
||||
Vector *vec = PG_GETARG_VECTOR_P(0);
|
||||
StringInfoData buf;
|
||||
int i;
|
||||
|
||||
pq_begintypsend(&buf);
|
||||
pq_sendint(&buf, vec->dim, sizeof(int16));
|
||||
pq_sendint(&buf, vec->unused, sizeof(int16));
|
||||
for (i = 0; i < vec->dim; i++)
|
||||
for (int i = 0; i < vec->dim; i++)
|
||||
pq_sendfloat4(&buf, vec->x[i]);
|
||||
|
||||
PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
|
||||
@@ -398,17 +431,18 @@ vector_send(PG_FUNCTION_ARGS)
|
||||
|
||||
/*
|
||||
* Convert vector to vector
|
||||
* This is needed to check the type modifier
|
||||
*/
|
||||
PGDLLEXPORT PG_FUNCTION_INFO_V1(vector);
|
||||
Datum
|
||||
vector(PG_FUNCTION_ARGS)
|
||||
{
|
||||
Vector *arg = PG_GETARG_VECTOR_P(0);
|
||||
Vector *vec = PG_GETARG_VECTOR_P(0);
|
||||
int32 typmod = PG_GETARG_INT32(1);
|
||||
|
||||
CheckExpectedDim(typmod, arg->dim);
|
||||
CheckExpectedDim(typmod, vec->dim);
|
||||
|
||||
PG_RETURN_POINTER(arg);
|
||||
PG_RETURN_POINTER(vec);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -420,13 +454,11 @@ array_to_vector(PG_FUNCTION_ARGS)
|
||||
{
|
||||
ArrayType *array = PG_GETARG_ARRAYTYPE_P(0);
|
||||
int32 typmod = PG_GETARG_INT32(1);
|
||||
int i;
|
||||
Vector *result;
|
||||
int16 typlen;
|
||||
bool typbyval;
|
||||
char typalign;
|
||||
Datum *elemsp;
|
||||
bool *nullsp;
|
||||
int nelemsp;
|
||||
|
||||
if (ARR_NDIM(array) > 1)
|
||||
@@ -434,36 +466,55 @@ array_to_vector(PG_FUNCTION_ARGS)
|
||||
(errcode(ERRCODE_DATA_EXCEPTION),
|
||||
errmsg("array must be 1-D")));
|
||||
|
||||
if (ARR_HASNULL(array) && array_contains_nulls(array))
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
|
||||
errmsg("array must not contain nulls")));
|
||||
|
||||
get_typlenbyvalalign(ARR_ELEMTYPE(array), &typlen, &typbyval, &typalign);
|
||||
deconstruct_array(array, ARR_ELEMTYPE(array), typlen, typbyval, typalign, &elemsp, &nullsp, &nelemsp);
|
||||
deconstruct_array(array, ARR_ELEMTYPE(array), typlen, typbyval, typalign, &elemsp, NULL, &nelemsp);
|
||||
|
||||
CheckDim(nelemsp);
|
||||
CheckExpectedDim(typmod, nelemsp);
|
||||
|
||||
result = InitVector(nelemsp);
|
||||
for (i = 0; i < nelemsp; i++)
|
||||
|
||||
if (ARR_ELEMTYPE(array) == INT4OID)
|
||||
{
|
||||
if (nullsp[i])
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
|
||||
errmsg("array must not containing NULLs")));
|
||||
|
||||
/* TODO Move outside loop in 0.5.0 */
|
||||
if (ARR_ELEMTYPE(array) == INT4OID)
|
||||
for (int i = 0; i < nelemsp; i++)
|
||||
result->x[i] = DatumGetInt32(elemsp[i]);
|
||||
else if (ARR_ELEMTYPE(array) == FLOAT8OID)
|
||||
result->x[i] = DatumGetFloat8(elemsp[i]);
|
||||
else if (ARR_ELEMTYPE(array) == FLOAT4OID)
|
||||
result->x[i] = DatumGetFloat4(elemsp[i]);
|
||||
else if (ARR_ELEMTYPE(array) == NUMERICOID)
|
||||
result->x[i] = DatumGetFloat4(DirectFunctionCall1(numeric_float4, elemsp[i]));
|
||||
else
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_DATA_EXCEPTION),
|
||||
errmsg("unsupported array type")));
|
||||
|
||||
CheckElement(result->x[i]);
|
||||
}
|
||||
else if (ARR_ELEMTYPE(array) == FLOAT8OID)
|
||||
{
|
||||
for (int i = 0; i < nelemsp; i++)
|
||||
result->x[i] = DatumGetFloat8(elemsp[i]);
|
||||
}
|
||||
else if (ARR_ELEMTYPE(array) == FLOAT4OID)
|
||||
{
|
||||
for (int i = 0; i < nelemsp; i++)
|
||||
result->x[i] = DatumGetFloat4(elemsp[i]);
|
||||
}
|
||||
else if (ARR_ELEMTYPE(array) == NUMERICOID)
|
||||
{
|
||||
for (int i = 0; i < nelemsp; i++)
|
||||
result->x[i] = DatumGetFloat4(DirectFunctionCall1(numeric_float4, elemsp[i]));
|
||||
}
|
||||
else
|
||||
{
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_DATA_EXCEPTION),
|
||||
errmsg("unsupported array type")));
|
||||
}
|
||||
|
||||
/*
|
||||
* Free allocation from deconstruct_array. Do not free individual elements
|
||||
* when pass-by-reference since they point to original array.
|
||||
*/
|
||||
pfree(elemsp);
|
||||
|
||||
/* Check elements */
|
||||
for (int i = 0; i < result->dim; i++)
|
||||
CheckElement(result->x[i]);
|
||||
|
||||
PG_RETURN_POINTER(result);
|
||||
}
|
||||
@@ -478,11 +529,10 @@ vector_to_float4(PG_FUNCTION_ARGS)
|
||||
Vector *vec = PG_GETARG_VECTOR_P(0);
|
||||
Datum *datums;
|
||||
ArrayType *result;
|
||||
int i;
|
||||
|
||||
datums = (Datum *) palloc(sizeof(Datum) * vec->dim);
|
||||
|
||||
for (i = 0; i < vec->dim; i++)
|
||||
for (int i = 0; i < vec->dim; i++)
|
||||
datums[i] = Float4GetDatum(vec->x[i]);
|
||||
|
||||
/* Use TYPALIGN_INT for float4 */
|
||||
@@ -493,6 +543,28 @@ vector_to_float4(PG_FUNCTION_ARGS)
|
||||
PG_RETURN_POINTER(result);
|
||||
}
|
||||
|
||||
/*
|
||||
* Convert half vector to vector
|
||||
*/
|
||||
PGDLLEXPORT PG_FUNCTION_INFO_V1(halfvec_to_vector);
|
||||
Datum
|
||||
halfvec_to_vector(PG_FUNCTION_ARGS)
|
||||
{
|
||||
HalfVector *vec = PG_GETARG_HALFVEC_P(0);
|
||||
int32 typmod = PG_GETARG_INT32(1);
|
||||
Vector *result;
|
||||
|
||||
CheckDim(vec->dim);
|
||||
CheckExpectedDim(typmod, vec->dim);
|
||||
|
||||
result = InitVector(vec->dim);
|
||||
|
||||
for (int i = 0; i < vec->dim; i++)
|
||||
result->x[i] = HalfToFloat4(vec->x[i]);
|
||||
|
||||
PG_RETURN_POINTER(result);
|
||||
}
|
||||
|
||||
/*
|
||||
* Get the L2 distance between vectors
|
||||
*/
|
||||
@@ -504,8 +576,8 @@ l2_distance(PG_FUNCTION_ARGS)
|
||||
Vector *b = PG_GETARG_VECTOR_P(1);
|
||||
float *ax = a->x;
|
||||
float *bx = b->x;
|
||||
double distance = 0.0;
|
||||
double diff;
|
||||
float distance = 0.0;
|
||||
float diff;
|
||||
|
||||
CheckDims(a, b);
|
||||
|
||||
@@ -516,7 +588,7 @@ l2_distance(PG_FUNCTION_ARGS)
|
||||
distance += diff * diff;
|
||||
}
|
||||
|
||||
PG_RETURN_FLOAT8(sqrt(distance));
|
||||
PG_RETURN_FLOAT8(sqrt((double) distance));
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -531,8 +603,8 @@ vector_l2_squared_distance(PG_FUNCTION_ARGS)
|
||||
Vector *b = PG_GETARG_VECTOR_P(1);
|
||||
float *ax = a->x;
|
||||
float *bx = b->x;
|
||||
double distance = 0.0;
|
||||
double diff;
|
||||
float distance = 0.0;
|
||||
float diff;
|
||||
|
||||
CheckDims(a, b);
|
||||
|
||||
@@ -543,7 +615,7 @@ vector_l2_squared_distance(PG_FUNCTION_ARGS)
|
||||
distance += diff * diff;
|
||||
}
|
||||
|
||||
PG_RETURN_FLOAT8(distance);
|
||||
PG_RETURN_FLOAT8((double) distance);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -557,7 +629,7 @@ inner_product(PG_FUNCTION_ARGS)
|
||||
Vector *b = PG_GETARG_VECTOR_P(1);
|
||||
float *ax = a->x;
|
||||
float *bx = b->x;
|
||||
double distance = 0.0;
|
||||
float distance = 0.0;
|
||||
|
||||
CheckDims(a, b);
|
||||
|
||||
@@ -565,7 +637,7 @@ inner_product(PG_FUNCTION_ARGS)
|
||||
for (int i = 0; i < a->dim; i++)
|
||||
distance += ax[i] * bx[i];
|
||||
|
||||
PG_RETURN_FLOAT8(distance);
|
||||
PG_RETURN_FLOAT8((double) distance);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -579,7 +651,7 @@ vector_negative_inner_product(PG_FUNCTION_ARGS)
|
||||
Vector *b = PG_GETARG_VECTOR_P(1);
|
||||
float *ax = a->x;
|
||||
float *bx = b->x;
|
||||
double distance = 0.0;
|
||||
float distance = 0.0;
|
||||
|
||||
CheckDims(a, b);
|
||||
|
||||
@@ -587,7 +659,7 @@ vector_negative_inner_product(PG_FUNCTION_ARGS)
|
||||
for (int i = 0; i < a->dim; i++)
|
||||
distance += ax[i] * bx[i];
|
||||
|
||||
PG_RETURN_FLOAT8(distance * -1);
|
||||
PG_RETURN_FLOAT8((double) distance * -1);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -601,9 +673,10 @@ cosine_distance(PG_FUNCTION_ARGS)
|
||||
Vector *b = PG_GETARG_VECTOR_P(1);
|
||||
float *ax = a->x;
|
||||
float *bx = b->x;
|
||||
double distance = 0.0;
|
||||
double norma = 0.0;
|
||||
double normb = 0.0;
|
||||
float distance = 0.0;
|
||||
float norma = 0.0;
|
||||
float normb = 0.0;
|
||||
double similarity;
|
||||
|
||||
CheckDims(a, b);
|
||||
|
||||
@@ -616,7 +689,21 @@ cosine_distance(PG_FUNCTION_ARGS)
|
||||
}
|
||||
|
||||
/* Use sqrt(a * b) over sqrt(a) * sqrt(b) */
|
||||
PG_RETURN_FLOAT8(1 - (distance / sqrt(norma * normb)));
|
||||
similarity = (double) distance / sqrt((double) norma * (double) normb);
|
||||
|
||||
#ifdef _MSC_VER
|
||||
/* /fp:fast may not propagate NaN */
|
||||
if (isnan(similarity))
|
||||
PG_RETURN_FLOAT8(NAN);
|
||||
#endif
|
||||
|
||||
/* Keep in range */
|
||||
if (similarity > 1)
|
||||
similarity = 1.0;
|
||||
else if (similarity < -1)
|
||||
similarity = -1.0;
|
||||
|
||||
PG_RETURN_FLOAT8(1.0 - similarity);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -630,13 +717,18 @@ vector_spherical_distance(PG_FUNCTION_ARGS)
|
||||
{
|
||||
Vector *a = PG_GETARG_VECTOR_P(0);
|
||||
Vector *b = PG_GETARG_VECTOR_P(1);
|
||||
double distance = 0.0;
|
||||
float *ax = a->x;
|
||||
float *bx = b->x;
|
||||
float dp = 0.0;
|
||||
double distance;
|
||||
|
||||
CheckDims(a, b);
|
||||
|
||||
/* Auto-vectorized */
|
||||
for (int i = 0; i < a->dim; i++)
|
||||
distance += a->x[i] * b->x[i];
|
||||
dp += ax[i] * bx[i];
|
||||
|
||||
distance = (double) dp;
|
||||
|
||||
/* Prevent NaN with acos with loss of precision */
|
||||
if (distance > 1)
|
||||
@@ -647,6 +739,28 @@ vector_spherical_distance(PG_FUNCTION_ARGS)
|
||||
PG_RETURN_FLOAT8(acos(distance) / M_PI);
|
||||
}
|
||||
|
||||
/*
|
||||
* Get the L1 distance between two vectors
|
||||
*/
|
||||
PGDLLEXPORT PG_FUNCTION_INFO_V1(l1_distance);
|
||||
Datum
|
||||
l1_distance(PG_FUNCTION_ARGS)
|
||||
{
|
||||
Vector *a = PG_GETARG_VECTOR_P(0);
|
||||
Vector *b = PG_GETARG_VECTOR_P(1);
|
||||
float *ax = a->x;
|
||||
float *bx = b->x;
|
||||
float distance = 0.0;
|
||||
|
||||
CheckDims(a, b);
|
||||
|
||||
/* Auto-vectorized */
|
||||
for (int i = 0; i < a->dim; i++)
|
||||
distance += fabsf(ax[i] - bx[i]);
|
||||
|
||||
PG_RETURN_FLOAT8((double) distance);
|
||||
}
|
||||
|
||||
/*
|
||||
* Get the dimensions of a vector
|
||||
*/
|
||||
@@ -672,7 +786,7 @@ vector_norm(PG_FUNCTION_ARGS)
|
||||
|
||||
/* Auto-vectorized */
|
||||
for (int i = 0; i < a->dim; i++)
|
||||
norm += ax[i] * ax[i];
|
||||
norm += (double) ax[i] * (double) ax[i];
|
||||
|
||||
PG_RETURN_FLOAT8(sqrt(norm));
|
||||
}
|
||||
@@ -743,17 +857,102 @@ vector_sub(PG_FUNCTION_ARGS)
|
||||
PG_RETURN_POINTER(result);
|
||||
}
|
||||
|
||||
/*
|
||||
* Multiply vectors
|
||||
*/
|
||||
PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_mul);
|
||||
Datum
|
||||
vector_mul(PG_FUNCTION_ARGS)
|
||||
{
|
||||
Vector *a = PG_GETARG_VECTOR_P(0);
|
||||
Vector *b = PG_GETARG_VECTOR_P(1);
|
||||
float *ax = a->x;
|
||||
float *bx = b->x;
|
||||
Vector *result;
|
||||
float *rx;
|
||||
|
||||
CheckDims(a, b);
|
||||
|
||||
result = InitVector(a->dim);
|
||||
rx = result->x;
|
||||
|
||||
/* Auto-vectorized */
|
||||
for (int i = 0, imax = a->dim; i < imax; i++)
|
||||
rx[i] = ax[i] * bx[i];
|
||||
|
||||
/* Check for overflow and underflow */
|
||||
for (int i = 0, imax = a->dim; i < imax; i++)
|
||||
{
|
||||
if (isinf(rx[i]))
|
||||
float_overflow_error();
|
||||
|
||||
if (rx[i] == 0 && !(ax[i] == 0 || bx[i] == 0))
|
||||
float_underflow_error();
|
||||
}
|
||||
|
||||
PG_RETURN_POINTER(result);
|
||||
}
|
||||
|
||||
/*
|
||||
* Quantize a vector
|
||||
*/
|
||||
PGDLLEXPORT PG_FUNCTION_INFO_V1(quantize_binary);
|
||||
Datum
|
||||
quantize_binary(PG_FUNCTION_ARGS)
|
||||
{
|
||||
Vector *a = PG_GETARG_VECTOR_P(0);
|
||||
float *ax = a->x;
|
||||
VarBit *result = InitBitVector(a->dim);
|
||||
unsigned char *rx = VARBITS(result);
|
||||
|
||||
for (int i = 0; i < a->dim; i++)
|
||||
rx[i / 8] |= (ax[i] > 0) << (7 - (i % 8));
|
||||
|
||||
PG_RETURN_VARBIT_P(result);
|
||||
}
|
||||
|
||||
/*
|
||||
* Get a subvector
|
||||
*/
|
||||
PGDLLEXPORT PG_FUNCTION_INFO_V1(subvector);
|
||||
Datum
|
||||
subvector(PG_FUNCTION_ARGS)
|
||||
{
|
||||
Vector *a = PG_GETARG_VECTOR_P(0);
|
||||
int32 start = PG_GETARG_INT32(1);
|
||||
int32 count = PG_GETARG_INT32(2);
|
||||
int32 end = start + count;
|
||||
float *ax = a->x;
|
||||
Vector *result;
|
||||
int dim;
|
||||
|
||||
/* Indexing starts at 1, like substring */
|
||||
if (start < 1)
|
||||
start = 1;
|
||||
|
||||
if (end > a->dim)
|
||||
end = a->dim + 1;
|
||||
|
||||
dim = end - start;
|
||||
CheckDim(dim);
|
||||
result = InitVector(dim);
|
||||
|
||||
for (int i = 0; i < dim; i++)
|
||||
result->x[i] = ax[start - 1 + i];
|
||||
|
||||
PG_RETURN_POINTER(result);
|
||||
}
|
||||
|
||||
/*
|
||||
* Internal helper to compare vectors
|
||||
*/
|
||||
int
|
||||
vector_cmp_internal(Vector * a, Vector * b)
|
||||
{
|
||||
int i;
|
||||
int dim = Min(a->dim, b->dim);
|
||||
|
||||
CheckDims(a, b);
|
||||
|
||||
for (i = 0; i < a->dim; i++)
|
||||
/* Check values before dimensions to be consistent with Postgres arrays */
|
||||
for (int i = 0; i < dim; i++)
|
||||
{
|
||||
if (a->x[i] < b->x[i])
|
||||
return -1;
|
||||
@@ -761,6 +960,13 @@ vector_cmp_internal(Vector * a, Vector * b)
|
||||
if (a->x[i] > b->x[i])
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (a->dim < b->dim)
|
||||
return -1;
|
||||
|
||||
if (a->dim > b->dim)
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -771,8 +977,8 @@ PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_lt);
|
||||
Datum
|
||||
vector_lt(PG_FUNCTION_ARGS)
|
||||
{
|
||||
Vector *a = (Vector *) PG_GETARG_VECTOR_P(0);
|
||||
Vector *b = (Vector *) PG_GETARG_VECTOR_P(1);
|
||||
Vector *a = PG_GETARG_VECTOR_P(0);
|
||||
Vector *b = PG_GETARG_VECTOR_P(1);
|
||||
|
||||
PG_RETURN_BOOL(vector_cmp_internal(a, b) < 0);
|
||||
}
|
||||
@@ -784,8 +990,8 @@ PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_le);
|
||||
Datum
|
||||
vector_le(PG_FUNCTION_ARGS)
|
||||
{
|
||||
Vector *a = (Vector *) PG_GETARG_VECTOR_P(0);
|
||||
Vector *b = (Vector *) PG_GETARG_VECTOR_P(1);
|
||||
Vector *a = PG_GETARG_VECTOR_P(0);
|
||||
Vector *b = PG_GETARG_VECTOR_P(1);
|
||||
|
||||
PG_RETURN_BOOL(vector_cmp_internal(a, b) <= 0);
|
||||
}
|
||||
@@ -797,8 +1003,8 @@ PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_eq);
|
||||
Datum
|
||||
vector_eq(PG_FUNCTION_ARGS)
|
||||
{
|
||||
Vector *a = (Vector *) PG_GETARG_VECTOR_P(0);
|
||||
Vector *b = (Vector *) PG_GETARG_VECTOR_P(1);
|
||||
Vector *a = PG_GETARG_VECTOR_P(0);
|
||||
Vector *b = PG_GETARG_VECTOR_P(1);
|
||||
|
||||
PG_RETURN_BOOL(vector_cmp_internal(a, b) == 0);
|
||||
}
|
||||
@@ -810,8 +1016,8 @@ PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_ne);
|
||||
Datum
|
||||
vector_ne(PG_FUNCTION_ARGS)
|
||||
{
|
||||
Vector *a = (Vector *) PG_GETARG_VECTOR_P(0);
|
||||
Vector *b = (Vector *) PG_GETARG_VECTOR_P(1);
|
||||
Vector *a = PG_GETARG_VECTOR_P(0);
|
||||
Vector *b = PG_GETARG_VECTOR_P(1);
|
||||
|
||||
PG_RETURN_BOOL(vector_cmp_internal(a, b) != 0);
|
||||
}
|
||||
@@ -823,8 +1029,8 @@ PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_ge);
|
||||
Datum
|
||||
vector_ge(PG_FUNCTION_ARGS)
|
||||
{
|
||||
Vector *a = (Vector *) PG_GETARG_VECTOR_P(0);
|
||||
Vector *b = (Vector *) PG_GETARG_VECTOR_P(1);
|
||||
Vector *a = PG_GETARG_VECTOR_P(0);
|
||||
Vector *b = PG_GETARG_VECTOR_P(1);
|
||||
|
||||
PG_RETURN_BOOL(vector_cmp_internal(a, b) >= 0);
|
||||
}
|
||||
@@ -836,8 +1042,8 @@ PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_gt);
|
||||
Datum
|
||||
vector_gt(PG_FUNCTION_ARGS)
|
||||
{
|
||||
Vector *a = (Vector *) PG_GETARG_VECTOR_P(0);
|
||||
Vector *b = (Vector *) PG_GETARG_VECTOR_P(1);
|
||||
Vector *a = PG_GETARG_VECTOR_P(0);
|
||||
Vector *b = PG_GETARG_VECTOR_P(1);
|
||||
|
||||
PG_RETURN_BOOL(vector_cmp_internal(a, b) > 0);
|
||||
}
|
||||
@@ -849,8 +1055,8 @@ PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_cmp);
|
||||
Datum
|
||||
vector_cmp(PG_FUNCTION_ARGS)
|
||||
{
|
||||
Vector *a = (Vector *) PG_GETARG_VECTOR_P(0);
|
||||
Vector *b = (Vector *) PG_GETARG_VECTOR_P(1);
|
||||
Vector *a = PG_GETARG_VECTOR_P(0);
|
||||
Vector *b = PG_GETARG_VECTOR_P(1);
|
||||
|
||||
PG_RETURN_INT32(vector_cmp_internal(a, b));
|
||||
}
|
||||
@@ -1019,3 +1225,26 @@ vector_avg(PG_FUNCTION_ARGS)
|
||||
|
||||
PG_RETURN_POINTER(result);
|
||||
}
|
||||
|
||||
/*
|
||||
* Convert sparse vector to dense vector
|
||||
*/
|
||||
PGDLLEXPORT PG_FUNCTION_INFO_V1(sparsevec_to_vector);
|
||||
Datum
|
||||
sparsevec_to_vector(PG_FUNCTION_ARGS)
|
||||
{
|
||||
SparseVector *svec = PG_GETARG_SPARSEVEC_P(0);
|
||||
int32 typmod = PG_GETARG_INT32(1);
|
||||
Vector *result;
|
||||
int dim = svec->dim;
|
||||
float *values = SPARSEVEC_VALUES(svec);
|
||||
|
||||
CheckDim(dim);
|
||||
CheckExpectedDim(typmod, dim);
|
||||
|
||||
result = InitVector(dim);
|
||||
for (int i = 0; i < svec->nnz; i++)
|
||||
result->x[svec->indices[i]] = values[i];
|
||||
|
||||
PG_RETURN_POINTER(result);
|
||||
}
|
||||
|
||||
24
src/vector.h
24
src/vector.h
@@ -1,12 +1,6 @@
|
||||
#ifndef VECTOR_H
|
||||
#define VECTOR_H
|
||||
|
||||
#include "postgres.h"
|
||||
|
||||
#if PG_VERSION_NUM >= 160000
|
||||
#include "varatt.h"
|
||||
#endif
|
||||
|
||||
#define VECTOR_MAX_DIM 16000
|
||||
|
||||
#define VECTOR_SIZE(_dim) (offsetof(Vector, x) + sizeof(float)*(_dim))
|
||||
@@ -22,24 +16,8 @@ typedef struct Vector
|
||||
float x[FLEXIBLE_ARRAY_MEMBER];
|
||||
} Vector;
|
||||
|
||||
Vector *InitVector(int dim);
|
||||
void PrintVector(char *msg, Vector * vector);
|
||||
int vector_cmp_internal(Vector * a, Vector * b);
|
||||
|
||||
/*
|
||||
* Allocate and initialize a new vector
|
||||
*/
|
||||
static inline Vector *
|
||||
InitVector(int dim)
|
||||
{
|
||||
Vector *result;
|
||||
int size;
|
||||
|
||||
size = VECTOR_SIZE(dim);
|
||||
result = (Vector *) palloc0(size);
|
||||
SET_VARSIZE(result, size);
|
||||
result->dim = dim;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
64
test/expected/bit_functions.out
Normal file
64
test/expected/bit_functions.out
Normal file
@@ -0,0 +1,64 @@
|
||||
SELECT hamming_distance(B'111', B'111');
|
||||
hamming_distance
|
||||
------------------
|
||||
0
|
||||
(1 row)
|
||||
|
||||
SELECT hamming_distance(B'111', B'110');
|
||||
hamming_distance
|
||||
------------------
|
||||
1
|
||||
(1 row)
|
||||
|
||||
SELECT hamming_distance(B'111', B'100');
|
||||
hamming_distance
|
||||
------------------
|
||||
2
|
||||
(1 row)
|
||||
|
||||
SELECT hamming_distance(B'111', B'000');
|
||||
hamming_distance
|
||||
------------------
|
||||
3
|
||||
(1 row)
|
||||
|
||||
SELECT hamming_distance(B'111', B'00');
|
||||
ERROR: different bit lengths 3 and 2
|
||||
SELECT jaccard_distance(B'1111', B'1111');
|
||||
jaccard_distance
|
||||
------------------
|
||||
0
|
||||
(1 row)
|
||||
|
||||
SELECT jaccard_distance(B'1111', B'1110');
|
||||
jaccard_distance
|
||||
------------------
|
||||
0.25
|
||||
(1 row)
|
||||
|
||||
SELECT jaccard_distance(B'1111', B'1100');
|
||||
jaccard_distance
|
||||
------------------
|
||||
0.5
|
||||
(1 row)
|
||||
|
||||
SELECT jaccard_distance(B'1111', B'1000');
|
||||
jaccard_distance
|
||||
------------------
|
||||
0.75
|
||||
(1 row)
|
||||
|
||||
SELECT jaccard_distance(B'1111', B'0000');
|
||||
jaccard_distance
|
||||
------------------
|
||||
1
|
||||
(1 row)
|
||||
|
||||
SELECT jaccard_distance(B'1100', B'1000');
|
||||
jaccard_distance
|
||||
------------------
|
||||
0.5
|
||||
(1 row)
|
||||
|
||||
SELECT jaccard_distance(B'1111', B'000');
|
||||
ERROR: different bit lengths 4 and 3
|
||||
@@ -29,7 +29,7 @@ SELECT ARRAY[1,2,3]::numeric[]::vector;
|
||||
(1 row)
|
||||
|
||||
SELECT '{NULL}'::real[]::vector;
|
||||
ERROR: array must not containing NULLs
|
||||
ERROR: array must not contain nulls
|
||||
SELECT '{NaN}'::real[]::vector;
|
||||
ERROR: NaN not allowed in vector
|
||||
SELECT '{Infinity}'::real[]::vector;
|
||||
@@ -38,12 +38,38 @@ SELECT '{-Infinity}'::real[]::vector;
|
||||
ERROR: infinite value not allowed in vector
|
||||
SELECT '{}'::real[]::vector;
|
||||
ERROR: vector must have at least 1 dimension
|
||||
SELECT '{{1}}'::real[]::vector;
|
||||
ERROR: array must be 1-D
|
||||
SELECT '[1,2,3]'::vector::real[];
|
||||
float4
|
||||
---------
|
||||
{1,2,3}
|
||||
(1 row)
|
||||
|
||||
SELECT '[1,2,3]'::vector::halfvec;
|
||||
halfvec
|
||||
---------
|
||||
[1,2,3]
|
||||
(1 row)
|
||||
|
||||
SELECT '[1,2,3]'::halfvec::vector;
|
||||
vector
|
||||
---------
|
||||
[1,2,3]
|
||||
(1 row)
|
||||
|
||||
SELECT '[1,2,3]'::vector::halfvec(2);
|
||||
ERROR: expected 2 dimensions, not 3
|
||||
SELECT '[1,2,3]'::halfvec::vector(2);
|
||||
ERROR: expected 2 dimensions, not 3
|
||||
SELECT '[65520]'::vector::halfvec;
|
||||
ERROR: infinite value not allowed in halfvec
|
||||
SELECT '[1e-8]'::vector::halfvec;
|
||||
halfvec
|
||||
---------
|
||||
[0]
|
||||
(1 row)
|
||||
|
||||
SELECT array_agg(n)::vector FROM generate_series(1, 16001) n;
|
||||
ERROR: vector cannot have more than 16000 dimensions
|
||||
SELECT array_to_vector(array_agg(n), 16001, false) FROM generate_series(1, 16001) n;
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
CREATE TABLE t (val vector(3));
|
||||
INSERT INTO t (val) VALUES ('[0,0,0]'), ('[1,2,3]'), ('[1,1,1]'), (NULL);
|
||||
CREATE TABLE t2 (val vector(3));
|
||||
CREATE TABLE t (val vector(3), val2 halfvec(3));
|
||||
INSERT INTO t (val, val2) VALUES ('[0,0,0]', '[0,0,0]'), ('[1,2,3]', '[1,2,3]'), ('[1,1,1]', '[1,1,1]'), (NULL, NULL);
|
||||
CREATE TABLE t2 (val vector(3), val2 halfvec(3));
|
||||
\copy t TO 'results/data.bin' WITH (FORMAT binary)
|
||||
\copy t2 FROM 'results/data.bin' WITH (FORMAT binary)
|
||||
SELECT * FROM t2 ORDER BY val;
|
||||
val
|
||||
---------
|
||||
[0,0,0]
|
||||
[1,1,1]
|
||||
[1,2,3]
|
||||
|
||||
val | val2
|
||||
---------+---------
|
||||
[0,0,0] | [0,0,0]
|
||||
[1,1,1] | [1,1,1]
|
||||
[1,2,3] | [1,2,3]
|
||||
|
|
||||
(4 rows)
|
||||
|
||||
DROP TABLE t;
|
||||
|
||||
@@ -1,110 +0,0 @@
|
||||
SELECT '[1,2,3]'::vector + '[4,5,6]';
|
||||
?column?
|
||||
----------
|
||||
[5,7,9]
|
||||
(1 row)
|
||||
|
||||
SELECT '[3e38]'::vector + '[3e38]';
|
||||
ERROR: value out of range: overflow
|
||||
SELECT '[1,2,3]'::vector - '[4,5,6]';
|
||||
?column?
|
||||
------------
|
||||
[-3,-3,-3]
|
||||
(1 row)
|
||||
|
||||
SELECT '[-3e38]'::vector - '[3e38]';
|
||||
ERROR: value out of range: overflow
|
||||
SELECT vector_dims('[1,2,3]');
|
||||
vector_dims
|
||||
-------------
|
||||
3
|
||||
(1 row)
|
||||
|
||||
SELECT round(vector_norm('[1,1]')::numeric, 5);
|
||||
round
|
||||
---------
|
||||
1.41421
|
||||
(1 row)
|
||||
|
||||
SELECT vector_norm('[3,4]');
|
||||
vector_norm
|
||||
-------------
|
||||
5
|
||||
(1 row)
|
||||
|
||||
SELECT vector_norm('[0,1]');
|
||||
vector_norm
|
||||
-------------
|
||||
1
|
||||
(1 row)
|
||||
|
||||
SELECT l2_distance('[0,0]', '[3,4]');
|
||||
l2_distance
|
||||
-------------
|
||||
5
|
||||
(1 row)
|
||||
|
||||
SELECT l2_distance('[0,0]', '[0,1]');
|
||||
l2_distance
|
||||
-------------
|
||||
1
|
||||
(1 row)
|
||||
|
||||
SELECT l2_distance('[1,2]', '[3]');
|
||||
ERROR: different vector dimensions 2 and 1
|
||||
SELECT inner_product('[1,2]', '[3,4]');
|
||||
inner_product
|
||||
---------------
|
||||
11
|
||||
(1 row)
|
||||
|
||||
SELECT inner_product('[1,2]', '[3]');
|
||||
ERROR: different vector dimensions 2 and 1
|
||||
SELECT cosine_distance('[1,2]', '[2,4]');
|
||||
cosine_distance
|
||||
-----------------
|
||||
0
|
||||
(1 row)
|
||||
|
||||
SELECT cosine_distance('[1,2]', '[0,0]');
|
||||
cosine_distance
|
||||
-----------------
|
||||
NaN
|
||||
(1 row)
|
||||
|
||||
SELECT cosine_distance('[1,1]', '[1,1]');
|
||||
cosine_distance
|
||||
-----------------
|
||||
0
|
||||
(1 row)
|
||||
|
||||
SELECT cosine_distance('[1,1]', '[-1,-1]');
|
||||
cosine_distance
|
||||
-----------------
|
||||
2
|
||||
(1 row)
|
||||
|
||||
SELECT cosine_distance('[1,2]', '[3]');
|
||||
ERROR: different vector dimensions 2 and 1
|
||||
SELECT avg(v) FROM unnest(ARRAY['[1,2,3]'::vector, '[3,5,7]']) v;
|
||||
avg
|
||||
-----------
|
||||
[2,3.5,5]
|
||||
(1 row)
|
||||
|
||||
SELECT avg(v) FROM unnest(ARRAY['[1,2,3]'::vector, '[3,5,7]', NULL]) v;
|
||||
avg
|
||||
-----------
|
||||
[2,3.5,5]
|
||||
(1 row)
|
||||
|
||||
SELECT avg(v) FROM unnest(ARRAY[]::vector[]) v;
|
||||
avg
|
||||
-----
|
||||
|
||||
(1 row)
|
||||
|
||||
SELECT avg(v) FROM unnest(ARRAY['[1,2]'::vector, '[3]']) v;
|
||||
ERROR: expected 2 dimensions, not 1
|
||||
SELECT vector_avg(array_agg(n)) FROM generate_series(1, 16002) n;
|
||||
ERROR: vector cannot have more than 16000 dimensions
|
||||
146
test/expected/halfvec_functions.out
Normal file
146
test/expected/halfvec_functions.out
Normal file
@@ -0,0 +1,146 @@
|
||||
SELECT l2_distance('[0,0]'::halfvec, '[3,4]');
|
||||
l2_distance
|
||||
-------------
|
||||
5
|
||||
(1 row)
|
||||
|
||||
SELECT l2_distance('[0,0]'::halfvec, '[0,1]');
|
||||
l2_distance
|
||||
-------------
|
||||
1
|
||||
(1 row)
|
||||
|
||||
SELECT l2_distance('[1,2]'::halfvec, '[3]');
|
||||
ERROR: different halfvec dimensions 2 and 1
|
||||
SELECT '[0,0]'::halfvec <-> '[3,4]';
|
||||
?column?
|
||||
----------
|
||||
5
|
||||
(1 row)
|
||||
|
||||
SELECT inner_product('[1,2]'::halfvec, '[3,4]');
|
||||
inner_product
|
||||
---------------
|
||||
11
|
||||
(1 row)
|
||||
|
||||
SELECT inner_product('[1,2]'::halfvec, '[3]');
|
||||
ERROR: different halfvec dimensions 2 and 1
|
||||
SELECT inner_product('[65504]'::halfvec, '[65504]');
|
||||
inner_product
|
||||
---------------
|
||||
4290774016
|
||||
(1 row)
|
||||
|
||||
SELECT '[1,2]'::halfvec <#> '[3,4]';
|
||||
?column?
|
||||
----------
|
||||
-11
|
||||
(1 row)
|
||||
|
||||
SELECT cosine_distance('[1,2]'::halfvec, '[2,4]');
|
||||
cosine_distance
|
||||
-----------------
|
||||
0
|
||||
(1 row)
|
||||
|
||||
SELECT cosine_distance('[1,2]'::halfvec, '[0,0]');
|
||||
cosine_distance
|
||||
-----------------
|
||||
NaN
|
||||
(1 row)
|
||||
|
||||
SELECT cosine_distance('[1,1]'::halfvec, '[1,1]');
|
||||
cosine_distance
|
||||
-----------------
|
||||
0
|
||||
(1 row)
|
||||
|
||||
SELECT cosine_distance('[1,0]'::halfvec, '[0,2]');
|
||||
cosine_distance
|
||||
-----------------
|
||||
1
|
||||
(1 row)
|
||||
|
||||
SELECT cosine_distance('[1,1]'::halfvec, '[-1,-1]');
|
||||
cosine_distance
|
||||
-----------------
|
||||
2
|
||||
(1 row)
|
||||
|
||||
SELECT cosine_distance('[1,2]'::halfvec, '[3]');
|
||||
ERROR: different halfvec dimensions 2 and 1
|
||||
SELECT cosine_distance('[1,1]'::halfvec, '[1.1,1.1]');
|
||||
cosine_distance
|
||||
-----------------
|
||||
0
|
||||
(1 row)
|
||||
|
||||
SELECT cosine_distance('[1,1]'::halfvec, '[-1.1,-1.1]');
|
||||
cosine_distance
|
||||
-----------------
|
||||
2
|
||||
(1 row)
|
||||
|
||||
SELECT '[1,2]'::halfvec <=> '[2,4]';
|
||||
?column?
|
||||
----------
|
||||
0
|
||||
(1 row)
|
||||
|
||||
SELECT l1_distance('[0,0]'::halfvec, '[3,4]');
|
||||
l1_distance
|
||||
-------------
|
||||
7
|
||||
(1 row)
|
||||
|
||||
SELECT l1_distance('[0,0]'::halfvec, '[0,1]');
|
||||
l1_distance
|
||||
-------------
|
||||
1
|
||||
(1 row)
|
||||
|
||||
SELECT l1_distance('[1,2]'::halfvec, '[3]');
|
||||
ERROR: different halfvec dimensions 2 and 1
|
||||
SELECT quantize_binary('[1,0,-1]'::halfvec);
|
||||
quantize_binary
|
||||
-----------------
|
||||
100
|
||||
(1 row)
|
||||
|
||||
SELECT quantize_binary('[0,0.1,-0.2,-0.3,0.4,0.5,0.6,-0.7,0.8,-0.9,1]'::halfvec);
|
||||
quantize_binary
|
||||
-----------------
|
||||
01001110101
|
||||
(1 row)
|
||||
|
||||
SELECT subvector('[1,2,3,4,5]'::halfvec, 1, 3);
|
||||
subvector
|
||||
-----------
|
||||
[1,2,3]
|
||||
(1 row)
|
||||
|
||||
SELECT subvector('[1,2,3,4,5]'::halfvec, 3, 2);
|
||||
subvector
|
||||
-----------
|
||||
[3,4]
|
||||
(1 row)
|
||||
|
||||
SELECT subvector('[1,2,3,4,5]'::halfvec, -1, 3);
|
||||
subvector
|
||||
-----------
|
||||
[1]
|
||||
(1 row)
|
||||
|
||||
SELECT subvector('[1,2,3,4,5]'::halfvec, 3, 9);
|
||||
subvector
|
||||
-----------
|
||||
[3,4,5]
|
||||
(1 row)
|
||||
|
||||
SELECT subvector('[1,2,3,4,5]'::halfvec, 1, 0);
|
||||
ERROR: halfvec must have at least 1 dimension
|
||||
SELECT subvector('[1,2,3,4,5]'::halfvec, 3, -1);
|
||||
ERROR: halfvec must have at least 1 dimension
|
||||
SELECT subvector('[1,2,3,4,5]'::halfvec, -1, 2);
|
||||
ERROR: halfvec must have at least 1 dimension
|
||||
147
test/expected/halfvec_input.out
Normal file
147
test/expected/halfvec_input.out
Normal file
@@ -0,0 +1,147 @@
|
||||
SELECT '[1,2,3]'::halfvec;
|
||||
halfvec
|
||||
---------
|
||||
[1,2,3]
|
||||
(1 row)
|
||||
|
||||
SELECT '[-1,-2,-3]'::halfvec;
|
||||
halfvec
|
||||
------------
|
||||
[-1,-2,-3]
|
||||
(1 row)
|
||||
|
||||
SELECT '[1.,2.,3.]'::halfvec;
|
||||
halfvec
|
||||
---------
|
||||
[1,2,3]
|
||||
(1 row)
|
||||
|
||||
SELECT ' [ 1, 2 , 3 ] '::halfvec;
|
||||
halfvec
|
||||
---------
|
||||
[1,2,3]
|
||||
(1 row)
|
||||
|
||||
SELECT '[1.23456]'::halfvec;
|
||||
halfvec
|
||||
------------
|
||||
[1.234375]
|
||||
(1 row)
|
||||
|
||||
SELECT '[hello,1]'::halfvec;
|
||||
ERROR: invalid input syntax for type halfvec: "[hello,1]"
|
||||
LINE 1: SELECT '[hello,1]'::halfvec;
|
||||
^
|
||||
SELECT '[NaN,1]'::halfvec;
|
||||
ERROR: NaN not allowed in halfvec
|
||||
LINE 1: SELECT '[NaN,1]'::halfvec;
|
||||
^
|
||||
SELECT '[Infinity,1]'::halfvec;
|
||||
ERROR: infinite value not allowed in halfvec
|
||||
LINE 1: SELECT '[Infinity,1]'::halfvec;
|
||||
^
|
||||
SELECT '[-Infinity,1]'::halfvec;
|
||||
ERROR: infinite value not allowed in halfvec
|
||||
LINE 1: SELECT '[-Infinity,1]'::halfvec;
|
||||
^
|
||||
SELECT '[65519,-65519]'::halfvec;
|
||||
halfvec
|
||||
----------------
|
||||
[65504,-65504]
|
||||
(1 row)
|
||||
|
||||
SELECT '[65520,-65520]'::halfvec;
|
||||
ERROR: value out of range: overflow
|
||||
LINE 1: SELECT '[65520,-65520]'::halfvec;
|
||||
^
|
||||
SELECT '[1e-8,-1e-8]'::halfvec;
|
||||
ERROR: value out of range: underflow
|
||||
LINE 1: SELECT '[1e-8,-1e-8]'::halfvec;
|
||||
^
|
||||
SELECT '[4e38,1]'::halfvec;
|
||||
ERROR: infinite value not allowed in halfvec
|
||||
LINE 1: SELECT '[4e38,1]'::halfvec;
|
||||
^
|
||||
SELECT '[1,2,3'::halfvec;
|
||||
ERROR: malformed halfvec literal: "[1,2,3"
|
||||
LINE 1: SELECT '[1,2,3'::halfvec;
|
||||
^
|
||||
DETAIL: Unexpected end of input.
|
||||
SELECT '[1,2,3]9'::halfvec;
|
||||
ERROR: malformed halfvec literal: "[1,2,3]9"
|
||||
LINE 1: SELECT '[1,2,3]9'::halfvec;
|
||||
^
|
||||
DETAIL: Junk after closing right brace.
|
||||
SELECT '1,2,3'::halfvec;
|
||||
ERROR: malformed halfvec literal: "1,2,3"
|
||||
LINE 1: SELECT '1,2,3'::halfvec;
|
||||
^
|
||||
DETAIL: Vector contents must start with "[".
|
||||
SELECT ''::halfvec;
|
||||
ERROR: malformed halfvec literal: ""
|
||||
LINE 1: SELECT ''::halfvec;
|
||||
^
|
||||
DETAIL: Vector contents must start with "[".
|
||||
SELECT '['::halfvec;
|
||||
ERROR: malformed halfvec literal: "["
|
||||
LINE 1: SELECT '['::halfvec;
|
||||
^
|
||||
DETAIL: Unexpected end of input.
|
||||
SELECT '[,'::halfvec;
|
||||
ERROR: malformed halfvec literal: "[,"
|
||||
LINE 1: SELECT '[,'::halfvec;
|
||||
^
|
||||
DETAIL: Unexpected end of input.
|
||||
SELECT '[]'::halfvec;
|
||||
ERROR: halfvec must have at least 1 dimension
|
||||
LINE 1: SELECT '[]'::halfvec;
|
||||
^
|
||||
SELECT '[1,]'::halfvec;
|
||||
ERROR: invalid input syntax for type halfvec: "[1,]"
|
||||
LINE 1: SELECT '[1,]'::halfvec;
|
||||
^
|
||||
SELECT '[1a]'::halfvec;
|
||||
ERROR: invalid input syntax for type halfvec: "[1a]"
|
||||
LINE 1: SELECT '[1a]'::halfvec;
|
||||
^
|
||||
SELECT '[1,,3]'::halfvec;
|
||||
ERROR: malformed halfvec literal: "[1,,3]"
|
||||
LINE 1: SELECT '[1,,3]'::halfvec;
|
||||
^
|
||||
SELECT '[1, ,3]'::halfvec;
|
||||
ERROR: invalid input syntax for type halfvec: "[1, ,3]"
|
||||
LINE 1: SELECT '[1, ,3]'::halfvec;
|
||||
^
|
||||
SELECT '[1,2,3]'::halfvec(3);
|
||||
halfvec
|
||||
---------
|
||||
[1,2,3]
|
||||
(1 row)
|
||||
|
||||
SELECT '[1,2,3]'::halfvec(2);
|
||||
ERROR: expected 2 dimensions, not 3
|
||||
SELECT '[1,2,3]'::halfvec(3, 2);
|
||||
ERROR: invalid type modifier
|
||||
LINE 1: SELECT '[1,2,3]'::halfvec(3, 2);
|
||||
^
|
||||
SELECT '[1,2,3]'::halfvec('a');
|
||||
ERROR: invalid input syntax for type integer: "a"
|
||||
LINE 1: SELECT '[1,2,3]'::halfvec('a');
|
||||
^
|
||||
SELECT '[1,2,3]'::halfvec(0);
|
||||
ERROR: dimensions for type halfvec must be at least 1
|
||||
LINE 1: SELECT '[1,2,3]'::halfvec(0);
|
||||
^
|
||||
SELECT '[1,2,3]'::halfvec(16001);
|
||||
ERROR: dimensions for type halfvec cannot exceed 16000
|
||||
LINE 1: SELECT '[1,2,3]'::halfvec(16001);
|
||||
^
|
||||
SELECT unnest('{"[1,2,3]", "[4,5,6]"}'::halfvec[]);
|
||||
unnest
|
||||
---------
|
||||
[1,2,3]
|
||||
[4,5,6]
|
||||
(2 rows)
|
||||
|
||||
SELECT '{"[1,2,3]"}'::halfvec(2)[];
|
||||
ERROR: expected 2 dimensions, not 3
|
||||
21
test/expected/hnsw_bit_hamming.out
Normal file
21
test/expected/hnsw_bit_hamming.out
Normal file
@@ -0,0 +1,21 @@
|
||||
SET enable_seqscan = off;
|
||||
CREATE TABLE t (val bit(3));
|
||||
INSERT INTO t (val) VALUES (B'000'), (B'100'), (B'111'), (NULL);
|
||||
CREATE INDEX ON t USING hnsw (val bit_hamming_ops);
|
||||
INSERT INTO t (val) VALUES (B'110');
|
||||
SELECT * FROM t ORDER BY val <~> B'111';
|
||||
val
|
||||
-----
|
||||
111
|
||||
110
|
||||
100
|
||||
000
|
||||
(4 rows)
|
||||
|
||||
SELECT COUNT(*) FROM (SELECT * FROM t ORDER BY val <~> (SELECT NULL::bit)) t2;
|
||||
count
|
||||
-------
|
||||
4
|
||||
(1 row)
|
||||
|
||||
DROP TABLE t;
|
||||
21
test/expected/hnsw_bit_jaccard.out
Normal file
21
test/expected/hnsw_bit_jaccard.out
Normal file
@@ -0,0 +1,21 @@
|
||||
SET enable_seqscan = off;
|
||||
CREATE TABLE t (val bit(4));
|
||||
INSERT INTO t (val) VALUES (B'0000'), (B'1100'), (B'1111'), (NULL);
|
||||
CREATE INDEX ON t USING hnsw (val bit_jaccard_ops);
|
||||
INSERT INTO t (val) VALUES (B'1110');
|
||||
SELECT * FROM t ORDER BY val <%> B'1111';
|
||||
val
|
||||
------
|
||||
1111
|
||||
1110
|
||||
1100
|
||||
0000
|
||||
(4 rows)
|
||||
|
||||
SELECT COUNT(*) FROM (SELECT * FROM t ORDER BY val <%> (SELECT NULL::bit)) t2;
|
||||
count
|
||||
-------
|
||||
4
|
||||
(1 row)
|
||||
|
||||
DROP TABLE t;
|
||||
26
test/expected/hnsw_halfvec_cosine.out
Normal file
26
test/expected/hnsw_halfvec_cosine.out
Normal file
@@ -0,0 +1,26 @@
|
||||
SET enable_seqscan = off;
|
||||
CREATE TABLE t (val halfvec(3));
|
||||
INSERT INTO t (val) VALUES ('[0,0,0]'), ('[1,2,3]'), ('[1,1,1]'), (NULL);
|
||||
CREATE INDEX ON t USING hnsw (val halfvec_cosine_ops);
|
||||
INSERT INTO t (val) VALUES ('[1,2,4]');
|
||||
SELECT * FROM t ORDER BY val <=> '[3,3,3]';
|
||||
val
|
||||
---------
|
||||
[1,1,1]
|
||||
[1,2,3]
|
||||
[1,2,4]
|
||||
(3 rows)
|
||||
|
||||
SELECT COUNT(*) FROM (SELECT * FROM t ORDER BY val <=> '[0,0,0]') t2;
|
||||
count
|
||||
-------
|
||||
3
|
||||
(1 row)
|
||||
|
||||
SELECT COUNT(*) FROM (SELECT * FROM t ORDER BY val <=> (SELECT NULL::halfvec)) t2;
|
||||
count
|
||||
-------
|
||||
3
|
||||
(1 row)
|
||||
|
||||
DROP TABLE t;
|
||||
21
test/expected/hnsw_halfvec_ip.out
Normal file
21
test/expected/hnsw_halfvec_ip.out
Normal file
@@ -0,0 +1,21 @@
|
||||
SET enable_seqscan = off;
|
||||
CREATE TABLE t (val halfvec(3));
|
||||
INSERT INTO t (val) VALUES ('[0,0,0]'), ('[1,2,3]'), ('[1,1,1]'), (NULL);
|
||||
CREATE INDEX ON t USING hnsw (val halfvec_ip_ops);
|
||||
INSERT INTO t (val) VALUES ('[1,2,4]');
|
||||
SELECT * FROM t ORDER BY val <#> '[3,3,3]';
|
||||
val
|
||||
---------
|
||||
[1,2,4]
|
||||
[1,2,3]
|
||||
[1,1,1]
|
||||
[0,0,0]
|
||||
(4 rows)
|
||||
|
||||
SELECT COUNT(*) FROM (SELECT * FROM t ORDER BY val <#> (SELECT NULL::halfvec)) t2;
|
||||
count
|
||||
-------
|
||||
4
|
||||
(1 row)
|
||||
|
||||
DROP TABLE t;
|
||||
33
test/expected/hnsw_halfvec_l2.out
Normal file
33
test/expected/hnsw_halfvec_l2.out
Normal file
@@ -0,0 +1,33 @@
|
||||
SET enable_seqscan = off;
|
||||
CREATE TABLE t (val halfvec(3));
|
||||
INSERT INTO t (val) VALUES ('[0,0,0]'), ('[1,2,3]'), ('[1,1,1]'), (NULL);
|
||||
CREATE INDEX ON t USING hnsw (val halfvec_l2_ops);
|
||||
INSERT INTO t (val) VALUES ('[1,2,4]');
|
||||
SELECT * FROM t ORDER BY val <-> '[3,3,3]';
|
||||
val
|
||||
---------
|
||||
[1,2,3]
|
||||
[1,2,4]
|
||||
[1,1,1]
|
||||
[0,0,0]
|
||||
(4 rows)
|
||||
|
||||
SELECT COUNT(*) FROM (SELECT * FROM t ORDER BY val <-> (SELECT NULL::halfvec)) t2;
|
||||
count
|
||||
-------
|
||||
4
|
||||
(1 row)
|
||||
|
||||
SELECT COUNT(*) FROM t;
|
||||
count
|
||||
-------
|
||||
5
|
||||
(1 row)
|
||||
|
||||
TRUNCATE t;
|
||||
SELECT * FROM t ORDER BY val <-> '[3,3,3]';
|
||||
val
|
||||
-----
|
||||
(0 rows)
|
||||
|
||||
DROP TABLE t;
|
||||
26
test/expected/hnsw_options.out
Normal file
26
test/expected/hnsw_options.out
Normal file
@@ -0,0 +1,26 @@
|
||||
CREATE TABLE t (val vector(3));
|
||||
CREATE INDEX ON t USING hnsw (val vector_l2_ops) WITH (m = 1);
|
||||
ERROR: value 1 out of bounds for option "m"
|
||||
DETAIL: Valid values are between "2" and "100".
|
||||
CREATE INDEX ON t USING hnsw (val vector_l2_ops) WITH (m = 101);
|
||||
ERROR: value 101 out of bounds for option "m"
|
||||
DETAIL: Valid values are between "2" and "100".
|
||||
CREATE INDEX ON t USING hnsw (val vector_l2_ops) WITH (ef_construction = 3);
|
||||
ERROR: value 3 out of bounds for option "ef_construction"
|
||||
DETAIL: Valid values are between "4" and "1000".
|
||||
CREATE INDEX ON t USING hnsw (val vector_l2_ops) WITH (ef_construction = 1001);
|
||||
ERROR: value 1001 out of bounds for option "ef_construction"
|
||||
DETAIL: Valid values are between "4" and "1000".
|
||||
CREATE INDEX ON t USING hnsw (val vector_l2_ops) WITH (m = 16, ef_construction = 31);
|
||||
ERROR: ef_construction must be greater than or equal to 2 * m
|
||||
SHOW hnsw.ef_search;
|
||||
hnsw.ef_search
|
||||
----------------
|
||||
40
|
||||
(1 row)
|
||||
|
||||
SET hnsw.ef_search = 0;
|
||||
ERROR: 0 is outside the valid range for parameter "hnsw.ef_search" (1 .. 1000)
|
||||
SET hnsw.ef_search = 1001;
|
||||
ERROR: 1001 is outside the valid range for parameter "hnsw.ef_search" (1 .. 1000)
|
||||
DROP TABLE t;
|
||||
26
test/expected/hnsw_sparsevec_cosine.out
Normal file
26
test/expected/hnsw_sparsevec_cosine.out
Normal file
@@ -0,0 +1,26 @@
|
||||
SET enable_seqscan = off;
|
||||
CREATE TABLE t (val sparsevec(3));
|
||||
INSERT INTO t (val) VALUES ('{}/3'), ('{0:1,1:2,2:3}/3'), ('{0:1,1:1,2:1}/3'), (NULL);
|
||||
CREATE INDEX ON t USING hnsw (val sparsevec_cosine_ops);
|
||||
INSERT INTO t (val) VALUES ('{0:1,1:2,2:4}/3');
|
||||
SELECT * FROM t ORDER BY val <=> '{0:3,1:3,2:3}/3';
|
||||
val
|
||||
-----------------
|
||||
{0:1,1:1,2:1}/3
|
||||
{0:1,1:2,2:3}/3
|
||||
{0:1,1:2,2:4}/3
|
||||
(3 rows)
|
||||
|
||||
SELECT COUNT(*) FROM (SELECT * FROM t ORDER BY val <=> '{}/3') t2;
|
||||
count
|
||||
-------
|
||||
3
|
||||
(1 row)
|
||||
|
||||
SELECT COUNT(*) FROM (SELECT * FROM t ORDER BY val <=> (SELECT NULL::sparsevec)) t2;
|
||||
count
|
||||
-------
|
||||
3
|
||||
(1 row)
|
||||
|
||||
DROP TABLE t;
|
||||
21
test/expected/hnsw_sparsevec_ip.out
Normal file
21
test/expected/hnsw_sparsevec_ip.out
Normal file
@@ -0,0 +1,21 @@
|
||||
SET enable_seqscan = off;
|
||||
CREATE TABLE t (val sparsevec(3));
|
||||
INSERT INTO t (val) VALUES ('{}/3'), ('{0:1,1:2,2:3}/3'), ('{0:1,1:1,2:1}/3'), (NULL);
|
||||
CREATE INDEX ON t USING hnsw (val sparsevec_ip_ops);
|
||||
INSERT INTO t (val) VALUES ('{0:1,1:2,2:4}/3');
|
||||
SELECT * FROM t ORDER BY val <#> '{0:3,1:3,2:3}/3';
|
||||
val
|
||||
-----------------
|
||||
{0:1,1:2,2:4}/3
|
||||
{0:1,1:2,2:3}/3
|
||||
{0:1,1:1,2:1}/3
|
||||
{}/3
|
||||
(4 rows)
|
||||
|
||||
SELECT COUNT(*) FROM (SELECT * FROM t ORDER BY val <#> (SELECT NULL::sparsevec)) t2;
|
||||
count
|
||||
-------
|
||||
4
|
||||
(1 row)
|
||||
|
||||
DROP TABLE t;
|
||||
43
test/expected/hnsw_sparsevec_l2.out
Normal file
43
test/expected/hnsw_sparsevec_l2.out
Normal file
@@ -0,0 +1,43 @@
|
||||
SET enable_seqscan = off;
|
||||
CREATE TABLE t (val sparsevec(3));
|
||||
INSERT INTO t (val) VALUES ('{}/3'), ('{0:1,1:2,2:3}/3'), ('{0:1,1:1,2:1}/3'), (NULL);
|
||||
CREATE INDEX ON t USING hnsw (val sparsevec_l2_ops);
|
||||
INSERT INTO t (val) VALUES ('{0:1,1:2,2:4}/3');
|
||||
SELECT * FROM t ORDER BY val <-> '{0:3,1:3,2:3}/3';
|
||||
val
|
||||
-----------------
|
||||
{0:1,1:2,2:3}/3
|
||||
{0:1,1:2,2:4}/3
|
||||
{0:1,1:1,2:1}/3
|
||||
{}/3
|
||||
(4 rows)
|
||||
|
||||
SELECT COUNT(*) FROM (SELECT * FROM t ORDER BY val <-> (SELECT NULL::sparsevec)) t2;
|
||||
count
|
||||
-------
|
||||
4
|
||||
(1 row)
|
||||
|
||||
SELECT COUNT(*) FROM t;
|
||||
count
|
||||
-------
|
||||
5
|
||||
(1 row)
|
||||
|
||||
TRUNCATE t;
|
||||
SELECT * FROM t ORDER BY val <-> '{0:3,1:3,2:3}/3';
|
||||
val
|
||||
-----
|
||||
(0 rows)
|
||||
|
||||
DROP TABLE t;
|
||||
-- TODO move
|
||||
CREATE TABLE t (val sparsevec(1001));
|
||||
INSERT INTO t (val) VALUES (array_fill(1, ARRAY[1001])::vector::sparsevec);
|
||||
CREATE INDEX ON t USING hnsw (val sparsevec_l2_ops);
|
||||
ERROR: sparsevec cannot have more than 1000 non-zero elements for hnsw index
|
||||
TRUNCATE t;
|
||||
CREATE INDEX ON t USING hnsw (val sparsevec_l2_ops);
|
||||
INSERT INTO t (val) VALUES (array_fill(1, ARRAY[1001])::vector::sparsevec);
|
||||
ERROR: sparsevec cannot have more than 1000 non-zero elements for hnsw index
|
||||
DROP TABLE t;
|
||||
13
test/expected/hnsw_unlogged.out
Normal file
13
test/expected/hnsw_unlogged.out
Normal file
@@ -0,0 +1,13 @@
|
||||
SET enable_seqscan = off;
|
||||
CREATE UNLOGGED TABLE t (val vector(3));
|
||||
INSERT INTO t (val) VALUES ('[0,0,0]'), ('[1,2,3]'), ('[1,1,1]'), (NULL);
|
||||
CREATE INDEX ON t USING hnsw (val vector_l2_ops);
|
||||
SELECT * FROM t ORDER BY val <-> '[3,3,3]';
|
||||
val
|
||||
---------
|
||||
[1,2,3]
|
||||
[1,1,1]
|
||||
[0,0,0]
|
||||
(3 rows)
|
||||
|
||||
DROP TABLE t;
|
||||
26
test/expected/hnsw_vector_cosine.out
Normal file
26
test/expected/hnsw_vector_cosine.out
Normal file
@@ -0,0 +1,26 @@
|
||||
SET enable_seqscan = off;
|
||||
CREATE TABLE t (val vector(3));
|
||||
INSERT INTO t (val) VALUES ('[0,0,0]'), ('[1,2,3]'), ('[1,1,1]'), (NULL);
|
||||
CREATE INDEX ON t USING hnsw (val vector_cosine_ops);
|
||||
INSERT INTO t (val) VALUES ('[1,2,4]');
|
||||
SELECT * FROM t ORDER BY val <=> '[3,3,3]';
|
||||
val
|
||||
---------
|
||||
[1,1,1]
|
||||
[1,2,3]
|
||||
[1,2,4]
|
||||
(3 rows)
|
||||
|
||||
SELECT COUNT(*) FROM (SELECT * FROM t ORDER BY val <=> '[0,0,0]') t2;
|
||||
count
|
||||
-------
|
||||
3
|
||||
(1 row)
|
||||
|
||||
SELECT COUNT(*) FROM (SELECT * FROM t ORDER BY val <=> (SELECT NULL::vector)) t2;
|
||||
count
|
||||
-------
|
||||
3
|
||||
(1 row)
|
||||
|
||||
DROP TABLE t;
|
||||
21
test/expected/hnsw_vector_ip.out
Normal file
21
test/expected/hnsw_vector_ip.out
Normal file
@@ -0,0 +1,21 @@
|
||||
SET enable_seqscan = off;
|
||||
CREATE TABLE t (val vector(3));
|
||||
INSERT INTO t (val) VALUES ('[0,0,0]'), ('[1,2,3]'), ('[1,1,1]'), (NULL);
|
||||
CREATE INDEX ON t USING hnsw (val vector_ip_ops);
|
||||
INSERT INTO t (val) VALUES ('[1,2,4]');
|
||||
SELECT * FROM t ORDER BY val <#> '[3,3,3]';
|
||||
val
|
||||
---------
|
||||
[1,2,4]
|
||||
[1,2,3]
|
||||
[1,1,1]
|
||||
[0,0,0]
|
||||
(4 rows)
|
||||
|
||||
SELECT COUNT(*) FROM (SELECT * FROM t ORDER BY val <#> (SELECT NULL::vector)) t2;
|
||||
count
|
||||
-------
|
||||
4
|
||||
(1 row)
|
||||
|
||||
DROP TABLE t;
|
||||
@@ -1,7 +1,7 @@
|
||||
SET enable_seqscan = off;
|
||||
CREATE TABLE t (val vector(3));
|
||||
INSERT INTO t (val) VALUES ('[0,0,0]'), ('[1,2,3]'), ('[1,1,1]'), (NULL);
|
||||
CREATE INDEX ON t USING ivfflat (val) WITH (lists = 1);
|
||||
CREATE INDEX ON t USING hnsw (val vector_l2_ops);
|
||||
INSERT INTO t (val) VALUES ('[1,2,4]');
|
||||
SELECT * FROM t ORDER BY val <-> '[3,3,3]';
|
||||
val
|
||||
@@ -12,10 +12,11 @@ SELECT * FROM t ORDER BY val <-> '[3,3,3]';
|
||||
[0,0,0]
|
||||
(4 rows)
|
||||
|
||||
SELECT * FROM t ORDER BY val <-> (SELECT NULL::vector);
|
||||
val
|
||||
-----
|
||||
(0 rows)
|
||||
SELECT COUNT(*) FROM (SELECT * FROM t ORDER BY val <-> (SELECT NULL::vector)) t2;
|
||||
count
|
||||
-------
|
||||
4
|
||||
(1 row)
|
||||
|
||||
SELECT COUNT(*) FROM t;
|
||||
count
|
||||
@@ -23,4 +24,10 @@ SELECT COUNT(*) FROM t;
|
||||
5
|
||||
(1 row)
|
||||
|
||||
TRUNCATE t;
|
||||
SELECT * FROM t ORDER BY val <-> '[3,3,3]';
|
||||
val
|
||||
-----
|
||||
(0 rows)
|
||||
|
||||
DROP TABLE t;
|
||||
@@ -1,9 +1,8 @@
|
||||
SET enable_seqscan = off;
|
||||
CREATE TABLE t (val vector(3));
|
||||
CREATE INDEX ON t USING ivfflat (val) WITH (lists = 0);
|
||||
CREATE INDEX ON t USING ivfflat (val vector_l2_ops) WITH (lists = 0);
|
||||
ERROR: value 0 out of bounds for option "lists"
|
||||
DETAIL: Valid values are between "1" and "32768".
|
||||
CREATE INDEX ON t USING ivfflat (val) WITH (lists = 32769);
|
||||
CREATE INDEX ON t USING ivfflat (val vector_l2_ops) WITH (lists = 32769);
|
||||
ERROR: value 32769 out of bounds for option "lists"
|
||||
DETAIL: Valid values are between "1" and "32768".
|
||||
SHOW ivfflat.probes;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
SET enable_seqscan = off;
|
||||
CREATE UNLOGGED TABLE t (val vector(3));
|
||||
INSERT INTO t (val) VALUES ('[0,0,0]'), ('[1,2,3]'), ('[1,1,1]'), (NULL);
|
||||
CREATE INDEX ON t USING ivfflat (val) WITH (lists = 1);
|
||||
CREATE INDEX ON t USING ivfflat (val vector_l2_ops) WITH (lists = 1);
|
||||
SELECT * FROM t ORDER BY val <-> '[3,3,3]';
|
||||
val
|
||||
---------
|
||||
|
||||
@@ -11,9 +11,16 @@ SELECT * FROM t ORDER BY val <=> '[3,3,3]';
|
||||
[1,2,4]
|
||||
(3 rows)
|
||||
|
||||
SELECT * FROM t ORDER BY val <=> (SELECT NULL::vector);
|
||||
val
|
||||
-----
|
||||
(0 rows)
|
||||
SELECT COUNT(*) FROM (SELECT * FROM t ORDER BY val <=> '[0,0,0]') t2;
|
||||
count
|
||||
-------
|
||||
3
|
||||
(1 row)
|
||||
|
||||
SELECT COUNT(*) FROM (SELECT * FROM t ORDER BY val <=> (SELECT NULL::vector)) t2;
|
||||
count
|
||||
-------
|
||||
3
|
||||
(1 row)
|
||||
|
||||
DROP TABLE t;
|
||||
@@ -12,9 +12,10 @@ SELECT * FROM t ORDER BY val <#> '[3,3,3]';
|
||||
[0,0,0]
|
||||
(4 rows)
|
||||
|
||||
SELECT * FROM t ORDER BY val <#> (SELECT NULL::vector);
|
||||
val
|
||||
-----
|
||||
(0 rows)
|
||||
SELECT COUNT(*) FROM (SELECT * FROM t ORDER BY val <#> (SELECT NULL::vector)) t2;
|
||||
count
|
||||
-------
|
||||
4
|
||||
(1 row)
|
||||
|
||||
DROP TABLE t;
|
||||
39
test/expected/ivfflat_vector_l2.out
Normal file
39
test/expected/ivfflat_vector_l2.out
Normal file
@@ -0,0 +1,39 @@
|
||||
SET enable_seqscan = off;
|
||||
CREATE TABLE t (val vector(3));
|
||||
INSERT INTO t (val) VALUES ('[0,0,0]'), ('[1,2,3]'), ('[1,1,1]'), (NULL);
|
||||
CREATE INDEX ON t USING ivfflat (val vector_l2_ops) WITH (lists = 1);
|
||||
INSERT INTO t (val) VALUES ('[1,2,4]');
|
||||
SELECT * FROM t ORDER BY val <-> '[3,3,3]';
|
||||
val
|
||||
---------
|
||||
[1,2,3]
|
||||
[1,2,4]
|
||||
[1,1,1]
|
||||
[0,0,0]
|
||||
(4 rows)
|
||||
|
||||
SELECT * FROM t ORDER BY val <-> (SELECT NULL::vector);
|
||||
val
|
||||
---------
|
||||
[0,0,0]
|
||||
[1,1,1]
|
||||
[1,2,3]
|
||||
[1,2,4]
|
||||
(4 rows)
|
||||
|
||||
SELECT COUNT(*) FROM t;
|
||||
count
|
||||
-------
|
||||
5
|
||||
(1 row)
|
||||
|
||||
TRUNCATE t;
|
||||
NOTICE: ivfflat index created with little data
|
||||
DETAIL: This will cause low recall.
|
||||
HINT: Drop the index until the table has more data.
|
||||
SELECT * FROM t ORDER BY val <-> '[3,3,3]';
|
||||
val
|
||||
-----
|
||||
(0 rows)
|
||||
|
||||
DROP TABLE t;
|
||||
92
test/expected/sparsevec_functions.out
Normal file
92
test/expected/sparsevec_functions.out
Normal file
@@ -0,0 +1,92 @@
|
||||
SELECT l2_distance('{}/2'::sparsevec, '{0:3,1:4}/2');
|
||||
l2_distance
|
||||
-------------
|
||||
5
|
||||
(1 row)
|
||||
|
||||
SELECT l2_distance('{}/2'::sparsevec, '{1:1}/2');
|
||||
l2_distance
|
||||
-------------
|
||||
1
|
||||
(1 row)
|
||||
|
||||
SELECT '{}/2'::sparsevec <-> '{0:3,1:4}/2';
|
||||
?column?
|
||||
----------
|
||||
5
|
||||
(1 row)
|
||||
|
||||
SELECT inner_product('{0:1,1:2}/2'::sparsevec, '{0:2,1:4}/2');
|
||||
inner_product
|
||||
---------------
|
||||
10
|
||||
(1 row)
|
||||
|
||||
SELECT sparsevec_negative_inner_product('{0:1,1:2}/2', '{0:2,1:4}/2');
|
||||
sparsevec_negative_inner_product
|
||||
----------------------------------
|
||||
-10
|
||||
(1 row)
|
||||
|
||||
SELECT cosine_distance('{0:1,1:2}/2'::sparsevec, '{0:2,1:4}/2');
|
||||
cosine_distance
|
||||
-----------------
|
||||
0
|
||||
(1 row)
|
||||
|
||||
SELECT cosine_distance('{0:1,1:2}/2'::sparsevec, '{}/2');
|
||||
cosine_distance
|
||||
-----------------
|
||||
NaN
|
||||
(1 row)
|
||||
|
||||
SELECT cosine_distance('{0:1,1:1}/2'::sparsevec, '{0:-1,1:-1}/2');
|
||||
cosine_distance
|
||||
-----------------
|
||||
2
|
||||
(1 row)
|
||||
|
||||
SELECT cosine_distance('{0:1}/2'::sparsevec, '{1:2}/2');
|
||||
cosine_distance
|
||||
-----------------
|
||||
1
|
||||
(1 row)
|
||||
|
||||
SELECT cosine_distance('{}/1'::sparsevec, '{}/1');
|
||||
cosine_distance
|
||||
-----------------
|
||||
NaN
|
||||
(1 row)
|
||||
|
||||
SELECT cosine_distance('{0:1}/2'::sparsevec, '{0:1}/3');
|
||||
ERROR: different sparsevec dimensions 2 and 3
|
||||
SELECT subvector('{0:1,1:2,2:3,3:4,4:5}/5'::sparsevec, 1, 3);
|
||||
subvector
|
||||
-----------------
|
||||
{0:1,1:2,2:3}/3
|
||||
(1 row)
|
||||
|
||||
SELECT subvector('{0:1,1:2,2:3,3:4,4:5}/5'::sparsevec, 3, 2);
|
||||
subvector
|
||||
-------------
|
||||
{2:3,3:4}/2
|
||||
(1 row)
|
||||
|
||||
SELECT subvector('{0:1,1:2,2:3,3:4,4:5}/5'::sparsevec, -1, 3);
|
||||
subvector
|
||||
-----------
|
||||
{0:1}/1
|
||||
(1 row)
|
||||
|
||||
SELECT subvector('{0:1,1:2,2:3,3:4,4:5}/5'::sparsevec, 3, 9);
|
||||
subvector
|
||||
-----------------
|
||||
{2:3,3:4,4:5}/3
|
||||
(1 row)
|
||||
|
||||
SELECT subvector('{0:1,1:2,2:3,3:4,4:5}/5'::sparsevec, 1, 0);
|
||||
ERROR: sparsevec must have at least 1 dimension
|
||||
SELECT subvector('{0:1,1:2,2:3,3:4,4:5}/5'::sparsevec, 3, -1);
|
||||
ERROR: sparsevec must have at least 1 dimension
|
||||
SELECT subvector('{0:1,1:2,2:3,3:4,4:5}/5'::sparsevec, -1, 2);
|
||||
ERROR: sparsevec must have at least 1 dimension
|
||||
62
test/expected/sparsevec_input.out
Normal file
62
test/expected/sparsevec_input.out
Normal file
@@ -0,0 +1,62 @@
|
||||
SELECT '{0:1.5,2:3.5}/5'::sparsevec;
|
||||
sparsevec
|
||||
-----------------
|
||||
{0:1.5,2:3.5}/5
|
||||
(1 row)
|
||||
|
||||
SELECT '{0:1.5,2:3.5}/5'::sparsevec::vector;
|
||||
vector
|
||||
-----------------
|
||||
[1.5,0,3.5,0,0]
|
||||
(1 row)
|
||||
|
||||
SELECT '{0:1.5,2:3.5}/5'::sparsevec::vector(5);
|
||||
vector
|
||||
-----------------
|
||||
[1.5,0,3.5,0,0]
|
||||
(1 row)
|
||||
|
||||
SELECT '{0:1.5,2:3.5}/5'::sparsevec::vector(4);
|
||||
ERROR: expected 4 dimensions, not 5
|
||||
SELECT '[0,1.5,0,3.5,0]'::vector::sparsevec;
|
||||
sparsevec
|
||||
-----------------
|
||||
{1:1.5,3:3.5}/5
|
||||
(1 row)
|
||||
|
||||
SELECT '{0:0,1:1,2:0}/3'::sparsevec;
|
||||
sparsevec
|
||||
-----------
|
||||
{1:1}/3
|
||||
(1 row)
|
||||
|
||||
SELECT '{1:1,0:1}/2'::sparsevec;
|
||||
ERROR: indexes must be in ascending order
|
||||
LINE 1: SELECT '{1:1,0:1}/2'::sparsevec;
|
||||
^
|
||||
SELECT '{}/5'::sparsevec;
|
||||
sparsevec
|
||||
-----------
|
||||
{}/5
|
||||
(1 row)
|
||||
|
||||
SELECT '{}/-1'::sparsevec;
|
||||
ERROR: sparsevec must have at least 1 dimension
|
||||
LINE 1: SELECT '{}/-1'::sparsevec;
|
||||
^
|
||||
SELECT '{}/100001'::sparsevec;
|
||||
ERROR: sparsevec cannot have more than 100000 dimensions
|
||||
LINE 1: SELECT '{}/100001'::sparsevec;
|
||||
^
|
||||
SELECT '{}/16001'::sparsevec::vector;
|
||||
ERROR: vector cannot have more than 16000 dimensions
|
||||
SELECT '{-1:1}/1'::sparsevec;
|
||||
ERROR: index "-1" is out of range for type sparsevec
|
||||
LINE 1: SELECT '{-1:1}/1'::sparsevec;
|
||||
^
|
||||
SELECT '{1:1}/1'::sparsevec;
|
||||
ERROR: index must be less than dimensions
|
||||
LINE 1: SELECT '{1:1}/1'::sparsevec;
|
||||
^
|
||||
SELECT '{}/1'::sparsevec(2);
|
||||
ERROR: expected 2 dimensions, not 1
|
||||
366
test/expected/vector_functions.out
Normal file
366
test/expected/vector_functions.out
Normal file
@@ -0,0 +1,366 @@
|
||||
SELECT '[1,2,3]'::vector + '[4,5,6]';
|
||||
?column?
|
||||
----------
|
||||
[5,7,9]
|
||||
(1 row)
|
||||
|
||||
SELECT '[3e38]'::vector + '[3e38]';
|
||||
ERROR: value out of range: overflow
|
||||
SELECT '[1,2,3]'::vector - '[4,5,6]';
|
||||
?column?
|
||||
------------
|
||||
[-3,-3,-3]
|
||||
(1 row)
|
||||
|
||||
SELECT '[-3e38]'::vector - '[3e38]';
|
||||
ERROR: value out of range: overflow
|
||||
SELECT '[1,2,3]'::vector * '[4,5,6]';
|
||||
?column?
|
||||
-----------
|
||||
[4,10,18]
|
||||
(1 row)
|
||||
|
||||
SELECT '[1e37]'::vector * '[1e37]';
|
||||
ERROR: value out of range: overflow
|
||||
SELECT '[1e-37]'::vector * '[1e-37]';
|
||||
ERROR: value out of range: underflow
|
||||
SELECT '[1,2,3]'::vector < '[1,2,3]';
|
||||
?column?
|
||||
----------
|
||||
f
|
||||
(1 row)
|
||||
|
||||
SELECT '[1,2,3]'::vector < '[1,2]';
|
||||
?column?
|
||||
----------
|
||||
f
|
||||
(1 row)
|
||||
|
||||
SELECT '[1,2,3]'::vector <= '[1,2,3]';
|
||||
?column?
|
||||
----------
|
||||
t
|
||||
(1 row)
|
||||
|
||||
SELECT '[1,2,3]'::vector <= '[1,2]';
|
||||
?column?
|
||||
----------
|
||||
f
|
||||
(1 row)
|
||||
|
||||
SELECT '[1,2,3]'::vector = '[1,2,3]';
|
||||
?column?
|
||||
----------
|
||||
t
|
||||
(1 row)
|
||||
|
||||
SELECT '[1,2,3]'::vector = '[1,2]';
|
||||
?column?
|
||||
----------
|
||||
f
|
||||
(1 row)
|
||||
|
||||
SELECT '[1,2,3]'::vector != '[1,2,3]';
|
||||
?column?
|
||||
----------
|
||||
f
|
||||
(1 row)
|
||||
|
||||
SELECT '[1,2,3]'::vector != '[1,2]';
|
||||
?column?
|
||||
----------
|
||||
t
|
||||
(1 row)
|
||||
|
||||
SELECT '[1,2,3]'::vector >= '[1,2,3]';
|
||||
?column?
|
||||
----------
|
||||
t
|
||||
(1 row)
|
||||
|
||||
SELECT '[1,2,3]'::vector >= '[1,2]';
|
||||
?column?
|
||||
----------
|
||||
t
|
||||
(1 row)
|
||||
|
||||
SELECT '[1,2,3]'::vector > '[1,2,3]';
|
||||
?column?
|
||||
----------
|
||||
f
|
||||
(1 row)
|
||||
|
||||
SELECT '[1,2,3]'::vector > '[1,2]';
|
||||
?column?
|
||||
----------
|
||||
t
|
||||
(1 row)
|
||||
|
||||
SELECT vector_cmp('[1,2,3]', '[1,2,3]');
|
||||
vector_cmp
|
||||
------------
|
||||
0
|
||||
(1 row)
|
||||
|
||||
SELECT vector_cmp('[1,2,3]', '[0,0,0]');
|
||||
vector_cmp
|
||||
------------
|
||||
1
|
||||
(1 row)
|
||||
|
||||
SELECT vector_cmp('[0,0,0]', '[1,2,3]');
|
||||
vector_cmp
|
||||
------------
|
||||
-1
|
||||
(1 row)
|
||||
|
||||
SELECT vector_cmp('[1,2]', '[1,2,3]');
|
||||
vector_cmp
|
||||
------------
|
||||
-1
|
||||
(1 row)
|
||||
|
||||
SELECT vector_cmp('[1,2,3]', '[1,2]');
|
||||
vector_cmp
|
||||
------------
|
||||
1
|
||||
(1 row)
|
||||
|
||||
SELECT vector_cmp('[1,2]', '[2,3,4]');
|
||||
vector_cmp
|
||||
------------
|
||||
-1
|
||||
(1 row)
|
||||
|
||||
SELECT vector_cmp('[2,3]', '[1,2,3]');
|
||||
vector_cmp
|
||||
------------
|
||||
1
|
||||
(1 row)
|
||||
|
||||
SELECT vector_dims('[1,2,3]');
|
||||
vector_dims
|
||||
-------------
|
||||
3
|
||||
(1 row)
|
||||
|
||||
SELECT round(vector_norm('[1,1]')::numeric, 5);
|
||||
round
|
||||
---------
|
||||
1.41421
|
||||
(1 row)
|
||||
|
||||
SELECT vector_norm('[3,4]');
|
||||
vector_norm
|
||||
-------------
|
||||
5
|
||||
(1 row)
|
||||
|
||||
SELECT vector_norm('[0,1]');
|
||||
vector_norm
|
||||
-------------
|
||||
1
|
||||
(1 row)
|
||||
|
||||
SELECT vector_norm('[3e37,4e37]')::real;
|
||||
vector_norm
|
||||
-------------
|
||||
5e+37
|
||||
(1 row)
|
||||
|
||||
SELECT l2_distance('[0,0]'::vector, '[3,4]');
|
||||
l2_distance
|
||||
-------------
|
||||
5
|
||||
(1 row)
|
||||
|
||||
SELECT l2_distance('[0,0]'::vector, '[0,1]');
|
||||
l2_distance
|
||||
-------------
|
||||
1
|
||||
(1 row)
|
||||
|
||||
SELECT l2_distance('[1,2]'::vector, '[3]');
|
||||
ERROR: different vector dimensions 2 and 1
|
||||
SELECT l2_distance('[3e38]'::vector, '[-3e38]');
|
||||
l2_distance
|
||||
-------------
|
||||
Infinity
|
||||
(1 row)
|
||||
|
||||
SELECT inner_product('[1,2]'::vector, '[3,4]');
|
||||
inner_product
|
||||
---------------
|
||||
11
|
||||
(1 row)
|
||||
|
||||
SELECT inner_product('[1,2]'::vector, '[3]');
|
||||
ERROR: different vector dimensions 2 and 1
|
||||
SELECT inner_product('[3e38]'::vector, '[3e38]');
|
||||
inner_product
|
||||
---------------
|
||||
Infinity
|
||||
(1 row)
|
||||
|
||||
SELECT cosine_distance('[1,2]'::vector, '[2,4]');
|
||||
cosine_distance
|
||||
-----------------
|
||||
0
|
||||
(1 row)
|
||||
|
||||
SELECT cosine_distance('[1,2]'::vector, '[0,0]');
|
||||
cosine_distance
|
||||
-----------------
|
||||
NaN
|
||||
(1 row)
|
||||
|
||||
SELECT cosine_distance('[1,1]'::vector, '[1,1]');
|
||||
cosine_distance
|
||||
-----------------
|
||||
0
|
||||
(1 row)
|
||||
|
||||
SELECT cosine_distance('[1,0]'::vector, '[0,2]');
|
||||
cosine_distance
|
||||
-----------------
|
||||
1
|
||||
(1 row)
|
||||
|
||||
SELECT cosine_distance('[1,1]'::vector, '[-1,-1]');
|
||||
cosine_distance
|
||||
-----------------
|
||||
2
|
||||
(1 row)
|
||||
|
||||
SELECT cosine_distance('[1,2]'::vector, '[3]');
|
||||
ERROR: different vector dimensions 2 and 1
|
||||
SELECT cosine_distance('[1,1]'::vector, '[1.1,1.1]');
|
||||
cosine_distance
|
||||
-----------------
|
||||
0
|
||||
(1 row)
|
||||
|
||||
SELECT cosine_distance('[1,1]'::vector, '[-1.1,-1.1]');
|
||||
cosine_distance
|
||||
-----------------
|
||||
2
|
||||
(1 row)
|
||||
|
||||
SELECT cosine_distance('[3e38]'::vector, '[3e38]');
|
||||
cosine_distance
|
||||
-----------------
|
||||
NaN
|
||||
(1 row)
|
||||
|
||||
SELECT l1_distance('[0,0]'::vector, '[3,4]');
|
||||
l1_distance
|
||||
-------------
|
||||
7
|
||||
(1 row)
|
||||
|
||||
SELECT l1_distance('[0,0]'::vector, '[0,1]');
|
||||
l1_distance
|
||||
-------------
|
||||
1
|
||||
(1 row)
|
||||
|
||||
SELECT l1_distance('[1,2]'::vector, '[3]');
|
||||
ERROR: different vector dimensions 2 and 1
|
||||
SELECT l1_distance('[3e38]'::vector, '[-3e38]');
|
||||
l1_distance
|
||||
-------------
|
||||
Infinity
|
||||
(1 row)
|
||||
|
||||
SELECT quantize_binary('[1,0,-1]'::vector);
|
||||
quantize_binary
|
||||
-----------------
|
||||
100
|
||||
(1 row)
|
||||
|
||||
SELECT quantize_binary('[0,0.1,-0.2,-0.3,0.4,0.5,0.6,-0.7,0.8,-0.9,1]'::vector);
|
||||
quantize_binary
|
||||
-----------------
|
||||
01001110101
|
||||
(1 row)
|
||||
|
||||
SELECT subvector('[1,2,3,4,5]'::vector, 1, 3);
|
||||
subvector
|
||||
-----------
|
||||
[1,2,3]
|
||||
(1 row)
|
||||
|
||||
SELECT subvector('[1,2,3,4,5]'::vector, 3, 2);
|
||||
subvector
|
||||
-----------
|
||||
[3,4]
|
||||
(1 row)
|
||||
|
||||
SELECT subvector('[1,2,3,4,5]'::vector, -1, 3);
|
||||
subvector
|
||||
-----------
|
||||
[1]
|
||||
(1 row)
|
||||
|
||||
SELECT subvector('[1,2,3,4,5]'::vector, 3, 9);
|
||||
subvector
|
||||
-----------
|
||||
[3,4,5]
|
||||
(1 row)
|
||||
|
||||
SELECT subvector('[1,2,3,4,5]'::vector, 1, 0);
|
||||
ERROR: vector must have at least 1 dimension
|
||||
SELECT subvector('[1,2,3,4,5]'::vector, 3, -1);
|
||||
ERROR: vector must have at least 1 dimension
|
||||
SELECT subvector('[1,2,3,4,5]'::vector, -1, 2);
|
||||
ERROR: vector must have at least 1 dimension
|
||||
SELECT avg(v) FROM unnest(ARRAY['[1,2,3]'::vector, '[3,5,7]']) v;
|
||||
avg
|
||||
-----------
|
||||
[2,3.5,5]
|
||||
(1 row)
|
||||
|
||||
SELECT avg(v) FROM unnest(ARRAY['[1,2,3]'::vector, '[3,5,7]', NULL]) v;
|
||||
avg
|
||||
-----------
|
||||
[2,3.5,5]
|
||||
(1 row)
|
||||
|
||||
SELECT avg(v) FROM unnest(ARRAY[]::vector[]) v;
|
||||
avg
|
||||
-----
|
||||
|
||||
(1 row)
|
||||
|
||||
SELECT avg(v) FROM unnest(ARRAY['[1,2]'::vector, '[3]']) v;
|
||||
ERROR: expected 2 dimensions, not 1
|
||||
SELECT avg(v) FROM unnest(ARRAY['[3e38]'::vector, '[3e38]']) v;
|
||||
avg
|
||||
---------
|
||||
[3e+38]
|
||||
(1 row)
|
||||
|
||||
SELECT vector_avg(array_agg(n)) FROM generate_series(1, 16002) n;
|
||||
ERROR: vector cannot have more than 16000 dimensions
|
||||
SELECT sum(v) FROM unnest(ARRAY['[1,2,3]'::vector, '[3,5,7]']) v;
|
||||
sum
|
||||
----------
|
||||
[4,7,10]
|
||||
(1 row)
|
||||
|
||||
SELECT sum(v) FROM unnest(ARRAY['[1,2,3]'::vector, '[3,5,7]', NULL]) v;
|
||||
sum
|
||||
----------
|
||||
[4,7,10]
|
||||
(1 row)
|
||||
|
||||
SELECT sum(v) FROM unnest(ARRAY[]::vector[]) v;
|
||||
sum
|
||||
-----
|
||||
|
||||
(1 row)
|
||||
|
||||
SELECT sum(v) FROM unnest(ARRAY['[1,2]'::vector, '[3]']) v;
|
||||
ERROR: different vector dimensions 2 and 1
|
||||
SELECT sum(v) FROM unnest(ARRAY['[3e38]'::vector, '[3e38]']) v;
|
||||
ERROR: value out of range: overflow
|
||||
@@ -63,9 +63,25 @@ SELECT '[1.5e-38,-1.5e-38]'::vector;
|
||||
(1 row)
|
||||
|
||||
SELECT '[4e38,1]'::vector;
|
||||
ERROR: infinite value not allowed in vector
|
||||
ERROR: "4e38" is out of range for type vector
|
||||
LINE 1: SELECT '[4e38,1]'::vector;
|
||||
^
|
||||
SELECT '[-4e38,1]'::vector;
|
||||
ERROR: "-4e38" is out of range for type vector
|
||||
LINE 1: SELECT '[-4e38,1]'::vector;
|
||||
^
|
||||
SELECT '[1e-46,1]'::vector;
|
||||
vector
|
||||
--------
|
||||
[0,1]
|
||||
(1 row)
|
||||
|
||||
SELECT '[-1e-46,1]'::vector;
|
||||
vector
|
||||
--------
|
||||
[-0,1]
|
||||
(1 row)
|
||||
|
||||
SELECT '[1,2,3'::vector;
|
||||
ERROR: malformed vector literal: "[1,2,3"
|
||||
LINE 1: SELECT '[1,2,3'::vector;
|
||||
@@ -81,6 +97,11 @@ ERROR: malformed vector literal: "1,2,3"
|
||||
LINE 1: SELECT '1,2,3'::vector;
|
||||
^
|
||||
DETAIL: Vector contents must start with "[".
|
||||
SELECT ''::vector;
|
||||
ERROR: malformed vector literal: ""
|
||||
LINE 1: SELECT ''::vector;
|
||||
^
|
||||
DETAIL: Vector contents must start with "[".
|
||||
SELECT '['::vector;
|
||||
ERROR: malformed vector literal: "["
|
||||
LINE 1: SELECT '['::vector;
|
||||
@@ -111,8 +132,30 @@ SELECT '[1, ,3]'::vector;
|
||||
ERROR: invalid input syntax for type vector: "[1, ,3]"
|
||||
LINE 1: SELECT '[1, ,3]'::vector;
|
||||
^
|
||||
SELECT '[1,2,3]'::vector(3);
|
||||
vector
|
||||
---------
|
||||
[1,2,3]
|
||||
(1 row)
|
||||
|
||||
SELECT '[1,2,3]'::vector(2);
|
||||
ERROR: expected 2 dimensions, not 3
|
||||
SELECT '[1,2,3]'::vector(3, 2);
|
||||
ERROR: invalid type modifier
|
||||
LINE 1: SELECT '[1,2,3]'::vector(3, 2);
|
||||
^
|
||||
SELECT '[1,2,3]'::vector('a');
|
||||
ERROR: invalid input syntax for type integer: "a"
|
||||
LINE 1: SELECT '[1,2,3]'::vector('a');
|
||||
^
|
||||
SELECT '[1,2,3]'::vector(0);
|
||||
ERROR: dimensions for type vector must be at least 1
|
||||
LINE 1: SELECT '[1,2,3]'::vector(0);
|
||||
^
|
||||
SELECT '[1,2,3]'::vector(16001);
|
||||
ERROR: dimensions for type vector cannot exceed 16000
|
||||
LINE 1: SELECT '[1,2,3]'::vector(16001);
|
||||
^
|
||||
SELECT unnest('{"[1,2,3]", "[4,5,6]"}'::vector[]);
|
||||
unnest
|
||||
---------
|
||||
13
test/sql/bit_functions.sql
Normal file
13
test/sql/bit_functions.sql
Normal file
@@ -0,0 +1,13 @@
|
||||
SELECT hamming_distance(B'111', B'111');
|
||||
SELECT hamming_distance(B'111', B'110');
|
||||
SELECT hamming_distance(B'111', B'100');
|
||||
SELECT hamming_distance(B'111', B'000');
|
||||
SELECT hamming_distance(B'111', B'00');
|
||||
|
||||
SELECT jaccard_distance(B'1111', B'1111');
|
||||
SELECT jaccard_distance(B'1111', B'1110');
|
||||
SELECT jaccard_distance(B'1111', B'1100');
|
||||
SELECT jaccard_distance(B'1111', B'1000');
|
||||
SELECT jaccard_distance(B'1111', B'0000');
|
||||
SELECT jaccard_distance(B'1100', B'1000');
|
||||
SELECT jaccard_distance(B'1111', B'000');
|
||||
@@ -8,7 +8,14 @@ SELECT '{NaN}'::real[]::vector;
|
||||
SELECT '{Infinity}'::real[]::vector;
|
||||
SELECT '{-Infinity}'::real[]::vector;
|
||||
SELECT '{}'::real[]::vector;
|
||||
SELECT '{{1}}'::real[]::vector;
|
||||
SELECT '[1,2,3]'::vector::real[];
|
||||
SELECT '[1,2,3]'::vector::halfvec;
|
||||
SELECT '[1,2,3]'::halfvec::vector;
|
||||
SELECT '[1,2,3]'::vector::halfvec(2);
|
||||
SELECT '[1,2,3]'::halfvec::vector(2);
|
||||
SELECT '[65520]'::vector::halfvec;
|
||||
SELECT '[1e-8]'::vector::halfvec;
|
||||
SELECT array_agg(n)::vector FROM generate_series(1, 16001) n;
|
||||
SELECT array_to_vector(array_agg(n), 16001, false) FROM generate_series(1, 16001) n;
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
CREATE TABLE t (val vector(3));
|
||||
INSERT INTO t (val) VALUES ('[0,0,0]'), ('[1,2,3]'), ('[1,1,1]'), (NULL);
|
||||
CREATE TABLE t (val vector(3), val2 halfvec(3));
|
||||
INSERT INTO t (val, val2) VALUES ('[0,0,0]', '[0,0,0]'), ('[1,2,3]', '[1,2,3]'), ('[1,1,1]', '[1,1,1]'), (NULL, NULL);
|
||||
|
||||
CREATE TABLE t2 (val vector(3));
|
||||
CREATE TABLE t2 (val vector(3), val2 halfvec(3));
|
||||
|
||||
\copy t TO 'results/data.bin' WITH (FORMAT binary)
|
||||
\copy t2 FROM 'results/data.bin' WITH (FORMAT binary)
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
SELECT '[1,2,3]'::vector + '[4,5,6]';
|
||||
SELECT '[3e38]'::vector + '[3e38]';
|
||||
SELECT '[1,2,3]'::vector - '[4,5,6]';
|
||||
SELECT '[-3e38]'::vector - '[3e38]';
|
||||
|
||||
SELECT vector_dims('[1,2,3]');
|
||||
|
||||
SELECT round(vector_norm('[1,1]')::numeric, 5);
|
||||
SELECT vector_norm('[3,4]');
|
||||
SELECT vector_norm('[0,1]');
|
||||
|
||||
SELECT l2_distance('[0,0]', '[3,4]');
|
||||
SELECT l2_distance('[0,0]', '[0,1]');
|
||||
SELECT l2_distance('[1,2]', '[3]');
|
||||
|
||||
SELECT inner_product('[1,2]', '[3,4]');
|
||||
SELECT inner_product('[1,2]', '[3]');
|
||||
|
||||
SELECT cosine_distance('[1,2]', '[2,4]');
|
||||
SELECT cosine_distance('[1,2]', '[0,0]');
|
||||
SELECT cosine_distance('[1,1]', '[1,1]');
|
||||
SELECT cosine_distance('[1,1]', '[-1,-1]');
|
||||
SELECT cosine_distance('[1,2]', '[3]');
|
||||
|
||||
SELECT avg(v) FROM unnest(ARRAY['[1,2,3]'::vector, '[3,5,7]']) v;
|
||||
SELECT avg(v) FROM unnest(ARRAY['[1,2,3]'::vector, '[3,5,7]', NULL]) v;
|
||||
SELECT avg(v) FROM unnest(ARRAY[]::vector[]) v;
|
||||
SELECT avg(v) FROM unnest(ARRAY['[1,2]'::vector, '[3]']) v;
|
||||
SELECT vector_avg(array_agg(n)) FROM generate_series(1, 16002) n;
|
||||
34
test/sql/halfvec_functions.sql
Normal file
34
test/sql/halfvec_functions.sql
Normal file
@@ -0,0 +1,34 @@
|
||||
SELECT l2_distance('[0,0]'::halfvec, '[3,4]');
|
||||
SELECT l2_distance('[0,0]'::halfvec, '[0,1]');
|
||||
SELECT l2_distance('[1,2]'::halfvec, '[3]');
|
||||
SELECT '[0,0]'::halfvec <-> '[3,4]';
|
||||
|
||||
SELECT inner_product('[1,2]'::halfvec, '[3,4]');
|
||||
SELECT inner_product('[1,2]'::halfvec, '[3]');
|
||||
SELECT inner_product('[65504]'::halfvec, '[65504]');
|
||||
SELECT '[1,2]'::halfvec <#> '[3,4]';
|
||||
|
||||
SELECT cosine_distance('[1,2]'::halfvec, '[2,4]');
|
||||
SELECT cosine_distance('[1,2]'::halfvec, '[0,0]');
|
||||
SELECT cosine_distance('[1,1]'::halfvec, '[1,1]');
|
||||
SELECT cosine_distance('[1,0]'::halfvec, '[0,2]');
|
||||
SELECT cosine_distance('[1,1]'::halfvec, '[-1,-1]');
|
||||
SELECT cosine_distance('[1,2]'::halfvec, '[3]');
|
||||
SELECT cosine_distance('[1,1]'::halfvec, '[1.1,1.1]');
|
||||
SELECT cosine_distance('[1,1]'::halfvec, '[-1.1,-1.1]');
|
||||
SELECT '[1,2]'::halfvec <=> '[2,4]';
|
||||
|
||||
SELECT l1_distance('[0,0]'::halfvec, '[3,4]');
|
||||
SELECT l1_distance('[0,0]'::halfvec, '[0,1]');
|
||||
SELECT l1_distance('[1,2]'::halfvec, '[3]');
|
||||
|
||||
SELECT quantize_binary('[1,0,-1]'::halfvec);
|
||||
SELECT quantize_binary('[0,0.1,-0.2,-0.3,0.4,0.5,0.6,-0.7,0.8,-0.9,1]'::halfvec);
|
||||
|
||||
SELECT subvector('[1,2,3,4,5]'::halfvec, 1, 3);
|
||||
SELECT subvector('[1,2,3,4,5]'::halfvec, 3, 2);
|
||||
SELECT subvector('[1,2,3,4,5]'::halfvec, -1, 3);
|
||||
SELECT subvector('[1,2,3,4,5]'::halfvec, 3, 9);
|
||||
SELECT subvector('[1,2,3,4,5]'::halfvec, 1, 0);
|
||||
SELECT subvector('[1,2,3,4,5]'::halfvec, 3, -1);
|
||||
SELECT subvector('[1,2,3,4,5]'::halfvec, -1, 2);
|
||||
34
test/sql/halfvec_input.sql
Normal file
34
test/sql/halfvec_input.sql
Normal file
@@ -0,0 +1,34 @@
|
||||
SELECT '[1,2,3]'::halfvec;
|
||||
SELECT '[-1,-2,-3]'::halfvec;
|
||||
SELECT '[1.,2.,3.]'::halfvec;
|
||||
SELECT ' [ 1, 2 , 3 ] '::halfvec;
|
||||
SELECT '[1.23456]'::halfvec;
|
||||
SELECT '[hello,1]'::halfvec;
|
||||
SELECT '[NaN,1]'::halfvec;
|
||||
SELECT '[Infinity,1]'::halfvec;
|
||||
SELECT '[-Infinity,1]'::halfvec;
|
||||
SELECT '[65519,-65519]'::halfvec;
|
||||
SELECT '[65520,-65520]'::halfvec;
|
||||
SELECT '[1e-8,-1e-8]'::halfvec;
|
||||
SELECT '[4e38,1]'::halfvec;
|
||||
SELECT '[1,2,3'::halfvec;
|
||||
SELECT '[1,2,3]9'::halfvec;
|
||||
SELECT '1,2,3'::halfvec;
|
||||
SELECT ''::halfvec;
|
||||
SELECT '['::halfvec;
|
||||
SELECT '[,'::halfvec;
|
||||
SELECT '[]'::halfvec;
|
||||
SELECT '[1,]'::halfvec;
|
||||
SELECT '[1a]'::halfvec;
|
||||
SELECT '[1,,3]'::halfvec;
|
||||
SELECT '[1, ,3]'::halfvec;
|
||||
|
||||
SELECT '[1,2,3]'::halfvec(3);
|
||||
SELECT '[1,2,3]'::halfvec(2);
|
||||
SELECT '[1,2,3]'::halfvec(3, 2);
|
||||
SELECT '[1,2,3]'::halfvec('a');
|
||||
SELECT '[1,2,3]'::halfvec(0);
|
||||
SELECT '[1,2,3]'::halfvec(16001);
|
||||
|
||||
SELECT unnest('{"[1,2,3]", "[4,5,6]"}'::halfvec[]);
|
||||
SELECT '{"[1,2,3]"}'::halfvec(2)[];
|
||||
12
test/sql/hnsw_bit_hamming.sql
Normal file
12
test/sql/hnsw_bit_hamming.sql
Normal file
@@ -0,0 +1,12 @@
|
||||
SET enable_seqscan = off;
|
||||
|
||||
CREATE TABLE t (val bit(3));
|
||||
INSERT INTO t (val) VALUES (B'000'), (B'100'), (B'111'), (NULL);
|
||||
CREATE INDEX ON t USING hnsw (val bit_hamming_ops);
|
||||
|
||||
INSERT INTO t (val) VALUES (B'110');
|
||||
|
||||
SELECT * FROM t ORDER BY val <~> B'111';
|
||||
SELECT COUNT(*) FROM (SELECT * FROM t ORDER BY val <~> (SELECT NULL::bit)) t2;
|
||||
|
||||
DROP TABLE t;
|
||||
12
test/sql/hnsw_bit_jaccard.sql
Normal file
12
test/sql/hnsw_bit_jaccard.sql
Normal file
@@ -0,0 +1,12 @@
|
||||
SET enable_seqscan = off;
|
||||
|
||||
CREATE TABLE t (val bit(4));
|
||||
INSERT INTO t (val) VALUES (B'0000'), (B'1100'), (B'1111'), (NULL);
|
||||
CREATE INDEX ON t USING hnsw (val bit_jaccard_ops);
|
||||
|
||||
INSERT INTO t (val) VALUES (B'1110');
|
||||
|
||||
SELECT * FROM t ORDER BY val <%> B'1111';
|
||||
SELECT COUNT(*) FROM (SELECT * FROM t ORDER BY val <%> (SELECT NULL::bit)) t2;
|
||||
|
||||
DROP TABLE t;
|
||||
13
test/sql/hnsw_halfvec_cosine.sql
Normal file
13
test/sql/hnsw_halfvec_cosine.sql
Normal file
@@ -0,0 +1,13 @@
|
||||
SET enable_seqscan = off;
|
||||
|
||||
CREATE TABLE t (val halfvec(3));
|
||||
INSERT INTO t (val) VALUES ('[0,0,0]'), ('[1,2,3]'), ('[1,1,1]'), (NULL);
|
||||
CREATE INDEX ON t USING hnsw (val halfvec_cosine_ops);
|
||||
|
||||
INSERT INTO t (val) VALUES ('[1,2,4]');
|
||||
|
||||
SELECT * FROM t ORDER BY val <=> '[3,3,3]';
|
||||
SELECT COUNT(*) FROM (SELECT * FROM t ORDER BY val <=> '[0,0,0]') t2;
|
||||
SELECT COUNT(*) FROM (SELECT * FROM t ORDER BY val <=> (SELECT NULL::halfvec)) t2;
|
||||
|
||||
DROP TABLE t;
|
||||
12
test/sql/hnsw_halfvec_ip.sql
Normal file
12
test/sql/hnsw_halfvec_ip.sql
Normal file
@@ -0,0 +1,12 @@
|
||||
SET enable_seqscan = off;
|
||||
|
||||
CREATE TABLE t (val halfvec(3));
|
||||
INSERT INTO t (val) VALUES ('[0,0,0]'), ('[1,2,3]'), ('[1,1,1]'), (NULL);
|
||||
CREATE INDEX ON t USING hnsw (val halfvec_ip_ops);
|
||||
|
||||
INSERT INTO t (val) VALUES ('[1,2,4]');
|
||||
|
||||
SELECT * FROM t ORDER BY val <#> '[3,3,3]';
|
||||
SELECT COUNT(*) FROM (SELECT * FROM t ORDER BY val <#> (SELECT NULL::halfvec)) t2;
|
||||
|
||||
DROP TABLE t;
|
||||
16
test/sql/hnsw_halfvec_l2.sql
Normal file
16
test/sql/hnsw_halfvec_l2.sql
Normal file
@@ -0,0 +1,16 @@
|
||||
SET enable_seqscan = off;
|
||||
|
||||
CREATE TABLE t (val halfvec(3));
|
||||
INSERT INTO t (val) VALUES ('[0,0,0]'), ('[1,2,3]'), ('[1,1,1]'), (NULL);
|
||||
CREATE INDEX ON t USING hnsw (val halfvec_l2_ops);
|
||||
|
||||
INSERT INTO t (val) VALUES ('[1,2,4]');
|
||||
|
||||
SELECT * FROM t ORDER BY val <-> '[3,3,3]';
|
||||
SELECT COUNT(*) FROM (SELECT * FROM t ORDER BY val <-> (SELECT NULL::halfvec)) t2;
|
||||
SELECT COUNT(*) FROM t;
|
||||
|
||||
TRUNCATE t;
|
||||
SELECT * FROM t ORDER BY val <-> '[3,3,3]';
|
||||
|
||||
DROP TABLE t;
|
||||
13
test/sql/hnsw_options.sql
Normal file
13
test/sql/hnsw_options.sql
Normal file
@@ -0,0 +1,13 @@
|
||||
CREATE TABLE t (val vector(3));
|
||||
CREATE INDEX ON t USING hnsw (val vector_l2_ops) WITH (m = 1);
|
||||
CREATE INDEX ON t USING hnsw (val vector_l2_ops) WITH (m = 101);
|
||||
CREATE INDEX ON t USING hnsw (val vector_l2_ops) WITH (ef_construction = 3);
|
||||
CREATE INDEX ON t USING hnsw (val vector_l2_ops) WITH (ef_construction = 1001);
|
||||
CREATE INDEX ON t USING hnsw (val vector_l2_ops) WITH (m = 16, ef_construction = 31);
|
||||
|
||||
SHOW hnsw.ef_search;
|
||||
|
||||
SET hnsw.ef_search = 0;
|
||||
SET hnsw.ef_search = 1001;
|
||||
|
||||
DROP TABLE t;
|
||||
13
test/sql/hnsw_sparsevec_cosine.sql
Normal file
13
test/sql/hnsw_sparsevec_cosine.sql
Normal file
@@ -0,0 +1,13 @@
|
||||
SET enable_seqscan = off;
|
||||
|
||||
CREATE TABLE t (val sparsevec(3));
|
||||
INSERT INTO t (val) VALUES ('{}/3'), ('{0:1,1:2,2:3}/3'), ('{0:1,1:1,2:1}/3'), (NULL);
|
||||
CREATE INDEX ON t USING hnsw (val sparsevec_cosine_ops);
|
||||
|
||||
INSERT INTO t (val) VALUES ('{0:1,1:2,2:4}/3');
|
||||
|
||||
SELECT * FROM t ORDER BY val <=> '{0:3,1:3,2:3}/3';
|
||||
SELECT COUNT(*) FROM (SELECT * FROM t ORDER BY val <=> '{}/3') t2;
|
||||
SELECT COUNT(*) FROM (SELECT * FROM t ORDER BY val <=> (SELECT NULL::sparsevec)) t2;
|
||||
|
||||
DROP TABLE t;
|
||||
12
test/sql/hnsw_sparsevec_ip.sql
Normal file
12
test/sql/hnsw_sparsevec_ip.sql
Normal file
@@ -0,0 +1,12 @@
|
||||
SET enable_seqscan = off;
|
||||
|
||||
CREATE TABLE t (val sparsevec(3));
|
||||
INSERT INTO t (val) VALUES ('{}/3'), ('{0:1,1:2,2:3}/3'), ('{0:1,1:1,2:1}/3'), (NULL);
|
||||
CREATE INDEX ON t USING hnsw (val sparsevec_ip_ops);
|
||||
|
||||
INSERT INTO t (val) VALUES ('{0:1,1:2,2:4}/3');
|
||||
|
||||
SELECT * FROM t ORDER BY val <#> '{0:3,1:3,2:3}/3';
|
||||
SELECT COUNT(*) FROM (SELECT * FROM t ORDER BY val <#> (SELECT NULL::sparsevec)) t2;
|
||||
|
||||
DROP TABLE t;
|
||||
25
test/sql/hnsw_sparsevec_l2.sql
Normal file
25
test/sql/hnsw_sparsevec_l2.sql
Normal file
@@ -0,0 +1,25 @@
|
||||
SET enable_seqscan = off;
|
||||
|
||||
CREATE TABLE t (val sparsevec(3));
|
||||
INSERT INTO t (val) VALUES ('{}/3'), ('{0:1,1:2,2:3}/3'), ('{0:1,1:1,2:1}/3'), (NULL);
|
||||
CREATE INDEX ON t USING hnsw (val sparsevec_l2_ops);
|
||||
|
||||
INSERT INTO t (val) VALUES ('{0:1,1:2,2:4}/3');
|
||||
|
||||
SELECT * FROM t ORDER BY val <-> '{0:3,1:3,2:3}/3';
|
||||
SELECT COUNT(*) FROM (SELECT * FROM t ORDER BY val <-> (SELECT NULL::sparsevec)) t2;
|
||||
SELECT COUNT(*) FROM t;
|
||||
|
||||
TRUNCATE t;
|
||||
SELECT * FROM t ORDER BY val <-> '{0:3,1:3,2:3}/3';
|
||||
|
||||
DROP TABLE t;
|
||||
|
||||
-- TODO move
|
||||
CREATE TABLE t (val sparsevec(1001));
|
||||
INSERT INTO t (val) VALUES (array_fill(1, ARRAY[1001])::vector::sparsevec);
|
||||
CREATE INDEX ON t USING hnsw (val sparsevec_l2_ops);
|
||||
TRUNCATE t;
|
||||
CREATE INDEX ON t USING hnsw (val sparsevec_l2_ops);
|
||||
INSERT INTO t (val) VALUES (array_fill(1, ARRAY[1001])::vector::sparsevec);
|
||||
DROP TABLE t;
|
||||
9
test/sql/hnsw_unlogged.sql
Normal file
9
test/sql/hnsw_unlogged.sql
Normal file
@@ -0,0 +1,9 @@
|
||||
SET enable_seqscan = off;
|
||||
|
||||
CREATE UNLOGGED TABLE t (val vector(3));
|
||||
INSERT INTO t (val) VALUES ('[0,0,0]'), ('[1,2,3]'), ('[1,1,1]'), (NULL);
|
||||
CREATE INDEX ON t USING hnsw (val vector_l2_ops);
|
||||
|
||||
SELECT * FROM t ORDER BY val <-> '[3,3,3]';
|
||||
|
||||
DROP TABLE t;
|
||||
13
test/sql/hnsw_vector_cosine.sql
Normal file
13
test/sql/hnsw_vector_cosine.sql
Normal file
@@ -0,0 +1,13 @@
|
||||
SET enable_seqscan = off;
|
||||
|
||||
CREATE TABLE t (val vector(3));
|
||||
INSERT INTO t (val) VALUES ('[0,0,0]'), ('[1,2,3]'), ('[1,1,1]'), (NULL);
|
||||
CREATE INDEX ON t USING hnsw (val vector_cosine_ops);
|
||||
|
||||
INSERT INTO t (val) VALUES ('[1,2,4]');
|
||||
|
||||
SELECT * FROM t ORDER BY val <=> '[3,3,3]';
|
||||
SELECT COUNT(*) FROM (SELECT * FROM t ORDER BY val <=> '[0,0,0]') t2;
|
||||
SELECT COUNT(*) FROM (SELECT * FROM t ORDER BY val <=> (SELECT NULL::vector)) t2;
|
||||
|
||||
DROP TABLE t;
|
||||
12
test/sql/hnsw_vector_ip.sql
Normal file
12
test/sql/hnsw_vector_ip.sql
Normal file
@@ -0,0 +1,12 @@
|
||||
SET enable_seqscan = off;
|
||||
|
||||
CREATE TABLE t (val vector(3));
|
||||
INSERT INTO t (val) VALUES ('[0,0,0]'), ('[1,2,3]'), ('[1,1,1]'), (NULL);
|
||||
CREATE INDEX ON t USING hnsw (val vector_ip_ops);
|
||||
|
||||
INSERT INTO t (val) VALUES ('[1,2,4]');
|
||||
|
||||
SELECT * FROM t ORDER BY val <#> '[3,3,3]';
|
||||
SELECT COUNT(*) FROM (SELECT * FROM t ORDER BY val <#> (SELECT NULL::vector)) t2;
|
||||
|
||||
DROP TABLE t;
|
||||
16
test/sql/hnsw_vector_l2.sql
Normal file
16
test/sql/hnsw_vector_l2.sql
Normal file
@@ -0,0 +1,16 @@
|
||||
SET enable_seqscan = off;
|
||||
|
||||
CREATE TABLE t (val vector(3));
|
||||
INSERT INTO t (val) VALUES ('[0,0,0]'), ('[1,2,3]'), ('[1,1,1]'), (NULL);
|
||||
CREATE INDEX ON t USING hnsw (val vector_l2_ops);
|
||||
|
||||
INSERT INTO t (val) VALUES ('[1,2,4]');
|
||||
|
||||
SELECT * FROM t ORDER BY val <-> '[3,3,3]';
|
||||
SELECT COUNT(*) FROM (SELECT * FROM t ORDER BY val <-> (SELECT NULL::vector)) t2;
|
||||
SELECT COUNT(*) FROM t;
|
||||
|
||||
TRUNCATE t;
|
||||
SELECT * FROM t ORDER BY val <-> '[3,3,3]';
|
||||
|
||||
DROP TABLE t;
|
||||
@@ -1,8 +1,6 @@
|
||||
SET enable_seqscan = off;
|
||||
|
||||
CREATE TABLE t (val vector(3));
|
||||
CREATE INDEX ON t USING ivfflat (val) WITH (lists = 0);
|
||||
CREATE INDEX ON t USING ivfflat (val) WITH (lists = 32769);
|
||||
CREATE INDEX ON t USING ivfflat (val vector_l2_ops) WITH (lists = 0);
|
||||
CREATE INDEX ON t USING ivfflat (val vector_l2_ops) WITH (lists = 32769);
|
||||
|
||||
SHOW ivfflat.probes;
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ SET enable_seqscan = off;
|
||||
|
||||
CREATE UNLOGGED TABLE t (val vector(3));
|
||||
INSERT INTO t (val) VALUES ('[0,0,0]'), ('[1,2,3]'), ('[1,1,1]'), (NULL);
|
||||
CREATE INDEX ON t USING ivfflat (val) WITH (lists = 1);
|
||||
CREATE INDEX ON t USING ivfflat (val vector_l2_ops) WITH (lists = 1);
|
||||
|
||||
SELECT * FROM t ORDER BY val <-> '[3,3,3]';
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ CREATE INDEX ON t USING ivfflat (val vector_cosine_ops) WITH (lists = 1);
|
||||
INSERT INTO t (val) VALUES ('[1,2,4]');
|
||||
|
||||
SELECT * FROM t ORDER BY val <=> '[3,3,3]';
|
||||
SELECT * FROM t ORDER BY val <=> (SELECT NULL::vector);
|
||||
SELECT COUNT(*) FROM (SELECT * FROM t ORDER BY val <=> '[0,0,0]') t2;
|
||||
SELECT COUNT(*) FROM (SELECT * FROM t ORDER BY val <=> (SELECT NULL::vector)) t2;
|
||||
|
||||
DROP TABLE t;
|
||||
@@ -7,6 +7,6 @@ CREATE INDEX ON t USING ivfflat (val vector_ip_ops) WITH (lists = 1);
|
||||
INSERT INTO t (val) VALUES ('[1,2,4]');
|
||||
|
||||
SELECT * FROM t ORDER BY val <#> '[3,3,3]';
|
||||
SELECT * FROM t ORDER BY val <#> (SELECT NULL::vector);
|
||||
SELECT COUNT(*) FROM (SELECT * FROM t ORDER BY val <#> (SELECT NULL::vector)) t2;
|
||||
|
||||
DROP TABLE t;
|
||||
@@ -2,7 +2,7 @@ SET enable_seqscan = off;
|
||||
|
||||
CREATE TABLE t (val vector(3));
|
||||
INSERT INTO t (val) VALUES ('[0,0,0]'), ('[1,2,3]'), ('[1,1,1]'), (NULL);
|
||||
CREATE INDEX ON t USING ivfflat (val) WITH (lists = 1);
|
||||
CREATE INDEX ON t USING ivfflat (val vector_l2_ops) WITH (lists = 1);
|
||||
|
||||
INSERT INTO t (val) VALUES ('[1,2,4]');
|
||||
|
||||
@@ -10,4 +10,7 @@ SELECT * FROM t ORDER BY val <-> '[3,3,3]';
|
||||
SELECT * FROM t ORDER BY val <-> (SELECT NULL::vector);
|
||||
SELECT COUNT(*) FROM t;
|
||||
|
||||
TRUNCATE t;
|
||||
SELECT * FROM t ORDER BY val <-> '[3,3,3]';
|
||||
|
||||
DROP TABLE t;
|
||||
21
test/sql/sparsevec_functions.sql
Normal file
21
test/sql/sparsevec_functions.sql
Normal file
@@ -0,0 +1,21 @@
|
||||
SELECT l2_distance('{}/2'::sparsevec, '{0:3,1:4}/2');
|
||||
SELECT l2_distance('{}/2'::sparsevec, '{1:1}/2');
|
||||
SELECT '{}/2'::sparsevec <-> '{0:3,1:4}/2';
|
||||
|
||||
SELECT inner_product('{0:1,1:2}/2'::sparsevec, '{0:2,1:4}/2');
|
||||
SELECT sparsevec_negative_inner_product('{0:1,1:2}/2', '{0:2,1:4}/2');
|
||||
|
||||
SELECT cosine_distance('{0:1,1:2}/2'::sparsevec, '{0:2,1:4}/2');
|
||||
SELECT cosine_distance('{0:1,1:2}/2'::sparsevec, '{}/2');
|
||||
SELECT cosine_distance('{0:1,1:1}/2'::sparsevec, '{0:-1,1:-1}/2');
|
||||
SELECT cosine_distance('{0:1}/2'::sparsevec, '{1:2}/2');
|
||||
SELECT cosine_distance('{}/1'::sparsevec, '{}/1');
|
||||
SELECT cosine_distance('{0:1}/2'::sparsevec, '{0:1}/3');
|
||||
|
||||
SELECT subvector('{0:1,1:2,2:3,3:4,4:5}/5'::sparsevec, 1, 3);
|
||||
SELECT subvector('{0:1,1:2,2:3,3:4,4:5}/5'::sparsevec, 3, 2);
|
||||
SELECT subvector('{0:1,1:2,2:3,3:4,4:5}/5'::sparsevec, -1, 3);
|
||||
SELECT subvector('{0:1,1:2,2:3,3:4,4:5}/5'::sparsevec, 3, 9);
|
||||
SELECT subvector('{0:1,1:2,2:3,3:4,4:5}/5'::sparsevec, 1, 0);
|
||||
SELECT subvector('{0:1,1:2,2:3,3:4,4:5}/5'::sparsevec, 3, -1);
|
||||
SELECT subvector('{0:1,1:2,2:3,3:4,4:5}/5'::sparsevec, -1, 2);
|
||||
19
test/sql/sparsevec_input.sql
Normal file
19
test/sql/sparsevec_input.sql
Normal file
@@ -0,0 +1,19 @@
|
||||
SELECT '{0:1.5,2:3.5}/5'::sparsevec;
|
||||
SELECT '{0:1.5,2:3.5}/5'::sparsevec::vector;
|
||||
SELECT '{0:1.5,2:3.5}/5'::sparsevec::vector(5);
|
||||
SELECT '{0:1.5,2:3.5}/5'::sparsevec::vector(4);
|
||||
SELECT '[0,1.5,0,3.5,0]'::vector::sparsevec;
|
||||
|
||||
SELECT '{0:0,1:1,2:0}/3'::sparsevec;
|
||||
|
||||
SELECT '{1:1,0:1}/2'::sparsevec;
|
||||
|
||||
SELECT '{}/5'::sparsevec;
|
||||
SELECT '{}/-1'::sparsevec;
|
||||
SELECT '{}/100001'::sparsevec;
|
||||
SELECT '{}/16001'::sparsevec::vector;
|
||||
|
||||
SELECT '{-1:1}/1'::sparsevec;
|
||||
SELECT '{1:1}/1'::sparsevec;
|
||||
|
||||
SELECT '{}/1'::sparsevec(2);
|
||||
83
test/sql/vector_functions.sql
Normal file
83
test/sql/vector_functions.sql
Normal file
@@ -0,0 +1,83 @@
|
||||
SELECT '[1,2,3]'::vector + '[4,5,6]';
|
||||
SELECT '[3e38]'::vector + '[3e38]';
|
||||
SELECT '[1,2,3]'::vector - '[4,5,6]';
|
||||
SELECT '[-3e38]'::vector - '[3e38]';
|
||||
SELECT '[1,2,3]'::vector * '[4,5,6]';
|
||||
SELECT '[1e37]'::vector * '[1e37]';
|
||||
SELECT '[1e-37]'::vector * '[1e-37]';
|
||||
|
||||
SELECT '[1,2,3]'::vector < '[1,2,3]';
|
||||
SELECT '[1,2,3]'::vector < '[1,2]';
|
||||
SELECT '[1,2,3]'::vector <= '[1,2,3]';
|
||||
SELECT '[1,2,3]'::vector <= '[1,2]';
|
||||
SELECT '[1,2,3]'::vector = '[1,2,3]';
|
||||
SELECT '[1,2,3]'::vector = '[1,2]';
|
||||
SELECT '[1,2,3]'::vector != '[1,2,3]';
|
||||
SELECT '[1,2,3]'::vector != '[1,2]';
|
||||
SELECT '[1,2,3]'::vector >= '[1,2,3]';
|
||||
SELECT '[1,2,3]'::vector >= '[1,2]';
|
||||
SELECT '[1,2,3]'::vector > '[1,2,3]';
|
||||
SELECT '[1,2,3]'::vector > '[1,2]';
|
||||
|
||||
SELECT vector_cmp('[1,2,3]', '[1,2,3]');
|
||||
SELECT vector_cmp('[1,2,3]', '[0,0,0]');
|
||||
SELECT vector_cmp('[0,0,0]', '[1,2,3]');
|
||||
SELECT vector_cmp('[1,2]', '[1,2,3]');
|
||||
SELECT vector_cmp('[1,2,3]', '[1,2]');
|
||||
SELECT vector_cmp('[1,2]', '[2,3,4]');
|
||||
SELECT vector_cmp('[2,3]', '[1,2,3]');
|
||||
|
||||
SELECT vector_dims('[1,2,3]');
|
||||
|
||||
SELECT round(vector_norm('[1,1]')::numeric, 5);
|
||||
SELECT vector_norm('[3,4]');
|
||||
SELECT vector_norm('[0,1]');
|
||||
SELECT vector_norm('[3e37,4e37]')::real;
|
||||
|
||||
SELECT l2_distance('[0,0]'::vector, '[3,4]');
|
||||
SELECT l2_distance('[0,0]'::vector, '[0,1]');
|
||||
SELECT l2_distance('[1,2]'::vector, '[3]');
|
||||
SELECT l2_distance('[3e38]'::vector, '[-3e38]');
|
||||
|
||||
SELECT inner_product('[1,2]'::vector, '[3,4]');
|
||||
SELECT inner_product('[1,2]'::vector, '[3]');
|
||||
SELECT inner_product('[3e38]'::vector, '[3e38]');
|
||||
|
||||
SELECT cosine_distance('[1,2]'::vector, '[2,4]');
|
||||
SELECT cosine_distance('[1,2]'::vector, '[0,0]');
|
||||
SELECT cosine_distance('[1,1]'::vector, '[1,1]');
|
||||
SELECT cosine_distance('[1,0]'::vector, '[0,2]');
|
||||
SELECT cosine_distance('[1,1]'::vector, '[-1,-1]');
|
||||
SELECT cosine_distance('[1,2]'::vector, '[3]');
|
||||
SELECT cosine_distance('[1,1]'::vector, '[1.1,1.1]');
|
||||
SELECT cosine_distance('[1,1]'::vector, '[-1.1,-1.1]');
|
||||
SELECT cosine_distance('[3e38]'::vector, '[3e38]');
|
||||
|
||||
SELECT l1_distance('[0,0]'::vector, '[3,4]');
|
||||
SELECT l1_distance('[0,0]'::vector, '[0,1]');
|
||||
SELECT l1_distance('[1,2]'::vector, '[3]');
|
||||
SELECT l1_distance('[3e38]'::vector, '[-3e38]');
|
||||
|
||||
SELECT quantize_binary('[1,0,-1]'::vector);
|
||||
SELECT quantize_binary('[0,0.1,-0.2,-0.3,0.4,0.5,0.6,-0.7,0.8,-0.9,1]'::vector);
|
||||
|
||||
SELECT subvector('[1,2,3,4,5]'::vector, 1, 3);
|
||||
SELECT subvector('[1,2,3,4,5]'::vector, 3, 2);
|
||||
SELECT subvector('[1,2,3,4,5]'::vector, -1, 3);
|
||||
SELECT subvector('[1,2,3,4,5]'::vector, 3, 9);
|
||||
SELECT subvector('[1,2,3,4,5]'::vector, 1, 0);
|
||||
SELECT subvector('[1,2,3,4,5]'::vector, 3, -1);
|
||||
SELECT subvector('[1,2,3,4,5]'::vector, -1, 2);
|
||||
|
||||
SELECT avg(v) FROM unnest(ARRAY['[1,2,3]'::vector, '[3,5,7]']) v;
|
||||
SELECT avg(v) FROM unnest(ARRAY['[1,2,3]'::vector, '[3,5,7]', NULL]) v;
|
||||
SELECT avg(v) FROM unnest(ARRAY[]::vector[]) v;
|
||||
SELECT avg(v) FROM unnest(ARRAY['[1,2]'::vector, '[3]']) v;
|
||||
SELECT avg(v) FROM unnest(ARRAY['[3e38]'::vector, '[3e38]']) v;
|
||||
SELECT vector_avg(array_agg(n)) FROM generate_series(1, 16002) n;
|
||||
|
||||
SELECT sum(v) FROM unnest(ARRAY['[1,2,3]'::vector, '[3,5,7]']) v;
|
||||
SELECT sum(v) FROM unnest(ARRAY['[1,2,3]'::vector, '[3,5,7]', NULL]) v;
|
||||
SELECT sum(v) FROM unnest(ARRAY[]::vector[]) v;
|
||||
SELECT sum(v) FROM unnest(ARRAY['[1,2]'::vector, '[3]']) v;
|
||||
SELECT sum(v) FROM unnest(ARRAY['[3e38]'::vector, '[3e38]']) v;
|
||||
@@ -11,9 +11,13 @@ SELECT '[1.5e38,-1.5e38]'::vector;
|
||||
SELECT '[1.5e+38,-1.5e+38]'::vector;
|
||||
SELECT '[1.5e-38,-1.5e-38]'::vector;
|
||||
SELECT '[4e38,1]'::vector;
|
||||
SELECT '[-4e38,1]'::vector;
|
||||
SELECT '[1e-46,1]'::vector;
|
||||
SELECT '[-1e-46,1]'::vector;
|
||||
SELECT '[1,2,3'::vector;
|
||||
SELECT '[1,2,3]9'::vector;
|
||||
SELECT '1,2,3'::vector;
|
||||
SELECT ''::vector;
|
||||
SELECT '['::vector;
|
||||
SELECT '[,'::vector;
|
||||
SELECT '[]'::vector;
|
||||
@@ -21,7 +25,13 @@ SELECT '[1,]'::vector;
|
||||
SELECT '[1a]'::vector;
|
||||
SELECT '[1,,3]'::vector;
|
||||
SELECT '[1, ,3]'::vector;
|
||||
|
||||
SELECT '[1,2,3]'::vector(3);
|
||||
SELECT '[1,2,3]'::vector(2);
|
||||
SELECT '[1,2,3]'::vector(3, 2);
|
||||
SELECT '[1,2,3]'::vector('a');
|
||||
SELECT '[1,2,3]'::vector(0);
|
||||
SELECT '[1,2,3]'::vector(16001);
|
||||
|
||||
SELECT unnest('{"[1,2,3]", "[4,5,6]"}'::vector[]);
|
||||
SELECT '{"[1,2,3]"}'::vector(2)[];
|
||||
@@ -5,7 +5,7 @@ use strict;
|
||||
use warnings;
|
||||
use PostgresNode;
|
||||
use TestLib;
|
||||
use Test::More tests => 31;
|
||||
use Test::More;
|
||||
|
||||
my $dim = 32;
|
||||
|
||||
@@ -19,14 +19,13 @@ sub test_index_replay
|
||||
|
||||
# Wait for replica to catch up
|
||||
my $applname = $node_replica->name;
|
||||
|
||||
my $server_version_num = $node_primary->safe_psql("postgres", "SHOW server_version_num");
|
||||
my $caughtup_query = "SELECT pg_current_wal_lsn() <= replay_lsn FROM pg_stat_replication WHERE application_name = '$applname';";
|
||||
$node_primary->poll_query_until('postgres', $caughtup_query)
|
||||
or die "Timed out while waiting for replica 1 to catch up";
|
||||
|
||||
my @r = ();
|
||||
for (1 .. $dim) {
|
||||
for (1 .. $dim)
|
||||
{
|
||||
push(@r, rand());
|
||||
}
|
||||
my $sql = join(",", @r);
|
||||
@@ -52,11 +51,13 @@ my $array_sql = join(",", ('random()') x $dim);
|
||||
# Initialize primary node
|
||||
$node_primary = get_new_node('primary');
|
||||
$node_primary->init(allows_streaming => 1);
|
||||
if ($dim > 32) {
|
||||
if ($dim > 32)
|
||||
{
|
||||
# TODO use wal_keep_segments for Postgres < 13
|
||||
$node_primary->append_conf('postgresql.conf', qq(wal_keep_size = 1GB));
|
||||
}
|
||||
if ($dim > 1500) {
|
||||
if ($dim > 1500)
|
||||
{
|
||||
$node_primary->append_conf('postgresql.conf', qq(maintenance_work_mem = 128MB));
|
||||
}
|
||||
$node_primary->start;
|
||||
@@ -67,8 +68,7 @@ $node_primary->backup($backup_name);
|
||||
|
||||
# Create streaming replica linking to primary
|
||||
$node_replica = get_new_node('replica');
|
||||
$node_replica->init_from_backup($node_primary, $backup_name,
|
||||
has_streaming => 1);
|
||||
$node_replica->init_from_backup($node_primary, $backup_name, has_streaming => 1);
|
||||
$node_replica->start;
|
||||
|
||||
# Create ivfflat index on primary
|
||||
@@ -77,7 +77,7 @@ $node_primary->safe_psql("postgres", "CREATE TABLE tst (i int4, v vector($dim));
|
||||
$node_primary->safe_psql("postgres",
|
||||
"INSERT INTO tst SELECT i % 10, ARRAY[$array_sql] FROM generate_series(1, 100000) i;"
|
||||
);
|
||||
$node_primary->safe_psql("postgres", "CREATE INDEX ON tst USING ivfflat (v);");
|
||||
$node_primary->safe_psql("postgres", "CREATE INDEX ON tst USING ivfflat (v vector_l2_ops);");
|
||||
|
||||
# Test that queries give same result
|
||||
test_index_replay('initial');
|
||||
@@ -95,3 +95,5 @@ for my $i (1 .. 10)
|
||||
);
|
||||
test_index_replay("insert $i");
|
||||
}
|
||||
|
||||
done_testing();
|
||||
@@ -2,12 +2,13 @@ use strict;
|
||||
use warnings;
|
||||
use PostgresNode;
|
||||
use TestLib;
|
||||
use Test::More tests => 1;
|
||||
use Test::More;
|
||||
|
||||
my $dim = 3;
|
||||
|
||||
my @r = ();
|
||||
for (1 .. $dim) {
|
||||
for (1 .. $dim)
|
||||
{
|
||||
my $v = int(rand(1000)) + 1;
|
||||
push(@r, "i % $v");
|
||||
}
|
||||
@@ -24,7 +25,7 @@ $node->safe_psql("postgres", "CREATE TABLE tst (i int4, v vector($dim));");
|
||||
$node->safe_psql("postgres",
|
||||
"INSERT INTO tst SELECT i % 10, ARRAY[$array_sql] FROM generate_series(1, 100000) i;"
|
||||
);
|
||||
$node->safe_psql("postgres", "CREATE INDEX ON tst USING ivfflat (v);");
|
||||
$node->safe_psql("postgres", "CREATE INDEX ON tst USING ivfflat (v vector_l2_ops);");
|
||||
|
||||
# Get size
|
||||
my $size = $node->safe_psql("postgres", "SELECT pg_total_relation_size('tst_v_idx');");
|
||||
@@ -39,3 +40,5 @@ $node->safe_psql("postgres",
|
||||
# Check size
|
||||
my $new_size = $node->safe_psql("postgres", "SELECT pg_total_relation_size('tst_v_idx');");
|
||||
is($size, $new_size, "size does not change");
|
||||
|
||||
done_testing();
|
||||
128
test/t/003_ivfflat_build_recall.pl
Normal file
128
test/t/003_ivfflat_build_recall.pl
Normal file
@@ -0,0 +1,128 @@
|
||||
use strict;
|
||||
use warnings;
|
||||
use PostgresNode;
|
||||
use TestLib;
|
||||
use Test::More;
|
||||
|
||||
my $node;
|
||||
my @queries = ();
|
||||
my @expected;
|
||||
my $limit = 20;
|
||||
|
||||
sub test_recall
|
||||
{
|
||||
my ($probes, $min, $operator) = @_;
|
||||
my $correct = 0;
|
||||
my $total = 0;
|
||||
|
||||
my $explain = $node->safe_psql("postgres", qq(
|
||||
SET enable_seqscan = off;
|
||||
SET ivfflat.probes = $probes;
|
||||
EXPLAIN ANALYZE SELECT i FROM tst ORDER BY v $operator '$queries[0]' LIMIT $limit;
|
||||
));
|
||||
like($explain, qr/Index Scan using idx on tst/);
|
||||
|
||||
for my $i (0 .. $#queries)
|
||||
{
|
||||
my $actual = $node->safe_psql("postgres", qq(
|
||||
SET enable_seqscan = off;
|
||||
SET ivfflat.probes = $probes;
|
||||
SELECT i FROM tst ORDER BY v $operator '$queries[$i]' LIMIT $limit;
|
||||
));
|
||||
my @actual_ids = split("\n", $actual);
|
||||
my %actual_set = map { $_ => 1 } @actual_ids;
|
||||
|
||||
my @expected_ids = split("\n", $expected[$i]);
|
||||
|
||||
foreach (@expected_ids)
|
||||
{
|
||||
if (exists($actual_set{$_}))
|
||||
{
|
||||
$correct++;
|
||||
}
|
||||
$total++;
|
||||
}
|
||||
}
|
||||
|
||||
cmp_ok($correct / $total, ">=", $min, $operator);
|
||||
}
|
||||
|
||||
# Initialize node
|
||||
$node = get_new_node('node');
|
||||
$node->init;
|
||||
$node->start;
|
||||
|
||||
# Create table
|
||||
$node->safe_psql("postgres", "CREATE EXTENSION vector;");
|
||||
$node->safe_psql("postgres", "CREATE TABLE tst (i int4, v vector(3));");
|
||||
$node->safe_psql("postgres",
|
||||
"INSERT INTO tst SELECT i, ARRAY[random(), random(), random()] FROM generate_series(1, 100000) i;"
|
||||
);
|
||||
|
||||
# Generate queries
|
||||
for (1 .. 20)
|
||||
{
|
||||
my $r1 = rand();
|
||||
my $r2 = rand();
|
||||
my $r3 = rand();
|
||||
push(@queries, "[$r1,$r2,$r3]");
|
||||
}
|
||||
|
||||
# Check each index type
|
||||
my @operators = ("<->", "<#>", "<=>");
|
||||
my @opclasses = ("vector_l2_ops", "vector_ip_ops", "vector_cosine_ops");
|
||||
|
||||
for my $i (0 .. $#operators)
|
||||
{
|
||||
my $operator = $operators[$i];
|
||||
my $opclass = $opclasses[$i];
|
||||
|
||||
# Get exact results
|
||||
@expected = ();
|
||||
foreach (@queries)
|
||||
{
|
||||
my $res = $node->safe_psql("postgres", "SELECT i FROM tst ORDER BY v $operator '$_' LIMIT $limit;");
|
||||
push(@expected, $res);
|
||||
}
|
||||
|
||||
# Build index serially
|
||||
$node->safe_psql("postgres", qq(
|
||||
SET max_parallel_maintenance_workers = 0;
|
||||
CREATE INDEX idx ON tst USING ivfflat (v $opclass);
|
||||
));
|
||||
|
||||
# Test approximate results
|
||||
if ($operator ne "<#>")
|
||||
{
|
||||
# TODO Fix test (uniform random vectors all have similar inner product)
|
||||
test_recall(1, 0.71, $operator);
|
||||
test_recall(10, 0.95, $operator);
|
||||
}
|
||||
# Account for equal distances
|
||||
test_recall(100, 0.9925, $operator);
|
||||
|
||||
$node->safe_psql("postgres", "DROP INDEX idx;");
|
||||
|
||||
# Build index in parallel
|
||||
my ($ret, $stdout, $stderr) = $node->psql("postgres", qq(
|
||||
SET client_min_messages = DEBUG;
|
||||
SET min_parallel_table_scan_size = 1;
|
||||
CREATE INDEX idx ON tst USING ivfflat (v $opclass);
|
||||
));
|
||||
is($ret, 0, $stderr);
|
||||
like($stderr, qr/using \d+ parallel workers/);
|
||||
|
||||
# Test approximate results
|
||||
if ($operator ne "<#>")
|
||||
{
|
||||
# TODO Fix test (uniform random vectors all have similar inner product)
|
||||
test_recall(1, 0.71, $operator);
|
||||
test_recall(10, 0.95, $operator);
|
||||
}
|
||||
# Account for equal distances
|
||||
test_recall(100, 0.9925, $operator);
|
||||
|
||||
$node->safe_psql("postgres", "DROP INDEX idx;");
|
||||
}
|
||||
|
||||
done_testing();
|
||||
@@ -1,88 +0,0 @@
|
||||
use strict;
|
||||
use warnings;
|
||||
use PostgresNode;
|
||||
use TestLib;
|
||||
use Test::More tests => 9;
|
||||
|
||||
my $node;
|
||||
my @queries = ();
|
||||
my @expected;
|
||||
my $limit = 20;
|
||||
|
||||
sub test_recall
|
||||
{
|
||||
my ($probes, $min, $operator) = @_;
|
||||
my $correct = 0;
|
||||
my $total = 0;
|
||||
|
||||
for my $i (0 .. $#queries) {
|
||||
my $actual = $node->safe_psql("postgres", qq(
|
||||
SET enable_seqscan = off;
|
||||
SET ivfflat.probes = $probes;
|
||||
SELECT i FROM tst ORDER BY v $operator '$queries[$i]' LIMIT $limit;
|
||||
));
|
||||
my @actual_ids = split("\n", $actual);
|
||||
my %actual_set = map { $_ => 1 } @actual_ids;
|
||||
|
||||
my @expected_ids = split("\n", $expected[$i]);
|
||||
|
||||
foreach (@expected_ids) {
|
||||
if (exists($actual_set{$_})) {
|
||||
$correct++;
|
||||
}
|
||||
$total++;
|
||||
}
|
||||
}
|
||||
|
||||
cmp_ok($correct / $total, ">=", $min, $operator);
|
||||
}
|
||||
|
||||
# Initialize node
|
||||
$node = get_new_node('node');
|
||||
$node->init;
|
||||
$node->start;
|
||||
|
||||
# Create table
|
||||
$node->safe_psql("postgres", "CREATE EXTENSION vector;");
|
||||
$node->safe_psql("postgres", "CREATE TABLE tst (i int4, v vector(3));");
|
||||
$node->safe_psql("postgres",
|
||||
"INSERT INTO tst SELECT i, ARRAY[random(), random(), random()] FROM generate_series(1, 100000) i;"
|
||||
);
|
||||
|
||||
# Generate queries
|
||||
for (1..20) {
|
||||
my $r1 = rand();
|
||||
my $r2 = rand();
|
||||
my $r3 = rand();
|
||||
push(@queries, "[$r1,$r2,$r3]");
|
||||
}
|
||||
|
||||
# Check each index type
|
||||
my @operators = ("<->", "<#>", "<=>");
|
||||
|
||||
foreach (@operators) {
|
||||
my $operator = $_;
|
||||
|
||||
# Get exact results
|
||||
@expected = ();
|
||||
foreach (@queries) {
|
||||
my $res = $node->safe_psql("postgres", "SELECT i FROM tst ORDER BY v $operator '$_' LIMIT $limit;");
|
||||
push(@expected, $res);
|
||||
}
|
||||
|
||||
# Add index
|
||||
my $opclass;
|
||||
if ($operator == "<->") {
|
||||
$opclass = "vector_l2_ops";
|
||||
} elsif ($operator == "<#>") {
|
||||
$opclass = "vector_ip_ops";
|
||||
} else {
|
||||
$opclass = "vector_cosine_ops";
|
||||
}
|
||||
$node->safe_psql("postgres", "CREATE INDEX ON tst USING ivfflat (v $opclass);");
|
||||
|
||||
# Test approximate results
|
||||
test_recall(1, 0.75, $operator);
|
||||
test_recall(10, 0.95, $operator);
|
||||
test_recall(100, 1.0, $operator);
|
||||
}
|
||||
@@ -2,7 +2,7 @@ use strict;
|
||||
use warnings;
|
||||
use PostgresNode;
|
||||
use TestLib;
|
||||
use Test::More tests => 3;
|
||||
use Test::More;
|
||||
|
||||
# Initialize node
|
||||
my $node = get_new_node('node');
|
||||
@@ -20,7 +20,7 @@ sub test_centers
|
||||
{
|
||||
my ($lists, $min) = @_;
|
||||
|
||||
my ($ret, $stdout, $stderr) = $node->psql("postgres", "CREATE INDEX ON tst USING ivfflat (v) WITH (lists = $lists);");
|
||||
my ($ret, $stdout, $stderr) = $node->psql("postgres", "CREATE INDEX ON tst USING ivfflat (v vector_l2_ops) WITH (lists = $lists);");
|
||||
is($ret, 0, $stderr);
|
||||
}
|
||||
|
||||
@@ -34,3 +34,5 @@ $node->safe_psql("postgres",
|
||||
|
||||
# Test no error for duplicate centers
|
||||
test_centers(10);
|
||||
|
||||
done_testing();
|
||||
@@ -2,7 +2,7 @@ use strict;
|
||||
use warnings;
|
||||
use PostgresNode;
|
||||
use TestLib;
|
||||
use Test::More tests => 60;
|
||||
use Test::More;
|
||||
|
||||
# Initialize node
|
||||
my $node = get_new_node('node');
|
||||
@@ -18,24 +18,21 @@ $node->safe_psql("postgres",
|
||||
|
||||
# Check each index type
|
||||
my @operators = ("<->", "<#>", "<=>");
|
||||
foreach (@operators) {
|
||||
my $operator = $_;
|
||||
my @opclasses = ("vector_l2_ops", "vector_ip_ops", "vector_cosine_ops");
|
||||
|
||||
for my $i (0 .. $#operators)
|
||||
{
|
||||
my $operator = $operators[$i];
|
||||
my $opclass = $opclasses[$i];
|
||||
|
||||
# Add index
|
||||
my $opclass;
|
||||
if ($operator == "<->") {
|
||||
$opclass = "vector_l2_ops";
|
||||
} elsif ($operator == "<#>") {
|
||||
$opclass = "vector_ip_ops";
|
||||
} else {
|
||||
$opclass = "vector_cosine_ops";
|
||||
}
|
||||
$node->safe_psql("postgres", "CREATE INDEX ON tst USING ivfflat (v $opclass);");
|
||||
|
||||
# Test 100% recall
|
||||
for (1..20) {
|
||||
my $i = int(rand() * 100000);
|
||||
my $query = $node->safe_psql("postgres", "SELECT v FROM tst WHERE i = $i;");
|
||||
for (1 .. 20)
|
||||
{
|
||||
my $id = int(rand() * 100000);
|
||||
my $query = $node->safe_psql("postgres", "SELECT v FROM tst WHERE i = $id;");
|
||||
my $res = $node->safe_psql("postgres", qq(
|
||||
SET enable_seqscan = off;
|
||||
SELECT v FROM tst ORDER BY v <-> '$query' LIMIT 1;
|
||||
@@ -43,3 +40,5 @@ foreach (@operators) {
|
||||
is($res, $query);
|
||||
}
|
||||
}
|
||||
|
||||
done_testing();
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user