mirror of
https://github.com/pgvector/pgvector.git
synced 2026-07-22 20:15:46 +08:00
Compare commits
259 Commits
v0.1.1
...
windows-pr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bb127fce8d | ||
|
|
7ce4e1f53e | ||
|
|
6e41c81412 | ||
|
|
98ec05145b | ||
|
|
e302c99d4a | ||
|
|
d6ab29e772 | ||
|
|
6ad96fe8ca | ||
|
|
e52ef4f2b5 | ||
|
|
8c81213592 | ||
|
|
f77b044303 | ||
|
|
fab7b50c3a | ||
|
|
9e2e726010 | ||
|
|
2621f9f947 | ||
|
|
4fbe55f97e | ||
|
|
9c9489e888 | ||
|
|
d376011087 | ||
|
|
5107e057ad | ||
|
|
1c1812aeac | ||
|
|
da12d9d8b0 | ||
|
|
de26e1e78a | ||
|
|
8691d2ad53 | ||
|
|
573200c188 | ||
|
|
cd7cab804f | ||
|
|
84a8bdd661 | ||
|
|
7a13a6cd3c | ||
|
|
35d5cb9f06 | ||
|
|
bc6b9e4bba | ||
|
|
d605948be4 | ||
|
|
37a784d3f6 | ||
|
|
c74bb5495b | ||
|
|
cc35af160e | ||
|
|
a7f712b5a4 | ||
|
|
a532d82bda | ||
|
|
ccec96ba54 | ||
|
|
b7c5849ba7 | ||
|
|
135147893a | ||
|
|
00391953dd | ||
|
|
81bba1f37e | ||
|
|
2d8b7e5f81 | ||
|
|
cebae5e5ff | ||
|
|
4c3332fc4d | ||
|
|
386a3b5dd5 | ||
|
|
fb819eb8b2 | ||
|
|
3f8407460e | ||
|
|
059e88dda5 | ||
|
|
12f158d4bb | ||
|
|
2a3c0a769a | ||
|
|
349d844fa1 | ||
|
|
a57ca81d8f | ||
|
|
dc4a79a7e7 | ||
|
|
1652a130c5 | ||
|
|
a6743bd206 | ||
|
|
e25627074f | ||
|
|
b3cad93f3a | ||
|
|
379a76098e | ||
|
|
5af768752f | ||
|
|
f03bcc4fd8 | ||
|
|
74fc4b9aaa | ||
|
|
6870ba96ba | ||
|
|
2575cfdaa8 | ||
|
|
9ff5ceacca | ||
|
|
9a58ee609a | ||
|
|
66a31bef36 | ||
|
|
163ecdf063 | ||
|
|
c9c6b96eed | ||
|
|
48e9a6830b | ||
|
|
f22b47ca46 | ||
|
|
2a057c055c | ||
|
|
b89b58b893 | ||
|
|
2e77650e1d | ||
|
|
0a68b040fb | ||
|
|
434dc904c1 | ||
|
|
92a671eeb1 | ||
|
|
94eeb83abe | ||
|
|
6866939090 | ||
|
|
ba2fb6ad8c | ||
|
|
51fe8aff02 | ||
|
|
df3dc151d5 | ||
|
|
6687430589 | ||
|
|
f128a455d1 | ||
|
|
56cb5f3503 | ||
|
|
ecbf46938f | ||
|
|
4ca264ba02 | ||
|
|
f5458414b8 | ||
|
|
e64ed39acb | ||
|
|
0d025be9d3 | ||
|
|
fed60dce78 | ||
|
|
a37f5eea4a | ||
|
|
4bdb27e85a | ||
|
|
38f869e0bd | ||
|
|
ad8acc00d4 | ||
|
|
01926a418e | ||
|
|
9658d3c1ad | ||
|
|
6b9c6516f4 | ||
|
|
88be03a3fa | ||
|
|
bf5b2c8d7e | ||
|
|
ac65ec2856 | ||
|
|
7bba0e2a01 | ||
|
|
c35e9f3b84 | ||
|
|
9549d93260 | ||
|
|
310809d0e5 | ||
|
|
ce72ca8620 | ||
|
|
4f2c937a1f | ||
|
|
ff400ce5f1 | ||
|
|
41d11c62d6 | ||
|
|
fa5e90585d | ||
|
|
71d2908be1 | ||
|
|
d0a1c1d0ed | ||
|
|
8063201cdf | ||
|
|
2f52127bee | ||
|
|
4778039ffa | ||
|
|
e3c9d70acc | ||
|
|
5971e28f8c | ||
|
|
2da27e2f01 | ||
|
|
7dd934913d | ||
|
|
9b46987e73 | ||
|
|
868e1f99a4 | ||
|
|
354271178f | ||
|
|
fe9b218903 | ||
|
|
3cdd1c16fb | ||
|
|
4f7a5c92da | ||
|
|
9399575e02 | ||
|
|
e7539fe548 | ||
|
|
61860ee6d9 | ||
|
|
eb99730d87 | ||
|
|
e5da5cc532 | ||
|
|
f59b1184fc | ||
|
|
cab103a2e7 | ||
|
|
0094b70a36 | ||
|
|
46ed56d80f | ||
|
|
d390cc9fa6 | ||
|
|
02ae98791c | ||
|
|
b64c7d6228 | ||
|
|
76f117c81c | ||
|
|
5414b2bc9e | ||
|
|
6ba583b587 | ||
|
|
da88217fd8 | ||
|
|
d5da22f24b | ||
|
|
062e220936 | ||
|
|
3d2b867a58 | ||
|
|
5aeabc3648 | ||
|
|
687ec6d8d7 | ||
|
|
51dc18d5b7 | ||
|
|
cb108ebfd2 | ||
|
|
12bc4eb036 | ||
|
|
fd0e472316 | ||
|
|
9b11b05cd7 | ||
|
|
ab079c1911 | ||
|
|
f4eaa3de1f | ||
|
|
e47675d6ae | ||
|
|
20167ddc33 | ||
|
|
e14fa82626 | ||
|
|
2ae4de83fe | ||
|
|
5d2c4883dc | ||
|
|
fc1de9806a | ||
|
|
e375dd33f3 | ||
|
|
b643ef637a | ||
|
|
0ee5bfda65 | ||
|
|
c5bec2a534 | ||
|
|
40f65dc8ba | ||
|
|
9ec174137f | ||
|
|
423e478a85 | ||
|
|
8b301197fe | ||
|
|
c02feaa9ad | ||
|
|
729374a9b8 | ||
|
|
a8245d6cda | ||
|
|
5220a6ec7e | ||
|
|
ddd873f369 | ||
|
|
8735c4782a | ||
|
|
17ec3f3852 | ||
|
|
8df12a62e7 | ||
|
|
647c9002be | ||
|
|
3e6345f02a | ||
|
|
2b3484ddcd | ||
|
|
1a6debf281 | ||
|
|
bff4d7ea68 | ||
|
|
d5b979e4fa | ||
|
|
d52426b3fa | ||
|
|
b125b2debb | ||
|
|
3d7186a17c | ||
|
|
58ba2139ce | ||
|
|
fd93c9db86 | ||
|
|
8dde26920c | ||
|
|
9153a545a7 | ||
|
|
9c20f04a11 | ||
|
|
7c12d100ce | ||
|
|
d97d9a6561 | ||
|
|
f15ea301c5 | ||
|
|
cfa23f2c7a | ||
|
|
b5b7035edc | ||
|
|
478d62cdbd | ||
|
|
c7df9ef3a1 | ||
|
|
ea1b094d8e | ||
|
|
63d2965e8e | ||
|
|
e5e7a6ec15 | ||
|
|
ede572d80b | ||
|
|
154e4334fb | ||
|
|
77d54333f6 | ||
|
|
c95732340a | ||
|
|
2af9bdefbb | ||
|
|
bd36231871 | ||
|
|
523788d964 | ||
|
|
1eed57310a | ||
|
|
57e6a01214 | ||
|
|
b713cbc3b6 | ||
|
|
ebeac156c0 | ||
|
|
60bc93f938 | ||
|
|
87a1b3365e | ||
|
|
e9a762223b | ||
|
|
db83f4faa8 | ||
|
|
2cf71ec6bd | ||
|
|
a7510430e3 | ||
|
|
ea8f06e8f4 | ||
|
|
4e0c1a137c | ||
|
|
c758b7b188 | ||
|
|
3dffc01e31 | ||
|
|
b8f3688e41 | ||
|
|
67e317141b | ||
|
|
de2ce0b1eb | ||
|
|
86c1c95ef4 | ||
|
|
38be81cf20 | ||
|
|
62cc3398ed | ||
|
|
af86392a34 | ||
|
|
a9067a8101 | ||
|
|
6deca154e6 | ||
|
|
6342439eb8 | ||
|
|
21967765a3 | ||
|
|
d31b7e3997 | ||
|
|
efa85aaba1 | ||
|
|
3c820b5a50 | ||
|
|
199c0e5e34 | ||
|
|
bcab0ad8ae | ||
|
|
09e95fc8f9 | ||
|
|
6119cced74 | ||
|
|
f8c07edba1 | ||
|
|
baabbaeff9 | ||
|
|
f0993282fe | ||
|
|
01584d3df6 | ||
|
|
6f50c4f5bd | ||
|
|
31a953f8db | ||
|
|
7b3d0f2595 | ||
|
|
099b9e9027 | ||
|
|
e1f3caba49 | ||
|
|
3f0d2bbd2f | ||
|
|
c989b85f75 | ||
|
|
a9bbee4198 | ||
|
|
fb274f1197 | ||
|
|
88ba5da6a7 | ||
|
|
6a550c8b52 | ||
|
|
5748cb6b43 | ||
|
|
60814b8f39 | ||
|
|
773a63bed1 | ||
|
|
955a5a6082 | ||
|
|
8863920fdc | ||
|
|
6d9653d763 | ||
|
|
3a488c9f2f | ||
|
|
fe133bfe5d | ||
|
|
e279bb3d7e | ||
|
|
0aa39a8c0c |
8
.dockerignore
Normal file
8
.dockerignore
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
/.git/
|
||||||
|
/dist/
|
||||||
|
/results/
|
||||||
|
/tmp_check/
|
||||||
|
/sql/vector--?.?.?.sql
|
||||||
|
regression.*
|
||||||
|
*.o
|
||||||
|
*.so
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
root = true
|
root = true
|
||||||
|
|
||||||
[*.{c,h}]
|
[*.{c,h,pl,pm}]
|
||||||
indent_style = tab
|
indent_style = tab
|
||||||
indent_size = tab
|
indent_size = tab
|
||||||
tab_width = 4
|
tab_width = 4
|
||||||
|
|||||||
71
.github/workflows/build.yml
vendored
71
.github/workflows/build.yml
vendored
@@ -1,22 +1,65 @@
|
|||||||
name: build
|
name: build
|
||||||
on: [push, pull_request]
|
on: [push, pull_request]
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
ubuntu:
|
||||||
if: "!contains(github.event.head_commit.message, '[skip ci]')"
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
if: ${{ !startsWith(github.ref_name, 'windows') }}
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
postgres: [13, 12, 11, 10, 9.6]
|
postgres: [15, 14, 13, 12, 11, 10]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
- uses: ankane/setup-postgres@v1
|
- uses: ankane/setup-postgres@v1
|
||||||
with:
|
with:
|
||||||
postgres-version: ${{ matrix.postgres }}
|
postgres-version: ${{ matrix.postgres }}
|
||||||
- run: sudo apt-get update && sudo apt-get install postgresql-server-dev-${{ matrix.postgres }} libipc-run-perl
|
dev-files: true
|
||||||
- run: make
|
- run: make
|
||||||
- run: |
|
- run: |
|
||||||
export PG_CONFIG=`which pg_config`
|
export PG_CONFIG=`which pg_config`
|
||||||
sudo --preserve-env=PG_CONFIG make install
|
sudo --preserve-env=PG_CONFIG make install
|
||||||
- run: make installcheck
|
- run: make installcheck
|
||||||
- run: make prove_installcheck
|
- if: ${{ failure() }}
|
||||||
|
run: cat regression.diffs
|
||||||
|
- run: |
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install libipc-run-perl
|
||||||
|
make prove_installcheck
|
||||||
|
mac:
|
||||||
|
runs-on: macos-latest
|
||||||
|
if: ${{ !startsWith(github.ref_name, 'windows') }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- uses: ankane/setup-postgres@v1
|
||||||
|
with:
|
||||||
|
postgres-version: 14
|
||||||
|
- run: make
|
||||||
|
- run: make install
|
||||||
|
- run: make installcheck
|
||||||
|
- if: ${{ failure() }}
|
||||||
|
run: cat regression.diffs
|
||||||
|
- run: |
|
||||||
|
brew install cpanm
|
||||||
|
cpanm IPC::Run
|
||||||
|
wget -q https://github.com/postgres/postgres/archive/refs/tags/REL_14_5.tar.gz
|
||||||
|
tar xf REL_14_5.tar.gz
|
||||||
|
make prove_installcheck PROVE=prove PROVE_FLAGS="-I ./postgres-REL_14_5/src/test/perl" PERL5LIB="/Users/runner/perl5/lib/perl5"
|
||||||
|
windows:
|
||||||
|
runs-on: windows-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- uses: ankane/setup-postgres@v1
|
||||||
|
with:
|
||||||
|
postgres-version: 14
|
||||||
|
- run: |
|
||||||
|
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat"
|
||||||
|
nmake /NOLOGO /F Makefile.win
|
||||||
|
nmake /NOLOGO /F Makefile.win install
|
||||||
|
curl -Ls -o REL_14_5.tar.gz https://github.com/postgres/postgres/archive/refs/tags/REL_14_5.tar.gz
|
||||||
|
7z x REL_14_5.tar.gz
|
||||||
|
7z x REL_14_5.tar
|
||||||
|
ls ./postgres-REL_14_5/src/test/perl
|
||||||
|
set PROVE=prove
|
||||||
|
set PROVE_FLAGS="-I ./postgres-REL_14_5/src/test/perl"
|
||||||
|
nmake /NOLOGO /F Makefile.win prove_installcheck
|
||||||
|
shell: cmd
|
||||||
|
|||||||
7
.gitignore
vendored
7
.gitignore
vendored
@@ -1,5 +1,8 @@
|
|||||||
results
|
/dist/
|
||||||
tmp_check
|
/results/
|
||||||
|
/tmp_check/
|
||||||
|
/sql/vector--?.?.?.sql
|
||||||
regression.*
|
regression.*
|
||||||
*.o
|
*.o
|
||||||
*.so
|
*.so
|
||||||
|
*.bc
|
||||||
|
|||||||
89
CHANGELOG.md
89
CHANGELOG.md
@@ -1,3 +1,92 @@
|
|||||||
|
## 0.4.0 (unreleased)
|
||||||
|
|
||||||
|
- Changed text representation for vector elements to match `real`
|
||||||
|
- Improved accuracy of text parsing for certain inputs
|
||||||
|
- Added experimental support for Windows
|
||||||
|
|
||||||
|
## 0.3.2 (2022-11-22)
|
||||||
|
|
||||||
|
- Fixed `invalid memory alloc request size` error
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
|
||||||
|
- Fixed issue with inserts silently corrupting `ivfflat` indexes (introduced in 0.2.7)
|
||||||
|
- Fixed segmentation fault with index creation when lists > 6500
|
||||||
|
|
||||||
|
## 0.3.0 (2022-10-15)
|
||||||
|
|
||||||
|
- Added support for Postgres 15
|
||||||
|
- Dropped support for Postgres 9.6
|
||||||
|
|
||||||
|
## 0.2.7 (2022-07-31)
|
||||||
|
|
||||||
|
- Fixed `unexpected data beyond EOF` error
|
||||||
|
|
||||||
|
## 0.2.6 (2022-05-22)
|
||||||
|
|
||||||
|
- Improved performance of index creation for Postgres < 12
|
||||||
|
|
||||||
|
## 0.2.5 (2022-02-11)
|
||||||
|
|
||||||
|
- Reduced memory usage during index creation
|
||||||
|
- Fixed index creation exceeding `maintenance_work_mem`
|
||||||
|
- Fixed error with index creation when lists > 1600
|
||||||
|
|
||||||
|
## 0.2.4 (2022-02-06)
|
||||||
|
|
||||||
|
- Added support for parallel vacuum
|
||||||
|
- Fixed issue with index not reusing space
|
||||||
|
|
||||||
|
## 0.2.3 (2022-01-30)
|
||||||
|
|
||||||
|
- Added indexing progress for Postgres 12+
|
||||||
|
- Improved interrupt handling during index creation
|
||||||
|
|
||||||
|
## 0.2.2 (2022-01-15)
|
||||||
|
|
||||||
|
- Fixed compilation error on Mac ARM
|
||||||
|
|
||||||
|
## 0.2.1 (2022-01-02)
|
||||||
|
|
||||||
|
- Fixed `operator is not unique` error
|
||||||
|
|
||||||
|
## 0.2.0 (2021-10-03)
|
||||||
|
|
||||||
|
- Added support for Postgres 14
|
||||||
|
|
||||||
|
## 0.1.8 (2021-09-07)
|
||||||
|
|
||||||
|
- Added cast for `vector` to `real[]`
|
||||||
|
|
||||||
|
## 0.1.7 (2021-06-13)
|
||||||
|
|
||||||
|
- Added cast for `numeric[]` to `vector`
|
||||||
|
|
||||||
|
## 0.1.6 (2021-06-09)
|
||||||
|
|
||||||
|
- Fixed segmentation fault with `COUNT`
|
||||||
|
|
||||||
|
## 0.1.5 (2021-05-25)
|
||||||
|
|
||||||
|
- Reduced memory usage during index creation
|
||||||
|
|
||||||
|
## 0.1.4 (2021-05-09)
|
||||||
|
|
||||||
|
- Fixed kmeans for inner product
|
||||||
|
- Fixed multiple definition error with GCC 10
|
||||||
|
|
||||||
|
## 0.1.3 (2021-05-06)
|
||||||
|
|
||||||
|
- Added Dockerfile
|
||||||
|
- Fixed version
|
||||||
|
|
||||||
|
## 0.1.2 (2021-04-26)
|
||||||
|
|
||||||
|
- Vectorized distance calculations
|
||||||
|
- Improved cost estimation
|
||||||
|
|
||||||
## 0.1.1 (2021-04-25)
|
## 0.1.1 (2021-04-25)
|
||||||
|
|
||||||
- Added binary representation for `COPY`
|
- Added binary representation for `COPY`
|
||||||
|
|||||||
16
Dockerfile
Normal file
16
Dockerfile
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
FROM postgres:15
|
||||||
|
|
||||||
|
COPY . /tmp/pgvector
|
||||||
|
|
||||||
|
RUN apt-get update && \
|
||||||
|
apt-get install -y --no-install-recommends build-essential postgresql-server-dev-15 && \
|
||||||
|
cd /tmp/pgvector && \
|
||||||
|
make clean && \
|
||||||
|
make OPTFLAGS="" && \
|
||||||
|
make install && \
|
||||||
|
mkdir /usr/share/doc/pgvector && \
|
||||||
|
cp LICENSE README.md /usr/share/doc/pgvector && \
|
||||||
|
rm -r /tmp/pgvector && \
|
||||||
|
apt-get remove -y build-essential postgresql-server-dev-15 && \
|
||||||
|
apt-get autoremove -y && \
|
||||||
|
rm -rf /var/lib/apt/lists/*
|
||||||
49
META.json
Normal file
49
META.json
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
{
|
||||||
|
"name": "vector",
|
||||||
|
"abstract": "Open-source vector similarity search for Postgres",
|
||||||
|
"description": "Supports L2 distance, inner product, and cosine distance",
|
||||||
|
"version": "0.3.2",
|
||||||
|
"maintainer": [
|
||||||
|
"Andrew Kane <andrew@ankane.org>"
|
||||||
|
],
|
||||||
|
"license": {
|
||||||
|
"PostgreSQL": "http://www.postgresql.org/about/licence"
|
||||||
|
},
|
||||||
|
"prereqs": {
|
||||||
|
"runtime": {
|
||||||
|
"requires": {
|
||||||
|
"PostgreSQL": "10.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"provides": {
|
||||||
|
"vector": {
|
||||||
|
"file": "sql/vector.sql",
|
||||||
|
"docfile": "README.md",
|
||||||
|
"version": "0.3.2",
|
||||||
|
"abstract": "Open-source vector similarity search for Postgres"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"resources": {
|
||||||
|
"homepage": "https://github.com/pgvector/pgvector",
|
||||||
|
"bugtracker": {
|
||||||
|
"web": "https://github.com/pgvector/pgvector/issues"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"url": "https://github.com/pgvector/pgvector.git",
|
||||||
|
"web": "https://github.com/pgvector/pgvector",
|
||||||
|
"type": "git"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"generated_by": "Andrew Kane",
|
||||||
|
"meta-spec": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"url": "http://pgxn.org/meta/spec.txt"
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"vectors",
|
||||||
|
"datatype",
|
||||||
|
"nearest neighbor search",
|
||||||
|
"approximate nearest neighbors"
|
||||||
|
]
|
||||||
|
}
|
||||||
51
Makefile
51
Makefile
@@ -1,16 +1,59 @@
|
|||||||
EXTENSION = vector
|
EXTENSION = vector
|
||||||
DATA = vector--0.1.1.sql vector--0.1.0--0.1.1.sql
|
EXTVERSION = 0.3.2
|
||||||
|
|
||||||
MODULE_big = vector
|
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/ivfbuild.o src/ivfflat.o src/ivfinsert.o src/ivfkmeans.o src/ivfscan.o src/ivfutils.o src/ivfvacuum.o src/vector.o
|
||||||
|
|
||||||
TESTS = $(wildcard sql/*.sql)
|
TESTS = $(wildcard test/sql/*.sql)
|
||||||
|
REGRESS = $(patsubst test/sql/%.sql,%,$(TESTS))
|
||||||
|
REGRESS_OPTS = --inputdir=test --load-extension=vector
|
||||||
|
|
||||||
REGRESS = btree cast copy functions ivfflat_cosine ivfflat_ip ivfflat_l2 ivfflat_unlogged vector
|
OPTFLAGS = -march=native
|
||||||
|
|
||||||
|
# Mac ARM doesn't support -march=native
|
||||||
|
ifeq ($(shell uname -s), Darwin)
|
||||||
|
ifeq ($(shell uname -p), arm)
|
||||||
|
OPTFLAGS =
|
||||||
|
endif
|
||||||
|
endif
|
||||||
|
|
||||||
|
# For auto-vectorization:
|
||||||
|
# - GCC (needs -ftree-vectorize OR -O3) - https://gcc.gnu.org/projects/tree-ssa/vectorization.html
|
||||||
|
# - Clang (could use pragma instead) - https://llvm.org/docs/Vectorizers.html
|
||||||
|
PG_CFLAGS += $(OPTFLAGS) -ftree-vectorize -fassociative-math -fno-signed-zeros -fno-trapping-math
|
||||||
|
|
||||||
|
# Debug GCC auto-vectorization
|
||||||
|
# PG_CFLAGS += -fopt-info-vec
|
||||||
|
|
||||||
|
# Debug Clang auto-vectorization
|
||||||
|
# PG_CFLAGS += -Rpass=loop-vectorize -Rpass-analysis=loop-vectorize
|
||||||
|
|
||||||
|
all: sql/$(EXTENSION)--$(EXTVERSION).sql
|
||||||
|
|
||||||
|
sql/$(EXTENSION)--$(EXTVERSION).sql: sql/$(EXTENSION).sql
|
||||||
|
cp $< $@
|
||||||
|
|
||||||
|
EXTRA_CLEAN = sql/$(EXTENSION)--$(EXTVERSION).sql
|
||||||
|
|
||||||
PG_CONFIG ?= pg_config
|
PG_CONFIG ?= pg_config
|
||||||
PGXS := $(shell $(PG_CONFIG) --pgxs)
|
PGXS := $(shell $(PG_CONFIG) --pgxs)
|
||||||
include $(PGXS)
|
include $(PGXS)
|
||||||
|
|
||||||
|
# for Postgres 15
|
||||||
|
PROVE_FLAGS += -I ./test/perl
|
||||||
|
|
||||||
prove_installcheck:
|
prove_installcheck:
|
||||||
rm -rf $(CURDIR)/tmp_check
|
rm -rf $(CURDIR)/tmp_check
|
||||||
cd $(srcdir) && TESTDIR='$(CURDIR)' PATH="$(bindir):$$PATH" PGPORT='6$(DEF_PGPORT)' PG_REGRESS='$(top_builddir)/src/test/regress/pg_regress' $(PROVE) $(PG_PROVE_FLAGS) $(PROVE_FLAGS) $(if $(PROVE_TESTS),$(PROVE_TESTS),t/*.pl)
|
cd $(srcdir) && TESTDIR='$(CURDIR)' PATH="$(bindir):$$PATH" PGPORT='6$(DEF_PGPORT)' PG_REGRESS='$(top_builddir)/src/test/regress/pg_regress' $(PROVE) $(PG_PROVE_FLAGS) $(PROVE_FLAGS) $(if $(PROVE_TESTS),$(PROVE_TESTS),test/t/*.pl)
|
||||||
|
|
||||||
|
.PHONY: dist
|
||||||
|
|
||||||
|
dist:
|
||||||
|
mkdir -p dist
|
||||||
|
git archive --format zip --prefix=$(EXTENSION)-$(EXTVERSION)/ --output dist/$(EXTENSION)-$(EXTVERSION).zip master
|
||||||
|
|
||||||
|
.PHONY: docker
|
||||||
|
|
||||||
|
docker:
|
||||||
|
docker build --pull --no-cache -t ankane/pgvector:latest .
|
||||||
|
|||||||
62
Makefile.win
Normal file
62
Makefile.win
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
EXTENSION = vector
|
||||||
|
EXTVERSION = 0.3.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
|
||||||
|
|
||||||
|
REGRESS = btree cast copy functions input ivfflat_cosine ivfflat_ip ivfflat_l2 ivfflat_options ivfflat_unlogged
|
||||||
|
REGRESS_OPTS = --inputdir=test --load-extension=vector
|
||||||
|
|
||||||
|
# For /arch flags
|
||||||
|
# https://learn.microsoft.com/en-us/cpp/build/reference/arch-minimum-cpu-architecture
|
||||||
|
OPTFLAGS =
|
||||||
|
|
||||||
|
# For auto-vectorization:
|
||||||
|
# - MSVC (needs /O2 /fp:fast) - https://learn.microsoft.com/en-us/cpp/parallel/auto-parallelization-and-auto-vectorization?#auto-vectorizer
|
||||||
|
PG_CFLAGS = $(PG_CFLAGS) $(OPTFLAGS) /O2 /fp:fast
|
||||||
|
|
||||||
|
# Debug MSVC auto-vectorization
|
||||||
|
# https://learn.microsoft.com/en-us/cpp/error-messages/tool-errors/vectorizer-and-parallelizer-messages
|
||||||
|
# PG_CFLAGS = $(PG_CFLAGS) /Qvec-report:2
|
||||||
|
|
||||||
|
all: sql\$(EXTENSION)--$(EXTVERSION).sql
|
||||||
|
|
||||||
|
sql\$(EXTENSION)--$(EXTVERSION).sql: sql\$(EXTENSION).sql
|
||||||
|
copy sql\$(EXTENSION).sql $@
|
||||||
|
|
||||||
|
# TODO use pg_config
|
||||||
|
BINDIR = $(PGROOT)\bin
|
||||||
|
INCLUDEDIR = $(PGROOT)\include
|
||||||
|
INCLUDEDIR_SERVER = $(PGROOT)\include\server
|
||||||
|
LIBDIR = $(PGROOT)\lib
|
||||||
|
PKGLIBDIR = $(PGROOT)\lib
|
||||||
|
SHAREDIR = $(PGROOT)\share
|
||||||
|
|
||||||
|
CFLAGS = /nologo /I"$(INCLUDEDIR_SERVER)\port\win32_msvc" /I"$(INCLUDEDIR_SERVER)\port\win32" /I"$(INCLUDEDIR_SERVER)" /I"$(INCLUDEDIR)"
|
||||||
|
|
||||||
|
CFLAGS = $(CFLAGS) $(PG_CFLAGS)
|
||||||
|
|
||||||
|
SHLIB = src\$(EXTENSION).dll
|
||||||
|
|
||||||
|
LIBS = "$(LIBDIR)\postgres.lib"
|
||||||
|
|
||||||
|
.c.obj:
|
||||||
|
$(CC) $(CFLAGS) /c $< /Fo$@
|
||||||
|
|
||||||
|
$(SHLIB): $(OBJS)
|
||||||
|
$(CC) $(CFLAGS) $(OBJS) $(LIBS) /link /DLL /OUT:$(SHLIB)
|
||||||
|
|
||||||
|
all: $(SHLIB)
|
||||||
|
|
||||||
|
install:
|
||||||
|
copy $(SHLIB) "$(PKGLIBDIR)"
|
||||||
|
copy $(EXTENSION).control "$(SHAREDIR)\extension"
|
||||||
|
copy sql\$(EXTENSION)--*.sql "$(SHAREDIR)\extension"
|
||||||
|
|
||||||
|
installcheck:
|
||||||
|
"$(BINDIR)\pg_regress" --bindir="$(BINDIR)" $(REGRESS_OPTS) $(REGRESS)
|
||||||
|
|
||||||
|
prove_installcheck:
|
||||||
|
rm -rf tmp_check
|
||||||
|
set PGPORT=65432
|
||||||
|
set PG_REGRESS="$(BINDIR)\pg_regress"
|
||||||
|
$(PROVE) $(PG_PROVE_FLAGS) $(PROVE_FLAGS) test/t/*.pl
|
||||||
200
README.md
200
README.md
@@ -3,21 +3,21 @@
|
|||||||
Open-source vector similarity search for Postgres
|
Open-source vector similarity search for Postgres
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
CREATE TABLE table (column vector(3));
|
CREATE TABLE items (embedding vector(3));
|
||||||
CREATE INDEX ON table USING ivfflat (column);
|
CREATE INDEX ON items USING ivfflat (embedding vector_l2_ops);
|
||||||
SELECT * FROM table ORDER BY column <-> '[1,2,3]' LIMIT 5;
|
SELECT * FROM items ORDER BY embedding <-> '[1,2,3]' LIMIT 5;
|
||||||
```
|
```
|
||||||
|
|
||||||
Supports L2 distance, inner product, and cosine distance
|
Supports L2 distance, inner product, and cosine distance
|
||||||
|
|
||||||
[](https://github.com/ankane/pgvector/actions)
|
[](https://github.com/pgvector/pgvector/actions)
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
Compile and install the extension (supports Postgres 9.6+)
|
Compile and install the extension (supports Postgres 10+)
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
git clone --branch v0.1.1 https://github.com/ankane/pgvector.git
|
git clone --branch v0.3.2 https://github.com/pgvector/pgvector.git
|
||||||
cd pgvector
|
cd pgvector
|
||||||
make
|
make
|
||||||
make install # may need sudo
|
make install # may need sudo
|
||||||
@@ -29,24 +29,26 @@ Then load it in databases where you want to use it
|
|||||||
CREATE EXTENSION vector;
|
CREATE EXTENSION vector;
|
||||||
```
|
```
|
||||||
|
|
||||||
|
You can also install it with [Docker](#docker), [Homebrew](#homebrew), or [PGXN](#pgxn)
|
||||||
|
|
||||||
## Getting Started
|
## Getting Started
|
||||||
|
|
||||||
Create a vector column with 3 dimensions (replace `table` and `column` with non-reserved names)
|
Create a vector column with 3 dimensions
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
CREATE TABLE table (column vector(3));
|
CREATE TABLE items (embedding vector(3));
|
||||||
```
|
```
|
||||||
|
|
||||||
Insert values
|
Insert values
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
INSERT INTO table VALUES ('[1,2,3]'), ('[4,5,6]');
|
INSERT INTO items VALUES ('[1,2,3]'), ('[4,5,6]');
|
||||||
```
|
```
|
||||||
|
|
||||||
Get the nearest neighbor by L2 distance
|
Get the nearest neighbor by L2 distance
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
SELECT * FROM table ORDER BY column <-> '[3,1,2]' LIMIT 1;
|
SELECT * FROM items ORDER BY embedding <-> '[3,1,2]' LIMIT 1;
|
||||||
```
|
```
|
||||||
|
|
||||||
Also supports inner product (`<#>`) and cosine distance (`<=>`)
|
Also supports inner product (`<#>`) and cosine distance (`<=>`)
|
||||||
@@ -60,31 +62,33 @@ Speed up queries with an approximate index. Add an index for each distance funct
|
|||||||
L2 distance
|
L2 distance
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
CREATE INDEX ON table USING ivfflat (column);
|
CREATE INDEX ON items USING ivfflat (embedding vector_l2_ops);
|
||||||
```
|
```
|
||||||
|
|
||||||
Inner product
|
Inner product
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
CREATE INDEX ON table USING ivfflat (column vector_ip_ops);
|
CREATE INDEX ON items USING ivfflat (embedding vector_ip_ops);
|
||||||
```
|
```
|
||||||
|
|
||||||
Cosine distance
|
Cosine distance
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
CREATE INDEX ON table USING ivfflat (column vector_cosine_ops);
|
CREATE INDEX ON items USING ivfflat (embedding vector_cosine_ops);
|
||||||
```
|
```
|
||||||
|
|
||||||
Indexes should be created after the table has data for optimal clustering. Also, unlike typical indexes which only affect performance, you may see different results for queries after adding an approximate index.
|
Indexes should be created after the table has some data for optimal clustering. Also, unlike typical indexes which only affect performance, you may see different results for queries after adding an approximate index.
|
||||||
|
|
||||||
### Index Options
|
### Index Options
|
||||||
|
|
||||||
Specify the number of inverted lists (100 by default)
|
Specify the number of inverted lists (100 by default)
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
CREATE INDEX ON table USING ivfflat (column) WITH (lists = 100);
|
CREATE INDEX ON items USING ivfflat (embedding vector_l2_ops) WITH (lists = 100);
|
||||||
```
|
```
|
||||||
|
|
||||||
|
A [good place to start](https://github.com/facebookresearch/faiss/issues/112) is `4 * sqrt(rows)`
|
||||||
|
|
||||||
### Query Options
|
### Query Options
|
||||||
|
|
||||||
Specify the number of probes (1 by default)
|
Specify the number of probes (1 by default)
|
||||||
@@ -104,11 +108,63 @@ SELECT ...
|
|||||||
COMMIT;
|
COMMIT;
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 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;
|
||||||
|
```
|
||||||
|
|
||||||
|
The phases are:
|
||||||
|
|
||||||
|
1. `initializing`
|
||||||
|
2. `sampling table`
|
||||||
|
3. `performing k-means`
|
||||||
|
4. `sorting tuples`
|
||||||
|
5. `loading tuples`
|
||||||
|
|
||||||
|
Note: `tuples_done` and `tuples_total` are only populated during the `loading tuples` phase
|
||||||
|
|
||||||
|
### Partial Indexes
|
||||||
|
|
||||||
|
Consider [partial indexes](https://www.postgresql.org/docs/current/indexes-partial.html) for queries with a `WHERE` clause
|
||||||
|
|
||||||
|
```sql
|
||||||
|
SELECT * FROM items WHERE category_id = 123 ORDER BY embedding <-> '[3,1,2]' LIMIT 5;
|
||||||
|
```
|
||||||
|
|
||||||
|
can be indexed with:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE INDEX ON items USING ivfflat (embedding vector_l2_ops) WHERE (category_id = 123);
|
||||||
|
```
|
||||||
|
|
||||||
|
To index many different values of `category_id`, consider [partitioning](https://www.postgresql.org/docs/current/ddl-partitioning.html) on `category_id`.
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE TABLE items (embedding vector(3), category_id int) PARTITION BY LIST(category_id);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Performance
|
||||||
|
|
||||||
|
To speed up queries without an index, increase `max_parallel_workers_per_gather`.
|
||||||
|
|
||||||
|
```sql
|
||||||
|
SET max_parallel_workers_per_gather = 4;
|
||||||
|
```
|
||||||
|
|
||||||
|
To speed up queries with an 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);
|
||||||
|
```
|
||||||
|
|
||||||
## Reference
|
## Reference
|
||||||
|
|
||||||
### Vector Type
|
### Vector Type
|
||||||
|
|
||||||
Each vector takes `4 * dimensions + 8` bytes of storage. Each element is a float, and all elements must be finite (no `NaN`, `Infinity` or `-Infinity`). Vectors can have up to 1024 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 1024 dimensions.
|
||||||
|
|
||||||
### Vector Operators
|
### Vector Operators
|
||||||
|
|
||||||
@@ -134,16 +190,100 @@ vector_norm(vector) | Euclidean norm
|
|||||||
|
|
||||||
Libraries that use pgvector:
|
Libraries that use pgvector:
|
||||||
|
|
||||||
|
- [pgvector-python](https://github.com/pgvector/pgvector-python) (Python)
|
||||||
- [Neighbor](https://github.com/ankane/neighbor) (Ruby)
|
- [Neighbor](https://github.com/ankane/neighbor) (Ruby)
|
||||||
|
- [pgvector-ruby](https://github.com/pgvector/pgvector-ruby) (Ruby)
|
||||||
|
- [pgvector-node](https://github.com/pgvector/pgvector-node) (Node.js)
|
||||||
|
- [pgvector-go](https://github.com/pgvector/pgvector-go) (Go)
|
||||||
|
- [pgvector-php](https://github.com/pgvector/pgvector-php) (PHP)
|
||||||
|
- [pgvector-rust](https://github.com/pgvector/pgvector-rust) (Rust)
|
||||||
|
- [pgvector-cpp](https://github.com/pgvector/pgvector-cpp) (C++)
|
||||||
|
- [pgvector-elixir](https://github.com/pgvector/pgvector-elixir) (Elixir)
|
||||||
|
|
||||||
|
## Frequently Asked Questions
|
||||||
|
|
||||||
|
#### How many vectors can be stored in a single table?
|
||||||
|
|
||||||
|
A non-partitioned table has a limit of 32 TB by default in Postgres. A partitioned table can have thousands of partitions of that size.
|
||||||
|
|
||||||
|
#### Is replication supported?
|
||||||
|
|
||||||
|
Yes, pgvector uses the write-ahead log (WAL), which allows for replication and point-in-time recovery.
|
||||||
|
|
||||||
|
#### What if my data has more than 1024 dimensions?
|
||||||
|
|
||||||
|
Two things you can try are:
|
||||||
|
|
||||||
|
1. use dimensionality reduction
|
||||||
|
2. compile Postgres with a larger block size (`./configure --with-blocksize=32`) and edit the limit in `src/vector.h`
|
||||||
|
|
||||||
|
## Additional Installation Methods
|
||||||
|
|
||||||
|
### Docker
|
||||||
|
|
||||||
|
Get the [Docker image](https://hub.docker.com/r/ankane/pgvector) with:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
docker pull ankane/pgvector
|
||||||
|
```
|
||||||
|
|
||||||
|
This adds pgvector to the [Postgres image](https://hub.docker.com/_/postgres).
|
||||||
|
|
||||||
|
You can also build the image manually
|
||||||
|
|
||||||
|
```sh
|
||||||
|
git clone --branch v0.3.2 https://github.com/pgvector/pgvector.git
|
||||||
|
cd pgvector
|
||||||
|
docker build -t pgvector .
|
||||||
|
```
|
||||||
|
|
||||||
|
### Homebrew
|
||||||
|
|
||||||
|
With Homebrew Postgres, you can use:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
brew install pgvector/brew/pgvector
|
||||||
|
```
|
||||||
|
|
||||||
|
### PGXN
|
||||||
|
|
||||||
|
Install from the [PostgreSQL Extension Network](https://pgxn.org/dist/vector) with:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
pgxn install vector
|
||||||
|
```
|
||||||
|
|
||||||
|
## Hosted Postgres
|
||||||
|
|
||||||
|
Some Postgres providers only support specific extensions. To request a new extension:
|
||||||
|
|
||||||
|
- Amazon RDS - follow the instructions on [this page](https://aws.amazon.com/rds/postgresql/faqs/)
|
||||||
|
- Google Cloud SQL - follow the instructions on [this page](https://cloud.google.com/sql/docs/postgres/extensions#requesting-support-for-a-new-extension)
|
||||||
|
- DigitalOcean Managed Databases - vote or comment on [this page](https://ideas.digitalocean.com/app-framework-services/p/pgvector-extension-for-postgresql)
|
||||||
|
- Azure Database for PostgreSQL - follow the instructions on [this page](https://docs.microsoft.com/en-us/azure/postgresql/concepts-extensions#next-steps)
|
||||||
|
|
||||||
## Upgrading
|
## Upgrading
|
||||||
|
|
||||||
### 0.1.1
|
Install the latest version and run:
|
||||||
|
|
||||||
Follow the [installation instructions](#installation) and run:
|
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
ALTER EXTENSION vector UPDATE TO '0.1.1';
|
ALTER EXTENSION vector UPDATE;
|
||||||
|
```
|
||||||
|
|
||||||
|
## Upgrade Notes
|
||||||
|
|
||||||
|
### 0.3.1
|
||||||
|
|
||||||
|
If upgrading from 0.2.7 or 0.3.0, recreate all `ivfflat` indexes after upgrading to ensure all data is indexed.
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- Postgres 12+
|
||||||
|
REINDEX INDEX CONCURRENTLY index_name;
|
||||||
|
|
||||||
|
-- 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;
|
||||||
```
|
```
|
||||||
|
|
||||||
## Thanks
|
## Thanks
|
||||||
@@ -158,21 +298,21 @@ Thanks to:
|
|||||||
|
|
||||||
## History
|
## History
|
||||||
|
|
||||||
View the [changelog](https://github.com/ankane/pgvector/blob/master/CHANGELOG.md)
|
View the [changelog](https://github.com/pgvector/pgvector/blob/master/CHANGELOG.md)
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
Everyone is encouraged to help improve this project. Here are a few ways you can help:
|
Everyone is encouraged to help improve this project. Here are a few ways you can help:
|
||||||
|
|
||||||
- [Report bugs](https://github.com/ankane/pgvector/issues)
|
- [Report bugs](https://github.com/pgvector/pgvector/issues)
|
||||||
- Fix bugs and [submit pull requests](https://github.com/ankane/pgvector/pulls)
|
- Fix bugs and [submit pull requests](https://github.com/pgvector/pgvector/pulls)
|
||||||
- Write, clarify, or fix documentation
|
- Write, clarify, or fix documentation
|
||||||
- Suggest or add new features
|
- Suggest or add new features
|
||||||
|
|
||||||
To get started with development:
|
To get started with development:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
git clone https://github.com/ankane/pgvector.git
|
git clone https://github.com/pgvector/pgvector.git
|
||||||
cd pgvector
|
cd pgvector
|
||||||
make
|
make
|
||||||
make install
|
make install
|
||||||
@@ -188,15 +328,15 @@ make prove_installcheck # TAP tests
|
|||||||
To run single tests:
|
To run single tests:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
make installcheck REGRESS=vector # regression test
|
make installcheck REGRESS=functions # regression test
|
||||||
make prove_installcheck PROVE_TESTS=t/001_wal.pl # TAP test
|
make prove_installcheck PROVE_TESTS=test/t/001_wal.pl # TAP test
|
||||||
```
|
```
|
||||||
|
|
||||||
Directories
|
To enable benchmarking:
|
||||||
|
|
||||||
- `expected` - expected output for regression tests
|
```sh
|
||||||
- `sql` - regression tests
|
make clean && PG_CFLAGS=-DIVFFLAT_BENCH make && make install
|
||||||
- `t` - TAP tests
|
```
|
||||||
|
|
||||||
Resources for contributors
|
Resources for contributors
|
||||||
|
|
||||||
|
|||||||
2
sql/vector--0.1.1--0.1.3.sql
Normal file
2
sql/vector--0.1.1--0.1.3.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.1.3'" to load this file. \quit
|
||||||
2
sql/vector--0.1.3--0.1.4.sql
Normal file
2
sql/vector--0.1.3--0.1.4.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.1.4'" to load this file. \quit
|
||||||
2
sql/vector--0.1.4--0.1.5.sql
Normal file
2
sql/vector--0.1.4--0.1.5.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.1.5'" to load this file. \quit
|
||||||
2
sql/vector--0.1.5--0.1.6.sql
Normal file
2
sql/vector--0.1.5--0.1.6.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.1.6'" to load this file. \quit
|
||||||
8
sql/vector--0.1.6--0.1.7.sql
Normal file
8
sql/vector--0.1.6--0.1.7.sql
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
-- complain if script is sourced in psql, rather than via CREATE EXTENSION
|
||||||
|
\echo Use "ALTER EXTENSION vector UPDATE TO '0.1.7'" to load this file. \quit
|
||||||
|
|
||||||
|
CREATE FUNCTION array_to_vector(numeric[], integer, boolean) RETURNS vector
|
||||||
|
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||||
|
|
||||||
|
CREATE CAST (numeric[] AS vector)
|
||||||
|
WITH FUNCTION array_to_vector(numeric[], integer, boolean) AS IMPLICIT;
|
||||||
8
sql/vector--0.1.7--0.1.8.sql
Normal file
8
sql/vector--0.1.7--0.1.8.sql
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
-- complain if script is sourced in psql, rather than via CREATE EXTENSION
|
||||||
|
\echo Use "ALTER EXTENSION vector UPDATE TO '0.1.8'" to load this file. \quit
|
||||||
|
|
||||||
|
CREATE FUNCTION vector_to_float4(vector, integer, boolean) RETURNS real[]
|
||||||
|
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||||
|
|
||||||
|
CREATE CAST (vector AS real[])
|
||||||
|
WITH FUNCTION vector_to_float4(vector, integer, boolean) AS IMPLICIT;
|
||||||
2
sql/vector--0.1.8--0.2.0.sql
Normal file
2
sql/vector--0.1.8--0.2.0.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.2.0'" to load this file. \quit
|
||||||
19
sql/vector--0.2.0--0.2.1.sql
Normal file
19
sql/vector--0.2.0--0.2.1.sql
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
-- complain if script is sourced in psql, rather than via CREATE EXTENSION
|
||||||
|
\echo Use "ALTER EXTENSION vector UPDATE TO '0.2.1'" to load this file. \quit
|
||||||
|
|
||||||
|
DROP CAST (integer[] AS vector);
|
||||||
|
DROP CAST (real[] AS vector);
|
||||||
|
DROP CAST (double precision[] AS vector);
|
||||||
|
DROP CAST (numeric[] AS vector);
|
||||||
|
|
||||||
|
CREATE CAST (integer[] AS vector)
|
||||||
|
WITH FUNCTION array_to_vector(integer[], integer, boolean) AS ASSIGNMENT;
|
||||||
|
|
||||||
|
CREATE CAST (real[] AS vector)
|
||||||
|
WITH FUNCTION array_to_vector(real[], integer, boolean) AS ASSIGNMENT;
|
||||||
|
|
||||||
|
CREATE CAST (double precision[] AS vector)
|
||||||
|
WITH FUNCTION array_to_vector(double precision[], integer, boolean) AS ASSIGNMENT;
|
||||||
|
|
||||||
|
CREATE CAST (numeric[] AS vector)
|
||||||
|
WITH FUNCTION array_to_vector(numeric[], integer, boolean) AS ASSIGNMENT;
|
||||||
2
sql/vector--0.2.1--0.2.2.sql
Normal file
2
sql/vector--0.2.1--0.2.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.2.2'" to load this file. \quit
|
||||||
2
sql/vector--0.2.2--0.2.3.sql
Normal file
2
sql/vector--0.2.2--0.2.3.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.2.3'" to load this file. \quit
|
||||||
2
sql/vector--0.2.3--0.2.4.sql
Normal file
2
sql/vector--0.2.3--0.2.4.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.2.4'" to load this file. \quit
|
||||||
2
sql/vector--0.2.4--0.2.5.sql
Normal file
2
sql/vector--0.2.4--0.2.5.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.2.5'" to load this file. \quit
|
||||||
2
sql/vector--0.2.5--0.2.6.sql
Normal file
2
sql/vector--0.2.5--0.2.6.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.2.6'" to load this file. \quit
|
||||||
2
sql/vector--0.2.6--0.2.7.sql
Normal file
2
sql/vector--0.2.6--0.2.7.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.2.7'" to load this file. \quit
|
||||||
2
sql/vector--0.2.7--0.3.0.sql
Normal file
2
sql/vector--0.2.7--0.3.0.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.3.0'" to load this file. \quit
|
||||||
2
sql/vector--0.3.0--0.3.1.sql
Normal file
2
sql/vector--0.3.0--0.3.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.3.1'" to load this file. \quit
|
||||||
2
sql/vector--0.3.1--0.3.2.sql
Normal file
2
sql/vector--0.3.1--0.3.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.3.2'" to load this file. \quit
|
||||||
243
sql/vector.sql
243
sql/vector.sql
@@ -1,15 +1,230 @@
|
|||||||
SET client_min_messages = warning;
|
-- complain if script is sourced in psql, rather than via CREATE EXTENSION
|
||||||
CREATE EXTENSION IF NOT EXISTS vector;
|
\echo Use "CREATE EXTENSION vector" to load this file. \quit
|
||||||
|
|
||||||
SELECT '[1,2,3]'::vector;
|
-- type
|
||||||
SELECT '[-1,2,3]'::vector;
|
|
||||||
SELECT '[hello,1]'::vector;
|
CREATE TYPE vector;
|
||||||
SELECT '[NaN,1]'::vector;
|
|
||||||
SELECT '[Infinity,1]'::vector;
|
CREATE FUNCTION vector_in(cstring, oid, integer) RETURNS vector
|
||||||
SELECT '[-Infinity,1]'::vector;
|
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||||
SELECT '[1,2,3'::vector;
|
|
||||||
SELECT '[1,2,3]9'::vector;
|
CREATE FUNCTION vector_out(vector) RETURNS cstring
|
||||||
SELECT '1,2,3'::vector;
|
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||||
SELECT '[]'::vector;
|
|
||||||
SELECT '[1,]'::vector;
|
CREATE FUNCTION vector_typmod_in(cstring[]) RETURNS integer
|
||||||
SELECT '[1,2,3]'::vector(2);
|
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||||
|
|
||||||
|
CREATE FUNCTION vector_recv(internal, oid, integer) RETURNS vector
|
||||||
|
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||||
|
|
||||||
|
CREATE FUNCTION vector_send(vector) RETURNS bytea
|
||||||
|
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||||
|
|
||||||
|
CREATE TYPE vector (
|
||||||
|
INPUT = vector_in,
|
||||||
|
OUTPUT = vector_out,
|
||||||
|
TYPMOD_IN = vector_typmod_in,
|
||||||
|
RECEIVE = vector_recv,
|
||||||
|
SEND = vector_send
|
||||||
|
);
|
||||||
|
|
||||||
|
-- functions
|
||||||
|
|
||||||
|
CREATE FUNCTION l2_distance(vector, vector) RETURNS float8
|
||||||
|
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||||
|
|
||||||
|
CREATE FUNCTION inner_product(vector, vector) RETURNS float8
|
||||||
|
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||||
|
|
||||||
|
CREATE FUNCTION cosine_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;
|
||||||
|
|
||||||
|
CREATE FUNCTION vector_norm(vector) RETURNS float8
|
||||||
|
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||||
|
|
||||||
|
CREATE FUNCTION vector_add(vector, vector) RETURNS vector
|
||||||
|
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||||
|
|
||||||
|
CREATE FUNCTION vector_sub(vector, vector) RETURNS vector
|
||||||
|
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||||
|
|
||||||
|
-- private functions
|
||||||
|
|
||||||
|
CREATE FUNCTION vector_lt(vector, vector) RETURNS bool
|
||||||
|
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||||
|
|
||||||
|
CREATE FUNCTION vector_le(vector, vector) RETURNS bool
|
||||||
|
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||||
|
|
||||||
|
CREATE FUNCTION vector_eq(vector, vector) RETURNS bool
|
||||||
|
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||||
|
|
||||||
|
CREATE FUNCTION vector_ne(vector, vector) RETURNS bool
|
||||||
|
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||||
|
|
||||||
|
CREATE FUNCTION vector_ge(vector, vector) RETURNS bool
|
||||||
|
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||||
|
|
||||||
|
CREATE FUNCTION vector_gt(vector, vector) RETURNS bool
|
||||||
|
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||||
|
|
||||||
|
CREATE FUNCTION vector_cmp(vector, vector) RETURNS int4
|
||||||
|
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||||
|
|
||||||
|
CREATE FUNCTION vector_l2_squared_distance(vector, vector) RETURNS float8
|
||||||
|
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||||
|
|
||||||
|
CREATE FUNCTION vector_negative_inner_product(vector, vector) RETURNS float8
|
||||||
|
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||||
|
|
||||||
|
CREATE FUNCTION vector_spherical_distance(vector, vector) RETURNS float8
|
||||||
|
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||||
|
|
||||||
|
-- cast functions
|
||||||
|
|
||||||
|
CREATE FUNCTION vector(vector, integer, boolean) RETURNS vector
|
||||||
|
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||||
|
|
||||||
|
CREATE FUNCTION array_to_vector(integer[], integer, boolean) RETURNS vector
|
||||||
|
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||||
|
|
||||||
|
CREATE FUNCTION array_to_vector(real[], integer, boolean) RETURNS vector
|
||||||
|
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||||
|
|
||||||
|
CREATE FUNCTION array_to_vector(double precision[], integer, boolean) RETURNS vector
|
||||||
|
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||||
|
|
||||||
|
CREATE FUNCTION array_to_vector(numeric[], integer, boolean) RETURNS vector
|
||||||
|
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||||
|
|
||||||
|
CREATE FUNCTION vector_to_float4(vector, integer, boolean) RETURNS real[]
|
||||||
|
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||||
|
|
||||||
|
-- casts
|
||||||
|
|
||||||
|
CREATE CAST (vector AS vector)
|
||||||
|
WITH FUNCTION vector(vector, integer, boolean) AS IMPLICIT;
|
||||||
|
|
||||||
|
CREATE CAST (vector AS real[])
|
||||||
|
WITH FUNCTION vector_to_float4(vector, integer, boolean) AS IMPLICIT;
|
||||||
|
|
||||||
|
CREATE CAST (integer[] AS vector)
|
||||||
|
WITH FUNCTION array_to_vector(integer[], integer, boolean) AS ASSIGNMENT;
|
||||||
|
|
||||||
|
CREATE CAST (real[] AS vector)
|
||||||
|
WITH FUNCTION array_to_vector(real[], integer, boolean) AS ASSIGNMENT;
|
||||||
|
|
||||||
|
CREATE CAST (double precision[] AS vector)
|
||||||
|
WITH FUNCTION array_to_vector(double precision[], integer, boolean) AS ASSIGNMENT;
|
||||||
|
|
||||||
|
CREATE CAST (numeric[] AS vector)
|
||||||
|
WITH FUNCTION array_to_vector(numeric[], integer, boolean) AS ASSIGNMENT;
|
||||||
|
|
||||||
|
-- operators
|
||||||
|
|
||||||
|
CREATE OPERATOR <-> (
|
||||||
|
LEFTARG = vector, RIGHTARG = vector, PROCEDURE = l2_distance,
|
||||||
|
COMMUTATOR = '<->'
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE OPERATOR <#> (
|
||||||
|
LEFTARG = vector, RIGHTARG = vector, PROCEDURE = vector_negative_inner_product,
|
||||||
|
COMMUTATOR = '<#>'
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE OPERATOR <=> (
|
||||||
|
LEFTARG = vector, RIGHTARG = vector, PROCEDURE = cosine_distance,
|
||||||
|
COMMUTATOR = '<=>'
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE OPERATOR + (
|
||||||
|
LEFTARG = vector, RIGHTARG = vector, PROCEDURE = vector_add,
|
||||||
|
COMMUTATOR = +
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE OPERATOR - (
|
||||||
|
LEFTARG = vector, RIGHTARG = vector, PROCEDURE = vector_sub,
|
||||||
|
COMMUTATOR = -
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE OPERATOR < (
|
||||||
|
LEFTARG = vector, RIGHTARG = vector, PROCEDURE = vector_lt,
|
||||||
|
COMMUTATOR = > , NEGATOR = >= ,
|
||||||
|
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
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE OPERATOR = (
|
||||||
|
LEFTARG = vector, RIGHTARG = vector, PROCEDURE = vector_eq,
|
||||||
|
COMMUTATOR = = , NEGATOR = <> ,
|
||||||
|
RESTRICT = eqsel, JOIN = eqjoinsel
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE OPERATOR <> (
|
||||||
|
LEFTARG = vector, RIGHTARG = vector, PROCEDURE = vector_ne,
|
||||||
|
COMMUTATOR = <> , NEGATOR = = ,
|
||||||
|
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
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE OPERATOR > (
|
||||||
|
LEFTARG = vector, RIGHTARG = vector, PROCEDURE = vector_gt,
|
||||||
|
COMMUTATOR = < , NEGATOR = <= ,
|
||||||
|
RESTRICT = scalargtsel, JOIN = scalargtjoinsel
|
||||||
|
);
|
||||||
|
|
||||||
|
-- access method
|
||||||
|
|
||||||
|
CREATE FUNCTION ivfflathandler(internal) RETURNS index_am_handler
|
||||||
|
AS 'MODULE_PATHNAME' LANGUAGE C;
|
||||||
|
|
||||||
|
CREATE ACCESS METHOD ivfflat TYPE INDEX HANDLER ivfflathandler;
|
||||||
|
|
||||||
|
COMMENT ON ACCESS METHOD ivfflat IS 'ivfflat index access method';
|
||||||
|
|
||||||
|
-- opclasses
|
||||||
|
|
||||||
|
CREATE OPERATOR CLASS vector_ops
|
||||||
|
DEFAULT FOR TYPE vector USING btree AS
|
||||||
|
OPERATOR 1 < ,
|
||||||
|
OPERATOR 2 <= ,
|
||||||
|
OPERATOR 3 = ,
|
||||||
|
OPERATOR 4 >= ,
|
||||||
|
OPERATOR 5 > ,
|
||||||
|
FUNCTION 1 vector_cmp(vector, vector);
|
||||||
|
|
||||||
|
CREATE OPERATOR CLASS vector_l2_ops
|
||||||
|
DEFAULT FOR TYPE vector USING ivfflat AS
|
||||||
|
OPERATOR 1 <-> (vector, vector) FOR ORDER BY float_ops,
|
||||||
|
FUNCTION 1 vector_l2_squared_distance(vector, vector),
|
||||||
|
FUNCTION 3 l2_distance(vector, vector);
|
||||||
|
|
||||||
|
CREATE OPERATOR CLASS vector_ip_ops
|
||||||
|
FOR TYPE vector USING ivfflat AS
|
||||||
|
OPERATOR 1 <#> (vector, vector) FOR ORDER BY float_ops,
|
||||||
|
FUNCTION 1 vector_negative_inner_product(vector, vector),
|
||||||
|
FUNCTION 3 vector_spherical_distance(vector, vector),
|
||||||
|
FUNCTION 4 vector_norm(vector);
|
||||||
|
|
||||||
|
CREATE OPERATOR CLASS vector_cosine_ops
|
||||||
|
FOR TYPE vector USING ivfflat AS
|
||||||
|
OPERATOR 1 <=> (vector, vector) FOR ORDER BY float_ops,
|
||||||
|
FUNCTION 1 vector_negative_inner_product(vector, vector),
|
||||||
|
FUNCTION 2 vector_norm(vector),
|
||||||
|
FUNCTION 3 vector_spherical_distance(vector, vector),
|
||||||
|
FUNCTION 4 vector_norm(vector);
|
||||||
|
|||||||
187
src/ivfbuild.c
187
src/ivfbuild.c
@@ -7,8 +7,19 @@
|
|||||||
#include "miscadmin.h"
|
#include "miscadmin.h"
|
||||||
#include "storage/bufmgr.h"
|
#include "storage/bufmgr.h"
|
||||||
|
|
||||||
|
#if PG_VERSION_NUM >= 140000
|
||||||
|
#include "utils/backend_progress.h"
|
||||||
|
#elif PG_VERSION_NUM >= 120000
|
||||||
|
#include "pgstat.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
#if PG_VERSION_NUM >= 120000
|
#if PG_VERSION_NUM >= 120000
|
||||||
#include "access/tableam.h"
|
#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
|
#endif
|
||||||
|
|
||||||
#if PG_VERSION_NUM >= 110000
|
#if PG_VERSION_NUM >= 110000
|
||||||
@@ -25,6 +36,12 @@
|
|||||||
#define CALLBACK_ITEM_POINTER HeapTuple hup
|
#define CALLBACK_ITEM_POINTER HeapTuple hup
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if PG_VERSION_NUM >= 120000
|
||||||
|
#define UpdateProgress(index, val) pgstat_progress_update_param(index, val)
|
||||||
|
#else
|
||||||
|
#define UpdateProgress(index, val) ((void)val)
|
||||||
|
#endif
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Callback for sampling
|
* Callback for sampling
|
||||||
*/
|
*/
|
||||||
@@ -41,10 +58,13 @@ SampleCallback(Relation index, CALLBACK_ITEM_POINTER, Datum *values,
|
|||||||
if (isnull[0])
|
if (isnull[0])
|
||||||
return;
|
return;
|
||||||
|
|
||||||
/* Normalize the value */
|
/*
|
||||||
if (buildstate->normprocinfo != NULL)
|
* Normalize with KMEANS_NORM_PROC since spherical distance function
|
||||||
|
* expects unit vectors
|
||||||
|
*/
|
||||||
|
if (buildstate->kmeansnormprocinfo != NULL)
|
||||||
{
|
{
|
||||||
if (!IvfflatNormValue(buildstate->normprocinfo, buildstate->collation, &value, buildstate->normvec))
|
if (!IvfflatNormValue(buildstate->kmeansnormprocinfo, buildstate->collation, &value, buildstate->normvec))
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,7 +80,11 @@ SampleCallback(Relation index, CALLBACK_ITEM_POINTER, Datum *values,
|
|||||||
|
|
||||||
if (buildstate->rowstoskip <= 0)
|
if (buildstate->rowstoskip <= 0)
|
||||||
{
|
{
|
||||||
|
#if PG_VERSION_NUM >= 150000
|
||||||
|
int k = (int) (targsamples * sampler_random_fract(&buildstate->rstate.randstate));
|
||||||
|
#else
|
||||||
int k = (int) (targsamples * sampler_random_fract(buildstate->rstate.randstate));
|
int k = (int) (targsamples * sampler_random_fract(buildstate->rstate.randstate));
|
||||||
|
#endif
|
||||||
|
|
||||||
Assert(k >= 0 && k < targsamples);
|
Assert(k >= 0 && k < targsamples);
|
||||||
VectorArraySet(samples, k, DatumGetVector(value));
|
VectorArraySet(samples, k, DatumGetVector(value));
|
||||||
@@ -79,9 +103,11 @@ SampleRows(IvfflatBuildState * buildstate)
|
|||||||
int targsamples = buildstate->samples->maxlen;
|
int targsamples = buildstate->samples->maxlen;
|
||||||
BlockNumber totalblocks = RelationGetNumberOfBlocks(buildstate->heap);
|
BlockNumber totalblocks = RelationGetNumberOfBlocks(buildstate->heap);
|
||||||
|
|
||||||
|
UpdateProgress(PROGRESS_CREATEIDX_SUBPHASE, PROGRESS_IVFFLAT_PHASE_SAMPLE);
|
||||||
|
|
||||||
buildstate->rowstoskip = -1;
|
buildstate->rowstoskip = -1;
|
||||||
|
|
||||||
BlockSampler_Init(&buildstate->bs, totalblocks, targsamples, random());
|
BlockSampler_Init(&buildstate->bs, totalblocks, targsamples, RandomInt());
|
||||||
|
|
||||||
reservoir_init_selection_state(&buildstate->rstate, targsamples);
|
reservoir_init_selection_state(&buildstate->rstate, targsamples);
|
||||||
while (BlockSampler_HasMore(&buildstate->bs))
|
while (BlockSampler_HasMore(&buildstate->bs))
|
||||||
@@ -90,13 +116,13 @@ SampleRows(IvfflatBuildState * buildstate)
|
|||||||
|
|
||||||
#if PG_VERSION_NUM >= 120000
|
#if PG_VERSION_NUM >= 120000
|
||||||
table_index_build_range_scan(buildstate->heap, buildstate->index, buildstate->indexInfo,
|
table_index_build_range_scan(buildstate->heap, buildstate->index, buildstate->indexInfo,
|
||||||
false, true, true, targblock, 1, SampleCallback, (void *) buildstate, NULL);
|
false, true, false, targblock, 1, SampleCallback, (void *) buildstate, NULL);
|
||||||
#elif PG_VERSION_NUM >= 110000
|
#elif PG_VERSION_NUM >= 110000
|
||||||
IndexBuildHeapRangeScan(buildstate->heap, buildstate->index, buildstate->indexInfo,
|
IndexBuildHeapRangeScan(buildstate->heap, buildstate->index, buildstate->indexInfo,
|
||||||
true, true, targblock, 1, SampleCallback, (void *) buildstate, NULL);
|
false, true, targblock, 1, SampleCallback, (void *) buildstate, NULL);
|
||||||
#else
|
#else
|
||||||
IndexBuildHeapRangeScan(buildstate->heap, buildstate->index, buildstate->indexInfo,
|
IndexBuildHeapRangeScan(buildstate->heap, buildstate->index, buildstate->indexInfo,
|
||||||
true, true, targblock, 1, SampleCallback, (void *) buildstate);
|
false, true, targblock, 1, SampleCallback, (void *) buildstate);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -143,16 +169,20 @@ BuildCallback(Relation index, CALLBACK_ITEM_POINTER, Datum *values,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef IVFFLAT_KMEANS_DEBUG
|
||||||
|
buildstate->inertia += minDistance;
|
||||||
|
buildstate->listSums[closestCenter] += minDistance;
|
||||||
|
buildstate->listCounts[closestCenter]++;
|
||||||
|
#endif
|
||||||
|
|
||||||
/* Create a virtual tuple */
|
/* Create a virtual tuple */
|
||||||
ExecClearTuple(slot);
|
ExecClearTuple(slot);
|
||||||
slot->tts_values[0] = Int32GetDatum(closestCenter);
|
slot->tts_values[0] = Int32GetDatum(closestCenter);
|
||||||
slot->tts_isnull[0] = false;
|
slot->tts_isnull[0] = false;
|
||||||
slot->tts_values[1] = Int32GetDatum(ItemPointerGetBlockNumberNoCheck(tid));
|
slot->tts_values[1] = PointerGetDatum(tid);
|
||||||
slot->tts_isnull[1] = false;
|
slot->tts_isnull[1] = false;
|
||||||
slot->tts_values[2] = Int32GetDatum(ItemPointerGetOffsetNumberNoCheck(tid));
|
slot->tts_values[2] = value;
|
||||||
slot->tts_isnull[2] = false;
|
slot->tts_isnull[2] = false;
|
||||||
slot->tts_values[3] = value;
|
|
||||||
slot->tts_isnull[3] = false;
|
|
||||||
ExecStoreVirtualTuple(slot);
|
ExecStoreVirtualTuple(slot);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -162,6 +192,8 @@ BuildCallback(Relation index, CALLBACK_ITEM_POINTER, Datum *values,
|
|||||||
* need not save it.
|
* need not save it.
|
||||||
*/
|
*/
|
||||||
tuplesort_puttupleslot(buildstate->sortstate, slot);
|
tuplesort_puttupleslot(buildstate->sortstate, slot);
|
||||||
|
|
||||||
|
buildstate->indtuples++;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -172,8 +204,6 @@ GetNextTuple(Tuplesortstate *sortstate, TupleDesc tupdesc, TupleTableSlot *slot,
|
|||||||
{
|
{
|
||||||
Datum value;
|
Datum value;
|
||||||
bool isnull;
|
bool isnull;
|
||||||
int tupblk;
|
|
||||||
int tupoff;
|
|
||||||
|
|
||||||
#if PG_VERSION_NUM >= 100000
|
#if PG_VERSION_NUM >= 100000
|
||||||
if (tuplesort_gettupleslot(sortstate, true, false, slot, NULL))
|
if (tuplesort_gettupleslot(sortstate, true, false, slot, NULL))
|
||||||
@@ -182,13 +212,11 @@ GetNextTuple(Tuplesortstate *sortstate, TupleDesc tupdesc, TupleTableSlot *slot,
|
|||||||
#endif
|
#endif
|
||||||
{
|
{
|
||||||
*list = DatumGetInt32(slot_getattr(slot, 1, &isnull));
|
*list = DatumGetInt32(slot_getattr(slot, 1, &isnull));
|
||||||
tupblk = DatumGetInt32(slot_getattr(slot, 2, &isnull));
|
value = slot_getattr(slot, 3, &isnull);
|
||||||
tupoff = DatumGetInt32(slot_getattr(slot, 3, &isnull));
|
|
||||||
value = slot_getattr(slot, 4, &isnull);
|
|
||||||
|
|
||||||
/* Form the index tuple */
|
/* Form the index tuple */
|
||||||
*itup = index_form_tuple(tupdesc, &value, &isnull);
|
*itup = index_form_tuple(tupdesc, &value, &isnull);
|
||||||
ItemPointerSet(&(*itup)->t_tid, tupblk, tupoff);
|
(*itup)->t_tid = *((ItemPointer) DatumGetPointer(slot_getattr(slot, 2, &isnull)));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
*list = -1;
|
*list = -1;
|
||||||
@@ -205,10 +233,11 @@ InsertTuples(Relation index, IvfflatBuildState * buildstate, ForkNumber forkNum)
|
|||||||
GenericXLogState *state;
|
GenericXLogState *state;
|
||||||
int list;
|
int list;
|
||||||
IndexTuple itup = NULL; /* silence compiler warning */
|
IndexTuple itup = NULL; /* silence compiler warning */
|
||||||
BlockNumber startPage = InvalidBlockNumber;
|
BlockNumber startPage;
|
||||||
BlockNumber insertPage = InvalidBlockNumber;
|
BlockNumber insertPage;
|
||||||
Size itemsz;
|
Size itemsz;
|
||||||
int i;
|
int i;
|
||||||
|
int64 inserted = 0;
|
||||||
|
|
||||||
#if PG_VERSION_NUM >= 120000
|
#if PG_VERSION_NUM >= 120000
|
||||||
TupleTableSlot *slot = MakeSingleTupleTableSlot(buildstate->tupdesc, &TTSOpsMinimalTuple);
|
TupleTableSlot *slot = MakeSingleTupleTableSlot(buildstate->tupdesc, &TTSOpsMinimalTuple);
|
||||||
@@ -217,12 +246,20 @@ InsertTuples(Relation index, IvfflatBuildState * buildstate, ForkNumber forkNum)
|
|||||||
#endif
|
#endif
|
||||||
TupleDesc tupdesc = RelationGetDescr(index);
|
TupleDesc tupdesc = RelationGetDescr(index);
|
||||||
|
|
||||||
|
UpdateProgress(PROGRESS_CREATEIDX_SUBPHASE, PROGRESS_IVFFLAT_PHASE_LOAD);
|
||||||
|
|
||||||
|
UpdateProgress(PROGRESS_CREATEIDX_TUPLES_TOTAL, buildstate->indtuples);
|
||||||
|
|
||||||
GetNextTuple(buildstate->sortstate, tupdesc, slot, &itup, &list);
|
GetNextTuple(buildstate->sortstate, tupdesc, slot, &itup, &list);
|
||||||
|
|
||||||
for (i = 0; i < buildstate->centers->length; i++)
|
for (i = 0; i < buildstate->centers->length; i++)
|
||||||
{
|
{
|
||||||
|
/* Can take a while, so ensure we can interrupt */
|
||||||
|
/* Needs to be called when no buffer locks are held */
|
||||||
|
CHECK_FOR_INTERRUPTS();
|
||||||
|
|
||||||
buf = IvfflatNewBuffer(index, forkNum);
|
buf = IvfflatNewBuffer(index, forkNum);
|
||||||
IvfflatInitPage(index, &buf, &page, &state);
|
IvfflatInitRegisterPage(index, &buf, &page, &state);
|
||||||
|
|
||||||
startPage = BufferGetBlockNumber(buf);
|
startPage = BufferGetBlockNumber(buf);
|
||||||
|
|
||||||
@@ -238,7 +275,9 @@ InsertTuples(Relation index, IvfflatBuildState * buildstate, ForkNumber forkNum)
|
|||||||
if (PageAddItem(page, (Item) itup, itemsz, InvalidOffsetNumber, false, false) == InvalidOffsetNumber)
|
if (PageAddItem(page, (Item) itup, itemsz, InvalidOffsetNumber, false, false) == InvalidOffsetNumber)
|
||||||
elog(ERROR, "failed to add index item to \"%s\"", RelationGetRelationName(index));
|
elog(ERROR, "failed to add index item to \"%s\"", RelationGetRelationName(index));
|
||||||
|
|
||||||
buildstate->indtuples += 1;
|
pfree(itup);
|
||||||
|
|
||||||
|
UpdateProgress(PROGRESS_CREATEIDX_TUPLES_DONE, ++inserted);
|
||||||
|
|
||||||
GetNextTuple(buildstate->sortstate, tupdesc, slot, &itup, &list);
|
GetNextTuple(buildstate->sortstate, tupdesc, slot, &itup, &list);
|
||||||
}
|
}
|
||||||
@@ -248,7 +287,7 @@ InsertTuples(Relation index, IvfflatBuildState * buildstate, ForkNumber forkNum)
|
|||||||
IvfflatCommitBuffer(buf, state);
|
IvfflatCommitBuffer(buf, state);
|
||||||
|
|
||||||
/* Set the start and insert pages */
|
/* Set the start and insert pages */
|
||||||
IvfflatUpdateList(index, state, buildstate->listInfo[i], insertPage, startPage, forkNum);
|
IvfflatUpdateList(index, state, buildstate->listInfo[i], insertPage, InvalidBlockNumber, startPage, forkNum);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -275,21 +314,27 @@ InitBuildState(IvfflatBuildState * buildstate, Relation heap, Relation index, In
|
|||||||
/* Get support functions */
|
/* Get support functions */
|
||||||
buildstate->procinfo = index_getprocinfo(index, 1, IVFFLAT_DISTANCE_PROC);
|
buildstate->procinfo = index_getprocinfo(index, 1, IVFFLAT_DISTANCE_PROC);
|
||||||
buildstate->normprocinfo = IvfflatOptionalProcInfo(index, IVFFLAT_NORM_PROC);
|
buildstate->normprocinfo = IvfflatOptionalProcInfo(index, IVFFLAT_NORM_PROC);
|
||||||
|
buildstate->kmeansnormprocinfo = IvfflatOptionalProcInfo(index, IVFFLAT_KMEANS_NORM_PROC);
|
||||||
buildstate->collation = index->rd_indcollation[0];
|
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)
|
||||||
|
elog(ERROR, "dimensions must be greater than one for this opclass");
|
||||||
|
|
||||||
/* Create tuple description for sorting */
|
/* Create tuple description for sorting */
|
||||||
#if PG_VERSION_NUM >= 120000
|
#if PG_VERSION_NUM >= 120000
|
||||||
buildstate->tupdesc = CreateTemplateTupleDesc(4);
|
buildstate->tupdesc = CreateTemplateTupleDesc(3);
|
||||||
#else
|
#else
|
||||||
buildstate->tupdesc = CreateTemplateTupleDesc(4, false);
|
buildstate->tupdesc = CreateTemplateTupleDesc(3, false);
|
||||||
#endif
|
#endif
|
||||||
TupleDescInitEntry(buildstate->tupdesc, (AttrNumber) 1, "list", INT4OID, -1, 0);
|
TupleDescInitEntry(buildstate->tupdesc, (AttrNumber) 1, "list", INT4OID, -1, 0);
|
||||||
TupleDescInitEntry(buildstate->tupdesc, (AttrNumber) 2, "blkno", INT4OID, -1, 0);
|
TupleDescInitEntry(buildstate->tupdesc, (AttrNumber) 2, "tid", TIDOID, -1, 0);
|
||||||
TupleDescInitEntry(buildstate->tupdesc, (AttrNumber) 3, "offset", INT4OID, -1, 0);
|
|
||||||
#if PG_VERSION_NUM >= 110000
|
#if PG_VERSION_NUM >= 110000
|
||||||
TupleDescInitEntry(buildstate->tupdesc, (AttrNumber) 4, "vector", RelationGetDescr(index)->attrs[0].atttypid, -1, 0);
|
TupleDescInitEntry(buildstate->tupdesc, (AttrNumber) 3, "vector", RelationGetDescr(index)->attrs[0].atttypid, -1, 0);
|
||||||
#else
|
#else
|
||||||
TupleDescInitEntry(buildstate->tupdesc, (AttrNumber) 4, "vector", RelationGetDescr(index)->attrs[0]->atttypid, -1, 0);
|
TupleDescInitEntry(buildstate->tupdesc, (AttrNumber) 3, "vector", RelationGetDescr(index)->attrs[0]->atttypid, -1, 0);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if PG_VERSION_NUM >= 120000
|
#if PG_VERSION_NUM >= 120000
|
||||||
@@ -303,6 +348,12 @@ InitBuildState(IvfflatBuildState * buildstate, Relation heap, Relation index, In
|
|||||||
|
|
||||||
/* Reuse for each tuple */
|
/* Reuse for each tuple */
|
||||||
buildstate->normvec = InitVector(buildstate->dimensions);
|
buildstate->normvec = InitVector(buildstate->dimensions);
|
||||||
|
|
||||||
|
#ifdef IVFFLAT_KMEANS_DEBUG
|
||||||
|
buildstate->inertia = 0;
|
||||||
|
buildstate->listSums = palloc0(sizeof(double) * buildstate->lists);
|
||||||
|
buildstate->listCounts = palloc0(sizeof(int) * buildstate->lists);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -311,9 +362,14 @@ InitBuildState(IvfflatBuildState * buildstate, Relation heap, Relation index, In
|
|||||||
static void
|
static void
|
||||||
FreeBuildState(IvfflatBuildState * buildstate)
|
FreeBuildState(IvfflatBuildState * buildstate)
|
||||||
{
|
{
|
||||||
pfree(buildstate->centers);
|
VectorArrayFree(buildstate->centers);
|
||||||
pfree(buildstate->listInfo);
|
pfree(buildstate->listInfo);
|
||||||
pfree(buildstate->normvec);
|
pfree(buildstate->normvec);
|
||||||
|
|
||||||
|
#ifdef IVFFLAT_KMEANS_DEBUG
|
||||||
|
pfree(buildstate->listSums);
|
||||||
|
pfree(buildstate->listCounts);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -330,16 +386,22 @@ ComputeCenters(IvfflatBuildState * buildstate)
|
|||||||
if (numSamples < 10000)
|
if (numSamples < 10000)
|
||||||
numSamples = 10000;
|
numSamples = 10000;
|
||||||
|
|
||||||
/* Sample samples */
|
/* Skip samples for unlogged table */
|
||||||
|
if (buildstate->heap == NULL)
|
||||||
|
numSamples = 1;
|
||||||
|
|
||||||
|
/* Sample rows */
|
||||||
|
/* TODO Ensure within maintenance_work_mem */
|
||||||
buildstate->samples = VectorArrayInit(numSamples, buildstate->dimensions);
|
buildstate->samples = VectorArrayInit(numSamples, buildstate->dimensions);
|
||||||
if (buildstate->heap != NULL)
|
if (buildstate->heap != NULL)
|
||||||
SampleRows(buildstate);
|
SampleRows(buildstate);
|
||||||
|
|
||||||
/* Calculate centers */
|
/* Calculate centers */
|
||||||
IvfflatKmeans(buildstate->index, buildstate->samples, buildstate->centers);
|
UpdateProgress(PROGRESS_CREATEIDX_SUBPHASE, PROGRESS_IVFFLAT_PHASE_KMEANS);
|
||||||
|
IvfflatBench("k-means", IvfflatKmeans(buildstate->index, buildstate->samples, buildstate->centers));
|
||||||
|
|
||||||
/* Free samples before we allocate more memory */
|
/* Free samples before we allocate more memory */
|
||||||
pfree(buildstate->samples);
|
VectorArrayFree(buildstate->samples);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -354,7 +416,7 @@ CreateMetaPage(Relation index, int dimensions, int lists, ForkNumber forkNum)
|
|||||||
IvfflatMetaPage metap;
|
IvfflatMetaPage metap;
|
||||||
|
|
||||||
buf = IvfflatNewBuffer(index, forkNum);
|
buf = IvfflatNewBuffer(index, forkNum);
|
||||||
IvfflatInitPage(index, &buf, &page, &state);
|
IvfflatInitRegisterPage(index, &buf, &page, &state);
|
||||||
|
|
||||||
/* Set metapage data */
|
/* Set metapage data */
|
||||||
metap = IvfflatPageGetMeta(page);
|
metap = IvfflatPageGetMeta(page);
|
||||||
@@ -387,7 +449,7 @@ CreateListPages(Relation index, VectorArray centers, int dimensions,
|
|||||||
list = palloc(itemsz);
|
list = palloc(itemsz);
|
||||||
|
|
||||||
buf = IvfflatNewBuffer(index, forkNum);
|
buf = IvfflatNewBuffer(index, forkNum);
|
||||||
IvfflatInitPage(index, &buf, &page, &state);
|
IvfflatInitRegisterPage(index, &buf, &page, &state);
|
||||||
|
|
||||||
for (i = 0; i < lists; i++)
|
for (i = 0; i < lists; i++)
|
||||||
{
|
{
|
||||||
@@ -415,6 +477,51 @@ CreateListPages(Relation index, VectorArray centers, int dimensions,
|
|||||||
pfree(list);
|
pfree(list);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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)
|
||||||
|
{
|
||||||
|
double db = 0.0;
|
||||||
|
|
||||||
|
/* Calculate average distance */
|
||||||
|
for (int i = 0; i < buildstate->lists; i++)
|
||||||
|
{
|
||||||
|
if (buildstate->listCounts[i] > 0)
|
||||||
|
buildstate->listSums[i] /= buildstate->listCounts[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < buildstate->lists; i++)
|
||||||
|
{
|
||||||
|
double max = 0.0;
|
||||||
|
double distance;
|
||||||
|
|
||||||
|
for (int j = 0; j < buildstate->lists; j++)
|
||||||
|
{
|
||||||
|
if (j == i)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
distance = DatumGetFloat8(FunctionCall2Coll(buildstate->procinfo, buildstate->collation, PointerGetDatum(VectorArrayGet(buildstate->centers, i)), PointerGetDatum(VectorArrayGet(buildstate->centers, j))));
|
||||||
|
distance = (buildstate->listSums[i] + buildstate->listSums[j]) / distance;
|
||||||
|
|
||||||
|
if (distance > max)
|
||||||
|
max = distance;
|
||||||
|
}
|
||||||
|
db += max;
|
||||||
|
}
|
||||||
|
db /= buildstate->lists;
|
||||||
|
elog(INFO, "davies-bouldin: %.3f", db);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Create entry pages
|
* Create entry pages
|
||||||
*/
|
*/
|
||||||
@@ -426,6 +533,8 @@ CreateEntryPages(IvfflatBuildState * buildstate, ForkNumber forkNum)
|
|||||||
Oid sortCollations[] = {InvalidOid};
|
Oid sortCollations[] = {InvalidOid};
|
||||||
bool nullsFirstFlags[] = {false};
|
bool nullsFirstFlags[] = {false};
|
||||||
|
|
||||||
|
UpdateProgress(PROGRESS_CREATEIDX_SUBPHASE, PROGRESS_IVFFLAT_PHASE_SORT);
|
||||||
|
|
||||||
#if PG_VERSION_NUM >= 110000
|
#if PG_VERSION_NUM >= 110000
|
||||||
buildstate->sortstate = tuplesort_begin_heap(buildstate->tupdesc, 1, attNums, sortOperators, sortCollations, nullsFirstFlags, maintenance_work_mem, NULL, false);
|
buildstate->sortstate = tuplesort_begin_heap(buildstate->tupdesc, 1, attNums, sortOperators, sortCollations, nullsFirstFlags, maintenance_work_mem, NULL, false);
|
||||||
#else
|
#else
|
||||||
@@ -447,8 +556,14 @@ CreateEntryPages(IvfflatBuildState * buildstate, ForkNumber forkNum)
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Sort and insert */
|
/* Sort */
|
||||||
tuplesort_performsort(buildstate->sortstate);
|
tuplesort_performsort(buildstate->sortstate);
|
||||||
|
|
||||||
|
#ifdef IVFFLAT_KMEANS_DEBUG
|
||||||
|
PrintKmeansMetrics(buildstate);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Insert */
|
||||||
InsertTuples(buildstate->index, buildstate, forkNum);
|
InsertTuples(buildstate->index, buildstate, forkNum);
|
||||||
tuplesort_end(buildstate->sortstate);
|
tuplesort_end(buildstate->sortstate);
|
||||||
}
|
}
|
||||||
@@ -467,7 +582,7 @@ BuildIndex(Relation heap, Relation index, IndexInfo *indexInfo,
|
|||||||
/* Create pages */
|
/* Create pages */
|
||||||
CreateMetaPage(index, buildstate->dimensions, buildstate->lists, forkNum);
|
CreateMetaPage(index, buildstate->dimensions, buildstate->lists, forkNum);
|
||||||
CreateListPages(index, buildstate->centers, buildstate->dimensions, buildstate->lists, forkNum, &buildstate->listInfo);
|
CreateListPages(index, buildstate->centers, buildstate->dimensions, buildstate->lists, forkNum, &buildstate->listInfo);
|
||||||
CreateEntryPages(buildstate, forkNum);
|
IvfflatBench("CreateEntryPages", CreateEntryPages(buildstate, forkNum));
|
||||||
|
|
||||||
FreeBuildState(buildstate);
|
FreeBuildState(buildstate);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,18 @@
|
|||||||
#include "postgres.h"
|
#include "postgres.h"
|
||||||
|
|
||||||
|
#include <float.h>
|
||||||
|
|
||||||
#include "access/amapi.h"
|
#include "access/amapi.h"
|
||||||
#include "commands/vacuum.h"
|
#include "commands/vacuum.h"
|
||||||
#include "ivfflat.h"
|
#include "ivfflat.h"
|
||||||
#include "utils/guc.h"
|
#include "utils/guc.h"
|
||||||
#include "utils/selfuncs.h"
|
#include "utils/selfuncs.h"
|
||||||
|
|
||||||
|
#if PG_VERSION_NUM >= 120000
|
||||||
|
#include "commands/progress.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
int ivfflat_probes;
|
||||||
static relopt_kind ivfflat_relopt_kind;
|
static relopt_kind ivfflat_relopt_kind;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -27,10 +34,33 @@ _PG_init(void)
|
|||||||
1, 1, IVFFLAT_MAX_LISTS, PGC_USERSET, 0, NULL, NULL, NULL);
|
1, 1, IVFFLAT_MAX_LISTS, PGC_USERSET, 0, NULL, NULL, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Get the name of index build phase
|
||||||
|
*/
|
||||||
|
#if PG_VERSION_NUM >= 120000
|
||||||
|
static char *
|
||||||
|
ivfflatbuildphasename(int64 phasenum)
|
||||||
|
{
|
||||||
|
switch (phasenum)
|
||||||
|
{
|
||||||
|
case PROGRESS_CREATEIDX_SUBPHASE_INITIALIZE:
|
||||||
|
return "initializing";
|
||||||
|
case PROGRESS_IVFFLAT_PHASE_SAMPLE:
|
||||||
|
return "sampling table";
|
||||||
|
case PROGRESS_IVFFLAT_PHASE_KMEANS:
|
||||||
|
return "performing k-means";
|
||||||
|
case PROGRESS_IVFFLAT_PHASE_SORT:
|
||||||
|
return "sorting tuples";
|
||||||
|
case PROGRESS_IVFFLAT_PHASE_LOAD:
|
||||||
|
return "loading tuples";
|
||||||
|
default:
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Estimate the cost of an index scan
|
* Estimate the cost of an index scan
|
||||||
*
|
|
||||||
* TODO Improve cost estimation
|
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
ivfflatcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
|
ivfflatcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
|
||||||
@@ -42,22 +72,47 @@ ivfflatcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
|
|||||||
)
|
)
|
||||||
{
|
{
|
||||||
GenericCosts costs;
|
GenericCosts costs;
|
||||||
|
int lists;
|
||||||
|
double ratio;
|
||||||
|
Relation indexRel;
|
||||||
#if PG_VERSION_NUM < 120000
|
#if PG_VERSION_NUM < 120000
|
||||||
List *qinfos;
|
List *qinfos;
|
||||||
|
|
||||||
qinfos = deconstruct_indexquals(path);
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/* Never use index without order */
|
||||||
|
if (path->indexorderbys == NULL)
|
||||||
|
{
|
||||||
|
*indexStartupCost = DBL_MAX;
|
||||||
|
*indexTotalCost = DBL_MAX;
|
||||||
|
*indexSelectivity = 0;
|
||||||
|
*indexCorrelation = 0;
|
||||||
|
#if PG_VERSION_NUM >= 100000
|
||||||
|
*indexPages = 0;
|
||||||
|
#endif
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
MemSet(&costs, 0, sizeof(costs));
|
MemSet(&costs, 0, sizeof(costs));
|
||||||
|
|
||||||
#if PG_VERSION_NUM >= 120000
|
#if PG_VERSION_NUM >= 120000
|
||||||
genericcostestimate(root, path, loop_count, &costs);
|
genericcostestimate(root, path, loop_count, &costs);
|
||||||
#else
|
#else
|
||||||
|
qinfos = deconstruct_indexquals(path);
|
||||||
genericcostestimate(root, path, loop_count, qinfos, &costs);
|
genericcostestimate(root, path, loop_count, qinfos, &costs);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
*indexStartupCost = costs.indexStartupCost;
|
indexRel = index_open(path->indexinfo->indexoid, NoLock);
|
||||||
|
lists = IvfflatGetLists(indexRel);
|
||||||
|
index_close(indexRel, NoLock);
|
||||||
|
|
||||||
|
ratio = ((double) ivfflat_probes) / lists;
|
||||||
|
if (ratio > 1)
|
||||||
|
ratio = 1;
|
||||||
|
|
||||||
|
costs.indexTotalCost *= ratio;
|
||||||
|
|
||||||
|
/* Startup cost and total cost are same */
|
||||||
|
*indexStartupCost = costs.indexTotalCost;
|
||||||
*indexTotalCost = costs.indexTotalCost;
|
*indexTotalCost = costs.indexTotalCost;
|
||||||
*indexSelectivity = costs.indexSelectivity;
|
*indexSelectivity = costs.indexSelectivity;
|
||||||
*indexCorrelation = costs.indexCorrelation;
|
*indexCorrelation = costs.indexCorrelation;
|
||||||
@@ -104,7 +159,12 @@ ivfflatvalidate(Oid opclassoid)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
PG_FUNCTION_INFO_V1(ivfflathandler);
|
/*
|
||||||
|
* Define index handler
|
||||||
|
*
|
||||||
|
* See https://www.postgresql.org/docs/current/index-api.html
|
||||||
|
*/
|
||||||
|
PGDLLEXPORT PG_FUNCTION_INFO_V1(ivfflathandler);
|
||||||
Datum
|
Datum
|
||||||
ivfflathandler(PG_FUNCTION_ARGS)
|
ivfflathandler(PG_FUNCTION_ARGS)
|
||||||
{
|
{
|
||||||
@@ -134,23 +194,27 @@ ivfflathandler(PG_FUNCTION_ARGS)
|
|||||||
#endif
|
#endif
|
||||||
#if PG_VERSION_NUM >= 130000
|
#if PG_VERSION_NUM >= 130000
|
||||||
amroutine->amusemaintenanceworkmem = false; /* not used during VACUUM */
|
amroutine->amusemaintenanceworkmem = false; /* not used during VACUUM */
|
||||||
amroutine->amparallelvacuumoptions = VACUUM_OPTION_NO_PARALLEL; /* TODO support parallel */
|
amroutine->amparallelvacuumoptions = VACUUM_OPTION_PARALLEL_BULKDEL;
|
||||||
#endif
|
#endif
|
||||||
amroutine->amkeytype = InvalidOid;
|
amroutine->amkeytype = InvalidOid;
|
||||||
|
|
||||||
|
/* Interface functions */
|
||||||
amroutine->ambuild = ivfflatbuild;
|
amroutine->ambuild = ivfflatbuild;
|
||||||
amroutine->ambuildempty = ivfflatbuildempty;
|
amroutine->ambuildempty = ivfflatbuildempty;
|
||||||
amroutine->aminsert = ivfflatinsert;
|
amroutine->aminsert = ivfflatinsert;
|
||||||
amroutine->ambulkdelete = ivfflatbulkdelete;
|
amroutine->ambulkdelete = ivfflatbulkdelete;
|
||||||
amroutine->amvacuumcleanup = ivfflatvacuumcleanup;
|
amroutine->amvacuumcleanup = ivfflatvacuumcleanup;
|
||||||
amroutine->amcanreturn = NULL;
|
amroutine->amcanreturn = NULL; /* tuple not included in heapsort */
|
||||||
amroutine->amcostestimate = ivfflatcostestimate;
|
amroutine->amcostestimate = ivfflatcostestimate;
|
||||||
amroutine->amoptions = ivfflatoptions;
|
amroutine->amoptions = ivfflatoptions;
|
||||||
amroutine->amproperty = NULL; /* TODO AMPROP_DISTANCE_ORDERABLE */
|
amroutine->amproperty = NULL; /* TODO AMPROP_DISTANCE_ORDERABLE */
|
||||||
#if PG_VERSION_NUM >= 120000
|
#if PG_VERSION_NUM >= 120000
|
||||||
amroutine->ambuildphasename = NULL;
|
amroutine->ambuildphasename = ivfflatbuildphasename;
|
||||||
#endif
|
#endif
|
||||||
amroutine->amvalidate = ivfflatvalidate;
|
amroutine->amvalidate = ivfflatvalidate;
|
||||||
|
#if PG_VERSION_NUM >= 140000
|
||||||
|
amroutine->amadjustmembers = NULL;
|
||||||
|
#endif
|
||||||
amroutine->ambeginscan = ivfflatbeginscan;
|
amroutine->ambeginscan = ivfflatbeginscan;
|
||||||
amroutine->amrescan = ivfflatrescan;
|
amroutine->amrescan = ivfflatrescan;
|
||||||
amroutine->amgettuple = ivfflatgettuple;
|
amroutine->amgettuple = ivfflatgettuple;
|
||||||
@@ -158,6 +222,8 @@ ivfflathandler(PG_FUNCTION_ARGS)
|
|||||||
amroutine->amendscan = ivfflatendscan;
|
amroutine->amendscan = ivfflatendscan;
|
||||||
amroutine->ammarkpos = NULL;
|
amroutine->ammarkpos = NULL;
|
||||||
amroutine->amrestrpos = NULL;
|
amroutine->amrestrpos = NULL;
|
||||||
|
|
||||||
|
/* Interface functions to support parallel index scans */
|
||||||
#if PG_VERSION_NUM >= 100000
|
#if PG_VERSION_NUM >= 100000
|
||||||
amroutine->amestimateparallelscan = NULL;
|
amroutine->amestimateparallelscan = NULL;
|
||||||
amroutine->aminitparallelscan = NULL;
|
amroutine->aminitparallelscan = NULL;
|
||||||
|
|||||||
@@ -3,13 +3,26 @@
|
|||||||
|
|
||||||
#include "postgres.h"
|
#include "postgres.h"
|
||||||
|
|
||||||
|
#if PG_VERSION_NUM < 100000
|
||||||
|
#error "Requires PostgreSQL 10+"
|
||||||
|
#endif
|
||||||
|
|
||||||
#include "access/generic_xlog.h"
|
#include "access/generic_xlog.h"
|
||||||
#include "access/reloptions.h"
|
#include "access/reloptions.h"
|
||||||
#include "nodes/execnodes.h"
|
#include "nodes/execnodes.h"
|
||||||
|
#include "port.h" /* for strtof() and random() */
|
||||||
#include "utils/sampling.h"
|
#include "utils/sampling.h"
|
||||||
#include "utils/tuplesort.h"
|
#include "utils/tuplesort.h"
|
||||||
#include "vector.h"
|
#include "vector.h"
|
||||||
|
|
||||||
|
#if PG_VERSION_NUM >= 150000
|
||||||
|
#include "common/pg_prng.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef IVFFLAT_BENCH
|
||||||
|
#include "portability/instr_time.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
/* Support functions */
|
/* Support functions */
|
||||||
#define IVFFLAT_DISTANCE_PROC 1
|
#define IVFFLAT_DISTANCE_PROC 1
|
||||||
#define IVFFLAT_NORM_PROC 2
|
#define IVFFLAT_NORM_PROC 2
|
||||||
@@ -27,25 +40,53 @@
|
|||||||
#define IVFFLAT_DEFAULT_LISTS 100
|
#define IVFFLAT_DEFAULT_LISTS 100
|
||||||
#define IVFFLAT_MAX_LISTS 32768
|
#define IVFFLAT_MAX_LISTS 32768
|
||||||
|
|
||||||
|
/* Build phases */
|
||||||
|
/* PROGRESS_CREATEIDX_SUBPHASE_INITIALIZE is 1 */
|
||||||
|
#define PROGRESS_IVFFLAT_PHASE_SAMPLE 2
|
||||||
|
#define PROGRESS_IVFFLAT_PHASE_KMEANS 3
|
||||||
|
#define PROGRESS_IVFFLAT_PHASE_SORT 4
|
||||||
|
#define PROGRESS_IVFFLAT_PHASE_LOAD 5
|
||||||
|
|
||||||
#define IVFFLAT_LIST_SIZE(_dim) (offsetof(IvfflatListData, center) + VECTOR_SIZE(_dim))
|
#define IVFFLAT_LIST_SIZE(_dim) (offsetof(IvfflatListData, center) + VECTOR_SIZE(_dim))
|
||||||
|
|
||||||
#define IvfflatPageGetOpaque(page) ((IvfflatPageOpaque) PageGetSpecialPointer(page))
|
#define IvfflatPageGetOpaque(page) ((IvfflatPageOpaque) PageGetSpecialPointer(page))
|
||||||
#define IvfflatPageGetMeta(page) ((IvfflatMetaPageData *) PageGetContents(page))
|
#define IvfflatPageGetMeta(page) ((IvfflatMetaPageData *) PageGetContents(page))
|
||||||
|
|
||||||
#if PG_VERSION_NUM < 100000
|
#ifdef IVFFLAT_BENCH
|
||||||
#define ItemPointerGetBlockNumberNoCheck ItemPointerGetBlockNumber
|
#define IvfflatBench(name, code) \
|
||||||
#define ItemPointerGetOffsetNumberNoCheck ItemPointerGetOffsetNumber
|
do { \
|
||||||
|
instr_time start; \
|
||||||
|
instr_time duration; \
|
||||||
|
INSTR_TIME_SET_CURRENT(start); \
|
||||||
|
(code); \
|
||||||
|
INSTR_TIME_SET_CURRENT(duration); \
|
||||||
|
INSTR_TIME_SUBTRACT(duration, start); \
|
||||||
|
elog(INFO, "%s: %.3f ms", name, INSTR_TIME_GET_MILLISEC(duration)); \
|
||||||
|
} while (0)
|
||||||
|
#else
|
||||||
|
#define IvfflatBench(name, code) (code)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if PG_VERSION_NUM >= 150000
|
||||||
|
#define RandomDouble() pg_prng_double(&pg_global_prng_state)
|
||||||
|
#define RandomInt() pg_prng_uint32(&pg_global_prng_state)
|
||||||
|
#else
|
||||||
|
#define RandomDouble() (((double) random()) / MAX_RANDOM_VALUE)
|
||||||
|
#define RandomInt() random()
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/* Variables */
|
/* Variables */
|
||||||
int ivfflat_probes;
|
extern int ivfflat_probes;
|
||||||
|
|
||||||
|
/* Exported functions */
|
||||||
|
PGDLLEXPORT void _PG_init(void);
|
||||||
|
|
||||||
typedef struct VectorArrayData
|
typedef struct VectorArrayData
|
||||||
{
|
{
|
||||||
int length;
|
int length;
|
||||||
int maxlen;
|
int maxlen;
|
||||||
int dim;
|
int dim;
|
||||||
Vector items[FLEXIBLE_ARRAY_MEMBER];
|
Vector *items;
|
||||||
} VectorArrayData;
|
} VectorArrayData;
|
||||||
|
|
||||||
typedef VectorArrayData * VectorArray;
|
typedef VectorArrayData * VectorArray;
|
||||||
@@ -81,6 +122,7 @@ typedef struct IvfflatBuildState
|
|||||||
/* Support functions */
|
/* Support functions */
|
||||||
FmgrInfo *procinfo;
|
FmgrInfo *procinfo;
|
||||||
FmgrInfo *normprocinfo;
|
FmgrInfo *normprocinfo;
|
||||||
|
FmgrInfo *kmeansnormprocinfo;
|
||||||
Oid collation;
|
Oid collation;
|
||||||
|
|
||||||
/* Variables */
|
/* Variables */
|
||||||
@@ -89,6 +131,12 @@ typedef struct IvfflatBuildState
|
|||||||
ListInfo *listInfo;
|
ListInfo *listInfo;
|
||||||
Vector *normvec;
|
Vector *normvec;
|
||||||
|
|
||||||
|
#ifdef IVFFLAT_KMEANS_DEBUG
|
||||||
|
double inertia;
|
||||||
|
double *listSums;
|
||||||
|
int *listCounts;
|
||||||
|
#endif
|
||||||
|
|
||||||
/* Sampling */
|
/* Sampling */
|
||||||
BlockSamplerData bs;
|
BlockSamplerData bs;
|
||||||
ReservoirStateData rstate;
|
ReservoirStateData rstate;
|
||||||
@@ -130,6 +178,7 @@ typedef IvfflatListData * IvfflatList;
|
|||||||
|
|
||||||
typedef struct IvfflatScanList
|
typedef struct IvfflatScanList
|
||||||
{
|
{
|
||||||
|
pairingheap_node ph_node;
|
||||||
BlockNumber startPage;
|
BlockNumber startPage;
|
||||||
double distance;
|
double distance;
|
||||||
} IvfflatScanList;
|
} IvfflatScanList;
|
||||||
@@ -151,34 +200,40 @@ typedef struct IvfflatScanOpaqueData
|
|||||||
FmgrInfo *normprocinfo;
|
FmgrInfo *normprocinfo;
|
||||||
Oid collation;
|
Oid collation;
|
||||||
|
|
||||||
|
/* Lists */
|
||||||
|
pairingheap *listQueue;
|
||||||
IvfflatScanList lists[FLEXIBLE_ARRAY_MEMBER]; /* must come last */
|
IvfflatScanList lists[FLEXIBLE_ARRAY_MEMBER]; /* must come last */
|
||||||
} IvfflatScanOpaqueData;
|
} IvfflatScanOpaqueData;
|
||||||
|
|
||||||
typedef IvfflatScanOpaqueData * IvfflatScanOpaque;
|
typedef IvfflatScanOpaqueData * IvfflatScanOpaque;
|
||||||
|
|
||||||
#define VECTOR_ARRAY_SIZE(_length, _dim) (offsetof(VectorArrayData, items) + _length * VECTOR_SIZE(_dim))
|
#define VECTOR_ARRAY_SIZE(_length, _dim) (sizeof(VectorArrayData) + (_length) * VECTOR_SIZE(_dim))
|
||||||
#define VECTOR_ARRAY_OFFSET(_arr, _offset) ((char*) _arr + offsetof(VectorArrayData, items) + (_offset) * VECTOR_SIZE(_arr->dim))
|
#define VECTOR_ARRAY_OFFSET(_arr, _offset) ((char*) (_arr)->items + (_offset) * VECTOR_SIZE((_arr)->dim))
|
||||||
#define VectorArrayGet(_arr, _offset) ((Vector *) VECTOR_ARRAY_OFFSET(_arr, _offset))
|
#define VectorArrayGet(_arr, _offset) ((Vector *) VECTOR_ARRAY_OFFSET(_arr, _offset))
|
||||||
#define VectorArraySet(_arr, _offset, _val) (memcpy(VECTOR_ARRAY_OFFSET(_arr, _offset), _val, VECTOR_SIZE(_arr->dim)))
|
#define VectorArraySet(_arr, _offset, _val) memcpy(VECTOR_ARRAY_OFFSET(_arr, _offset), _val, VECTOR_SIZE((_arr)->dim))
|
||||||
|
|
||||||
/* Methods */
|
/* Methods */
|
||||||
void _PG_init(void);
|
|
||||||
VectorArray VectorArrayInit(int maxlen, int dimensions);
|
VectorArray VectorArrayInit(int maxlen, int dimensions);
|
||||||
|
void VectorArrayFree(VectorArray arr);
|
||||||
void PrintVectorArray(char *msg, VectorArray arr);
|
void PrintVectorArray(char *msg, VectorArray arr);
|
||||||
void IvfflatKmeans(Relation index, VectorArray samples, VectorArray centers);
|
void IvfflatKmeans(Relation index, VectorArray samples, VectorArray centers);
|
||||||
FmgrInfo *IvfflatOptionalProcInfo(Relation rel, uint16 procnum);
|
FmgrInfo *IvfflatOptionalProcInfo(Relation rel, uint16 procnum);
|
||||||
bool IvfflatNormValue(FmgrInfo *procinfo, Oid collation, Datum *value, Vector * result);
|
bool IvfflatNormValue(FmgrInfo *procinfo, Oid collation, Datum *value, Vector * result);
|
||||||
int IvfflatGetLists(Relation index);
|
int IvfflatGetLists(Relation index);
|
||||||
void IvfflatUpdateList(Relation index, GenericXLogState *state, ListInfo listInfo, BlockNumber insertPage, BlockNumber startPage, ForkNumber forkNum);
|
void IvfflatUpdateList(Relation index, GenericXLogState *state, ListInfo listInfo, BlockNumber insertPage, BlockNumber originalInsertPage, BlockNumber startPage, ForkNumber forkNum);
|
||||||
void IvfflatCommitBuffer(Buffer buf, GenericXLogState *state);
|
void IvfflatCommitBuffer(Buffer buf, GenericXLogState *state);
|
||||||
void IvfflatAppendPage(Relation index, Buffer *buf, Page *page, GenericXLogState **state, ForkNumber forkNum);
|
void IvfflatAppendPage(Relation index, Buffer *buf, Page *page, GenericXLogState **state, ForkNumber forkNum);
|
||||||
Buffer IvfflatNewBuffer(Relation index, ForkNumber forkNum);
|
Buffer IvfflatNewBuffer(Relation index, ForkNumber forkNum);
|
||||||
void IvfflatInitPage(Relation index, Buffer *buf, Page *page, GenericXLogState **state);
|
void IvfflatInitPage(Buffer buf, Page page);
|
||||||
|
void IvfflatInitRegisterPage(Relation index, Buffer *buf, Page *page, GenericXLogState **state);
|
||||||
|
|
||||||
/* Index access methods */
|
/* Index access methods */
|
||||||
IndexBuildResult *ivfflatbuild(Relation heap, Relation index, IndexInfo *indexInfo);
|
IndexBuildResult *ivfflatbuild(Relation heap, Relation index, IndexInfo *indexInfo);
|
||||||
void ivfflatbuildempty(Relation index);
|
void ivfflatbuildempty(Relation index);
|
||||||
bool ivfflatinsert(Relation index, Datum *values, bool *isnull, ItemPointer heap_tid, Relation heap, IndexUniqueCheck checkUnique
|
bool ivfflatinsert(Relation index, Datum *values, bool *isnull, ItemPointer heap_tid, Relation heap, IndexUniqueCheck checkUnique
|
||||||
|
#if PG_VERSION_NUM >= 140000
|
||||||
|
,bool indexUnchanged
|
||||||
|
#endif
|
||||||
#if PG_VERSION_NUM >= 100000
|
#if PG_VERSION_NUM >= 100000
|
||||||
,IndexInfo *indexInfo
|
,IndexInfo *indexInfo
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -53,18 +53,6 @@ FindInsertPage(Relation rel, Datum *values, BlockNumber *insertPage, ListInfo *
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* Prepare to insert an index tuple
|
|
||||||
*/
|
|
||||||
static void
|
|
||||||
LoadInsertPage(Relation index, Buffer *buf, Page *page, GenericXLogState **state, BlockNumber insertPage)
|
|
||||||
{
|
|
||||||
*buf = ReadBuffer(index, insertPage);
|
|
||||||
LockBuffer(*buf, BUFFER_LOCK_EXCLUSIVE);
|
|
||||||
*state = GenericXLogStart(index);
|
|
||||||
*page = GenericXLogRegisterBuffer(*state, *buf, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Insert a tuple into the index
|
* Insert a tuple into the index
|
||||||
*/
|
*/
|
||||||
@@ -77,20 +65,28 @@ InsertTuple(Relation rel, IndexTuple itup, Relation heapRel, Datum *values)
|
|||||||
Size itemsz;
|
Size itemsz;
|
||||||
BlockNumber insertPage = InvalidBlockNumber;
|
BlockNumber insertPage = InvalidBlockNumber;
|
||||||
ListInfo listInfo;
|
ListInfo listInfo;
|
||||||
bool newPage = false;
|
BlockNumber originalInsertPage;
|
||||||
|
|
||||||
/* Find the insert page - sets the page and list info */
|
/* Find the insert page - sets the page and list info */
|
||||||
FindInsertPage(rel, values, &insertPage, &listInfo);
|
FindInsertPage(rel, values, &insertPage, &listInfo);
|
||||||
Assert(BlockNumberIsValid(insertPage));
|
Assert(BlockNumberIsValid(insertPage));
|
||||||
|
originalInsertPage = insertPage;
|
||||||
|
|
||||||
itemsz = MAXALIGN(IndexTupleSize(itup));
|
itemsz = MAXALIGN(IndexTupleSize(itup));
|
||||||
Assert(itemsz <= BLCKSZ - MAXALIGN(SizeOfPageHeaderData) - MAXALIGN(sizeof(IvfflatPageOpaqueData)));
|
Assert(itemsz <= BLCKSZ - MAXALIGN(SizeOfPageHeaderData) - MAXALIGN(sizeof(IvfflatPageOpaqueData)));
|
||||||
|
|
||||||
LoadInsertPage(rel, &buf, &page, &state, insertPage);
|
|
||||||
|
|
||||||
/* Find a page to insert the item */
|
/* Find a page to insert the item */
|
||||||
while (PageGetFreeSpace(page) < itemsz)
|
for (;;)
|
||||||
{
|
{
|
||||||
|
buf = ReadBuffer(rel, insertPage);
|
||||||
|
LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
|
||||||
|
|
||||||
|
state = GenericXLogStart(rel);
|
||||||
|
page = GenericXLogRegisterBuffer(state, buf, 0);
|
||||||
|
|
||||||
|
if (PageGetFreeSpace(page) >= itemsz)
|
||||||
|
break;
|
||||||
|
|
||||||
insertPage = IvfflatPageGetOpaque(page)->nextblkno;
|
insertPage = IvfflatPageGetOpaque(page)->nextblkno;
|
||||||
|
|
||||||
if (BlockNumberIsValid(insertPage))
|
if (BlockNumberIsValid(insertPage))
|
||||||
@@ -98,16 +94,45 @@ InsertTuple(Relation rel, IndexTuple itup, Relation heapRel, Datum *values)
|
|||||||
/* Move to next page */
|
/* Move to next page */
|
||||||
GenericXLogAbort(state);
|
GenericXLogAbort(state);
|
||||||
UnlockReleaseBuffer(buf);
|
UnlockReleaseBuffer(buf);
|
||||||
|
|
||||||
LoadInsertPage(rel, &buf, &page, &state, insertPage);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
/* Add a new page */
|
Buffer metabuf;
|
||||||
IvfflatAppendPage(rel, &buf, &page, &state, MAIN_FORKNUM);
|
Buffer newbuf;
|
||||||
|
Page newpage;
|
||||||
|
|
||||||
insertPage = BufferGetBlockNumber(buf);
|
/*
|
||||||
newPage = true;
|
* 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);
|
||||||
|
|
||||||
|
/* Init new page */
|
||||||
|
IvfflatInitPage(newbuf, newpage);
|
||||||
|
|
||||||
|
/* Update insert page */
|
||||||
|
insertPage = BufferGetBlockNumber(newbuf);
|
||||||
|
|
||||||
|
/* Update previous buffer */
|
||||||
|
IvfflatPageGetOpaque(page)->nextblkno = insertPage;
|
||||||
|
|
||||||
|
/* Commit */
|
||||||
|
MarkBufferDirty(newbuf);
|
||||||
|
MarkBufferDirty(buf);
|
||||||
|
GenericXLogFinish(state);
|
||||||
|
|
||||||
|
/* Unlock extend relation lock as early as possible */
|
||||||
|
UnlockReleaseBuffer(metabuf);
|
||||||
|
|
||||||
|
/* Unlock rest */
|
||||||
|
UnlockReleaseBuffer(newbuf);
|
||||||
|
UnlockReleaseBuffer(buf);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,8 +143,8 @@ InsertTuple(Relation rel, IndexTuple itup, Relation heapRel, Datum *values)
|
|||||||
IvfflatCommitBuffer(buf, state);
|
IvfflatCommitBuffer(buf, state);
|
||||||
|
|
||||||
/* Update the insert page */
|
/* Update the insert page */
|
||||||
if (newPage)
|
if (insertPage != originalInsertPage)
|
||||||
IvfflatUpdateList(rel, state, listInfo, insertPage, InvalidBlockNumber, MAIN_FORKNUM);
|
IvfflatUpdateList(rel, state, listInfo, insertPage, originalInsertPage, InvalidBlockNumber, MAIN_FORKNUM);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -128,6 +153,9 @@ InsertTuple(Relation rel, IndexTuple itup, Relation heapRel, Datum *values)
|
|||||||
bool
|
bool
|
||||||
ivfflatinsert(Relation index, Datum *values, bool *isnull, ItemPointer heap_tid,
|
ivfflatinsert(Relation index, Datum *values, bool *isnull, ItemPointer heap_tid,
|
||||||
Relation heap, IndexUniqueCheck checkUnique
|
Relation heap, IndexUniqueCheck checkUnique
|
||||||
|
#if PG_VERSION_NUM >= 140000
|
||||||
|
,bool indexUnchanged
|
||||||
|
#endif
|
||||||
#if PG_VERSION_NUM >= 100000
|
#if PG_VERSION_NUM >= 100000
|
||||||
,IndexInfo *indexInfo
|
,IndexInfo *indexInfo
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -11,17 +11,17 @@
|
|||||||
* https://theory.stanford.edu/~sergei/papers/kMeansPP-soda.pdf
|
* https://theory.stanford.edu/~sergei/papers/kMeansPP-soda.pdf
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
InitCenters(Relation index, VectorArray samples, VectorArray centers, double *lowerBound)
|
InitCenters(Relation index, VectorArray samples, VectorArray centers, float *lowerBound)
|
||||||
{
|
{
|
||||||
FmgrInfo *procinfo;
|
FmgrInfo *procinfo;
|
||||||
Oid collation;
|
Oid collation;
|
||||||
int i;
|
int i;
|
||||||
int j;
|
int64 j;
|
||||||
double distance;
|
double distance;
|
||||||
double sum;
|
double sum;
|
||||||
double choice;
|
double choice;
|
||||||
Vector *vec;
|
Vector *vec;
|
||||||
double *weight = palloc(samples->length * sizeof(double));
|
float *weight = palloc(samples->length * sizeof(float));
|
||||||
int numCenters = centers->maxlen;
|
int numCenters = centers->maxlen;
|
||||||
int numSamples = samples->length;
|
int numSamples = samples->length;
|
||||||
|
|
||||||
@@ -29,7 +29,7 @@ InitCenters(Relation index, VectorArray samples, VectorArray centers, double *lo
|
|||||||
collation = index->rd_indcollation[0];
|
collation = index->rd_indcollation[0];
|
||||||
|
|
||||||
/* Choose an initial center uniformly at random */
|
/* Choose an initial center uniformly at random */
|
||||||
VectorArraySet(centers, 0, VectorArrayGet(samples, random() % samples->length));
|
VectorArraySet(centers, 0, VectorArrayGet(samples, RandomInt() % samples->length));
|
||||||
centers->length++;
|
centers->length++;
|
||||||
|
|
||||||
for (j = 0; j < numSamples; j++)
|
for (j = 0; j < numSamples; j++)
|
||||||
@@ -66,7 +66,7 @@ InitCenters(Relation index, VectorArray samples, VectorArray centers, double *lo
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
/* Choose new center using weighted probability distribution. */
|
/* Choose new center using weighted probability distribution. */
|
||||||
choice = sum * (((double) random()) / MAX_RANDOM_VALUE);
|
choice = sum * RandomDouble();
|
||||||
for (j = 0; j < numSamples - 1; j++)
|
for (j = 0; j < numSamples - 1; j++)
|
||||||
{
|
{
|
||||||
choice -= weight[j];
|
choice -= weight[j];
|
||||||
@@ -121,15 +121,18 @@ QuickCenters(Relation index, VectorArray samples, VectorArray centers)
|
|||||||
FmgrInfo *normprocinfo = IvfflatOptionalProcInfo(index, IVFFLAT_KMEANS_NORM_PROC);
|
FmgrInfo *normprocinfo = IvfflatOptionalProcInfo(index, IVFFLAT_KMEANS_NORM_PROC);
|
||||||
|
|
||||||
/* Copy existing vectors while avoiding duplicates */
|
/* Copy existing vectors while avoiding duplicates */
|
||||||
qsort(samples->items, samples->length, VECTOR_SIZE(samples->dim), CompareVectors);
|
if (samples->length > 0)
|
||||||
for (i = 0; i < samples->length; i++)
|
|
||||||
{
|
{
|
||||||
vec = VectorArrayGet(samples, i);
|
qsort(samples->items, samples->length, VECTOR_SIZE(samples->dim), CompareVectors);
|
||||||
|
for (i = 0; i < samples->length; i++)
|
||||||
if (i == 0 || CompareVectors(vec, VectorArrayGet(samples, i - 1)) != 0)
|
|
||||||
{
|
{
|
||||||
VectorArraySet(centers, centers->length, vec);
|
vec = VectorArrayGet(samples, i);
|
||||||
centers->length++;
|
|
||||||
|
if (i == 0 || CompareVectors(vec, VectorArrayGet(samples, i - 1)) != 0)
|
||||||
|
{
|
||||||
|
VectorArraySet(centers, centers->length, vec);
|
||||||
|
centers->length++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -142,7 +145,7 @@ QuickCenters(Relation index, VectorArray samples, VectorArray centers)
|
|||||||
vec->dim = dimensions;
|
vec->dim = dimensions;
|
||||||
|
|
||||||
for (j = 0; j < dimensions; j++)
|
for (j = 0; j < dimensions; j++)
|
||||||
vec->x[j] = ((double) random()) / MAX_RANDOM_VALUE;
|
vec->x[j] = RandomDouble();
|
||||||
|
|
||||||
/* Normalize if needed (only needed for random centers) */
|
/* Normalize if needed (only needed for random centers) */
|
||||||
if (normprocinfo != NULL)
|
if (normprocinfo != NULL)
|
||||||
@@ -169,19 +172,19 @@ ElkanKmeans(Relation index, VectorArray samples, VectorArray centers)
|
|||||||
Vector *vec;
|
Vector *vec;
|
||||||
Vector *newCenter;
|
Vector *newCenter;
|
||||||
int iteration;
|
int iteration;
|
||||||
int j;
|
int64 j;
|
||||||
int k;
|
int64 k;
|
||||||
int dimensions = centers->dim;
|
int dimensions = centers->dim;
|
||||||
int numCenters = centers->maxlen;
|
int numCenters = centers->maxlen;
|
||||||
int numSamples = samples->length;
|
int numSamples = samples->length;
|
||||||
VectorArray newCenters;
|
VectorArray newCenters;
|
||||||
int *centerCounts;
|
int *centerCounts;
|
||||||
int *closestCenters;
|
int *closestCenters;
|
||||||
double *lowerBound;
|
float *lowerBound;
|
||||||
double *upperBound;
|
float *upperBound;
|
||||||
double *s;
|
float *s;
|
||||||
double *halfcdist;
|
float *halfcdist;
|
||||||
double *newcdist;
|
float *newcdist;
|
||||||
int changes;
|
int changes;
|
||||||
double minDistance;
|
double minDistance;
|
||||||
int closestCenter;
|
int closestCenter;
|
||||||
@@ -191,19 +194,47 @@ ElkanKmeans(Relation index, VectorArray samples, VectorArray centers)
|
|||||||
double dxcx;
|
double dxcx;
|
||||||
double dxc;
|
double dxc;
|
||||||
|
|
||||||
|
/* Calculate allocation sizes */
|
||||||
|
Size samplesSize = VECTOR_ARRAY_SIZE(samples->maxlen, samples->dim);
|
||||||
|
Size centersSize = VECTOR_ARRAY_SIZE(centers->maxlen, centers->dim);
|
||||||
|
Size newCentersSize = VECTOR_ARRAY_SIZE(numCenters, dimensions);
|
||||||
|
Size centerCountsSize = sizeof(int) * numCenters;
|
||||||
|
Size closestCentersSize = sizeof(int) * numSamples;
|
||||||
|
Size lowerBoundSize = sizeof(float) * numSamples * numCenters;
|
||||||
|
Size upperBoundSize = sizeof(float) * numSamples;
|
||||||
|
Size sSize = sizeof(float) * numCenters;
|
||||||
|
Size halfcdistSize = sizeof(float) * numCenters * numCenters;
|
||||||
|
Size newcdistSize = sizeof(float) * numCenters;
|
||||||
|
|
||||||
|
/* Calculate total size */
|
||||||
|
Size totalSize = samplesSize + centersSize + newCentersSize + centerCountsSize + closestCentersSize + lowerBoundSize + upperBoundSize + sSize + halfcdistSize + newcdistSize;
|
||||||
|
|
||||||
|
/* Check memory requirements */
|
||||||
|
/* Add one to error message to ceil */
|
||||||
|
if (totalSize / 1024 > maintenance_work_mem)
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
|
||||||
|
errmsg("memory required is %zu MB, maintenance_work_mem is %d MB",
|
||||||
|
totalSize / (1024 * 1024) + 1, maintenance_work_mem / 1024)));
|
||||||
|
|
||||||
|
/* Ensure indexing does not overflow */
|
||||||
|
if (numCenters * numCenters > INT_MAX)
|
||||||
|
elog(ERROR, "Indexing overflow detected. Please report a bug.");
|
||||||
|
|
||||||
/* Set support functions */
|
/* Set support functions */
|
||||||
procinfo = index_getprocinfo(index, 1, IVFFLAT_KMEANS_DISTANCE_PROC);
|
procinfo = index_getprocinfo(index, 1, IVFFLAT_KMEANS_DISTANCE_PROC);
|
||||||
normprocinfo = IvfflatOptionalProcInfo(index, IVFFLAT_KMEANS_NORM_PROC);
|
normprocinfo = IvfflatOptionalProcInfo(index, IVFFLAT_KMEANS_NORM_PROC);
|
||||||
collation = index->rd_indcollation[0];
|
collation = index->rd_indcollation[0];
|
||||||
|
|
||||||
/* Allocate space */
|
/* Allocate space */
|
||||||
centerCounts = palloc(sizeof(int) * numCenters);
|
/* Use float instead of double to save memory */
|
||||||
closestCenters = palloc(sizeof(int) * numSamples);
|
centerCounts = palloc(centerCountsSize);
|
||||||
lowerBound = palloc(sizeof(double) * numSamples * numCenters);
|
closestCenters = palloc(closestCentersSize);
|
||||||
upperBound = palloc(sizeof(double) * numSamples);
|
lowerBound = palloc_extended(lowerBoundSize, MCXT_ALLOC_HUGE);
|
||||||
s = palloc(sizeof(double) * numCenters);
|
upperBound = palloc(upperBoundSize);
|
||||||
halfcdist = palloc(sizeof(double) * numCenters * numCenters);
|
s = palloc(sSize);
|
||||||
newcdist = palloc(sizeof(double) * numCenters);
|
halfcdist = palloc_extended(halfcdistSize, MCXT_ALLOC_HUGE);
|
||||||
|
newcdist = palloc(newcdistSize);
|
||||||
|
|
||||||
newCenters = VectorArrayInit(numCenters, dimensions);
|
newCenters = VectorArrayInit(numCenters, dimensions);
|
||||||
for (j = 0; j < numCenters; j++)
|
for (j = 0; j < numCenters; j++)
|
||||||
@@ -222,8 +253,6 @@ ElkanKmeans(Relation index, VectorArray samples, VectorArray centers)
|
|||||||
minDistance = DBL_MAX;
|
minDistance = DBL_MAX;
|
||||||
closestCenter = -1;
|
closestCenter = -1;
|
||||||
|
|
||||||
vec = VectorArrayGet(samples, j);
|
|
||||||
|
|
||||||
/* Find closest center */
|
/* Find closest center */
|
||||||
for (k = 0; k < numCenters; k++)
|
for (k = 0; k < numCenters; k++)
|
||||||
{
|
{
|
||||||
@@ -376,7 +405,7 @@ ElkanKmeans(Relation index, VectorArray samples, VectorArray centers)
|
|||||||
{
|
{
|
||||||
/* TODO Handle empty centers properly */
|
/* TODO Handle empty centers properly */
|
||||||
for (k = 0; k < dimensions; k++)
|
for (k = 0; k < dimensions; k++)
|
||||||
vec->x[k] = ((double) random()) / MAX_RANDOM_VALUE;
|
vec->x[k] = RandomDouble();
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Normalize if needed */
|
/* Normalize if needed */
|
||||||
@@ -414,7 +443,7 @@ ElkanKmeans(Relation index, VectorArray samples, VectorArray centers)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
pfree(newCenters);
|
VectorArrayFree(newCenters);
|
||||||
pfree(centerCounts);
|
pfree(centerCounts);
|
||||||
pfree(closestCenters);
|
pfree(closestCenters);
|
||||||
pfree(lowerBound);
|
pfree(lowerBound);
|
||||||
|
|||||||
100
src/ivfscan.c
100
src/ivfscan.c
@@ -1,5 +1,7 @@
|
|||||||
#include "postgres.h"
|
#include "postgres.h"
|
||||||
|
|
||||||
|
#include <float.h>
|
||||||
|
|
||||||
#include "access/relscan.h"
|
#include "access/relscan.h"
|
||||||
#include "ivfflat.h"
|
#include "ivfflat.h"
|
||||||
#include "miscadmin.h"
|
#include "miscadmin.h"
|
||||||
@@ -17,14 +19,12 @@
|
|||||||
* Compare list distances
|
* Compare list distances
|
||||||
*/
|
*/
|
||||||
static int
|
static int
|
||||||
CompareLists(const void *a, const void *b)
|
CompareLists(const pairingheap_node *a, const pairingheap_node *b, void *arg)
|
||||||
{
|
{
|
||||||
double diff = (((IvfflatScanList *) a)->distance - ((IvfflatScanList *) b)->distance);
|
if (((const IvfflatScanList *) a)->distance > ((const IvfflatScanList *) b)->distance)
|
||||||
|
|
||||||
if (diff > 0)
|
|
||||||
return 1;
|
return 1;
|
||||||
|
|
||||||
if (diff < 0)
|
if (((const IvfflatScanList *) a)->distance < ((const IvfflatScanList *) b)->distance)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
@@ -45,6 +45,8 @@ GetScanLists(IndexScanDesc scan, Datum value)
|
|||||||
int listCount = 0;
|
int listCount = 0;
|
||||||
IvfflatScanOpaque so = (IvfflatScanOpaque) scan->opaque;
|
IvfflatScanOpaque so = (IvfflatScanOpaque) scan->opaque;
|
||||||
double distance;
|
double distance;
|
||||||
|
IvfflatScanList *scanlist;
|
||||||
|
double maxDistance = DBL_MAX;
|
||||||
|
|
||||||
/* Search all list pages */
|
/* Search all list pages */
|
||||||
while (BlockNumberIsValid(nextblkno))
|
while (BlockNumberIsValid(nextblkno))
|
||||||
@@ -62,21 +64,39 @@ GetScanLists(IndexScanDesc scan, Datum value)
|
|||||||
/* Use procinfo from the index instead of scan key for performance */
|
/* Use procinfo from the index instead of scan key for performance */
|
||||||
distance = DatumGetFloat8(FunctionCall2Coll(so->procinfo, so->collation, PointerGetDatum(&list->center), value));
|
distance = DatumGetFloat8(FunctionCall2Coll(so->procinfo, so->collation, PointerGetDatum(&list->center), value));
|
||||||
|
|
||||||
so->lists[listCount].startPage = list->startPage;
|
if (listCount < so->probes)
|
||||||
so->lists[listCount].distance = distance;
|
{
|
||||||
listCount++;
|
scanlist = &so->lists[listCount];
|
||||||
|
scanlist->startPage = list->startPage;
|
||||||
|
scanlist->distance = distance;
|
||||||
|
listCount++;
|
||||||
|
|
||||||
|
/* Add to heap */
|
||||||
|
pairingheap_add(so->listQueue, &scanlist->ph_node);
|
||||||
|
|
||||||
|
/* Calculate max distance */
|
||||||
|
if (listCount == so->probes)
|
||||||
|
maxDistance = ((IvfflatScanList *) pairingheap_first(so->listQueue))->distance;
|
||||||
|
}
|
||||||
|
else if (distance < maxDistance)
|
||||||
|
{
|
||||||
|
/* Remove */
|
||||||
|
scanlist = (IvfflatScanList *) pairingheap_remove_first(so->listQueue);
|
||||||
|
|
||||||
|
/* Reuse */
|
||||||
|
scanlist->startPage = list->startPage;
|
||||||
|
scanlist->distance = distance;
|
||||||
|
pairingheap_add(so->listQueue, &scanlist->ph_node);
|
||||||
|
|
||||||
|
/* Update max distance */
|
||||||
|
maxDistance = ((IvfflatScanList *) pairingheap_first(so->listQueue))->distance;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
nextblkno = IvfflatPageGetOpaque(cpage)->nextblkno;
|
nextblkno = IvfflatPageGetOpaque(cpage)->nextblkno;
|
||||||
|
|
||||||
UnlockReleaseBuffer(cbuf);
|
UnlockReleaseBuffer(cbuf);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Sort by distance */
|
|
||||||
qsort(so->lists, listCount, sizeof(IvfflatScanList), CompareLists);
|
|
||||||
|
|
||||||
if (so->probes > listCount)
|
|
||||||
so->probes = listCount;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -94,7 +114,6 @@ GetScanItems(IndexScanDesc scan, Datum value)
|
|||||||
OffsetNumber maxoffno;
|
OffsetNumber maxoffno;
|
||||||
Datum datum;
|
Datum datum;
|
||||||
bool isnull;
|
bool isnull;
|
||||||
int i;
|
|
||||||
TupleDesc tupdesc = RelationGetDescr(scan->indexRelation);
|
TupleDesc tupdesc = RelationGetDescr(scan->indexRelation);
|
||||||
|
|
||||||
#if PG_VERSION_NUM >= 120000
|
#if PG_VERSION_NUM >= 120000
|
||||||
@@ -111,9 +130,9 @@ GetScanItems(IndexScanDesc scan, Datum value)
|
|||||||
BufferAccessStrategy bas = GetAccessStrategy(BAS_BULKREAD);
|
BufferAccessStrategy bas = GetAccessStrategy(BAS_BULKREAD);
|
||||||
|
|
||||||
/* Search closest probes lists */
|
/* Search closest probes lists */
|
||||||
for (i = 0; i < so->probes; i++)
|
while (!pairingheap_is_empty(so->listQueue))
|
||||||
{
|
{
|
||||||
searchPage = so->lists[i].startPage;
|
searchPage = ((IvfflatScanList *) pairingheap_remove_first(so->listQueue))->startPage;
|
||||||
|
|
||||||
/* Search all entry pages for list */
|
/* Search all entry pages for list */
|
||||||
while (BlockNumberIsValid(searchPage))
|
while (BlockNumberIsValid(searchPage))
|
||||||
@@ -137,12 +156,10 @@ GetScanItems(IndexScanDesc scan, Datum value)
|
|||||||
ExecClearTuple(slot);
|
ExecClearTuple(slot);
|
||||||
slot->tts_values[0] = FunctionCall2Coll(so->procinfo, so->collation, datum, value);
|
slot->tts_values[0] = FunctionCall2Coll(so->procinfo, so->collation, datum, value);
|
||||||
slot->tts_isnull[0] = false;
|
slot->tts_isnull[0] = false;
|
||||||
slot->tts_values[1] = Int32GetDatum((int) ItemPointerGetBlockNumberNoCheck(&itup->t_tid));
|
slot->tts_values[1] = PointerGetDatum(&itup->t_tid);
|
||||||
slot->tts_isnull[1] = false;
|
slot->tts_isnull[1] = false;
|
||||||
slot->tts_values[2] = Int32GetDatum((int) ItemPointerGetOffsetNumberNoCheck(&itup->t_tid));
|
slot->tts_values[2] = Int32GetDatum((int) searchPage);
|
||||||
slot->tts_isnull[2] = false;
|
slot->tts_isnull[2] = false;
|
||||||
slot->tts_values[3] = Int32GetDatum((int) searchPage);
|
|
||||||
slot->tts_isnull[3] = false;
|
|
||||||
ExecStoreVirtualTuple(slot);
|
ExecStoreVirtualTuple(slot);
|
||||||
|
|
||||||
tuplesort_puttupleslot(so->sortstate, slot);
|
tuplesort_puttupleslot(so->sortstate, slot);
|
||||||
@@ -153,6 +170,8 @@ GetScanItems(IndexScanDesc scan, Datum value)
|
|||||||
UnlockReleaseBuffer(buf);
|
UnlockReleaseBuffer(buf);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tuplesort_performsort(so->sortstate);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -168,13 +187,18 @@ ivfflatbeginscan(Relation index, int nkeys, int norderbys)
|
|||||||
Oid sortOperators[] = {Float8LessOperator};
|
Oid sortOperators[] = {Float8LessOperator};
|
||||||
Oid sortCollations[] = {InvalidOid};
|
Oid sortCollations[] = {InvalidOid};
|
||||||
bool nullsFirstFlags[] = {false};
|
bool nullsFirstFlags[] = {false};
|
||||||
|
int probes = ivfflat_probes;
|
||||||
|
|
||||||
scan = RelationGetIndexScan(index, nkeys, norderbys);
|
scan = RelationGetIndexScan(index, nkeys, norderbys);
|
||||||
lists = IvfflatGetLists(scan->indexRelation);
|
lists = IvfflatGetLists(scan->indexRelation);
|
||||||
|
|
||||||
so = (IvfflatScanOpaque) palloc(offsetof(IvfflatScanOpaqueData, lists) + lists * sizeof(IvfflatScanList));
|
if (probes > lists)
|
||||||
|
probes = lists;
|
||||||
|
|
||||||
|
so = (IvfflatScanOpaque) palloc(offsetof(IvfflatScanOpaqueData, lists) + probes * sizeof(IvfflatScanList));
|
||||||
so->buf = InvalidBuffer;
|
so->buf = InvalidBuffer;
|
||||||
so->first = true;
|
so->first = true;
|
||||||
|
so->probes = probes;
|
||||||
|
|
||||||
/* Set support functions */
|
/* Set support functions */
|
||||||
so->procinfo = index_getprocinfo(index, 1, IVFFLAT_DISTANCE_PROC);
|
so->procinfo = index_getprocinfo(index, 1, IVFFLAT_DISTANCE_PROC);
|
||||||
@@ -183,14 +207,13 @@ ivfflatbeginscan(Relation index, int nkeys, int norderbys)
|
|||||||
|
|
||||||
/* Create tuple description for sorting */
|
/* Create tuple description for sorting */
|
||||||
#if PG_VERSION_NUM >= 120000
|
#if PG_VERSION_NUM >= 120000
|
||||||
so->tupdesc = CreateTemplateTupleDesc(4);
|
so->tupdesc = CreateTemplateTupleDesc(3);
|
||||||
#else
|
#else
|
||||||
so->tupdesc = CreateTemplateTupleDesc(4, false);
|
so->tupdesc = CreateTemplateTupleDesc(3, false);
|
||||||
#endif
|
#endif
|
||||||
TupleDescInitEntry(so->tupdesc, (AttrNumber) 1, "distance", FLOAT8OID, -1, 0);
|
TupleDescInitEntry(so->tupdesc, (AttrNumber) 1, "distance", FLOAT8OID, -1, 0);
|
||||||
TupleDescInitEntry(so->tupdesc, (AttrNumber) 2, "blkno", INT4OID, -1, 0);
|
TupleDescInitEntry(so->tupdesc, (AttrNumber) 2, "tid", TIDOID, -1, 0);
|
||||||
TupleDescInitEntry(so->tupdesc, (AttrNumber) 3, "offset", INT4OID, -1, 0);
|
TupleDescInitEntry(so->tupdesc, (AttrNumber) 3, "indexblkno", INT4OID, -1, 0);
|
||||||
TupleDescInitEntry(so->tupdesc, (AttrNumber) 4, "indexblkno", INT4OID, -1, 0);
|
|
||||||
|
|
||||||
/* Prep sort */
|
/* Prep sort */
|
||||||
#if PG_VERSION_NUM >= 110000
|
#if PG_VERSION_NUM >= 110000
|
||||||
@@ -205,6 +228,8 @@ ivfflatbeginscan(Relation index, int nkeys, int norderbys)
|
|||||||
so->slot = MakeSingleTupleTableSlot(so->tupdesc);
|
so->slot = MakeSingleTupleTableSlot(so->tupdesc);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
so->listQueue = pairingheap_allocate(CompareLists, scan);
|
||||||
|
|
||||||
scan->opaque = so;
|
scan->opaque = so;
|
||||||
|
|
||||||
return scan;
|
return scan;
|
||||||
@@ -224,7 +249,7 @@ ivfflatrescan(IndexScanDesc scan, ScanKey keys, int nkeys, ScanKey orderbys, int
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
so->first = true;
|
so->first = true;
|
||||||
so->probes = ivfflat_probes;
|
pairingheap_reset(so->listQueue);
|
||||||
|
|
||||||
if (keys && scan->numberOfKeys > 0)
|
if (keys && scan->numberOfKeys > 0)
|
||||||
memmove(scan->keyData, keys, scan->numberOfKeys * sizeof(ScanKeyData));
|
memmove(scan->keyData, keys, scan->numberOfKeys * sizeof(ScanKeyData));
|
||||||
@@ -251,6 +276,10 @@ ivfflatgettuple(IndexScanDesc scan, ScanDirection dir)
|
|||||||
{
|
{
|
||||||
Datum value;
|
Datum value;
|
||||||
|
|
||||||
|
/* Safety check */
|
||||||
|
if (scan->orderByData == NULL)
|
||||||
|
elog(ERROR, "cannot scan ivfflat index without order");
|
||||||
|
|
||||||
/* No items will match if null */
|
/* No items will match if null */
|
||||||
if (scan->orderByData->sk_flags & SK_ISNULL)
|
if (scan->orderByData->sk_flags & SK_ISNULL)
|
||||||
return false;
|
return false;
|
||||||
@@ -264,9 +293,8 @@ ivfflatgettuple(IndexScanDesc scan, ScanDirection dir)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
GetScanLists(scan, value);
|
IvfflatBench("GetScanLists", GetScanLists(scan, value));
|
||||||
GetScanItems(scan, value);
|
IvfflatBench("GetScanItems", GetScanItems(scan, value));
|
||||||
tuplesort_performsort(so->sortstate);
|
|
||||||
so->first = false;
|
so->first = false;
|
||||||
|
|
||||||
/* Clean up if we allocated a new value */
|
/* Clean up if we allocated a new value */
|
||||||
@@ -280,14 +308,13 @@ ivfflatgettuple(IndexScanDesc scan, ScanDirection dir)
|
|||||||
if (tuplesort_gettupleslot(so->sortstate, true, so->slot, NULL))
|
if (tuplesort_gettupleslot(so->sortstate, true, so->slot, NULL))
|
||||||
#endif
|
#endif
|
||||||
{
|
{
|
||||||
BlockNumber blkno = DatumGetInt32(slot_getattr(so->slot, 2, &so->isnull));
|
ItemPointer tid = (ItemPointer) DatumGetPointer(slot_getattr(so->slot, 2, &so->isnull));
|
||||||
OffsetNumber offset = DatumGetInt32(slot_getattr(so->slot, 3, &so->isnull));
|
BlockNumber indexblkno = DatumGetInt32(slot_getattr(so->slot, 3, &so->isnull));
|
||||||
BlockNumber indexblkno = DatumGetInt32(slot_getattr(so->slot, 4, &so->isnull));
|
|
||||||
|
|
||||||
#if PG_VERSION_NUM >= 120000
|
#if PG_VERSION_NUM >= 120000
|
||||||
ItemPointerSet(&scan->xs_heaptid, blkno, offset);
|
scan->xs_heaptid = *tid;
|
||||||
#else
|
#else
|
||||||
ItemPointerSet(&scan->xs_ctup.t_self, blkno, offset);
|
scan->xs_ctup.t_self = *tid;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (BufferIsValid(so->buf))
|
if (BufferIsValid(so->buf))
|
||||||
@@ -320,6 +347,7 @@ ivfflatendscan(IndexScanDesc scan)
|
|||||||
if (BufferIsValid(so->buf))
|
if (BufferIsValid(so->buf))
|
||||||
ReleaseBuffer(so->buf);
|
ReleaseBuffer(so->buf);
|
||||||
|
|
||||||
|
pairingheap_free(so->listQueue);
|
||||||
tuplesort_end(so->sortstate);
|
tuplesort_end(so->sortstate);
|
||||||
|
|
||||||
pfree(so);
|
pfree(so);
|
||||||
|
|||||||
@@ -10,14 +10,25 @@
|
|||||||
VectorArray
|
VectorArray
|
||||||
VectorArrayInit(int maxlen, int dimensions)
|
VectorArrayInit(int maxlen, int dimensions)
|
||||||
{
|
{
|
||||||
VectorArray res = palloc0(VECTOR_ARRAY_SIZE(maxlen, dimensions));
|
VectorArray res = palloc(sizeof(VectorArrayData));
|
||||||
|
|
||||||
res->length = 0;
|
res->length = 0;
|
||||||
res->maxlen = maxlen;
|
res->maxlen = maxlen;
|
||||||
res->dim = dimensions;
|
res->dim = dimensions;
|
||||||
|
res->items = palloc_extended(maxlen * VECTOR_SIZE(dimensions), MCXT_ALLOC_ZERO | MCXT_ALLOC_HUGE);
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Free a vector array
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
VectorArrayFree(VectorArray arr)
|
||||||
|
{
|
||||||
|
pfree(arr->items);
|
||||||
|
pfree(arr);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Print vector array - useful for debugging
|
* Print vector array - useful for debugging
|
||||||
*/
|
*/
|
||||||
@@ -107,13 +118,22 @@ IvfflatNewBuffer(Relation index, ForkNumber forkNum)
|
|||||||
* Init page
|
* Init page
|
||||||
*/
|
*/
|
||||||
void
|
void
|
||||||
IvfflatInitPage(Relation index, Buffer *buf, Page *page, GenericXLogState **state)
|
IvfflatInitPage(Buffer buf, Page page)
|
||||||
|
{
|
||||||
|
PageInit(page, BufferGetPageSize(buf), sizeof(IvfflatPageOpaqueData));
|
||||||
|
IvfflatPageGetOpaque(page)->nextblkno = InvalidBlockNumber;
|
||||||
|
IvfflatPageGetOpaque(page)->page_id = IVFFLAT_PAGE_ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Init and register page
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
IvfflatInitRegisterPage(Relation index, Buffer *buf, Page *page, GenericXLogState **state)
|
||||||
{
|
{
|
||||||
*state = GenericXLogStart(index);
|
*state = GenericXLogStart(index);
|
||||||
*page = GenericXLogRegisterBuffer(*state, *buf, GENERIC_XLOG_FULL_IMAGE);
|
*page = GenericXLogRegisterBuffer(*state, *buf, GENERIC_XLOG_FULL_IMAGE);
|
||||||
PageInit(*page, BufferGetPageSize(*buf), sizeof(IvfflatPageOpaqueData));
|
IvfflatInitPage(*buf, *page);
|
||||||
IvfflatPageGetOpaque(*page)->nextblkno = InvalidBlockNumber;
|
|
||||||
IvfflatPageGetOpaque(*page)->page_id = IVFFLAT_PAGE_ID;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -135,17 +155,27 @@ IvfflatCommitBuffer(Buffer buf, GenericXLogState *state)
|
|||||||
void
|
void
|
||||||
IvfflatAppendPage(Relation index, Buffer *buf, Page *page, GenericXLogState **state, ForkNumber forkNum)
|
IvfflatAppendPage(Relation index, Buffer *buf, Page *page, GenericXLogState **state, ForkNumber forkNum)
|
||||||
{
|
{
|
||||||
Buffer prevbuf = *buf;
|
|
||||||
|
|
||||||
/* Get new buffer */
|
/* Get new buffer */
|
||||||
*buf = IvfflatNewBuffer(index, forkNum);
|
Buffer newbuf = IvfflatNewBuffer(index, forkNum);
|
||||||
|
Page newpage = GenericXLogRegisterBuffer(*state, newbuf, GENERIC_XLOG_FULL_IMAGE);
|
||||||
|
|
||||||
/* Update and commit previous buffer */
|
/* Update the previous buffer */
|
||||||
IvfflatPageGetOpaque(*page)->nextblkno = BufferGetBlockNumber(*buf);
|
IvfflatPageGetOpaque(*page)->nextblkno = BufferGetBlockNumber(newbuf);
|
||||||
IvfflatCommitBuffer(prevbuf, *state);
|
|
||||||
|
|
||||||
/* Init new page */
|
/* Init new page */
|
||||||
IvfflatInitPage(index, buf, page, state);
|
IvfflatInitPage(newbuf, newpage);
|
||||||
|
|
||||||
|
/* Commit */
|
||||||
|
MarkBufferDirty(*buf);
|
||||||
|
MarkBufferDirty(newbuf);
|
||||||
|
GenericXLogFinish(*state);
|
||||||
|
|
||||||
|
/* Unlock */
|
||||||
|
UnlockReleaseBuffer(*buf);
|
||||||
|
|
||||||
|
*state = GenericXLogStart(index);
|
||||||
|
*page = GenericXLogRegisterBuffer(*state, newbuf, GENERIC_XLOG_FULL_IMAGE);
|
||||||
|
*buf = newbuf;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -153,11 +183,13 @@ IvfflatAppendPage(Relation index, Buffer *buf, Page *page, GenericXLogState **st
|
|||||||
*/
|
*/
|
||||||
void
|
void
|
||||||
IvfflatUpdateList(Relation index, GenericXLogState *state, ListInfo listInfo,
|
IvfflatUpdateList(Relation index, GenericXLogState *state, ListInfo listInfo,
|
||||||
BlockNumber insertPage, BlockNumber startPage, ForkNumber forkNum)
|
BlockNumber insertPage, BlockNumber originalInsertPage,
|
||||||
|
BlockNumber startPage, ForkNumber forkNum)
|
||||||
{
|
{
|
||||||
Buffer buf;
|
Buffer buf;
|
||||||
Page page;
|
Page page;
|
||||||
IvfflatList list;
|
IvfflatList list;
|
||||||
|
bool changed = false;
|
||||||
|
|
||||||
buf = ReadBufferExtended(index, forkNum, listInfo.blkno, RBM_NORMAL, NULL);
|
buf = ReadBufferExtended(index, forkNum, listInfo.blkno, RBM_NORMAL, NULL);
|
||||||
LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
|
LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
|
||||||
@@ -165,12 +197,29 @@ IvfflatUpdateList(Relation index, GenericXLogState *state, ListInfo listInfo,
|
|||||||
page = GenericXLogRegisterBuffer(state, buf, 0);
|
page = GenericXLogRegisterBuffer(state, buf, 0);
|
||||||
list = (IvfflatList) PageGetItem(page, PageGetItemId(page, listInfo.offno));
|
list = (IvfflatList) PageGetItem(page, PageGetItemId(page, listInfo.offno));
|
||||||
|
|
||||||
if (BlockNumberIsValid(insertPage))
|
if (BlockNumberIsValid(insertPage) && insertPage != list->insertPage)
|
||||||
list->insertPage = insertPage;
|
{
|
||||||
|
/* Skip update if insert page is lower than original insert page */
|
||||||
|
/* This is needed to prevent insert from overwriting vacuum */
|
||||||
|
if (!BlockNumberIsValid(originalInsertPage) || insertPage >= originalInsertPage)
|
||||||
|
{
|
||||||
|
list->insertPage = insertPage;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (BlockNumberIsValid(startPage))
|
if (BlockNumberIsValid(startPage) && startPage != list->startPage)
|
||||||
|
{
|
||||||
list->startPage = startPage;
|
list->startPage = startPage;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
/* Could only commit if changed, but extra complexity isn't needed */
|
/* Only commit if changed */
|
||||||
IvfflatCommitBuffer(buf, state);
|
if (changed)
|
||||||
|
IvfflatCommitBuffer(buf, state);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
GenericXLogAbort(state);
|
||||||
|
UnlockReleaseBuffer(buf);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ ivfflatbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
|
|||||||
ItemPointer htup;
|
ItemPointer htup;
|
||||||
OffsetNumber deletable[MaxOffsetNumber];
|
OffsetNumber deletable[MaxOffsetNumber];
|
||||||
int ndeletable;
|
int ndeletable;
|
||||||
OffsetNumber startPages[MaxOffsetNumber];
|
BlockNumber startPages[MaxOffsetNumber];
|
||||||
BlockNumber nextblkno = IVFFLAT_HEAD_BLKNO;
|
BlockNumber nextblkno = IVFFLAT_HEAD_BLKNO;
|
||||||
BlockNumber searchPage;
|
BlockNumber searchPage;
|
||||||
BlockNumber insertPage;
|
BlockNumber insertPage;
|
||||||
@@ -98,6 +98,11 @@ ivfflatbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
|
|||||||
stats->num_index_tuples++;
|
stats->num_index_tuples++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Set to first free page */
|
||||||
|
/* Must be set before searchPage is updated */
|
||||||
|
if (!BlockNumberIsValid(insertPage) && ndeletable > 0)
|
||||||
|
insertPage = searchPage;
|
||||||
|
|
||||||
searchPage = IvfflatPageGetOpaque(page)->nextblkno;
|
searchPage = IvfflatPageGetOpaque(page)->nextblkno;
|
||||||
|
|
||||||
if (ndeletable > 0)
|
if (ndeletable > 0)
|
||||||
@@ -106,10 +111,6 @@ ivfflatbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
|
|||||||
PageIndexMultiDelete(page, deletable, ndeletable);
|
PageIndexMultiDelete(page, deletable, ndeletable);
|
||||||
MarkBufferDirty(buf);
|
MarkBufferDirty(buf);
|
||||||
GenericXLogFinish(state);
|
GenericXLogFinish(state);
|
||||||
|
|
||||||
/* Set to first free page */
|
|
||||||
if (!BlockNumberIsValid(insertPage))
|
|
||||||
insertPage = searchPage;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
GenericXLogAbort(state);
|
GenericXLogAbort(state);
|
||||||
@@ -123,10 +124,10 @@ ivfflatbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
|
|||||||
* We don't add or delete items from lists pages, so offset won't
|
* We don't add or delete items from lists pages, so offset won't
|
||||||
* change.
|
* change.
|
||||||
*/
|
*/
|
||||||
if (!BlockNumberIsValid(insertPage))
|
if (BlockNumberIsValid(insertPage))
|
||||||
{
|
{
|
||||||
listInfo.offno = coffno;
|
listInfo.offno = coffno;
|
||||||
IvfflatUpdateList(index, state, listInfo, insertPage, InvalidBlockNumber, MAIN_FORKNUM);
|
IvfflatUpdateList(index, state, listInfo, insertPage, InvalidBlockNumber, InvalidBlockNumber, MAIN_FORKNUM);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
183
src/vector.c
183
src/vector.c
@@ -10,9 +10,17 @@
|
|||||||
#include "utils/array.h"
|
#include "utils/array.h"
|
||||||
#include "utils/builtins.h"
|
#include "utils/builtins.h"
|
||||||
#include "utils/lsyscache.h"
|
#include "utils/lsyscache.h"
|
||||||
|
#include "utils/numeric.h"
|
||||||
|
|
||||||
#if PG_VERSION_NUM >= 120000
|
#if PG_VERSION_NUM >= 120000
|
||||||
|
#include "common/shortest_dec.h"
|
||||||
#include "utils/float.h"
|
#include "utils/float.h"
|
||||||
|
#else
|
||||||
|
#include <float.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if PG_VERSION_NUM < 130000
|
||||||
|
#define TYPALIGN_INT 'i'
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
PG_MODULE_MAGIC;
|
PG_MODULE_MAGIC;
|
||||||
@@ -101,14 +109,14 @@ PrintVector(char *msg, Vector * vector)
|
|||||||
/*
|
/*
|
||||||
* Convert textual representation to internal representation
|
* Convert textual representation to internal representation
|
||||||
*/
|
*/
|
||||||
PG_FUNCTION_INFO_V1(vector_in);
|
PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_in);
|
||||||
Datum
|
Datum
|
||||||
vector_in(PG_FUNCTION_ARGS)
|
vector_in(PG_FUNCTION_ARGS)
|
||||||
{
|
{
|
||||||
char *str = PG_GETARG_CSTRING(0);
|
char *str = PG_GETARG_CSTRING(0);
|
||||||
int32 typmod = PG_GETARG_INT32(2);
|
int32 typmod = PG_GETARG_INT32(2);
|
||||||
int i;
|
int i;
|
||||||
double x[VECTOR_MAX_DIM];
|
float x[VECTOR_MAX_DIM];
|
||||||
int dim = 0;
|
int dim = 0;
|
||||||
char *pt;
|
char *pt;
|
||||||
char *stringEnd;
|
char *stringEnd;
|
||||||
@@ -131,7 +139,8 @@ vector_in(PG_FUNCTION_ARGS)
|
|||||||
(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
|
(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
|
||||||
errmsg("vector cannot have more than %d dimensions", VECTOR_MAX_DIM)));
|
errmsg("vector cannot have more than %d dimensions", VECTOR_MAX_DIM)));
|
||||||
|
|
||||||
x[dim] = strtod(pt, &stringEnd);
|
/* Use strtof like float4in to avoid a double-rounding problem */
|
||||||
|
x[dim] = strtof(pt, &stringEnd);
|
||||||
CheckElement(x[dim]);
|
CheckElement(x[dim]);
|
||||||
dim++;
|
dim++;
|
||||||
|
|
||||||
@@ -177,35 +186,68 @@ vector_in(PG_FUNCTION_ARGS)
|
|||||||
/*
|
/*
|
||||||
* Convert internal representation to textual representation
|
* Convert internal representation to textual representation
|
||||||
*/
|
*/
|
||||||
PG_FUNCTION_INFO_V1(vector_out);
|
PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_out);
|
||||||
Datum
|
Datum
|
||||||
vector_out(PG_FUNCTION_ARGS)
|
vector_out(PG_FUNCTION_ARGS)
|
||||||
{
|
{
|
||||||
Vector *vector = PG_GETARG_VECTOR_P(0);
|
Vector *vector = PG_GETARG_VECTOR_P(0);
|
||||||
StringInfoData buf;
|
|
||||||
int dim = vector->dim;
|
int dim = vector->dim;
|
||||||
|
char *buf;
|
||||||
|
char *ptr;
|
||||||
int i;
|
int i;
|
||||||
|
int n;
|
||||||
|
|
||||||
initStringInfo(&buf);
|
#if PG_VERSION_NUM < 120000
|
||||||
|
int ndig = FLT_DIG + extra_float_digits;
|
||||||
|
|
||||||
appendStringInfoChar(&buf, '[');
|
if (ndig < 1)
|
||||||
|
ndig = 1;
|
||||||
|
|
||||||
|
#define FLOAT_SHORTEST_DECIMAL_LEN (ndig + 10)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Need:
|
||||||
|
*
|
||||||
|
* dim * (FLOAT_SHORTEST_DECIMAL_LEN - 1) bytes for
|
||||||
|
* float_to_shortest_decimal_bufn
|
||||||
|
*
|
||||||
|
* dim - 1 bytes for separator
|
||||||
|
*
|
||||||
|
* 3 bytes for [, ], and \0
|
||||||
|
*/
|
||||||
|
buf = (char *) palloc(FLOAT_SHORTEST_DECIMAL_LEN * dim + 2);
|
||||||
|
ptr = buf;
|
||||||
|
|
||||||
|
*ptr = '[';
|
||||||
|
ptr++;
|
||||||
for (i = 0; i < dim; i++)
|
for (i = 0; i < dim; i++)
|
||||||
{
|
{
|
||||||
if (i > 0)
|
if (i > 0)
|
||||||
appendStringInfoString(&buf, ",");
|
{
|
||||||
|
*ptr = ',';
|
||||||
|
ptr++;
|
||||||
|
}
|
||||||
|
|
||||||
appendStringInfoString(&buf, float8out_internal(vector->x[i]));
|
#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;
|
||||||
}
|
}
|
||||||
appendStringInfoChar(&buf, ']');
|
*ptr = ']';
|
||||||
|
ptr++;
|
||||||
|
*ptr = '\0';
|
||||||
|
|
||||||
PG_FREE_IF_COPY(vector, 0);
|
PG_FREE_IF_COPY(vector, 0);
|
||||||
PG_RETURN_CSTRING(buf.data);
|
PG_RETURN_CSTRING(buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Convert type modifier
|
* Convert type modifier
|
||||||
*/
|
*/
|
||||||
PG_FUNCTION_INFO_V1(vector_typmod_in);
|
PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_typmod_in);
|
||||||
Datum
|
Datum
|
||||||
vector_typmod_in(PG_FUNCTION_ARGS)
|
vector_typmod_in(PG_FUNCTION_ARGS)
|
||||||
{
|
{
|
||||||
@@ -236,7 +278,7 @@ vector_typmod_in(PG_FUNCTION_ARGS)
|
|||||||
/*
|
/*
|
||||||
* Convert external binary representation to internal representation
|
* Convert external binary representation to internal representation
|
||||||
*/
|
*/
|
||||||
PG_FUNCTION_INFO_V1(vector_recv);
|
PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_recv);
|
||||||
Datum
|
Datum
|
||||||
vector_recv(PG_FUNCTION_ARGS)
|
vector_recv(PG_FUNCTION_ARGS)
|
||||||
{
|
{
|
||||||
@@ -253,6 +295,11 @@ vector_recv(PG_FUNCTION_ARGS)
|
|||||||
CheckDim(dim);
|
CheckDim(dim);
|
||||||
CheckExpectedDim(typmod, dim);
|
CheckExpectedDim(typmod, dim);
|
||||||
|
|
||||||
|
if (unused != 0)
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_DATA_EXCEPTION),
|
||||||
|
errmsg("expected unused to be 0, not %d", unused)));
|
||||||
|
|
||||||
result = InitVector(dim);
|
result = InitVector(dim);
|
||||||
for (i = 0; i < dim; i++)
|
for (i = 0; i < dim; i++)
|
||||||
result->x[i] = pq_getmsgfloat4(buf);
|
result->x[i] = pq_getmsgfloat4(buf);
|
||||||
@@ -263,7 +310,7 @@ vector_recv(PG_FUNCTION_ARGS)
|
|||||||
/*
|
/*
|
||||||
* Convert internal representation to the external binary representation
|
* Convert internal representation to the external binary representation
|
||||||
*/
|
*/
|
||||||
PG_FUNCTION_INFO_V1(vector_send);
|
PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_send);
|
||||||
Datum
|
Datum
|
||||||
vector_send(PG_FUNCTION_ARGS)
|
vector_send(PG_FUNCTION_ARGS)
|
||||||
{
|
{
|
||||||
@@ -283,7 +330,7 @@ vector_send(PG_FUNCTION_ARGS)
|
|||||||
/*
|
/*
|
||||||
* Convert vector to vector
|
* Convert vector to vector
|
||||||
*/
|
*/
|
||||||
PG_FUNCTION_INFO_V1(vector);
|
PGDLLEXPORT PG_FUNCTION_INFO_V1(vector);
|
||||||
Datum
|
Datum
|
||||||
vector(PG_FUNCTION_ARGS)
|
vector(PG_FUNCTION_ARGS)
|
||||||
{
|
{
|
||||||
@@ -298,7 +345,7 @@ vector(PG_FUNCTION_ARGS)
|
|||||||
/*
|
/*
|
||||||
* Convert array to vector
|
* Convert array to vector
|
||||||
*/
|
*/
|
||||||
PG_FUNCTION_INFO_V1(array_to_vector);
|
PGDLLEXPORT PG_FUNCTION_INFO_V1(array_to_vector);
|
||||||
Datum
|
Datum
|
||||||
array_to_vector(PG_FUNCTION_ARGS)
|
array_to_vector(PG_FUNCTION_ARGS)
|
||||||
{
|
{
|
||||||
@@ -340,6 +387,8 @@ array_to_vector(PG_FUNCTION_ARGS)
|
|||||||
result->x[i] = DatumGetFloat8(elemsp[i]);
|
result->x[i] = DatumGetFloat8(elemsp[i]);
|
||||||
else if (ARR_ELEMTYPE(array) == FLOAT4OID)
|
else if (ARR_ELEMTYPE(array) == FLOAT4OID)
|
||||||
result->x[i] = DatumGetFloat4(elemsp[i]);
|
result->x[i] = DatumGetFloat4(elemsp[i]);
|
||||||
|
else if (ARR_ELEMTYPE(array) == NUMERICOID)
|
||||||
|
result->x[i] = DatumGetFloat4(DirectFunctionCall1(numeric_float4, NumericGetDatum(elemsp[i])));
|
||||||
else
|
else
|
||||||
ereport(ERROR,
|
ereport(ERROR,
|
||||||
(errcode(ERRCODE_DATA_EXCEPTION),
|
(errcode(ERRCODE_DATA_EXCEPTION),
|
||||||
@@ -351,15 +400,40 @@ array_to_vector(PG_FUNCTION_ARGS)
|
|||||||
PG_RETURN_POINTER(result);
|
PG_RETURN_POINTER(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Convert vector to float4[]
|
||||||
|
*/
|
||||||
|
PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_to_float4);
|
||||||
|
Datum
|
||||||
|
vector_to_float4(PG_FUNCTION_ARGS)
|
||||||
|
{
|
||||||
|
Vector *vec = PG_GETARG_VECTOR_P(0);
|
||||||
|
Datum *d;
|
||||||
|
ArrayType *result;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
d = (Datum *) palloc(sizeof(Datum) * vec->dim);
|
||||||
|
|
||||||
|
for (i = 0; i < vec->dim; i++)
|
||||||
|
d[i] = Float4GetDatum(vec->x[i]);
|
||||||
|
|
||||||
|
/* Use TYPALIGN_INT for float4 */
|
||||||
|
result = construct_array(d, vec->dim, FLOAT4OID, sizeof(float4), true, TYPALIGN_INT);
|
||||||
|
|
||||||
|
PG_RETURN_POINTER(result);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Get the L2 distance between vectors
|
* Get the L2 distance between vectors
|
||||||
*/
|
*/
|
||||||
PG_FUNCTION_INFO_V1(l2_distance);
|
PGDLLEXPORT PG_FUNCTION_INFO_V1(l2_distance);
|
||||||
Datum
|
Datum
|
||||||
l2_distance(PG_FUNCTION_ARGS)
|
l2_distance(PG_FUNCTION_ARGS)
|
||||||
{
|
{
|
||||||
Vector *a = PG_GETARG_VECTOR_P(0);
|
Vector *a = PG_GETARG_VECTOR_P(0);
|
||||||
Vector *b = PG_GETARG_VECTOR_P(1);
|
Vector *b = PG_GETARG_VECTOR_P(1);
|
||||||
|
float *ax = a->x;
|
||||||
|
float *bx = b->x;
|
||||||
double distance = 0.0;
|
double distance = 0.0;
|
||||||
double diff;
|
double diff;
|
||||||
|
|
||||||
@@ -367,7 +441,7 @@ l2_distance(PG_FUNCTION_ARGS)
|
|||||||
|
|
||||||
for (int i = 0; i < a->dim; i++)
|
for (int i = 0; i < a->dim; i++)
|
||||||
{
|
{
|
||||||
diff = a->x[i] - b->x[i];
|
diff = ax[i] - bx[i];
|
||||||
distance += diff * diff;
|
distance += diff * diff;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -378,12 +452,14 @@ l2_distance(PG_FUNCTION_ARGS)
|
|||||||
* Get the L2 squared distance between vectors
|
* Get the L2 squared distance between vectors
|
||||||
* This saves a sqrt calculation
|
* This saves a sqrt calculation
|
||||||
*/
|
*/
|
||||||
PG_FUNCTION_INFO_V1(vector_l2_squared_distance);
|
PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_l2_squared_distance);
|
||||||
Datum
|
Datum
|
||||||
vector_l2_squared_distance(PG_FUNCTION_ARGS)
|
vector_l2_squared_distance(PG_FUNCTION_ARGS)
|
||||||
{
|
{
|
||||||
Vector *a = PG_GETARG_VECTOR_P(0);
|
Vector *a = PG_GETARG_VECTOR_P(0);
|
||||||
Vector *b = PG_GETARG_VECTOR_P(1);
|
Vector *b = PG_GETARG_VECTOR_P(1);
|
||||||
|
float *ax = a->x;
|
||||||
|
float *bx = b->x;
|
||||||
double distance = 0.0;
|
double distance = 0.0;
|
||||||
double diff;
|
double diff;
|
||||||
|
|
||||||
@@ -391,7 +467,7 @@ vector_l2_squared_distance(PG_FUNCTION_ARGS)
|
|||||||
|
|
||||||
for (int i = 0; i < a->dim; i++)
|
for (int i = 0; i < a->dim; i++)
|
||||||
{
|
{
|
||||||
diff = a->x[i] - b->x[i];
|
diff = ax[i] - bx[i];
|
||||||
distance += diff * diff;
|
distance += diff * diff;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -401,18 +477,20 @@ vector_l2_squared_distance(PG_FUNCTION_ARGS)
|
|||||||
/*
|
/*
|
||||||
* Get the inner product of two vectors
|
* Get the inner product of two vectors
|
||||||
*/
|
*/
|
||||||
PG_FUNCTION_INFO_V1(inner_product);
|
PGDLLEXPORT PG_FUNCTION_INFO_V1(inner_product);
|
||||||
Datum
|
Datum
|
||||||
inner_product(PG_FUNCTION_ARGS)
|
inner_product(PG_FUNCTION_ARGS)
|
||||||
{
|
{
|
||||||
Vector *a = PG_GETARG_VECTOR_P(0);
|
Vector *a = PG_GETARG_VECTOR_P(0);
|
||||||
Vector *b = PG_GETARG_VECTOR_P(1);
|
Vector *b = PG_GETARG_VECTOR_P(1);
|
||||||
|
float *ax = a->x;
|
||||||
|
float *bx = b->x;
|
||||||
double distance = 0.0;
|
double distance = 0.0;
|
||||||
|
|
||||||
CheckDims(a, b);
|
CheckDims(a, b);
|
||||||
|
|
||||||
for (int i = 0; i < a->dim; i++)
|
for (int i = 0; i < a->dim; i++)
|
||||||
distance += a->x[i] * b->x[i];
|
distance += ax[i] * bx[i];
|
||||||
|
|
||||||
PG_RETURN_FLOAT8(distance);
|
PG_RETURN_FLOAT8(distance);
|
||||||
}
|
}
|
||||||
@@ -420,18 +498,20 @@ inner_product(PG_FUNCTION_ARGS)
|
|||||||
/*
|
/*
|
||||||
* Get the negative inner product of two vectors
|
* Get the negative inner product of two vectors
|
||||||
*/
|
*/
|
||||||
PG_FUNCTION_INFO_V1(vector_negative_inner_product);
|
PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_negative_inner_product);
|
||||||
Datum
|
Datum
|
||||||
vector_negative_inner_product(PG_FUNCTION_ARGS)
|
vector_negative_inner_product(PG_FUNCTION_ARGS)
|
||||||
{
|
{
|
||||||
Vector *a = PG_GETARG_VECTOR_P(0);
|
Vector *a = PG_GETARG_VECTOR_P(0);
|
||||||
Vector *b = PG_GETARG_VECTOR_P(1);
|
Vector *b = PG_GETARG_VECTOR_P(1);
|
||||||
|
float *ax = a->x;
|
||||||
|
float *bx = b->x;
|
||||||
double distance = 0.0;
|
double distance = 0.0;
|
||||||
|
|
||||||
CheckDims(a, b);
|
CheckDims(a, b);
|
||||||
|
|
||||||
for (int i = 0; i < a->dim; i++)
|
for (int i = 0; i < a->dim; i++)
|
||||||
distance += a->x[i] * b->x[i];
|
distance += ax[i] * bx[i];
|
||||||
|
|
||||||
PG_RETURN_FLOAT8(distance * -1);
|
PG_RETURN_FLOAT8(distance * -1);
|
||||||
}
|
}
|
||||||
@@ -439,12 +519,14 @@ vector_negative_inner_product(PG_FUNCTION_ARGS)
|
|||||||
/*
|
/*
|
||||||
* Get the cosine distance between two vectors
|
* Get the cosine distance between two vectors
|
||||||
*/
|
*/
|
||||||
PG_FUNCTION_INFO_V1(cosine_distance);
|
PGDLLEXPORT PG_FUNCTION_INFO_V1(cosine_distance);
|
||||||
Datum
|
Datum
|
||||||
cosine_distance(PG_FUNCTION_ARGS)
|
cosine_distance(PG_FUNCTION_ARGS)
|
||||||
{
|
{
|
||||||
Vector *a = PG_GETARG_VECTOR_P(0);
|
Vector *a = PG_GETARG_VECTOR_P(0);
|
||||||
Vector *b = PG_GETARG_VECTOR_P(1);
|
Vector *b = PG_GETARG_VECTOR_P(1);
|
||||||
|
float *ax = a->x;
|
||||||
|
float *bx = b->x;
|
||||||
double distance = 0.0;
|
double distance = 0.0;
|
||||||
double norma = 0.0;
|
double norma = 0.0;
|
||||||
double normb = 0.0;
|
double normb = 0.0;
|
||||||
@@ -453,9 +535,9 @@ cosine_distance(PG_FUNCTION_ARGS)
|
|||||||
|
|
||||||
for (int i = 0; i < a->dim; i++)
|
for (int i = 0; i < a->dim; i++)
|
||||||
{
|
{
|
||||||
distance += a->x[i] * b->x[i];
|
distance += ax[i] * bx[i];
|
||||||
norma += a->x[i] * a->x[i];
|
norma += ax[i] * ax[i];
|
||||||
normb += b->x[i] * b->x[i];
|
normb += bx[i] * bx[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
PG_RETURN_FLOAT8(1 - (distance / (sqrt(norma) * sqrt(normb))));
|
PG_RETURN_FLOAT8(1 - (distance / (sqrt(norma) * sqrt(normb))));
|
||||||
@@ -466,7 +548,7 @@ cosine_distance(PG_FUNCTION_ARGS)
|
|||||||
* Currently uses angular distance since needs to satisfy triangle inequality
|
* Currently uses angular distance since needs to satisfy triangle inequality
|
||||||
* Assumes inputs are unit vectors (skips norm)
|
* Assumes inputs are unit vectors (skips norm)
|
||||||
*/
|
*/
|
||||||
PG_FUNCTION_INFO_V1(vector_spherical_distance);
|
PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_spherical_distance);
|
||||||
Datum
|
Datum
|
||||||
vector_spherical_distance(PG_FUNCTION_ARGS)
|
vector_spherical_distance(PG_FUNCTION_ARGS)
|
||||||
{
|
{
|
||||||
@@ -491,7 +573,7 @@ vector_spherical_distance(PG_FUNCTION_ARGS)
|
|||||||
/*
|
/*
|
||||||
* Get the dimensions of a vector
|
* Get the dimensions of a vector
|
||||||
*/
|
*/
|
||||||
PG_FUNCTION_INFO_V1(vector_dims);
|
PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_dims);
|
||||||
Datum
|
Datum
|
||||||
vector_dims(PG_FUNCTION_ARGS)
|
vector_dims(PG_FUNCTION_ARGS)
|
||||||
{
|
{
|
||||||
@@ -503,15 +585,16 @@ vector_dims(PG_FUNCTION_ARGS)
|
|||||||
/*
|
/*
|
||||||
* Get the L2 norm of a vector
|
* Get the L2 norm of a vector
|
||||||
*/
|
*/
|
||||||
PG_FUNCTION_INFO_V1(vector_norm);
|
PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_norm);
|
||||||
Datum
|
Datum
|
||||||
vector_norm(PG_FUNCTION_ARGS)
|
vector_norm(PG_FUNCTION_ARGS)
|
||||||
{
|
{
|
||||||
Vector *a = PG_GETARG_VECTOR_P(0);
|
Vector *a = PG_GETARG_VECTOR_P(0);
|
||||||
|
float *ax = a->x;
|
||||||
double norm = 0.0;
|
double norm = 0.0;
|
||||||
|
|
||||||
for (int i = 0; i < a->dim; i++)
|
for (int i = 0; i < a->dim; i++)
|
||||||
norm += a->x[i] * a->x[i];
|
norm += ax[i] * ax[i];
|
||||||
|
|
||||||
PG_RETURN_FLOAT8(sqrt(norm));
|
PG_RETURN_FLOAT8(sqrt(norm));
|
||||||
}
|
}
|
||||||
@@ -519,20 +602,23 @@ vector_norm(PG_FUNCTION_ARGS)
|
|||||||
/*
|
/*
|
||||||
* Add vectors
|
* Add vectors
|
||||||
*/
|
*/
|
||||||
PG_FUNCTION_INFO_V1(vector_add);
|
PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_add);
|
||||||
Datum
|
Datum
|
||||||
vector_add(PG_FUNCTION_ARGS)
|
vector_add(PG_FUNCTION_ARGS)
|
||||||
{
|
{
|
||||||
Vector *a = PG_GETARG_VECTOR_P(0);
|
Vector *a = PG_GETARG_VECTOR_P(0);
|
||||||
Vector *b = PG_GETARG_VECTOR_P(1);
|
Vector *b = PG_GETARG_VECTOR_P(1);
|
||||||
|
float *ax = a->x;
|
||||||
|
float *bx = b->x;
|
||||||
Vector *result;
|
Vector *result;
|
||||||
int i;
|
float *rx;
|
||||||
|
|
||||||
CheckDims(a, b);
|
CheckDims(a, b);
|
||||||
|
|
||||||
result = InitVector(a->dim);
|
result = InitVector(a->dim);
|
||||||
for (i = 0; i < a->dim; i++)
|
rx = result->x;
|
||||||
result->x[i] = a->x[i] + b->x[i];
|
for (int i = 0, imax = a->dim; i < imax; i++)
|
||||||
|
rx[i] = ax[i] + bx[i];
|
||||||
|
|
||||||
PG_RETURN_POINTER(result);
|
PG_RETURN_POINTER(result);
|
||||||
}
|
}
|
||||||
@@ -540,20 +626,23 @@ vector_add(PG_FUNCTION_ARGS)
|
|||||||
/*
|
/*
|
||||||
* Subtract vectors
|
* Subtract vectors
|
||||||
*/
|
*/
|
||||||
PG_FUNCTION_INFO_V1(vector_sub);
|
PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_sub);
|
||||||
Datum
|
Datum
|
||||||
vector_sub(PG_FUNCTION_ARGS)
|
vector_sub(PG_FUNCTION_ARGS)
|
||||||
{
|
{
|
||||||
Vector *a = PG_GETARG_VECTOR_P(0);
|
Vector *a = PG_GETARG_VECTOR_P(0);
|
||||||
Vector *b = PG_GETARG_VECTOR_P(1);
|
Vector *b = PG_GETARG_VECTOR_P(1);
|
||||||
|
float *ax = a->x;
|
||||||
|
float *bx = b->x;
|
||||||
Vector *result;
|
Vector *result;
|
||||||
int i;
|
float *rx;
|
||||||
|
|
||||||
CheckDims(a, b);
|
CheckDims(a, b);
|
||||||
|
|
||||||
result = InitVector(a->dim);
|
result = InitVector(a->dim);
|
||||||
for (i = 0; i < a->dim; i++)
|
rx = result->x;
|
||||||
result->x[i] = a->x[i] - b->x[i];
|
for (int i = 0, imax = a->dim; i < imax; i++)
|
||||||
|
rx[i] = ax[i] - bx[i];
|
||||||
|
|
||||||
PG_RETURN_POINTER(result);
|
PG_RETURN_POINTER(result);
|
||||||
}
|
}
|
||||||
@@ -582,7 +671,7 @@ vector_cmp_internal(Vector * a, Vector * b)
|
|||||||
/*
|
/*
|
||||||
* Less than
|
* Less than
|
||||||
*/
|
*/
|
||||||
PG_FUNCTION_INFO_V1(vector_lt);
|
PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_lt);
|
||||||
Datum
|
Datum
|
||||||
vector_lt(PG_FUNCTION_ARGS)
|
vector_lt(PG_FUNCTION_ARGS)
|
||||||
{
|
{
|
||||||
@@ -595,7 +684,7 @@ vector_lt(PG_FUNCTION_ARGS)
|
|||||||
/*
|
/*
|
||||||
* Less than or equal
|
* Less than or equal
|
||||||
*/
|
*/
|
||||||
PG_FUNCTION_INFO_V1(vector_le);
|
PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_le);
|
||||||
Datum
|
Datum
|
||||||
vector_le(PG_FUNCTION_ARGS)
|
vector_le(PG_FUNCTION_ARGS)
|
||||||
{
|
{
|
||||||
@@ -608,7 +697,7 @@ vector_le(PG_FUNCTION_ARGS)
|
|||||||
/*
|
/*
|
||||||
* Equal
|
* Equal
|
||||||
*/
|
*/
|
||||||
PG_FUNCTION_INFO_V1(vector_eq);
|
PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_eq);
|
||||||
Datum
|
Datum
|
||||||
vector_eq(PG_FUNCTION_ARGS)
|
vector_eq(PG_FUNCTION_ARGS)
|
||||||
{
|
{
|
||||||
@@ -621,7 +710,7 @@ vector_eq(PG_FUNCTION_ARGS)
|
|||||||
/*
|
/*
|
||||||
* Not equal
|
* Not equal
|
||||||
*/
|
*/
|
||||||
PG_FUNCTION_INFO_V1(vector_ne);
|
PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_ne);
|
||||||
Datum
|
Datum
|
||||||
vector_ne(PG_FUNCTION_ARGS)
|
vector_ne(PG_FUNCTION_ARGS)
|
||||||
{
|
{
|
||||||
@@ -634,7 +723,7 @@ vector_ne(PG_FUNCTION_ARGS)
|
|||||||
/*
|
/*
|
||||||
* Greater than or equal
|
* Greater than or equal
|
||||||
*/
|
*/
|
||||||
PG_FUNCTION_INFO_V1(vector_ge);
|
PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_ge);
|
||||||
Datum
|
Datum
|
||||||
vector_ge(PG_FUNCTION_ARGS)
|
vector_ge(PG_FUNCTION_ARGS)
|
||||||
{
|
{
|
||||||
@@ -647,7 +736,7 @@ vector_ge(PG_FUNCTION_ARGS)
|
|||||||
/*
|
/*
|
||||||
* Greater than
|
* Greater than
|
||||||
*/
|
*/
|
||||||
PG_FUNCTION_INFO_V1(vector_gt);
|
PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_gt);
|
||||||
Datum
|
Datum
|
||||||
vector_gt(PG_FUNCTION_ARGS)
|
vector_gt(PG_FUNCTION_ARGS)
|
||||||
{
|
{
|
||||||
@@ -660,7 +749,7 @@ vector_gt(PG_FUNCTION_ARGS)
|
|||||||
/*
|
/*
|
||||||
* Compare vectors
|
* Compare vectors
|
||||||
*/
|
*/
|
||||||
PG_FUNCTION_INFO_V1(vector_cmp);
|
PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_cmp);
|
||||||
Datum
|
Datum
|
||||||
vector_cmp(PG_FUNCTION_ARGS)
|
vector_cmp(PG_FUNCTION_ARGS)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
SET client_min_messages = warning;
|
|
||||||
CREATE EXTENSION IF NOT EXISTS vector;
|
|
||||||
SET enable_seqscan = off;
|
SET enable_seqscan = off;
|
||||||
CREATE TABLE t (val vector(3));
|
CREATE TABLE t (val vector(3));
|
||||||
INSERT INTO t (val) VALUES ('[0,0,0]'), ('[1,2,3]'), ('[1,1,1]'), (NULL);
|
INSERT INTO t (val) VALUES ('[0,0,0]'), ('[1,2,3]'), ('[1,1,1]'), (NULL);
|
||||||
@@ -1,11 +1,15 @@
|
|||||||
SET client_min_messages = warning;
|
|
||||||
CREATE EXTENSION IF NOT EXISTS vector;
|
|
||||||
SELECT ARRAY[1,2,3]::vector;
|
SELECT ARRAY[1,2,3]::vector;
|
||||||
array
|
array
|
||||||
---------
|
---------
|
||||||
[1,2,3]
|
[1,2,3]
|
||||||
(1 row)
|
(1 row)
|
||||||
|
|
||||||
|
SELECT ARRAY[1.0,2.0,3.0]::vector;
|
||||||
|
array
|
||||||
|
---------
|
||||||
|
[1,2,3]
|
||||||
|
(1 row)
|
||||||
|
|
||||||
SELECT ARRAY[1,2,3]::float4[]::vector;
|
SELECT ARRAY[1,2,3]::float4[]::vector;
|
||||||
array
|
array
|
||||||
---------
|
---------
|
||||||
@@ -28,3 +32,18 @@ SELECT '{-Infinity}'::real[]::vector;
|
|||||||
ERROR: infinite value not allowed in vector
|
ERROR: infinite value not allowed in vector
|
||||||
SELECT '{}'::real[]::vector;
|
SELECT '{}'::real[]::vector;
|
||||||
ERROR: vector must have at least 1 dimension
|
ERROR: vector must have at least 1 dimension
|
||||||
|
SELECT '[1,2,3]'::vector::real[];
|
||||||
|
float4
|
||||||
|
---------
|
||||||
|
{1,2,3}
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT array_agg(n)::vector FROM generate_series(1, 1025) n;
|
||||||
|
ERROR: vector cannot have more than 1024 dimensions
|
||||||
|
-- ensure no error
|
||||||
|
SELECT ARRAY[1,2,3] = ARRAY[1,2,3];
|
||||||
|
?column?
|
||||||
|
----------
|
||||||
|
t
|
||||||
|
(1 row)
|
||||||
|
|
||||||
@@ -1,10 +1,8 @@
|
|||||||
SET client_min_messages = warning;
|
|
||||||
CREATE EXTENSION IF NOT EXISTS vector;
|
|
||||||
CREATE TABLE t (val vector(3));
|
CREATE TABLE t (val vector(3));
|
||||||
INSERT INTO t (val) VALUES ('[0,0,0]'), ('[1,2,3]'), ('[1,1,1]'), (NULL);
|
INSERT INTO t (val) VALUES ('[0,0,0]'), ('[1,2,3]'), ('[1,1,1]'), (NULL);
|
||||||
CREATE TABLE t2 (val vector(3));
|
CREATE TABLE t2 (val vector(3));
|
||||||
\copy t TO '/tmp/data.bin' WITH (FORMAT binary)
|
\copy t TO 'results/data.bin' WITH (FORMAT binary)
|
||||||
\copy t2 FROM '/tmp/data.bin' WITH (FORMAT binary)
|
\copy t2 FROM 'results/data.bin' WITH (FORMAT binary)
|
||||||
SELECT * FROM t2 ORDER BY val;
|
SELECT * FROM t2 ORDER BY val;
|
||||||
val
|
val
|
||||||
---------
|
---------
|
||||||
@@ -1,5 +1,3 @@
|
|||||||
SET client_min_messages = warning;
|
|
||||||
CREATE EXTENSION IF NOT EXISTS vector;
|
|
||||||
SELECT '[1,2,3]'::vector + '[4,5,6]';
|
SELECT '[1,2,3]'::vector + '[4,5,6]';
|
||||||
?column?
|
?column?
|
||||||
----------
|
----------
|
||||||
@@ -1,5 +1,3 @@
|
|||||||
SET client_min_messages = warning;
|
|
||||||
CREATE EXTENSION IF NOT EXISTS vector;
|
|
||||||
SELECT '[1,2,3]'::vector;
|
SELECT '[1,2,3]'::vector;
|
||||||
vector
|
vector
|
||||||
---------
|
---------
|
||||||
@@ -12,6 +10,12 @@ SELECT '[-1,2,3]'::vector;
|
|||||||
[-1,2,3]
|
[-1,2,3]
|
||||||
(1 row)
|
(1 row)
|
||||||
|
|
||||||
|
SELECT '[1.23456]'::vector;
|
||||||
|
vector
|
||||||
|
-----------
|
||||||
|
[1.23456]
|
||||||
|
(1 row)
|
||||||
|
|
||||||
SELECT '[hello,1]'::vector;
|
SELECT '[hello,1]'::vector;
|
||||||
ERROR: invalid input syntax for type vector: "hello"
|
ERROR: invalid input syntax for type vector: "hello"
|
||||||
LINE 1: SELECT '[hello,1]'::vector;
|
LINE 1: SELECT '[hello,1]'::vector;
|
||||||
@@ -53,3 +57,12 @@ LINE 1: SELECT '[1,]'::vector;
|
|||||||
^
|
^
|
||||||
SELECT '[1,2,3]'::vector(2);
|
SELECT '[1,2,3]'::vector(2);
|
||||||
ERROR: expected 2 dimensions, not 3
|
ERROR: expected 2 dimensions, not 3
|
||||||
|
SELECT unnest('{"[1,2,3]", "[4,5,6]"}'::vector[]);
|
||||||
|
unnest
|
||||||
|
---------
|
||||||
|
[1,2,3]
|
||||||
|
[4,5,6]
|
||||||
|
(2 rows)
|
||||||
|
|
||||||
|
SELECT '{"[1,2,3]"}'::vector(2)[];
|
||||||
|
ERROR: expected 2 dimensions, not 3
|
||||||
@@ -1,5 +1,3 @@
|
|||||||
SET client_min_messages = warning;
|
|
||||||
CREATE EXTENSION IF NOT EXISTS vector;
|
|
||||||
SET enable_seqscan = off;
|
SET enable_seqscan = off;
|
||||||
CREATE TABLE t (val vector(3));
|
CREATE TABLE t (val vector(3));
|
||||||
INSERT INTO t (val) VALUES ('[0,0,0]'), ('[1,2,3]'), ('[1,1,1]'), (NULL);
|
INSERT INTO t (val) VALUES ('[0,0,0]'), ('[1,2,3]'), ('[1,1,1]'), (NULL);
|
||||||
@@ -1,5 +1,3 @@
|
|||||||
SET client_min_messages = warning;
|
|
||||||
CREATE EXTENSION IF NOT EXISTS vector;
|
|
||||||
SET enable_seqscan = off;
|
SET enable_seqscan = off;
|
||||||
CREATE TABLE t (val vector(3));
|
CREATE TABLE t (val vector(3));
|
||||||
INSERT INTO t (val) VALUES ('[0,0,0]'), ('[1,2,3]'), ('[1,1,1]'), (NULL);
|
INSERT INTO t (val) VALUES ('[0,0,0]'), ('[1,2,3]'), ('[1,1,1]'), (NULL);
|
||||||
@@ -1,5 +1,3 @@
|
|||||||
SET client_min_messages = warning;
|
|
||||||
CREATE EXTENSION IF NOT EXISTS vector;
|
|
||||||
SET enable_seqscan = off;
|
SET enable_seqscan = off;
|
||||||
CREATE TABLE t (val vector(3));
|
CREATE TABLE t (val vector(3));
|
||||||
INSERT INTO t (val) VALUES ('[0,0,0]'), ('[1,2,3]'), ('[1,1,1]'), (NULL);
|
INSERT INTO t (val) VALUES ('[0,0,0]'), ('[1,2,3]'), ('[1,1,1]'), (NULL);
|
||||||
@@ -19,4 +17,10 @@ SELECT * FROM t ORDER BY val <-> (SELECT NULL::vector);
|
|||||||
-----
|
-----
|
||||||
(0 rows)
|
(0 rows)
|
||||||
|
|
||||||
|
SELECT COUNT(*) FROM t;
|
||||||
|
count
|
||||||
|
-------
|
||||||
|
5
|
||||||
|
(1 row)
|
||||||
|
|
||||||
DROP TABLE t;
|
DROP TABLE t;
|
||||||
15
test/expected/ivfflat_options.out
Normal file
15
test/expected/ivfflat_options.out
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
SET enable_seqscan = off;
|
||||||
|
CREATE TABLE t (val vector(3));
|
||||||
|
CREATE INDEX ON t USING ivfflat (val) 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);
|
||||||
|
ERROR: value 32769 out of bounds for option "lists"
|
||||||
|
DETAIL: Valid values are between "1" and "32768".
|
||||||
|
SHOW ivfflat.probes;
|
||||||
|
ivfflat.probes
|
||||||
|
----------------
|
||||||
|
1
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
DROP TABLE t;
|
||||||
@@ -1,5 +1,3 @@
|
|||||||
SET client_min_messages = warning;
|
|
||||||
CREATE EXTENSION IF NOT EXISTS vector;
|
|
||||||
SET enable_seqscan = off;
|
SET enable_seqscan = off;
|
||||||
CREATE UNLOGGED TABLE t (val vector(3));
|
CREATE UNLOGGED TABLE t (val vector(3));
|
||||||
INSERT INTO t (val) VALUES ('[0,0,0]'), ('[1,2,3]'), ('[1,1,1]'), (NULL);
|
INSERT INTO t (val) VALUES ('[0,0,0]'), ('[1,2,3]'), ('[1,1,1]'), (NULL);
|
||||||
8
test/perl/PostgresNode.pm
Normal file
8
test/perl/PostgresNode.pm
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
use PostgreSQL::Test::Cluster;
|
||||||
|
|
||||||
|
sub get_new_node
|
||||||
|
{
|
||||||
|
return PostgreSQL::Test::Cluster->new(@_);
|
||||||
|
}
|
||||||
|
|
||||||
|
1;
|
||||||
3
test/perl/TestLib.pm
Normal file
3
test/perl/TestLib.pm
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
use PostgreSQL::Test::Utils;
|
||||||
|
|
||||||
|
1;
|
||||||
@@ -1,5 +1,3 @@
|
|||||||
SET client_min_messages = warning;
|
|
||||||
CREATE EXTENSION IF NOT EXISTS vector;
|
|
||||||
SET enable_seqscan = off;
|
SET enable_seqscan = off;
|
||||||
|
|
||||||
CREATE TABLE t (val vector(3));
|
CREATE TABLE t (val vector(3));
|
||||||
@@ -1,7 +1,5 @@
|
|||||||
SET client_min_messages = warning;
|
|
||||||
CREATE EXTENSION IF NOT EXISTS vector;
|
|
||||||
|
|
||||||
SELECT ARRAY[1,2,3]::vector;
|
SELECT ARRAY[1,2,3]::vector;
|
||||||
|
SELECT ARRAY[1.0,2.0,3.0]::vector;
|
||||||
SELECT ARRAY[1,2,3]::float4[]::vector;
|
SELECT ARRAY[1,2,3]::float4[]::vector;
|
||||||
SELECT ARRAY[1,2,3]::float8[]::vector;
|
SELECT ARRAY[1,2,3]::float8[]::vector;
|
||||||
SELECT '{NULL}'::real[]::vector;
|
SELECT '{NULL}'::real[]::vector;
|
||||||
@@ -9,3 +7,8 @@ SELECT '{NaN}'::real[]::vector;
|
|||||||
SELECT '{Infinity}'::real[]::vector;
|
SELECT '{Infinity}'::real[]::vector;
|
||||||
SELECT '{-Infinity}'::real[]::vector;
|
SELECT '{-Infinity}'::real[]::vector;
|
||||||
SELECT '{}'::real[]::vector;
|
SELECT '{}'::real[]::vector;
|
||||||
|
SELECT '[1,2,3]'::vector::real[];
|
||||||
|
SELECT array_agg(n)::vector FROM generate_series(1, 1025) n;
|
||||||
|
|
||||||
|
-- ensure no error
|
||||||
|
SELECT ARRAY[1,2,3] = ARRAY[1,2,3];
|
||||||
@@ -1,13 +1,10 @@
|
|||||||
SET client_min_messages = warning;
|
|
||||||
CREATE EXTENSION IF NOT EXISTS vector;
|
|
||||||
|
|
||||||
CREATE TABLE t (val vector(3));
|
CREATE TABLE t (val vector(3));
|
||||||
INSERT INTO t (val) VALUES ('[0,0,0]'), ('[1,2,3]'), ('[1,1,1]'), (NULL);
|
INSERT INTO t (val) VALUES ('[0,0,0]'), ('[1,2,3]'), ('[1,1,1]'), (NULL);
|
||||||
|
|
||||||
CREATE TABLE t2 (val vector(3));
|
CREATE TABLE t2 (val vector(3));
|
||||||
|
|
||||||
\copy t TO '/tmp/data.bin' WITH (FORMAT binary)
|
\copy t TO 'results/data.bin' WITH (FORMAT binary)
|
||||||
\copy t2 FROM '/tmp/data.bin' WITH (FORMAT binary)
|
\copy t2 FROM 'results/data.bin' WITH (FORMAT binary)
|
||||||
|
|
||||||
SELECT * FROM t2 ORDER BY val;
|
SELECT * FROM t2 ORDER BY val;
|
||||||
|
|
||||||
@@ -1,6 +1,3 @@
|
|||||||
SET client_min_messages = warning;
|
|
||||||
CREATE EXTENSION IF NOT EXISTS vector;
|
|
||||||
|
|
||||||
SELECT '[1,2,3]'::vector + '[4,5,6]';
|
SELECT '[1,2,3]'::vector + '[4,5,6]';
|
||||||
SELECT '[1,2,3]'::vector - '[4,5,6]';
|
SELECT '[1,2,3]'::vector - '[4,5,6]';
|
||||||
|
|
||||||
16
test/sql/input.sql
Normal file
16
test/sql/input.sql
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
SELECT '[1,2,3]'::vector;
|
||||||
|
SELECT '[-1,2,3]'::vector;
|
||||||
|
SELECT '[1.23456]'::vector;
|
||||||
|
SELECT '[hello,1]'::vector;
|
||||||
|
SELECT '[NaN,1]'::vector;
|
||||||
|
SELECT '[Infinity,1]'::vector;
|
||||||
|
SELECT '[-Infinity,1]'::vector;
|
||||||
|
SELECT '[1,2,3'::vector;
|
||||||
|
SELECT '[1,2,3]9'::vector;
|
||||||
|
SELECT '1,2,3'::vector;
|
||||||
|
SELECT '[]'::vector;
|
||||||
|
SELECT '[1,]'::vector;
|
||||||
|
SELECT '[1,2,3]'::vector(2);
|
||||||
|
|
||||||
|
SELECT unnest('{"[1,2,3]", "[4,5,6]"}'::vector[]);
|
||||||
|
SELECT '{"[1,2,3]"}'::vector(2)[];
|
||||||
@@ -1,5 +1,3 @@
|
|||||||
SET client_min_messages = warning;
|
|
||||||
CREATE EXTENSION IF NOT EXISTS vector;
|
|
||||||
SET enable_seqscan = off;
|
SET enable_seqscan = off;
|
||||||
|
|
||||||
CREATE TABLE t (val vector(3));
|
CREATE TABLE t (val vector(3));
|
||||||
@@ -1,5 +1,3 @@
|
|||||||
SET client_min_messages = warning;
|
|
||||||
CREATE EXTENSION IF NOT EXISTS vector;
|
|
||||||
SET enable_seqscan = off;
|
SET enable_seqscan = off;
|
||||||
|
|
||||||
CREATE TABLE t (val vector(3));
|
CREATE TABLE t (val vector(3));
|
||||||
@@ -1,5 +1,3 @@
|
|||||||
SET client_min_messages = warning;
|
|
||||||
CREATE EXTENSION IF NOT EXISTS vector;
|
|
||||||
SET enable_seqscan = off;
|
SET enable_seqscan = off;
|
||||||
|
|
||||||
CREATE TABLE t (val vector(3));
|
CREATE TABLE t (val vector(3));
|
||||||
@@ -10,5 +8,6 @@ INSERT INTO t (val) VALUES ('[1,2,4]');
|
|||||||
|
|
||||||
SELECT * FROM t ORDER BY val <-> '[3,3,3]';
|
SELECT * FROM t ORDER BY val <-> '[3,3,3]';
|
||||||
SELECT * FROM t ORDER BY val <-> (SELECT NULL::vector);
|
SELECT * FROM t ORDER BY val <-> (SELECT NULL::vector);
|
||||||
|
SELECT COUNT(*) FROM t;
|
||||||
|
|
||||||
DROP TABLE t;
|
DROP TABLE t;
|
||||||
@@ -1,5 +1,3 @@
|
|||||||
SET client_min_messages = warning;
|
|
||||||
CREATE EXTENSION IF NOT EXISTS vector;
|
|
||||||
SET enable_seqscan = off;
|
SET enable_seqscan = off;
|
||||||
|
|
||||||
CREATE TABLE t (val vector(3));
|
CREATE TABLE t (val vector(3));
|
||||||
@@ -1,5 +1,3 @@
|
|||||||
SET client_min_messages = warning;
|
|
||||||
CREATE EXTENSION IF NOT EXISTS vector;
|
|
||||||
SET enable_seqscan = off;
|
SET enable_seqscan = off;
|
||||||
|
|
||||||
CREATE UNLOGGED TABLE t (val vector(3));
|
CREATE UNLOGGED TABLE t (val vector(3));
|
||||||
@@ -7,6 +7,8 @@ use PostgresNode;
|
|||||||
use TestLib;
|
use TestLib;
|
||||||
use Test::More tests => 31;
|
use Test::More tests => 31;
|
||||||
|
|
||||||
|
my $dim = 32;
|
||||||
|
|
||||||
my $node_primary;
|
my $node_primary;
|
||||||
my $node_replica;
|
my $node_replica;
|
||||||
|
|
||||||
@@ -21,21 +23,25 @@ sub test_index_replay
|
|||||||
my $caughtup_query;
|
my $caughtup_query;
|
||||||
my $server_version_num = $node_primary->safe_psql("postgres", "SHOW server_version_num");
|
my $server_version_num = $node_primary->safe_psql("postgres", "SHOW server_version_num");
|
||||||
if ($server_version_num >= 100000) {
|
if ($server_version_num >= 100000) {
|
||||||
$caughtup_query = "SELECT pg_current_wal_lsn() <= write_lsn FROM pg_stat_replication WHERE application_name = '$applname';";
|
$caughtup_query = "SELECT pg_current_wal_lsn() <= replay_lsn FROM pg_stat_replication WHERE application_name = '$applname';";
|
||||||
} else {
|
} else {
|
||||||
|
# TODO figure out why replay location doesn't work
|
||||||
$caughtup_query = "SELECT pg_current_xlog_location() <= write_location FROM pg_stat_replication WHERE application_name = '$applname';";
|
$caughtup_query = "SELECT pg_current_xlog_location() <= write_location FROM pg_stat_replication WHERE application_name = '$applname';";
|
||||||
}
|
}
|
||||||
|
|
||||||
$node_primary->poll_query_until('postgres', $caughtup_query)
|
$node_primary->poll_query_until('postgres', $caughtup_query)
|
||||||
or die "Timed out while waiting for replica 1 to catch up";
|
or die "Timed out while waiting for replica 1 to catch up";
|
||||||
|
|
||||||
my $r1 = rand();
|
my @r = ();
|
||||||
my $r2 = rand();
|
for (1 .. $dim) {
|
||||||
my $r3 = rand();
|
push(@r, rand());
|
||||||
|
}
|
||||||
|
my $sql = join(",", @r);
|
||||||
|
|
||||||
my $queries = qq(SET enable_seqscan=off;
|
my $queries = qq(
|
||||||
SELECT * FROM tst ORDER BY v <-> '[$r1,$r2,$r3]' LIMIT 10;
|
SET enable_seqscan = off;
|
||||||
);
|
SELECT * FROM tst ORDER BY v <-> '[$sql]' LIMIT 10;
|
||||||
|
);
|
||||||
|
|
||||||
# Run test queries and compare their result
|
# Run test queries and compare their result
|
||||||
my $primary_result = $node_primary->safe_psql("postgres", $queries);
|
my $primary_result = $node_primary->safe_psql("postgres", $queries);
|
||||||
@@ -45,9 +51,18 @@ SELECT * FROM tst ORDER BY v <-> '[$r1,$r2,$r3]' LIMIT 10;
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Use ARRAY[random(), random(), random(), ...] over
|
||||||
|
# SELECT array_agg(random()) FROM generate_series(1, $dim)
|
||||||
|
# to generate different values for each row
|
||||||
|
my $array_sql = join(",", ('random()') x $dim);
|
||||||
|
|
||||||
# Initialize primary node
|
# Initialize primary node
|
||||||
$node_primary = get_new_node('primary');
|
$node_primary = get_new_node('primary');
|
||||||
$node_primary->init(allows_streaming => 1);
|
$node_primary->init(allows_streaming => 1);
|
||||||
|
if ($dim > 32) {
|
||||||
|
# TODO use wal_keep_segments for Postgres < 13
|
||||||
|
$node_primary->append_conf('postgresql.conf', qq(wal_keep_size = 1GB));
|
||||||
|
}
|
||||||
$node_primary->start;
|
$node_primary->start;
|
||||||
my $backup_name = 'my_backup';
|
my $backup_name = 'my_backup';
|
||||||
|
|
||||||
@@ -62,12 +77,11 @@ $node_replica->start;
|
|||||||
|
|
||||||
# Create ivfflat index on primary
|
# Create ivfflat index on primary
|
||||||
$node_primary->safe_psql("postgres", "CREATE EXTENSION vector;");
|
$node_primary->safe_psql("postgres", "CREATE EXTENSION vector;");
|
||||||
$node_primary->safe_psql("postgres", "CREATE TABLE tst (i int4, v vector(3));");
|
$node_primary->safe_psql("postgres", "CREATE TABLE tst (i int4, v vector($dim));");
|
||||||
$node_primary->safe_psql("postgres",
|
$node_primary->safe_psql("postgres",
|
||||||
"INSERT INTO tst SELECT i%10, ARRAY[random(), random(), random()] FROM generate_series(1,100000) i;"
|
"INSERT INTO tst SELECT i % 10, ARRAY[$array_sql] FROM generate_series(1, 100000) i;"
|
||||||
);
|
);
|
||||||
$node_primary->safe_psql("postgres",
|
$node_primary->safe_psql("postgres", "CREATE INDEX ON tst USING ivfflat (v);");
|
||||||
"CREATE INDEX ON tst USING ivfflat (v);");
|
|
||||||
|
|
||||||
# Test that queries give same result
|
# Test that queries give same result
|
||||||
test_index_replay('initial');
|
test_index_replay('initial');
|
||||||
@@ -81,7 +95,7 @@ for my $i (1 .. 10)
|
|||||||
test_index_replay("vacuum $i");
|
test_index_replay("vacuum $i");
|
||||||
my ($start, $end) = (100001 + ($i - 1) * 10000, 100000 + $i * 10000);
|
my ($start, $end) = (100001 + ($i - 1) * 10000, 100000 + $i * 10000);
|
||||||
$node_primary->safe_psql("postgres",
|
$node_primary->safe_psql("postgres",
|
||||||
"INSERT INTO tst SELECT i%10, ARRAY[random(), random(), random()] FROM generate_series($start,$end) i;"
|
"INSERT INTO tst SELECT i % 10, ARRAY[$array_sql] FROM generate_series($start, $end) i;"
|
||||||
);
|
);
|
||||||
test_index_replay("insert $i");
|
test_index_replay("insert $i");
|
||||||
}
|
}
|
||||||
32
test/t/002_vacuum.pl
Normal file
32
test/t/002_vacuum.pl
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
use PostgresNode;
|
||||||
|
use TestLib;
|
||||||
|
use Test::More tests => 1;
|
||||||
|
|
||||||
|
# Initialize node
|
||||||
|
my $node = get_new_node('node');
|
||||||
|
$node->init;
|
||||||
|
$node->start;
|
||||||
|
|
||||||
|
# Create table and index
|
||||||
|
$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 % 10, ARRAY[i % 1000, i % 333, i % 55] FROM generate_series(1, 100000) i;"
|
||||||
|
);
|
||||||
|
$node->safe_psql("postgres", "CREATE INDEX ON tst USING ivfflat (v);");
|
||||||
|
|
||||||
|
# Get size
|
||||||
|
my $size = $node->safe_psql("postgres", "SELECT pg_total_relation_size('tst_v_idx');");
|
||||||
|
|
||||||
|
# Delete all, vacuum, and insert same data
|
||||||
|
$node->safe_psql("postgres", "DELETE FROM tst;");
|
||||||
|
$node->safe_psql("postgres", "VACUUM tst;");
|
||||||
|
$node->safe_psql("postgres",
|
||||||
|
"INSERT INTO tst SELECT i % 10, ARRAY[i % 1000, i % 333, i % 55] FROM generate_series(1, 100000) i;"
|
||||||
|
);
|
||||||
|
|
||||||
|
# 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");
|
||||||
88
test/t/003_recall.pl
Normal file
88
test/t/003_recall.pl
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
36
test/t/004_centers.pl
Normal file
36
test/t/004_centers.pl
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
use PostgresNode;
|
||||||
|
use TestLib;
|
||||||
|
use Test::More tests => 3;
|
||||||
|
|
||||||
|
# Initialize node
|
||||||
|
my $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, '[1,2,3]' FROM generate_series(1, 10) i;"
|
||||||
|
);
|
||||||
|
|
||||||
|
sub test_centers
|
||||||
|
{
|
||||||
|
my ($lists, $min) = @_;
|
||||||
|
|
||||||
|
my ($ret, $stdout, $stderr) = $node->psql("postgres", "CREATE INDEX ON tst USING ivfflat (v) WITH (lists = $lists);");
|
||||||
|
is($ret, 0, $stderr);
|
||||||
|
}
|
||||||
|
|
||||||
|
# Test no error for duplicate centers
|
||||||
|
test_centers(5);
|
||||||
|
test_centers(10);
|
||||||
|
|
||||||
|
$node->safe_psql("postgres",
|
||||||
|
"INSERT INTO tst SELECT i, '[4,5,6]' FROM generate_series(1, 10) i;"
|
||||||
|
);
|
||||||
|
|
||||||
|
# Test no error for duplicate centers
|
||||||
|
test_centers(10);
|
||||||
45
test/t/005_query_recall.pl
Normal file
45
test/t/005_query_recall.pl
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
use PostgresNode;
|
||||||
|
use TestLib;
|
||||||
|
use Test::More tests => 60;
|
||||||
|
|
||||||
|
# Initialize node
|
||||||
|
my $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 primary key, v vector(3));");
|
||||||
|
$node->safe_psql("postgres",
|
||||||
|
"INSERT INTO tst SELECT i, ARRAY[random(), random(), random()] FROM generate_series(1, 100000) i;"
|
||||||
|
);
|
||||||
|
|
||||||
|
# Check each index type
|
||||||
|
my @operators = ("<->", "<#>", "<=>");
|
||||||
|
foreach (@operators) {
|
||||||
|
my $operator = $_;
|
||||||
|
|
||||||
|
# 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;");
|
||||||
|
my $res = $node->safe_psql("postgres", qq(
|
||||||
|
SET enable_seqscan = off;
|
||||||
|
SELECT v FROM tst ORDER BY v <-> '$query' LIMIT 1;
|
||||||
|
));
|
||||||
|
is($res, $query);
|
||||||
|
}
|
||||||
|
}
|
||||||
31
test/t/006_lists.pl
Normal file
31
test/t/006_lists.pl
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
use PostgresNode;
|
||||||
|
use TestLib;
|
||||||
|
use Test::More tests => 3;
|
||||||
|
|
||||||
|
# Initialize node
|
||||||
|
my $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 (v vector(3));");
|
||||||
|
$node->safe_psql("postgres",
|
||||||
|
"INSERT INTO tst SELECT ARRAY[random(), random(), random()] FROM generate_series(1, 100000) i;"
|
||||||
|
);
|
||||||
|
|
||||||
|
$node->safe_psql("postgres", "CREATE INDEX lists50 ON tst USING ivfflat (v) WITH (lists = 50);");
|
||||||
|
$node->safe_psql("postgres", "CREATE INDEX lists100 ON tst USING ivfflat (v) WITH (lists = 100);");
|
||||||
|
|
||||||
|
# Test prefers more lists
|
||||||
|
my $res = $node->safe_psql("postgres", "EXPLAIN SELECT v FROM tst ORDER BY v <-> '[0.5,0.5,0.5]' LIMIT 10;");
|
||||||
|
like($res, qr/lists100/);
|
||||||
|
unlike($res, qr/lists50/);
|
||||||
|
|
||||||
|
# Test errors with too much memory
|
||||||
|
my ($ret, $stdout, $stderr) = $node->psql("postgres",
|
||||||
|
"CREATE INDEX lists10000 ON tst USING ivfflat (v) WITH (lists = 10000);"
|
||||||
|
);
|
||||||
|
like($stderr, qr/memory required is/);
|
||||||
45
test/t/007_inserts.pl
Normal file
45
test/t/007_inserts.pl
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
use PostgresNode;
|
||||||
|
use TestLib;
|
||||||
|
use Test::More tests => 5;
|
||||||
|
|
||||||
|
my $dim = 768;
|
||||||
|
|
||||||
|
my $array_sql = join(",", ('random()') x $dim);
|
||||||
|
|
||||||
|
# Initialize node
|
||||||
|
my $node = get_new_node('node');
|
||||||
|
$node->init;
|
||||||
|
$node->start;
|
||||||
|
|
||||||
|
# Create table and index
|
||||||
|
$node->safe_psql("postgres", "CREATE EXTENSION vector;");
|
||||||
|
$node->safe_psql("postgres", "CREATE TABLE tst (v vector($dim));");
|
||||||
|
$node->safe_psql("postgres",
|
||||||
|
"INSERT INTO tst SELECT ARRAY[$array_sql] FROM generate_series(1, 10000) i;"
|
||||||
|
);
|
||||||
|
$node->safe_psql("postgres", "CREATE INDEX ON tst USING ivfflat (v);");
|
||||||
|
|
||||||
|
$node->pgbench(
|
||||||
|
"--no-vacuum --client=5 --transactions=100",
|
||||||
|
0,
|
||||||
|
[qr{actually processed}],
|
||||||
|
[qr{^$}],
|
||||||
|
"concurrent INSERTs",
|
||||||
|
{
|
||||||
|
"007_inserts" => "INSERT INTO tst SELECT ARRAY[$array_sql] FROM generate_series(1, 10) i;"
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
my $expected = 10000 + 5 * 100 * 10;
|
||||||
|
|
||||||
|
my $count = $node->safe_psql("postgres", "SELECT COUNT(*) FROM tst;");
|
||||||
|
is($count, $expected);
|
||||||
|
|
||||||
|
$count = $node->safe_psql("postgres", qq(
|
||||||
|
SET enable_seqscan = off;
|
||||||
|
SET ivfflat.probes = 100;
|
||||||
|
SELECT COUNT(*) FROM (SELECT v FROM tst ORDER BY v <-> (SELECT v FROM tst LIMIT 1)) t;
|
||||||
|
));
|
||||||
|
is($count, $expected);
|
||||||
@@ -1,218 +0,0 @@
|
|||||||
-- complain if script is sourced in psql, rather than via CREATE EXTENSION
|
|
||||||
\echo Use "CREATE EXTENSION vector" to load this file. \quit
|
|
||||||
|
|
||||||
-- type
|
|
||||||
|
|
||||||
CREATE TYPE vector;
|
|
||||||
|
|
||||||
CREATE FUNCTION vector_in(cstring, oid, integer) RETURNS vector
|
|
||||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
|
||||||
|
|
||||||
CREATE FUNCTION vector_out(vector) RETURNS cstring
|
|
||||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
|
||||||
|
|
||||||
CREATE FUNCTION vector_typmod_in(cstring[]) RETURNS integer
|
|
||||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
|
||||||
|
|
||||||
CREATE FUNCTION vector_recv(internal, oid, integer) RETURNS vector
|
|
||||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
|
||||||
|
|
||||||
CREATE FUNCTION vector_send(vector) RETURNS bytea
|
|
||||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
|
||||||
|
|
||||||
CREATE TYPE vector (
|
|
||||||
INPUT = vector_in,
|
|
||||||
OUTPUT = vector_out,
|
|
||||||
TYPMOD_IN = vector_typmod_in,
|
|
||||||
RECEIVE = vector_recv,
|
|
||||||
SEND = vector_send
|
|
||||||
);
|
|
||||||
|
|
||||||
-- functions
|
|
||||||
|
|
||||||
CREATE FUNCTION l2_distance(vector, vector) RETURNS float8
|
|
||||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
|
||||||
|
|
||||||
CREATE FUNCTION inner_product(vector, vector) RETURNS float8
|
|
||||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
|
||||||
|
|
||||||
CREATE FUNCTION cosine_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;
|
|
||||||
|
|
||||||
CREATE FUNCTION vector_norm(vector) RETURNS float8
|
|
||||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
|
||||||
|
|
||||||
CREATE FUNCTION vector_add(vector, vector) RETURNS vector
|
|
||||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
|
||||||
|
|
||||||
CREATE FUNCTION vector_sub(vector, vector) RETURNS vector
|
|
||||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
|
||||||
|
|
||||||
-- private functions
|
|
||||||
|
|
||||||
CREATE FUNCTION vector_lt(vector, vector) RETURNS bool
|
|
||||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
|
||||||
|
|
||||||
CREATE FUNCTION vector_le(vector, vector) RETURNS bool
|
|
||||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
|
||||||
|
|
||||||
CREATE FUNCTION vector_eq(vector, vector) RETURNS bool
|
|
||||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
|
||||||
|
|
||||||
CREATE FUNCTION vector_ne(vector, vector) RETURNS bool
|
|
||||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
|
||||||
|
|
||||||
CREATE FUNCTION vector_ge(vector, vector) RETURNS bool
|
|
||||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
|
||||||
|
|
||||||
CREATE FUNCTION vector_gt(vector, vector) RETURNS bool
|
|
||||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
|
||||||
|
|
||||||
CREATE FUNCTION vector_cmp(vector, vector) RETURNS int4
|
|
||||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
|
||||||
|
|
||||||
CREATE FUNCTION vector_l2_squared_distance(vector, vector) RETURNS float8
|
|
||||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
|
||||||
|
|
||||||
CREATE FUNCTION vector_negative_inner_product(vector, vector) RETURNS float8
|
|
||||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
|
||||||
|
|
||||||
CREATE FUNCTION vector_spherical_distance(vector, vector) RETURNS float8
|
|
||||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
|
||||||
|
|
||||||
-- cast functions
|
|
||||||
|
|
||||||
CREATE FUNCTION vector(vector, integer, boolean) RETURNS vector
|
|
||||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
|
||||||
|
|
||||||
CREATE FUNCTION array_to_vector(integer[], integer, boolean) RETURNS vector
|
|
||||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
|
||||||
|
|
||||||
CREATE FUNCTION array_to_vector(real[], integer, boolean) RETURNS vector
|
|
||||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
|
||||||
|
|
||||||
CREATE FUNCTION array_to_vector(double precision[], integer, boolean) RETURNS vector
|
|
||||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
|
||||||
|
|
||||||
-- casts
|
|
||||||
|
|
||||||
CREATE CAST (vector AS vector)
|
|
||||||
WITH FUNCTION vector(vector, integer, boolean) AS IMPLICIT;
|
|
||||||
|
|
||||||
CREATE CAST (integer[] AS vector)
|
|
||||||
WITH FUNCTION array_to_vector(integer[], integer, boolean) AS IMPLICIT;
|
|
||||||
|
|
||||||
CREATE CAST (real[] AS vector)
|
|
||||||
WITH FUNCTION array_to_vector(real[], integer, boolean) AS IMPLICIT;
|
|
||||||
|
|
||||||
CREATE CAST (double precision[] AS vector)
|
|
||||||
WITH FUNCTION array_to_vector(double precision[], integer, boolean) AS IMPLICIT;
|
|
||||||
|
|
||||||
-- operators
|
|
||||||
|
|
||||||
CREATE OPERATOR <-> (
|
|
||||||
LEFTARG = vector, RIGHTARG = vector, PROCEDURE = l2_distance,
|
|
||||||
COMMUTATOR = '<->'
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE OPERATOR <#> (
|
|
||||||
LEFTARG = vector, RIGHTARG = vector, PROCEDURE = vector_negative_inner_product,
|
|
||||||
COMMUTATOR = '<#>'
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE OPERATOR <=> (
|
|
||||||
LEFTARG = vector, RIGHTARG = vector, PROCEDURE = cosine_distance,
|
|
||||||
COMMUTATOR = '<=>'
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE OPERATOR + (
|
|
||||||
LEFTARG = vector, RIGHTARG = vector, PROCEDURE = vector_add,
|
|
||||||
COMMUTATOR = +
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE OPERATOR - (
|
|
||||||
LEFTARG = vector, RIGHTARG = vector, PROCEDURE = vector_sub,
|
|
||||||
COMMUTATOR = -
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE OPERATOR < (
|
|
||||||
LEFTARG = vector, RIGHTARG = vector, PROCEDURE = vector_lt,
|
|
||||||
COMMUTATOR = > , NEGATOR = >= ,
|
|
||||||
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
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE OPERATOR = (
|
|
||||||
LEFTARG = vector, RIGHTARG = vector, PROCEDURE = vector_eq,
|
|
||||||
COMMUTATOR = = , NEGATOR = <> ,
|
|
||||||
RESTRICT = eqsel, JOIN = eqjoinsel
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE OPERATOR <> (
|
|
||||||
LEFTARG = vector, RIGHTARG = vector, PROCEDURE = vector_ne,
|
|
||||||
COMMUTATOR = <> , NEGATOR = = ,
|
|
||||||
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
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE OPERATOR > (
|
|
||||||
LEFTARG = vector, RIGHTARG = vector, PROCEDURE = vector_gt,
|
|
||||||
COMMUTATOR = < , NEGATOR = <= ,
|
|
||||||
RESTRICT = scalargtsel, JOIN = scalargtjoinsel
|
|
||||||
);
|
|
||||||
|
|
||||||
-- access method
|
|
||||||
|
|
||||||
CREATE FUNCTION ivfflathandler(internal) RETURNS index_am_handler
|
|
||||||
AS 'MODULE_PATHNAME' LANGUAGE C;
|
|
||||||
|
|
||||||
CREATE ACCESS METHOD ivfflat TYPE INDEX HANDLER ivfflathandler;
|
|
||||||
|
|
||||||
COMMENT ON ACCESS METHOD ivfflat IS 'ivfflat index access method';
|
|
||||||
|
|
||||||
-- opclasses
|
|
||||||
|
|
||||||
CREATE OPERATOR CLASS vector_ops
|
|
||||||
DEFAULT FOR TYPE vector USING btree AS
|
|
||||||
OPERATOR 1 < ,
|
|
||||||
OPERATOR 2 <= ,
|
|
||||||
OPERATOR 3 = ,
|
|
||||||
OPERATOR 4 >= ,
|
|
||||||
OPERATOR 5 > ,
|
|
||||||
FUNCTION 1 vector_cmp(vector, vector);
|
|
||||||
|
|
||||||
CREATE OPERATOR CLASS vector_l2_ops
|
|
||||||
DEFAULT FOR TYPE vector USING ivfflat AS
|
|
||||||
OPERATOR 1 <-> (vector, vector) FOR ORDER BY float_ops,
|
|
||||||
FUNCTION 1 vector_l2_squared_distance(vector, vector),
|
|
||||||
FUNCTION 3 l2_distance(vector, vector);
|
|
||||||
|
|
||||||
CREATE OPERATOR CLASS vector_ip_ops
|
|
||||||
FOR TYPE vector USING ivfflat AS
|
|
||||||
OPERATOR 1 <#> (vector, vector) FOR ORDER BY float_ops,
|
|
||||||
FUNCTION 1 vector_negative_inner_product(vector, vector),
|
|
||||||
FUNCTION 3 vector_spherical_distance(vector, vector),
|
|
||||||
FUNCTION 4 vector_norm(vector);
|
|
||||||
|
|
||||||
CREATE OPERATOR CLASS vector_cosine_ops
|
|
||||||
FOR TYPE vector USING ivfflat AS
|
|
||||||
OPERATOR 1 <=> (vector, vector) FOR ORDER BY float_ops,
|
|
||||||
FUNCTION 1 vector_negative_inner_product(vector, vector),
|
|
||||||
FUNCTION 2 vector_norm(vector),
|
|
||||||
FUNCTION 3 vector_spherical_distance(vector, vector),
|
|
||||||
FUNCTION 4 vector_norm(vector);
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
comment = 'vector data type and ivfflat access method'
|
comment = 'vector data type and ivfflat access method'
|
||||||
default_version = '0.1.1'
|
default_version = '0.3.2'
|
||||||
module_pathname = '$libdir/vector'
|
module_pathname = '$libdir/vector'
|
||||||
relocatable = true
|
relocatable = true
|
||||||
|
|||||||
Reference in New Issue
Block a user