mirror of
https://github.com/pgvector/pgvector.git
synced 2026-07-22 12:07:34 +08:00
Compare commits
101 Commits
half
...
hamming-di
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
76418fc093 | ||
|
|
34d5a8cf3f | ||
|
|
f9ae736c57 | ||
|
|
76c6dbb0a0 | ||
|
|
c8be3a369b | ||
|
|
e5d1a6bdbb | ||
|
|
f31d708c2b | ||
|
|
a1b1c99ff7 | ||
|
|
3ace98add6 | ||
|
|
083008c21e | ||
|
|
a1e526ef82 | ||
|
|
8ffb3718a4 | ||
|
|
2d0f162bd7 | ||
|
|
4c6928bd3c | ||
|
|
6fd05dd6f6 | ||
|
|
70106f5413 | ||
|
|
44b90be452 | ||
|
|
31572a7b28 | ||
|
|
2427290ea9 | ||
|
|
ca71ef7a51 | ||
|
|
8bd01ff006 | ||
|
|
cdb1c9a6d3 | ||
|
|
bf34ceef7c | ||
|
|
61b1566ea2 | ||
|
|
91acc3c178 | ||
|
|
885dd5b665 | ||
|
|
2a7b38bf1f | ||
|
|
4bd4a0996b | ||
|
|
490522b883 | ||
|
|
a1a38156d7 | ||
|
|
042ddfdc8a | ||
|
|
56870ce04d | ||
|
|
4ab77f3d24 | ||
|
|
cc9e6a6778 | ||
|
|
8f1b669c4f | ||
|
|
1ff9ab5133 | ||
|
|
4894dc5da1 | ||
|
|
7390f31261 | ||
|
|
b7304a3a4a | ||
|
|
018ceb7a46 | ||
|
|
0b2be00622 | ||
|
|
0ce497a1b1 | ||
|
|
c7d60346d8 | ||
|
|
597bfdc76b | ||
|
|
cbf3eb4fa5 | ||
|
|
cacd389f6d | ||
|
|
423cc2b06c | ||
|
|
85c4ef6a14 | ||
|
|
c6160a783a | ||
|
|
1881b857f9 | ||
|
|
51bde5fb22 | ||
|
|
10e65ce349 | ||
|
|
61279f5a59 | ||
|
|
72b3889e26 | ||
|
|
bb21b2decf | ||
|
|
8a65c0e831 | ||
|
|
7d75d423e4 | ||
|
|
6cad1f5de0 | ||
|
|
67eeade63c | ||
|
|
108fb09d7b | ||
|
|
65d060ac86 | ||
|
|
62ee33bb92 | ||
|
|
520e274dde | ||
|
|
9e680884bd | ||
|
|
19a0e1b341 | ||
|
|
c7fe1571ee | ||
|
|
cb4c770df2 | ||
|
|
85fdecd79b | ||
|
|
6132428914 | ||
|
|
81d13bd40f | ||
|
|
8ee37b60a0 | ||
|
|
9b73b3d1a6 | ||
|
|
cae630784b | ||
|
|
d87bcd2deb | ||
|
|
736576220a | ||
|
|
a508b120c1 | ||
|
|
9a782d29f8 | ||
|
|
1e422cd62b | ||
|
|
569c69580a | ||
|
|
59509c3a17 | ||
|
|
61738846af | ||
|
|
e8c3bf0cef | ||
|
|
50d1aed3d8 | ||
|
|
66e14d2434 | ||
|
|
42cd4c6833 | ||
|
|
dcbe0b6f0d | ||
|
|
f61d4087b5 | ||
|
|
57554e5b46 | ||
|
|
6738fa0bd7 | ||
|
|
9ab10aa674 | ||
|
|
ec41dfa1d7 | ||
|
|
43e0b3d9d4 | ||
|
|
2bff7ccaa2 | ||
|
|
e88a425c9b | ||
|
|
921427ee03 | ||
|
|
a59aa02dd9 | ||
|
|
2fef497b7e | ||
|
|
0e19a984fb | ||
|
|
a156f6c7ae | ||
|
|
c653ac524f | ||
|
|
bf0d56e78e |
6
.github/workflows/build.yml
vendored
6
.github/workflows/build.yml
vendored
@@ -60,7 +60,7 @@ jobs:
|
|||||||
wget -q https://github.com/postgres/postgres/archive/refs/tags/REL_14_5.tar.gz
|
wget -q https://github.com/postgres/postgres/archive/refs/tags/REL_14_5.tar.gz
|
||||||
tar xf REL_14_5.tar.gz
|
tar xf REL_14_5.tar.gz
|
||||||
- run: make prove_installcheck PROVE_FLAGS="-I ./postgres-REL_14_5/src/test/perl" PERL5LIB="/Users/runner/perl5/lib/perl5"
|
- run: make prove_installcheck PROVE_FLAGS="-I ./postgres-REL_14_5/src/test/perl" PERL5LIB="/Users/runner/perl5/lib/perl5"
|
||||||
- run: make clean && /usr/local/opt/llvm@15/bin/scan-build --status-bugs make
|
- run: make clean && /usr/local/opt/llvm@15/bin/scan-build --status-bugs make PG_CFLAGS="-DUSE_ASSERT_CHECKING"
|
||||||
windows:
|
windows:
|
||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
if: ${{ !startsWith(github.ref_name, 'mac') }}
|
if: ${{ !startsWith(github.ref_name, 'mac') }}
|
||||||
@@ -81,10 +81,10 @@ jobs:
|
|||||||
if: ${{ !startsWith(github.ref_name, 'mac') && !startsWith(github.ref_name, 'windows') }}
|
if: ${{ !startsWith(github.ref_name, 'mac') && !startsWith(github.ref_name, 'windows') }}
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
container:
|
container:
|
||||||
image: debian:11
|
image: debian:12
|
||||||
options: --platform linux/386
|
options: --platform linux/386
|
||||||
steps:
|
steps:
|
||||||
- run: apt-get update && apt-get install -y build-essential git libipc-run-perl postgresql-13 postgresql-server-dev-13 sudo
|
- run: apt-get update && apt-get install -y build-essential git libipc-run-perl postgresql-15 postgresql-server-dev-15 sudo
|
||||||
- run: service postgresql start
|
- run: service postgresql start
|
||||||
- run: |
|
- run: |
|
||||||
git clone https://github.com/${{ github.repository }}.git pgvector
|
git clone https://github.com/${{ github.repository }}.git pgvector
|
||||||
|
|||||||
15
CHANGELOG.md
15
CHANGELOG.md
@@ -1,6 +1,17 @@
|
|||||||
## 0.5.2 (unreleased)
|
## 0.6.0 (unreleased)
|
||||||
|
|
||||||
- Added support for on-disk parallel index builds for HNSW
|
If upgrading with Postgres < 13, see [this note](https://github.com/pgvector/pgvector#060).
|
||||||
|
|
||||||
|
- Changed storage for vector from `extended` to `external`
|
||||||
|
- Improved performance of HNSW
|
||||||
|
- Added support for parallel index builds for HNSW
|
||||||
|
- Added `hamming_distance` function
|
||||||
|
- Added validation for GUC parameters
|
||||||
|
- Reduced memory usage for HNSW index builds
|
||||||
|
- Reduced WAL generation for HNSW index builds
|
||||||
|
- Fixed error with logical replication
|
||||||
|
- Fixed `invalid memory alloc request size` error with HNSW index build
|
||||||
|
- Dropped support for Postgres 11
|
||||||
|
|
||||||
## 0.5.1 (2023-10-10)
|
## 0.5.1 (2023-10-10)
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
"prereqs": {
|
"prereqs": {
|
||||||
"runtime": {
|
"runtime": {
|
||||||
"requires": {
|
"requires": {
|
||||||
"PostgreSQL": "11.0.0"
|
"PostgreSQL": "12.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
4
Makefile
4
Makefile
@@ -3,8 +3,8 @@ EXTVERSION = 0.5.1
|
|||||||
|
|
||||||
MODULE_big = vector
|
MODULE_big = vector
|
||||||
DATA = $(wildcard sql/*--*.sql)
|
DATA = $(wildcard sql/*--*.sql)
|
||||||
OBJS = src/half.o src/hnsw.o src/hnswbuild.o src/hnswinsert.o src/hnswscan.o src/hnswutils.o src/hnswvacuum.o src/ivfbuild.o src/ivfflat.o src/ivfinsert.o src/ivfkmeans.o src/ivfscan.o src/ivfutils.o src/ivfvacuum.o src/vector.o
|
OBJS = src/hnsw.o src/hnswbuild.o src/hnswinsert.o src/hnswscan.o src/hnswutils.o src/hnswvacuum.o src/ivfbuild.o src/ivfflat.o src/ivfinsert.o src/ivfkmeans.o src/ivfscan.o src/ivfutils.o src/ivfvacuum.o src/vector.o
|
||||||
HEADERS = src/half.h src/vector.h
|
HEADERS = src/vector.h
|
||||||
|
|
||||||
TESTS = $(wildcard test/sql/*.sql)
|
TESTS = $(wildcard test/sql/*.sql)
|
||||||
REGRESS = $(patsubst test/sql/%.sql,%,$(TESTS))
|
REGRESS = $(patsubst test/sql/%.sql,%,$(TESTS))
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
EXTENSION = vector
|
EXTENSION = vector
|
||||||
EXTVERSION = 0.5.1
|
EXTVERSION = 0.5.1
|
||||||
|
|
||||||
OBJS = src\half.obj src\hnsw.obj src\hnswbuild.obj src\hnswinsert.obj src\hnswscan.obj src\hnswutils.obj src\hnswvacuum.obj src\ivfbuild.obj src\ivfflat.obj src\ivfinsert.obj src\ivfkmeans.obj src\ivfscan.obj src\ivfutils.obj src\ivfvacuum.obj src\vector.obj
|
OBJS = src\hnsw.obj src\hnswbuild.obj src\hnswinsert.obj src\hnswscan.obj src\hnswutils.obj src\hnswvacuum.obj src\ivfbuild.obj src\ivfflat.obj src\ivfinsert.obj src\ivfkmeans.obj src\ivfscan.obj src\ivfutils.obj src\ivfvacuum.obj src\vector.obj
|
||||||
HEADERS = src\half.h src\vector.h
|
HEADERS = src\vector.h
|
||||||
|
|
||||||
REGRESS = btree cast copy functions input ivfflat_cosine ivfflat_ip ivfflat_l2 ivfflat_options ivfflat_unlogged
|
REGRESS = btree cast copy functions input ivfflat_cosine ivfflat_ip ivfflat_l2 ivfflat_options ivfflat_unlogged
|
||||||
REGRESS_OPTS = --inputdir=test --load-extension=$(EXTENSION)
|
REGRESS_OPTS = --inputdir=test --load-extension=$(EXTENSION)
|
||||||
|
|||||||
231
README.md
231
README.md
@@ -161,80 +161,12 @@ You can add an index to use approximate nearest neighbor search, which trades so
|
|||||||
|
|
||||||
Supported index types are:
|
Supported index types are:
|
||||||
|
|
||||||
- [IVFFlat](#ivfflat)
|
|
||||||
- [HNSW](#hnsw) - added in 0.5.0
|
- [HNSW](#hnsw) - added in 0.5.0
|
||||||
|
- [IVFFlat](#ivfflat)
|
||||||
## IVFFlat
|
|
||||||
|
|
||||||
An IVFFlat index divides vectors into lists, and then searches a subset of those lists that are closest to the query vector. It has faster build times and uses less memory than HNSW, but has lower query performance (in terms of speed-recall tradeoff).
|
|
||||||
|
|
||||||
Three keys to achieving good recall are:
|
|
||||||
|
|
||||||
1. Create the index *after* the table has some data
|
|
||||||
2. Choose an appropriate number of lists - a good place to start is `rows / 1000` for up to 1M rows and `sqrt(rows)` for over 1M rows
|
|
||||||
3. When querying, specify an appropriate number of [probes](#query-options) (higher is better for recall, lower is better for speed) - a good place to start is `sqrt(lists)`
|
|
||||||
|
|
||||||
Add an index for each distance function you want to use.
|
|
||||||
|
|
||||||
L2 distance
|
|
||||||
|
|
||||||
```sql
|
|
||||||
CREATE INDEX ON items USING ivfflat (embedding vector_l2_ops) WITH (lists = 100);
|
|
||||||
```
|
|
||||||
|
|
||||||
Inner product
|
|
||||||
|
|
||||||
```sql
|
|
||||||
CREATE INDEX ON items USING ivfflat (embedding vector_ip_ops) WITH (lists = 100);
|
|
||||||
```
|
|
||||||
|
|
||||||
Cosine distance
|
|
||||||
|
|
||||||
```sql
|
|
||||||
CREATE INDEX ON items USING ivfflat (embedding vector_cosine_ops) WITH (lists = 100);
|
|
||||||
```
|
|
||||||
|
|
||||||
Vectors with up to 2,000 dimensions can be indexed.
|
|
||||||
|
|
||||||
### Query Options
|
|
||||||
|
|
||||||
Specify the number of probes (1 by default)
|
|
||||||
|
|
||||||
```sql
|
|
||||||
SET ivfflat.probes = 10;
|
|
||||||
```
|
|
||||||
|
|
||||||
A higher value provides better recall at the cost of speed, and it can be set to the number of lists for exact nearest neighbor search (at which point the planner won’t use the index)
|
|
||||||
|
|
||||||
Use `SET LOCAL` inside a transaction to set it for a single query
|
|
||||||
|
|
||||||
```sql
|
|
||||||
BEGIN;
|
|
||||||
SET LOCAL ivfflat.probes = 10;
|
|
||||||
SELECT ...
|
|
||||||
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, round(100.0 * tuples_done / nullif(tuples_total, 0), 1) AS "%" FROM pg_stat_progress_create_index;
|
|
||||||
```
|
|
||||||
|
|
||||||
The phases for IVFFlat are:
|
|
||||||
|
|
||||||
1. `initializing`
|
|
||||||
2. `performing k-means`
|
|
||||||
3. `assigning tuples`
|
|
||||||
4. `loading tuples`
|
|
||||||
|
|
||||||
Note: `%` is only populated during the `loading tuples` phase
|
|
||||||
|
|
||||||
## HNSW
|
## HNSW
|
||||||
|
|
||||||
An HNSW index creates a multilayer graph. It has slower build times and uses more memory than IVFFlat, but has better query performance (in terms of speed-recall tradeoff). There’s no training step like IVFFlat, so the index can be created without any data in the table.
|
An HNSW index creates a multilayer graph. It has better query performance than IVFFlat (in terms of speed-recall tradeoff), but has slower build times and uses more memory. Also, an index can be created without any data in the table since there isn’t a training step like IVFFlat.
|
||||||
|
|
||||||
Add an index for each distance function you want to use.
|
Add an index for each distance function you want to use.
|
||||||
|
|
||||||
@@ -290,6 +222,24 @@ SELECT ...
|
|||||||
COMMIT;
|
COMMIT;
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Index Build Time
|
||||||
|
|
||||||
|
Indexes build significantly faster when the graph fits into `maintenance_work_mem`
|
||||||
|
|
||||||
|
```sql
|
||||||
|
SET maintenance_work_mem = '8GB';
|
||||||
|
```
|
||||||
|
|
||||||
|
A notice is shown when the graph no longer fits
|
||||||
|
|
||||||
|
```text
|
||||||
|
NOTICE: hnsw graph no longer fits into maintenance_work_mem after 100000 tuples
|
||||||
|
DETAIL: Building will take significantly more time.
|
||||||
|
HINT: Increase maintenance_work_mem to speed up builds.
|
||||||
|
```
|
||||||
|
|
||||||
|
Note: Do not set `maintenance_work_mem` so high that it exhausts the memory on the server
|
||||||
|
|
||||||
### Indexing Progress
|
### Indexing Progress
|
||||||
|
|
||||||
Check [indexing progress](https://www.postgresql.org/docs/current/progress-reporting.html#CREATE-INDEX-PROGRESS-REPORTING) with Postgres 12+
|
Check [indexing progress](https://www.postgresql.org/docs/current/progress-reporting.html#CREATE-INDEX-PROGRESS-REPORTING) with Postgres 12+
|
||||||
@@ -303,6 +253,84 @@ The phases for HNSW are:
|
|||||||
1. `initializing`
|
1. `initializing`
|
||||||
2. `loading tuples`
|
2. `loading tuples`
|
||||||
|
|
||||||
|
## IVFFlat
|
||||||
|
|
||||||
|
An IVFFlat index divides vectors into lists, and then searches a subset of those lists that are closest to the query vector. It has faster build times and uses less memory than HNSW, but has lower query performance (in terms of speed-recall tradeoff).
|
||||||
|
|
||||||
|
Three keys to achieving good recall are:
|
||||||
|
|
||||||
|
1. Create the index *after* the table has some data
|
||||||
|
2. Choose an appropriate number of lists - a good place to start is `rows / 1000` for up to 1M rows and `sqrt(rows)` for over 1M rows
|
||||||
|
3. When querying, specify an appropriate number of [probes](#query-options) (higher is better for recall, lower is better for speed) - a good place to start is `sqrt(lists)`
|
||||||
|
|
||||||
|
Add an index for each distance function you want to use.
|
||||||
|
|
||||||
|
L2 distance
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE INDEX ON items USING ivfflat (embedding vector_l2_ops) WITH (lists = 100);
|
||||||
|
```
|
||||||
|
|
||||||
|
Inner product
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE INDEX ON items USING ivfflat (embedding vector_ip_ops) WITH (lists = 100);
|
||||||
|
```
|
||||||
|
|
||||||
|
Cosine distance
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE INDEX ON items USING ivfflat (embedding vector_cosine_ops) WITH (lists = 100);
|
||||||
|
```
|
||||||
|
|
||||||
|
Vectors with up to 2,000 dimensions can be indexed.
|
||||||
|
|
||||||
|
### Query Options
|
||||||
|
|
||||||
|
Specify the number of probes (1 by default)
|
||||||
|
|
||||||
|
```sql
|
||||||
|
SET ivfflat.probes = 10;
|
||||||
|
```
|
||||||
|
|
||||||
|
A higher value provides better recall at the cost of speed, and it can be set to the number of lists for exact nearest neighbor search (at which point the planner won’t use the index)
|
||||||
|
|
||||||
|
Use `SET LOCAL` inside a transaction to set it for a single query
|
||||||
|
|
||||||
|
```sql
|
||||||
|
BEGIN;
|
||||||
|
SET LOCAL ivfflat.probes = 10;
|
||||||
|
SELECT ...
|
||||||
|
COMMIT;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Index Build Time
|
||||||
|
|
||||||
|
Speed up index creation on large tables by increasing the number of parallel workers (2 by default)
|
||||||
|
|
||||||
|
```sql
|
||||||
|
SET max_parallel_maintenance_workers = 7; -- plus leader
|
||||||
|
```
|
||||||
|
|
||||||
|
For a large number of workers, you may also need to increase `max_parallel_workers` (8 by default)
|
||||||
|
|
||||||
|
### Indexing Progress
|
||||||
|
|
||||||
|
Check [indexing progress](https://www.postgresql.org/docs/current/progress-reporting.html#CREATE-INDEX-PROGRESS-REPORTING) with Postgres 12+
|
||||||
|
|
||||||
|
```sql
|
||||||
|
SELECT phase, round(100.0 * tuples_done / nullif(tuples_total, 0), 1) AS "%" FROM pg_stat_progress_create_index;
|
||||||
|
```
|
||||||
|
|
||||||
|
The phases for IVFFlat are:
|
||||||
|
|
||||||
|
1. `initializing`
|
||||||
|
2. `performing k-means`
|
||||||
|
3. `assigning tuples`
|
||||||
|
4. `loading tuples`
|
||||||
|
|
||||||
|
Note: `%` is only populated during the `loading tuples` phase
|
||||||
|
|
||||||
## Filtering
|
## Filtering
|
||||||
|
|
||||||
There are a few ways to index nearest neighbor queries with a `WHERE` clause
|
There are a few ways to index nearest neighbor queries with a `WHERE` clause
|
||||||
@@ -320,8 +348,7 @@ CREATE INDEX ON items (category_id);
|
|||||||
Or a [partial index](https://www.postgresql.org/docs/current/indexes-partial.html) on the vector column for approximate search
|
Or a [partial index](https://www.postgresql.org/docs/current/indexes-partial.html) on the vector column for approximate search
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
CREATE INDEX ON items USING ivfflat (embedding vector_l2_ops) WITH (lists = 100)
|
CREATE INDEX ON items USING hnsw (embedding vector_l2_ops) WHERE (category_id = 123);
|
||||||
WHERE (category_id = 123);
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Use [partitioning](https://www.postgresql.org/docs/current/ddl-partitioning.html) for approximate search on many different values of the `WHERE` columns
|
Use [partitioning](https://www.postgresql.org/docs/current/ddl-partitioning.html) for approximate search on many different values of the `WHERE` columns
|
||||||
@@ -555,7 +582,7 @@ sum(vector) → vector | sum | 0.5.0
|
|||||||
If your machine has multiple Postgres installations, specify the path to [pg_config](https://www.postgresql.org/docs/current/app-pgconfig.html) with:
|
If your machine has multiple Postgres installations, specify the path to [pg_config](https://www.postgresql.org/docs/current/app-pgconfig.html) with:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
export PG_CONFIG=/Applications/Postgres.app/Contents/Versions/latest/bin/pg_config
|
export PG_CONFIG=/Library/PostgreSQL/16/bin/pg_config
|
||||||
```
|
```
|
||||||
|
|
||||||
Then re-run the installation instructions (run `make clean` before `make` if needed). If `sudo` is needed for `make install`, use:
|
Then re-run the installation instructions (run `make clean` before `make` if needed). If `sudo` is needed for `make install`, use:
|
||||||
@@ -564,6 +591,14 @@ Then re-run the installation instructions (run `make clean` before `make` if nee
|
|||||||
sudo --preserve-env=PG_CONFIG make install
|
sudo --preserve-env=PG_CONFIG make install
|
||||||
```
|
```
|
||||||
|
|
||||||
|
A few common paths on Mac are:
|
||||||
|
|
||||||
|
- EDB installer - `/Library/PostgreSQL/16/bin/pg_config`
|
||||||
|
- Homebrew (arm64) - `/opt/homebrew/opt/postgresql@16/bin/pg_config`
|
||||||
|
- Homebrew (x86-64) - `/usr/local/opt/postgresql@16/bin/pg_config`
|
||||||
|
|
||||||
|
Note: Replace `16` with your Postgres server version
|
||||||
|
|
||||||
### Missing Header
|
### Missing Header
|
||||||
|
|
||||||
If compilation fails with `fatal error: postgres.h: No such file or directory`, make sure Postgres development files are installed on the server.
|
If compilation fails with `fatal error: postgres.h: No such file or directory`, make sure Postgres development files are installed on the server.
|
||||||
@@ -571,10 +606,14 @@ If compilation fails with `fatal error: postgres.h: No such file or directory`,
|
|||||||
For Ubuntu and Debian, use:
|
For Ubuntu and Debian, use:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
sudo apt install postgresql-server-dev-15
|
sudo apt install postgresql-server-dev-16
|
||||||
```
|
```
|
||||||
|
|
||||||
Note: Replace `15` with your Postgres server version
|
Note: Replace `16` with your Postgres server version
|
||||||
|
|
||||||
|
### Missing SDK
|
||||||
|
|
||||||
|
If compilation fails and the output includes `warning: no such sysroot directory` on Mac, reinstall Xcode Command Line Tools.
|
||||||
|
|
||||||
### Windows
|
### Windows
|
||||||
|
|
||||||
@@ -589,7 +628,7 @@ Note: The exact path will vary depending on your Visual Studio version and editi
|
|||||||
Then use `nmake` to build:
|
Then use `nmake` to build:
|
||||||
|
|
||||||
```cmd
|
```cmd
|
||||||
set "PGROOT=C:\Program Files\PostgreSQL\15"
|
set "PGROOT=C:\Program Files\PostgreSQL\16"
|
||||||
git clone --branch v0.5.1 https://github.com/pgvector/pgvector.git
|
git clone --branch v0.5.1 https://github.com/pgvector/pgvector.git
|
||||||
cd pgvector
|
cd pgvector
|
||||||
nmake /F Makefile.win
|
nmake /F Makefile.win
|
||||||
@@ -639,22 +678,22 @@ pgxn install vector
|
|||||||
Debian and Ubuntu packages are available from the [PostgreSQL APT Repository](https://wiki.postgresql.org/wiki/Apt). Follow the [setup instructions](https://wiki.postgresql.org/wiki/Apt#Quickstart) and run:
|
Debian and Ubuntu packages are available from the [PostgreSQL APT Repository](https://wiki.postgresql.org/wiki/Apt). Follow the [setup instructions](https://wiki.postgresql.org/wiki/Apt#Quickstart) and run:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
sudo apt install postgresql-15-pgvector
|
sudo apt install postgresql-16-pgvector
|
||||||
```
|
```
|
||||||
|
|
||||||
Note: Replace `15` with your Postgres server version
|
Note: Replace `16` with your Postgres server version
|
||||||
|
|
||||||
### Yum
|
### Yum
|
||||||
|
|
||||||
RPM packages are available from the [PostgreSQL Yum Repository](https://yum.postgresql.org/). Follow the [setup instructions](https://www.postgresql.org/download/linux/redhat/) for your distribution and run:
|
RPM packages are available from the [PostgreSQL Yum Repository](https://yum.postgresql.org/). Follow the [setup instructions](https://www.postgresql.org/download/linux/redhat/) for your distribution and run:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
sudo yum install pgvector_15
|
sudo yum install pgvector_16
|
||||||
# or
|
# or
|
||||||
sudo dnf install pgvector_15
|
sudo dnf install pgvector_16
|
||||||
```
|
```
|
||||||
|
|
||||||
Note: Replace `15` with your Postgres server version
|
Note: Replace `16` with your Postgres server version
|
||||||
|
|
||||||
### conda-forge
|
### conda-forge
|
||||||
|
|
||||||
@@ -690,6 +729,16 @@ SELECT extversion FROM pg_extension WHERE extname = 'vector';
|
|||||||
|
|
||||||
## Upgrade Notes
|
## Upgrade Notes
|
||||||
|
|
||||||
|
### 0.6.0 [unreleased]
|
||||||
|
|
||||||
|
If upgrading with Postgres < 13, remove this line from `sql/vector--0.5.1--0.6.0.sql`:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
ALTER TYPE vector SET (STORAGE = external);
|
||||||
|
```
|
||||||
|
|
||||||
|
Then run `make install` and `ALTER EXTENSION vector UPDATE;`.
|
||||||
|
|
||||||
### 0.4.0
|
### 0.4.0
|
||||||
|
|
||||||
If upgrading with Postgres < 13, remove this line from `sql/vector--0.3.2--0.4.0.sql`:
|
If upgrading with Postgres < 13, remove this line from `sql/vector--0.3.2--0.4.0.sql`:
|
||||||
@@ -764,7 +813,25 @@ make prove_installcheck PROVE_TESTS=test/t/001_wal.pl # TAP test
|
|||||||
To enable benchmarking:
|
To enable benchmarking:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
make clean && PG_CFLAGS=-DIVFFLAT_BENCH make && make install
|
make clean && PG_CFLAGS="-DIVFFLAT_BENCH" make && make install
|
||||||
|
```
|
||||||
|
|
||||||
|
To show memory usage:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
make clean && PG_CFLAGS="-DHNSW_MEMORY -DIVFFLAT_MEMORY" make && make install
|
||||||
|
```
|
||||||
|
|
||||||
|
To enable assertions:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
make clean && PG_CFLAGS="-DUSE_ASSERT_CHECKING" make && make install
|
||||||
|
```
|
||||||
|
|
||||||
|
To get k-means metrics:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
make clean && PG_CFLAGS="-DIVFFLAT_KMEANS_DEBUG" make && make install
|
||||||
```
|
```
|
||||||
|
|
||||||
Resources for contributors
|
Resources for contributors
|
||||||
|
|||||||
@@ -1,101 +1,8 @@
|
|||||||
-- complain if script is sourced in psql, rather than via CREATE EXTENSION
|
-- complain if script is sourced in psql, rather than via CREATE EXTENSION
|
||||||
\echo Use "ALTER EXTENSION vector UPDATE TO '0.6.0'" to load this file. \quit
|
\echo Use "ALTER EXTENSION vector UPDATE TO '0.6.0'" to load this file. \quit
|
||||||
|
|
||||||
CREATE TYPE half;
|
-- remove this single line for Postgres < 13
|
||||||
|
ALTER TYPE vector SET (STORAGE = external);
|
||||||
|
|
||||||
CREATE FUNCTION half_in(cstring, oid, integer) RETURNS half
|
CREATE FUNCTION hamming_distance(bytea, bytea) RETURNS float8
|
||||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||||
|
|
||||||
CREATE FUNCTION half_out(half) RETURNS cstring
|
|
||||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
|
||||||
|
|
||||||
CREATE FUNCTION half_recv(internal, oid, integer) RETURNS half
|
|
||||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
|
||||||
|
|
||||||
CREATE FUNCTION half_send(half) RETURNS bytea
|
|
||||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
|
||||||
|
|
||||||
CREATE TYPE half (
|
|
||||||
INPUT = half_in,
|
|
||||||
OUTPUT = half_out,
|
|
||||||
RECEIVE = half_recv,
|
|
||||||
SEND = half_send,
|
|
||||||
INTERNALLENGTH = 2,
|
|
||||||
PASSEDBYVALUE,
|
|
||||||
ALIGNMENT = int2
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE FUNCTION l2_distance(half[], half[]) RETURNS float8
|
|
||||||
AS 'MODULE_PATHNAME', 'half_l2_distance' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
|
||||||
|
|
||||||
CREATE FUNCTION inner_product(half[], half[]) RETURNS float8
|
|
||||||
AS 'MODULE_PATHNAME', 'half_inner_product' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
|
||||||
|
|
||||||
CREATE FUNCTION cosine_distance(half[], half[]) RETURNS float8
|
|
||||||
AS 'MODULE_PATHNAME', 'half_cosine_distance' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
|
||||||
|
|
||||||
CREATE FUNCTION l1_distance(half[], half[]) RETURNS float8
|
|
||||||
AS 'MODULE_PATHNAME', 'half_l1_distance' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
|
||||||
|
|
||||||
CREATE FUNCTION half_l2_squared_distance(half[], half[]) RETURNS float8
|
|
||||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
|
||||||
|
|
||||||
CREATE FUNCTION half_negative_inner_product(half[], half[]) RETURNS float8
|
|
||||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
|
||||||
|
|
||||||
CREATE FUNCTION float4_to_half(real, integer, boolean) RETURNS half
|
|
||||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
|
||||||
|
|
||||||
CREATE FUNCTION half_to_float4(half, integer, boolean) RETURNS real
|
|
||||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
|
||||||
|
|
||||||
CREATE FUNCTION float8_to_half(float8, integer, boolean) RETURNS half
|
|
||||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
|
||||||
|
|
||||||
CREATE FUNCTION half_to_float8(half, integer, boolean) RETURNS float8
|
|
||||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
|
||||||
|
|
||||||
CREATE FUNCTION integer_to_half(integer, integer, boolean) RETURNS half
|
|
||||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
|
||||||
|
|
||||||
CREATE FUNCTION numeric_to_half(numeric, integer, boolean) RETURNS half
|
|
||||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
|
||||||
|
|
||||||
CREATE FUNCTION half_to_numeric(half, integer, boolean) RETURNS numeric
|
|
||||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
|
||||||
|
|
||||||
CREATE CAST (real AS half)
|
|
||||||
WITH FUNCTION float4_to_half(real, integer, boolean) AS IMPLICIT;
|
|
||||||
|
|
||||||
CREATE CAST (half AS real)
|
|
||||||
WITH FUNCTION half_to_float4(half, integer, boolean) AS IMPLICIT;
|
|
||||||
|
|
||||||
CREATE CAST (float8 AS half)
|
|
||||||
WITH FUNCTION float8_to_half(float8, integer, boolean) AS IMPLICIT;
|
|
||||||
|
|
||||||
CREATE CAST (half AS float8)
|
|
||||||
WITH FUNCTION half_to_float8(half, integer, boolean) AS IMPLICIT;
|
|
||||||
|
|
||||||
CREATE CAST (integer AS half)
|
|
||||||
WITH FUNCTION integer_to_half(integer, integer, boolean) AS IMPLICIT;
|
|
||||||
|
|
||||||
CREATE CAST (numeric AS half)
|
|
||||||
WITH FUNCTION numeric_to_half(numeric, integer, boolean) AS IMPLICIT;
|
|
||||||
|
|
||||||
CREATE CAST (half AS numeric)
|
|
||||||
WITH FUNCTION half_to_numeric(half, integer, boolean) AS IMPLICIT;
|
|
||||||
|
|
||||||
CREATE OPERATOR <-> (
|
|
||||||
LEFTARG = half[], RIGHTARG = half[], PROCEDURE = l2_distance,
|
|
||||||
COMMUTATOR = '<->'
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE OPERATOR <#> (
|
|
||||||
LEFTARG = half[], RIGHTARG = half[], PROCEDURE = half_negative_inner_product,
|
|
||||||
COMMUTATOR = '<#>'
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE OPERATOR <=> (
|
|
||||||
LEFTARG = half[], RIGHTARG = half[], PROCEDURE = cosine_distance,
|
|
||||||
COMMUTATOR = '<=>'
|
|
||||||
);
|
|
||||||
|
|||||||
112
sql/vector.sql
112
sql/vector.sql
@@ -26,7 +26,7 @@ CREATE TYPE vector (
|
|||||||
TYPMOD_IN = vector_typmod_in,
|
TYPMOD_IN = vector_typmod_in,
|
||||||
RECEIVE = vector_recv,
|
RECEIVE = vector_recv,
|
||||||
SEND = vector_send,
|
SEND = vector_send,
|
||||||
STORAGE = extended
|
STORAGE = external
|
||||||
);
|
);
|
||||||
|
|
||||||
-- functions
|
-- functions
|
||||||
@@ -291,113 +291,7 @@ CREATE OPERATOR CLASS vector_cosine_ops
|
|||||||
FUNCTION 1 vector_negative_inner_product(vector, vector),
|
FUNCTION 1 vector_negative_inner_product(vector, vector),
|
||||||
FUNCTION 2 vector_norm(vector);
|
FUNCTION 2 vector_norm(vector);
|
||||||
|
|
||||||
-- half type
|
-- bytea functions
|
||||||
|
|
||||||
CREATE TYPE half;
|
CREATE FUNCTION hamming_distance(bytea, bytea) RETURNS float8
|
||||||
|
|
||||||
CREATE FUNCTION half_in(cstring, oid, integer) RETURNS half
|
|
||||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||||
|
|
||||||
CREATE FUNCTION half_out(half) RETURNS cstring
|
|
||||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
|
||||||
|
|
||||||
CREATE FUNCTION half_recv(internal, oid, integer) RETURNS half
|
|
||||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
|
||||||
|
|
||||||
CREATE FUNCTION half_send(half) RETURNS bytea
|
|
||||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
|
||||||
|
|
||||||
CREATE TYPE half (
|
|
||||||
INPUT = half_in,
|
|
||||||
OUTPUT = half_out,
|
|
||||||
RECEIVE = half_recv,
|
|
||||||
SEND = half_send,
|
|
||||||
INTERNALLENGTH = 2,
|
|
||||||
PASSEDBYVALUE,
|
|
||||||
ALIGNMENT = int2
|
|
||||||
);
|
|
||||||
|
|
||||||
-- half functions
|
|
||||||
|
|
||||||
CREATE FUNCTION l2_distance(half[], half[]) RETURNS float8
|
|
||||||
AS 'MODULE_PATHNAME', 'half_l2_distance' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
|
||||||
|
|
||||||
CREATE FUNCTION inner_product(half[], half[]) RETURNS float8
|
|
||||||
AS 'MODULE_PATHNAME', 'half_inner_product' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
|
||||||
|
|
||||||
CREATE FUNCTION cosine_distance(half[], half[]) RETURNS float8
|
|
||||||
AS 'MODULE_PATHNAME', 'half_cosine_distance' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
|
||||||
|
|
||||||
CREATE FUNCTION l1_distance(half[], half[]) RETURNS float8
|
|
||||||
AS 'MODULE_PATHNAME', 'half_l1_distance' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
|
||||||
|
|
||||||
-- half private functions
|
|
||||||
|
|
||||||
CREATE FUNCTION half_l2_squared_distance(half[], half[]) RETURNS float8
|
|
||||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
|
||||||
|
|
||||||
CREATE FUNCTION half_negative_inner_product(half[], half[]) RETURNS float8
|
|
||||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
|
||||||
|
|
||||||
-- half cast functions
|
|
||||||
|
|
||||||
CREATE FUNCTION float4_to_half(real, integer, boolean) RETURNS half
|
|
||||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
|
||||||
|
|
||||||
CREATE FUNCTION half_to_float4(half, integer, boolean) RETURNS real
|
|
||||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
|
||||||
|
|
||||||
CREATE FUNCTION float8_to_half(float8, integer, boolean) RETURNS half
|
|
||||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
|
||||||
|
|
||||||
CREATE FUNCTION half_to_float8(half, integer, boolean) RETURNS float8
|
|
||||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
|
||||||
|
|
||||||
CREATE FUNCTION integer_to_half(integer, integer, boolean) RETURNS half
|
|
||||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
|
||||||
|
|
||||||
CREATE FUNCTION numeric_to_half(numeric, integer, boolean) RETURNS half
|
|
||||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
|
||||||
|
|
||||||
CREATE FUNCTION half_to_numeric(half, integer, boolean) RETURNS numeric
|
|
||||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
|
||||||
|
|
||||||
-- half casts
|
|
||||||
|
|
||||||
CREATE CAST (real AS half)
|
|
||||||
WITH FUNCTION float4_to_half(real, integer, boolean) AS IMPLICIT;
|
|
||||||
|
|
||||||
CREATE CAST (half AS real)
|
|
||||||
WITH FUNCTION half_to_float4(half, integer, boolean) AS IMPLICIT;
|
|
||||||
|
|
||||||
CREATE CAST (float8 AS half)
|
|
||||||
WITH FUNCTION float8_to_half(float8, integer, boolean) AS IMPLICIT;
|
|
||||||
|
|
||||||
CREATE CAST (half AS float8)
|
|
||||||
WITH FUNCTION half_to_float8(half, integer, boolean) AS IMPLICIT;
|
|
||||||
|
|
||||||
CREATE CAST (integer AS half)
|
|
||||||
WITH FUNCTION integer_to_half(integer, integer, boolean) AS IMPLICIT;
|
|
||||||
|
|
||||||
CREATE CAST (numeric AS half)
|
|
||||||
WITH FUNCTION numeric_to_half(numeric, integer, boolean) AS IMPLICIT;
|
|
||||||
|
|
||||||
CREATE CAST (half AS numeric)
|
|
||||||
WITH FUNCTION half_to_numeric(half, integer, boolean) AS IMPLICIT;
|
|
||||||
|
|
||||||
-- half operators
|
|
||||||
|
|
||||||
CREATE OPERATOR <-> (
|
|
||||||
LEFTARG = half[], RIGHTARG = half[], PROCEDURE = l2_distance,
|
|
||||||
COMMUTATOR = '<->'
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE OPERATOR <#> (
|
|
||||||
LEFTARG = half[], RIGHTARG = half[], PROCEDURE = half_negative_inner_product,
|
|
||||||
COMMUTATOR = '<#>'
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE OPERATOR <=> (
|
|
||||||
LEFTARG = half[], RIGHTARG = half[], PROCEDURE = cosine_distance,
|
|
||||||
COMMUTATOR = '<=>'
|
|
||||||
);
|
|
||||||
|
|||||||
694
src/half.c
694
src/half.c
@@ -1,694 +0,0 @@
|
|||||||
#include "postgres.h"
|
|
||||||
|
|
||||||
#include <math.h>
|
|
||||||
|
|
||||||
#include "common/shortest_dec.h"
|
|
||||||
#include "fmgr.h"
|
|
||||||
#include "half.h"
|
|
||||||
#include "lib/stringinfo.h"
|
|
||||||
#include "libpq/pqformat.h"
|
|
||||||
#include "utils/array.h"
|
|
||||||
#include "utils/builtins.h"
|
|
||||||
#include "utils/float.h"
|
|
||||||
#include "utils/numeric.h"
|
|
||||||
|
|
||||||
#if PG_VERSION_NUM < 120003
|
|
||||||
static pg_noinline void
|
|
||||||
float_overflow_error(void)
|
|
||||||
{
|
|
||||||
ereport(ERROR,
|
|
||||||
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
|
|
||||||
errmsg("value out of range: overflow")));
|
|
||||||
}
|
|
||||||
|
|
||||||
static pg_noinline void
|
|
||||||
float_underflow_error(void)
|
|
||||||
{
|
|
||||||
ereport(ERROR,
|
|
||||||
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
|
|
||||||
errmsg("value out of range: underflow")));
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Check if array is a vector
|
|
||||||
*/
|
|
||||||
static void
|
|
||||||
CheckArrayIsVector(ArrayType *array)
|
|
||||||
{
|
|
||||||
if (ARR_NDIM(array) > 1)
|
|
||||||
ereport(ERROR,
|
|
||||||
(errcode(ERRCODE_DATA_EXCEPTION),
|
|
||||||
errmsg("array must be 1-D")));
|
|
||||||
|
|
||||||
if (ARR_HASNULL(array) && array_contains_nulls(array))
|
|
||||||
ereport(ERROR,
|
|
||||||
(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
|
|
||||||
errmsg("array must not contain nulls")));
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Check if dimensions are the same
|
|
||||||
*/
|
|
||||||
static int
|
|
||||||
CheckDims(ArrayType *a, ArrayType *b)
|
|
||||||
{
|
|
||||||
int dima;
|
|
||||||
int dimb;
|
|
||||||
|
|
||||||
CheckArrayIsVector(a);
|
|
||||||
CheckArrayIsVector(b);
|
|
||||||
|
|
||||||
dima = ARR_DIMS(a)[0];
|
|
||||||
dimb = ARR_DIMS(b)[0];
|
|
||||||
|
|
||||||
if (dima != dimb)
|
|
||||||
ereport(ERROR,
|
|
||||||
(errcode(ERRCODE_DATA_EXCEPTION),
|
|
||||||
errmsg("different dimensions %d and %d", dima, dimb)));
|
|
||||||
|
|
||||||
return dima;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Return the datum representation for a half
|
|
||||||
*/
|
|
||||||
static inline Datum
|
|
||||||
HalfGetDatum(half X)
|
|
||||||
{
|
|
||||||
union
|
|
||||||
{
|
|
||||||
half value;
|
|
||||||
int16 retval;
|
|
||||||
} myunion;
|
|
||||||
|
|
||||||
myunion.value = X;
|
|
||||||
return Int16GetDatum(myunion.retval);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Return the half value of a datum
|
|
||||||
*/
|
|
||||||
static inline half
|
|
||||||
DatumGetHalf(Datum X)
|
|
||||||
{
|
|
||||||
union
|
|
||||||
{
|
|
||||||
int16 value;
|
|
||||||
half retval;
|
|
||||||
} myunion;
|
|
||||||
|
|
||||||
myunion.value = DatumGetInt16(X);
|
|
||||||
return myunion.retval;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Get a half from a message buffer
|
|
||||||
*/
|
|
||||||
static half
|
|
||||||
pq_getmsghalf(StringInfo msg)
|
|
||||||
{
|
|
||||||
union
|
|
||||||
{
|
|
||||||
half h;
|
|
||||||
uint16 i;
|
|
||||||
} swap;
|
|
||||||
|
|
||||||
swap.i = pq_getmsgint(msg, 2);
|
|
||||||
return swap.h;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Append a half to a StringInfo buffer
|
|
||||||
*/
|
|
||||||
static void
|
|
||||||
pq_sendhalf(StringInfo buf, half h)
|
|
||||||
{
|
|
||||||
union
|
|
||||||
{
|
|
||||||
half h;
|
|
||||||
uint16 i;
|
|
||||||
} swap;
|
|
||||||
|
|
||||||
swap.h = h;
|
|
||||||
pq_sendint16(buf, swap.i);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Convert a half to a float4
|
|
||||||
*/
|
|
||||||
static float
|
|
||||||
HalfToFloat4(half num)
|
|
||||||
{
|
|
||||||
#ifdef FLT16_SUPPORT
|
|
||||||
return (float) num;
|
|
||||||
#else
|
|
||||||
/* TODO Improve performance */
|
|
||||||
/* TODO Check endianness */
|
|
||||||
uint16 bin = *((uint16 *) &num);
|
|
||||||
uint32 exponent = (bin & 0x7C00) >> 10;
|
|
||||||
uint32 mantissa = bin & 0x03FF;
|
|
||||||
|
|
||||||
/* Sign */
|
|
||||||
uint32 result = (bin & 0x8000) << 16;
|
|
||||||
|
|
||||||
if (exponent == 31)
|
|
||||||
{
|
|
||||||
if (mantissa == 0)
|
|
||||||
{
|
|
||||||
/* Infinite */
|
|
||||||
result |= 0x7F800000;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
/* NaN */
|
|
||||||
result |= 0x7FC00000;
|
|
||||||
result |= mantissa << 13;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (exponent == 0)
|
|
||||||
{
|
|
||||||
/* Subnormal */
|
|
||||||
if (mantissa != 0)
|
|
||||||
{
|
|
||||||
exponent = -14;
|
|
||||||
|
|
||||||
for (int i = 0; i < 10; i++)
|
|
||||||
{
|
|
||||||
mantissa <<= 1;
|
|
||||||
exponent -= 1;
|
|
||||||
|
|
||||||
if ((mantissa >> 10) % 2 == 1)
|
|
||||||
{
|
|
||||||
mantissa &= 0x03ff;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
result |= (exponent + 127) << 23;
|
|
||||||
result |= mantissa << 13;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
/* Normal */
|
|
||||||
result |= (exponent - 15 + 127) << 23;
|
|
||||||
result |= mantissa << 13;
|
|
||||||
}
|
|
||||||
|
|
||||||
return *((float *) &result);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Convert a float4 to a half
|
|
||||||
*/
|
|
||||||
static half
|
|
||||||
Float4ToHalfUnchecked(float num)
|
|
||||||
{
|
|
||||||
#ifdef FLT16_SUPPORT
|
|
||||||
return (_Float16) num;
|
|
||||||
#else
|
|
||||||
/* TODO Improve performance */
|
|
||||||
/* TODO Check endianness */
|
|
||||||
uint32 bin = *((uint32 *) &num);
|
|
||||||
int exponent = (bin & 0x7F800000) >> 23;
|
|
||||||
int mantissa = bin & 0x007FFFFF;
|
|
||||||
|
|
||||||
/* Sign */
|
|
||||||
uint16 result = (bin & 0x80000000) >> 16;
|
|
||||||
|
|
||||||
if (isinf(num))
|
|
||||||
{
|
|
||||||
/* Infinite */
|
|
||||||
result |= 0x7C00;
|
|
||||||
}
|
|
||||||
else if (isnan(num))
|
|
||||||
{
|
|
||||||
/* NaN */
|
|
||||||
result |= 0x7E00;
|
|
||||||
result |= mantissa >> 13;
|
|
||||||
}
|
|
||||||
else if (exponent > 98)
|
|
||||||
{
|
|
||||||
int m;
|
|
||||||
int gr;
|
|
||||||
int s;
|
|
||||||
|
|
||||||
exponent -= 127;
|
|
||||||
s = mantissa & 0x00000FFF;
|
|
||||||
|
|
||||||
/* Subnormal */
|
|
||||||
if (exponent < -14)
|
|
||||||
{
|
|
||||||
int diff = -exponent - 14;
|
|
||||||
|
|
||||||
mantissa >>= diff;
|
|
||||||
mantissa += 1 << (23 - diff);
|
|
||||||
s |= mantissa & 0x00000FFF;
|
|
||||||
}
|
|
||||||
|
|
||||||
m = mantissa >> 13;
|
|
||||||
|
|
||||||
/* Round */
|
|
||||||
gr = (mantissa >> 12) % 4;
|
|
||||||
if (gr == 3 || (gr == 1 && s != 0))
|
|
||||||
m += 1;
|
|
||||||
|
|
||||||
if (m == 1024)
|
|
||||||
{
|
|
||||||
m = 0;
|
|
||||||
exponent += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (exponent > 15)
|
|
||||||
{
|
|
||||||
/* Infinite */
|
|
||||||
result |= 0x7C00;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (exponent >= -14)
|
|
||||||
result |= (exponent + 15) << 10;
|
|
||||||
|
|
||||||
result |= m;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return *((half *) & result);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Check if half is infinite
|
|
||||||
*/
|
|
||||||
static inline bool
|
|
||||||
HalfIsInf(half num)
|
|
||||||
{
|
|
||||||
#ifdef FLT16_SUPPORT
|
|
||||||
return isinf(num);
|
|
||||||
#else
|
|
||||||
return (num << 1) == 0xF800;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Check if half is zero
|
|
||||||
*/
|
|
||||||
static inline bool
|
|
||||||
HalfIsZero(half num)
|
|
||||||
{
|
|
||||||
#ifdef FLT16_SUPPORT
|
|
||||||
return num == 0;
|
|
||||||
#else
|
|
||||||
return (num << 1) == 0x0000;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Convert a float4 to a half
|
|
||||||
*/
|
|
||||||
static half
|
|
||||||
Float4ToHalf(float num)
|
|
||||||
{
|
|
||||||
half result = Float4ToHalfUnchecked(num);
|
|
||||||
|
|
||||||
if (unlikely(HalfIsInf(result)) && !isinf(num))
|
|
||||||
float_overflow_error();
|
|
||||||
if (unlikely(HalfIsZero(result)) && num != 0.0)
|
|
||||||
float_underflow_error();
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Convert a float8 to a half
|
|
||||||
*/
|
|
||||||
static half
|
|
||||||
Float8ToHalf(double num)
|
|
||||||
{
|
|
||||||
/* TODO Convert directly for greater accuracy */
|
|
||||||
half result = Float4ToHalfUnchecked((float) num);
|
|
||||||
|
|
||||||
if (unlikely(HalfIsInf(result)) && !isinf(num))
|
|
||||||
float_overflow_error();
|
|
||||||
if (unlikely(HalfIsZero(result)) && num != 0.0)
|
|
||||||
float_underflow_error();
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Convert textual representation to internal representation
|
|
||||||
*/
|
|
||||||
PGDLLEXPORT PG_FUNCTION_INFO_V1(half_in);
|
|
||||||
Datum
|
|
||||||
half_in(PG_FUNCTION_ARGS)
|
|
||||||
{
|
|
||||||
char *num = PG_GETARG_CSTRING(0);
|
|
||||||
char *orig_num;
|
|
||||||
float val;
|
|
||||||
char *endptr;
|
|
||||||
|
|
||||||
orig_num = num;
|
|
||||||
|
|
||||||
/* Skip leading whitespace */
|
|
||||||
while (*num != '\0' && isspace((unsigned char) *num))
|
|
||||||
num++;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Check for an empty-string input to begin with, to avoid the vagaries of
|
|
||||||
* strtof() on different platforms.
|
|
||||||
*/
|
|
||||||
if (*num == '\0')
|
|
||||||
ereport(ERROR,
|
|
||||||
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
|
|
||||||
errmsg("invalid input syntax for type %s: \"%s\"",
|
|
||||||
"half", orig_num)));
|
|
||||||
|
|
||||||
val = strtof(num, &endptr);
|
|
||||||
|
|
||||||
if (val < -HALF_MAX || val > HALF_MAX)
|
|
||||||
ereport(ERROR,
|
|
||||||
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
|
|
||||||
errmsg("\"%s\" is out of range for type %s",
|
|
||||||
orig_num, "half")));
|
|
||||||
|
|
||||||
/* Skip trailing whitespace */
|
|
||||||
while (*endptr != '\0' && isspace((unsigned char) *endptr))
|
|
||||||
endptr++;
|
|
||||||
|
|
||||||
/* If there is any junk left at the end of the string, bail out */
|
|
||||||
if (*endptr != '\0')
|
|
||||||
ereport(ERROR,
|
|
||||||
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
|
|
||||||
errmsg("invalid input syntax for type %s: \"%s\"",
|
|
||||||
"half", orig_num)));
|
|
||||||
|
|
||||||
PG_RETURN_HALF(Float4ToHalf(val));
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Convert internal representation to textual representation
|
|
||||||
*/
|
|
||||||
PGDLLEXPORT PG_FUNCTION_INFO_V1(half_out);
|
|
||||||
Datum
|
|
||||||
half_out(PG_FUNCTION_ARGS)
|
|
||||||
{
|
|
||||||
float num = HalfToFloat4(PG_GETARG_HALF(0));
|
|
||||||
char *ascii = (char *) palloc(32);
|
|
||||||
int ndig = FLT_DIG + extra_float_digits;
|
|
||||||
|
|
||||||
if (extra_float_digits > 0)
|
|
||||||
{
|
|
||||||
float_to_shortest_decimal_buf(num, ascii);
|
|
||||||
PG_RETURN_CSTRING(ascii);
|
|
||||||
}
|
|
||||||
|
|
||||||
(void) pg_strfromd(ascii, 32, ndig, num);
|
|
||||||
PG_RETURN_CSTRING(ascii);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Convert external binary representation to internal representation
|
|
||||||
*/
|
|
||||||
PGDLLEXPORT PG_FUNCTION_INFO_V1(half_recv);
|
|
||||||
Datum
|
|
||||||
half_recv(PG_FUNCTION_ARGS)
|
|
||||||
{
|
|
||||||
StringInfo buf = (StringInfo) PG_GETARG_POINTER(0);
|
|
||||||
|
|
||||||
PG_RETURN_HALF(pq_getmsghalf(buf));
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Convert internal representation to the external binary representation
|
|
||||||
*/
|
|
||||||
PGDLLEXPORT PG_FUNCTION_INFO_V1(half_send);
|
|
||||||
Datum
|
|
||||||
half_send(PG_FUNCTION_ARGS)
|
|
||||||
{
|
|
||||||
half arg1 = PG_GETARG_HALF(0);
|
|
||||||
StringInfoData buf;
|
|
||||||
|
|
||||||
pq_begintypsend(&buf);
|
|
||||||
pq_sendhalf(&buf, arg1);
|
|
||||||
PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Convert integer to half
|
|
||||||
*/
|
|
||||||
PGDLLEXPORT PG_FUNCTION_INFO_V1(integer_to_half);
|
|
||||||
Datum
|
|
||||||
integer_to_half(PG_FUNCTION_ARGS)
|
|
||||||
{
|
|
||||||
int32 i = PG_GETARG_INT32(0);
|
|
||||||
|
|
||||||
/* TODO Figure out correct error */
|
|
||||||
float f = (float) i;
|
|
||||||
half h = Float4ToHalf(f);
|
|
||||||
|
|
||||||
PG_RETURN_HALF(h);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Convert numeric to half
|
|
||||||
*
|
|
||||||
* TODO Improve error message if out of range
|
|
||||||
*/
|
|
||||||
PGDLLEXPORT PG_FUNCTION_INFO_V1(numeric_to_half);
|
|
||||||
Datum
|
|
||||||
numeric_to_half(PG_FUNCTION_ARGS)
|
|
||||||
{
|
|
||||||
Numeric num = PG_GETARG_NUMERIC(0);
|
|
||||||
float f = DatumGetFloat4(DirectFunctionCall1(numeric_float4, NumericGetDatum(num)));
|
|
||||||
half h = Float4ToHalf(f);
|
|
||||||
|
|
||||||
PG_RETURN_HALF(h);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Convert half to numeric
|
|
||||||
*/
|
|
||||||
PGDLLEXPORT PG_FUNCTION_INFO_V1(half_to_numeric);
|
|
||||||
Datum
|
|
||||||
half_to_numeric(PG_FUNCTION_ARGS)
|
|
||||||
{
|
|
||||||
half h = PG_GETARG_HALF(0);
|
|
||||||
float f = HalfToFloat4(h);
|
|
||||||
Numeric num = DatumGetNumeric(DirectFunctionCall1(float4_numeric, Float4GetDatum(f)));
|
|
||||||
|
|
||||||
PG_RETURN_NUMERIC(num);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Convert float4 to half
|
|
||||||
*/
|
|
||||||
PGDLLEXPORT PG_FUNCTION_INFO_V1(float4_to_half);
|
|
||||||
Datum
|
|
||||||
float4_to_half(PG_FUNCTION_ARGS)
|
|
||||||
{
|
|
||||||
float f = PG_GETARG_FLOAT4(0);
|
|
||||||
half h = Float4ToHalf(f);
|
|
||||||
|
|
||||||
PG_RETURN_HALF(h);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Convert half to float4
|
|
||||||
*/
|
|
||||||
PGDLLEXPORT PG_FUNCTION_INFO_V1(half_to_float4);
|
|
||||||
Datum
|
|
||||||
half_to_float4(PG_FUNCTION_ARGS)
|
|
||||||
{
|
|
||||||
half h = PG_GETARG_HALF(0);
|
|
||||||
float f = HalfToFloat4(h);
|
|
||||||
|
|
||||||
PG_RETURN_FLOAT4(f);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Convert float8 to half
|
|
||||||
*/
|
|
||||||
PGDLLEXPORT PG_FUNCTION_INFO_V1(float8_to_half);
|
|
||||||
Datum
|
|
||||||
float8_to_half(PG_FUNCTION_ARGS)
|
|
||||||
{
|
|
||||||
float8 d = PG_GETARG_FLOAT8(0);
|
|
||||||
half h = Float8ToHalf(d);
|
|
||||||
|
|
||||||
PG_RETURN_HALF(h);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Convert half to float8
|
|
||||||
*/
|
|
||||||
PGDLLEXPORT PG_FUNCTION_INFO_V1(half_to_float8);
|
|
||||||
Datum
|
|
||||||
half_to_float8(PG_FUNCTION_ARGS)
|
|
||||||
{
|
|
||||||
half h = PG_GETARG_HALF(0);
|
|
||||||
float f = HalfToFloat4(h);
|
|
||||||
|
|
||||||
PG_RETURN_FLOAT8((double) f);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Get the L2 distance between half arrays
|
|
||||||
*/
|
|
||||||
PGDLLEXPORT PG_FUNCTION_INFO_V1(half_l2_distance);
|
|
||||||
Datum
|
|
||||||
half_l2_distance(PG_FUNCTION_ARGS)
|
|
||||||
{
|
|
||||||
ArrayType *a = PG_GETARG_ARRAYTYPE_P(0);
|
|
||||||
ArrayType *b = PG_GETARG_ARRAYTYPE_P(1);
|
|
||||||
half *ax = (half *) ARR_DATA_PTR(a);
|
|
||||||
half *bx = (half *) ARR_DATA_PTR(b);
|
|
||||||
float distance = 0.0;
|
|
||||||
int dim = CheckDims(a, b);
|
|
||||||
|
|
||||||
/* Auto-vectorized */
|
|
||||||
for (int i = 0; i < dim; i++)
|
|
||||||
{
|
|
||||||
float diff = HalfToFloat4(ax[i]) - HalfToFloat4(bx[i]);
|
|
||||||
|
|
||||||
distance += diff * diff;
|
|
||||||
}
|
|
||||||
|
|
||||||
PG_RETURN_FLOAT8(sqrt((double) distance));
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Get the L2 squared distance between half arrays
|
|
||||||
*/
|
|
||||||
PGDLLEXPORT PG_FUNCTION_INFO_V1(half_l2_squared_distance);
|
|
||||||
Datum
|
|
||||||
half_l2_squared_distance(PG_FUNCTION_ARGS)
|
|
||||||
{
|
|
||||||
ArrayType *a = PG_GETARG_ARRAYTYPE_P(0);
|
|
||||||
ArrayType *b = PG_GETARG_ARRAYTYPE_P(1);
|
|
||||||
half *ax = (half *) ARR_DATA_PTR(a);
|
|
||||||
half *bx = (half *) ARR_DATA_PTR(b);
|
|
||||||
float distance = 0.0;
|
|
||||||
int dim = CheckDims(a, b);
|
|
||||||
|
|
||||||
/* Auto-vectorized */
|
|
||||||
for (int i = 0; i < dim; i++)
|
|
||||||
{
|
|
||||||
float diff = HalfToFloat4(ax[i]) - HalfToFloat4(bx[i]);
|
|
||||||
|
|
||||||
distance += diff * diff;
|
|
||||||
}
|
|
||||||
|
|
||||||
PG_RETURN_FLOAT8((double) distance);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Get the inner product of two half arrays
|
|
||||||
*/
|
|
||||||
PGDLLEXPORT PG_FUNCTION_INFO_V1(half_inner_product);
|
|
||||||
Datum
|
|
||||||
half_inner_product(PG_FUNCTION_ARGS)
|
|
||||||
{
|
|
||||||
ArrayType *a = PG_GETARG_ARRAYTYPE_P(0);
|
|
||||||
ArrayType *b = PG_GETARG_ARRAYTYPE_P(1);
|
|
||||||
half *ax = (half *) ARR_DATA_PTR(a);
|
|
||||||
half *bx = (half *) ARR_DATA_PTR(b);
|
|
||||||
float distance = 0.0;
|
|
||||||
int dim = CheckDims(a, b);
|
|
||||||
|
|
||||||
/* Auto-vectorized */
|
|
||||||
for (int i = 0; i < dim; i++)
|
|
||||||
distance += HalfToFloat4(ax[i]) * HalfToFloat4(bx[i]);
|
|
||||||
|
|
||||||
PG_RETURN_FLOAT8((double) distance);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Get the negative inner product of two half arrays
|
|
||||||
*/
|
|
||||||
PGDLLEXPORT PG_FUNCTION_INFO_V1(half_negative_inner_product);
|
|
||||||
Datum
|
|
||||||
half_negative_inner_product(PG_FUNCTION_ARGS)
|
|
||||||
{
|
|
||||||
ArrayType *a = PG_GETARG_ARRAYTYPE_P(0);
|
|
||||||
ArrayType *b = PG_GETARG_ARRAYTYPE_P(1);
|
|
||||||
half *ax = (half *) ARR_DATA_PTR(a);
|
|
||||||
half *bx = (half *) ARR_DATA_PTR(b);
|
|
||||||
float distance = 0.0;
|
|
||||||
int dim = CheckDims(a, b);
|
|
||||||
|
|
||||||
/* Auto-vectorized */
|
|
||||||
for (int i = 0; i < dim; i++)
|
|
||||||
distance += HalfToFloat4(ax[i]) * HalfToFloat4(bx[i]);
|
|
||||||
|
|
||||||
PG_RETURN_FLOAT8((double) distance * -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Get the cosine distance between two half arrays
|
|
||||||
*/
|
|
||||||
PGDLLEXPORT PG_FUNCTION_INFO_V1(half_cosine_distance);
|
|
||||||
Datum
|
|
||||||
half_cosine_distance(PG_FUNCTION_ARGS)
|
|
||||||
{
|
|
||||||
ArrayType *a = PG_GETARG_ARRAYTYPE_P(0);
|
|
||||||
ArrayType *b = PG_GETARG_ARRAYTYPE_P(1);
|
|
||||||
half *ax = (half *) ARR_DATA_PTR(a);
|
|
||||||
half *bx = (half *) ARR_DATA_PTR(b);
|
|
||||||
float distance = 0.0;
|
|
||||||
float norma = 0.0;
|
|
||||||
float normb = 0.0;
|
|
||||||
double similarity;
|
|
||||||
int dim = CheckDims(a, b);
|
|
||||||
|
|
||||||
/* Auto-vectorized */
|
|
||||||
for (int i = 0; i < dim; i++)
|
|
||||||
{
|
|
||||||
float axi = HalfToFloat4(ax[i]);
|
|
||||||
float bxi = HalfToFloat4(bx[i]);
|
|
||||||
|
|
||||||
distance += axi * bxi;
|
|
||||||
norma += axi * axi;
|
|
||||||
normb += bxi * bxi;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Use sqrt(a * b) over sqrt(a) * sqrt(b) */
|
|
||||||
similarity = (double) distance / sqrt((double) norma * (double) normb);
|
|
||||||
|
|
||||||
#ifdef _MSC_VER
|
|
||||||
/* /fp:fast may not propagate NaN */
|
|
||||||
if (isnan(similarity))
|
|
||||||
PG_RETURN_FLOAT8(NAN);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/* Keep in range */
|
|
||||||
if (similarity > 1)
|
|
||||||
similarity = 1;
|
|
||||||
else if (similarity < -1)
|
|
||||||
similarity = -1;
|
|
||||||
|
|
||||||
PG_RETURN_FLOAT8(1 - similarity);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Get the L1 distance between two half arrays
|
|
||||||
*/
|
|
||||||
PGDLLEXPORT PG_FUNCTION_INFO_V1(half_l1_distance);
|
|
||||||
Datum
|
|
||||||
half_l1_distance(PG_FUNCTION_ARGS)
|
|
||||||
{
|
|
||||||
ArrayType *a = PG_GETARG_ARRAYTYPE_P(0);
|
|
||||||
ArrayType *b = PG_GETARG_ARRAYTYPE_P(1);
|
|
||||||
half *ax = (half *) ARR_DATA_PTR(a);
|
|
||||||
half *bx = (half *) ARR_DATA_PTR(b);
|
|
||||||
float distance = 0.0;
|
|
||||||
int dim = CheckDims(a, b);
|
|
||||||
|
|
||||||
/* Auto-vectorized */
|
|
||||||
for (int i = 0; i < dim; i++)
|
|
||||||
distance += fabsf(HalfToFloat4(ax[i]) - HalfToFloat4(bx[i]));
|
|
||||||
|
|
||||||
PG_RETURN_FLOAT8((double) distance);
|
|
||||||
}
|
|
||||||
23
src/half.h
23
src/half.h
@@ -1,23 +0,0 @@
|
|||||||
#ifndef HALF_H
|
|
||||||
#define HALF_H
|
|
||||||
|
|
||||||
#define __STDC_WANT_IEC_60559_TYPES_EXT__
|
|
||||||
|
|
||||||
#include <float.h>
|
|
||||||
|
|
||||||
#ifdef __FLT16_MAX__
|
|
||||||
#define FLT16_SUPPORT
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef FLT16_SUPPORT
|
|
||||||
#define half _Float16
|
|
||||||
#define HALF_MAX FLT16_MAX
|
|
||||||
#else
|
|
||||||
#define half uint16
|
|
||||||
#define HALF_MAX 65504
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define PG_GETARG_HALF(n) DatumGetHalf(PG_GETARG_DATUM(n))
|
|
||||||
#define PG_RETURN_HALF(x) return HalfGetDatum(x)
|
|
||||||
|
|
||||||
#endif
|
|
||||||
55
src/hnsw.c
55
src/hnsw.c
@@ -4,25 +4,57 @@
|
|||||||
#include <math.h>
|
#include <math.h>
|
||||||
|
|
||||||
#include "access/amapi.h"
|
#include "access/amapi.h"
|
||||||
|
#include "access/reloptions.h"
|
||||||
|
#include "commands/progress.h"
|
||||||
#include "commands/vacuum.h"
|
#include "commands/vacuum.h"
|
||||||
#include "hnsw.h"
|
#include "hnsw.h"
|
||||||
#include "utils/guc.h"
|
#include "utils/guc.h"
|
||||||
#include "utils/selfuncs.h"
|
#include "utils/selfuncs.h"
|
||||||
|
|
||||||
#if PG_VERSION_NUM >= 120000
|
#if PG_VERSION_NUM < 150000
|
||||||
#include "commands/progress.h"
|
#define MarkGUCPrefixReserved(x) EmitWarningsOnPlaceholders(x)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
int hnsw_ef_search;
|
int hnsw_ef_search;
|
||||||
bool hnsw_enable_parallel_build;
|
int hnsw_lock_tranche_id;
|
||||||
static relopt_kind hnsw_relopt_kind;
|
static relopt_kind hnsw_relopt_kind;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Assign a tranche ID for our LWLocks. This only needs to be done by one
|
||||||
|
* backend, as the tranche ID is remembered in shared memory.
|
||||||
|
*
|
||||||
|
* This shared memory area is very small, so we just allocate it from the
|
||||||
|
* "slop" that PostgreSQL reserves for small allocations like this. If
|
||||||
|
* this grows bigger, we should use a shmem_request_hook and
|
||||||
|
* RequestAddinShmemSpace() to pre-reserve space for this.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
HnswInitLockTranche(void)
|
||||||
|
{
|
||||||
|
int *tranche_ids;
|
||||||
|
bool found;
|
||||||
|
|
||||||
|
LWLockAcquire(AddinShmemInitLock, LW_EXCLUSIVE);
|
||||||
|
tranche_ids = ShmemInitStruct("hnsw LWLock ids",
|
||||||
|
sizeof(int) * 1,
|
||||||
|
&found);
|
||||||
|
if (!found)
|
||||||
|
tranche_ids[0] = LWLockNewTrancheId();
|
||||||
|
hnsw_lock_tranche_id = tranche_ids[0];
|
||||||
|
LWLockRelease(AddinShmemInitLock);
|
||||||
|
|
||||||
|
/* Per-backend registration of the tranche ID */
|
||||||
|
LWLockRegisterTranche(hnsw_lock_tranche_id, "HnswBuild");
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Initialize index options and variables
|
* Initialize index options and variables
|
||||||
*/
|
*/
|
||||||
void
|
void
|
||||||
HnswInit(void)
|
HnswInit(void)
|
||||||
{
|
{
|
||||||
|
HnswInitLockTranche();
|
||||||
|
|
||||||
hnsw_relopt_kind = add_reloption_kind();
|
hnsw_relopt_kind = add_reloption_kind();
|
||||||
add_int_reloption(hnsw_relopt_kind, "m", "Max number of connections",
|
add_int_reloption(hnsw_relopt_kind, "m", "Max number of connections",
|
||||||
HNSW_DEFAULT_M, HNSW_MIN_M, HNSW_MAX_M
|
HNSW_DEFAULT_M, HNSW_MIN_M, HNSW_MAX_M
|
||||||
@@ -41,16 +73,12 @@ HnswInit(void)
|
|||||||
"Valid range is 1..1000.", &hnsw_ef_search,
|
"Valid range is 1..1000.", &hnsw_ef_search,
|
||||||
HNSW_DEFAULT_EF_SEARCH, HNSW_MIN_EF_SEARCH, HNSW_MAX_EF_SEARCH, PGC_USERSET, 0, NULL, NULL, NULL);
|
HNSW_DEFAULT_EF_SEARCH, HNSW_MIN_EF_SEARCH, HNSW_MAX_EF_SEARCH, PGC_USERSET, 0, NULL, NULL, NULL);
|
||||||
|
|
||||||
/* Behind a variable for now since can be slower than building in memory */
|
MarkGUCPrefixReserved("hnsw");
|
||||||
DefineCustomBoolVariable("hnsw.enable_parallel_build", "Enables or disables building indexes in parallel",
|
|
||||||
NULL, &hnsw_enable_parallel_build,
|
|
||||||
false, PGC_USERSET, 0, NULL, NULL, NULL);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Get the name of index build phase
|
* Get the name of index build phase
|
||||||
*/
|
*/
|
||||||
#if PG_VERSION_NUM >= 120000
|
|
||||||
static char *
|
static char *
|
||||||
hnswbuildphasename(int64 phasenum)
|
hnswbuildphasename(int64 phasenum)
|
||||||
{
|
{
|
||||||
@@ -64,7 +92,6 @@ hnswbuildphasename(int64 phasenum)
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Estimate the cost of an index scan
|
* Estimate the cost of an index scan
|
||||||
@@ -79,9 +106,6 @@ hnswcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
|
|||||||
int m;
|
int m;
|
||||||
int entryLevel;
|
int entryLevel;
|
||||||
Relation index;
|
Relation index;
|
||||||
#if PG_VERSION_NUM < 120000
|
|
||||||
List *qinfos;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/* Never use index without order */
|
/* Never use index without order */
|
||||||
if (path->indexorderbys == NULL)
|
if (path->indexorderbys == NULL)
|
||||||
@@ -107,12 +131,7 @@ hnswcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
|
|||||||
/* Account for number of tuples (or entry level), m, and ef_search */
|
/* Account for number of tuples (or entry level), m, and ef_search */
|
||||||
costs.numIndexTuples = (entryLevel + 2) * m;
|
costs.numIndexTuples = (entryLevel + 2) * m;
|
||||||
|
|
||||||
#if PG_VERSION_NUM >= 120000
|
|
||||||
genericcostestimate(root, path, loop_count, &costs);
|
genericcostestimate(root, path, loop_count, &costs);
|
||||||
#else
|
|
||||||
qinfos = deconstruct_indexquals(path);
|
|
||||||
genericcostestimate(root, path, loop_count, qinfos, &costs);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/* Use total cost since most work happens before first tuple is returned */
|
/* Use total cost since most work happens before first tuple is returned */
|
||||||
*indexStartupCost = costs.indexTotalCost;
|
*indexStartupCost = costs.indexTotalCost;
|
||||||
@@ -206,9 +225,7 @@ hnswhandler(PG_FUNCTION_ARGS)
|
|||||||
amroutine->amcostestimate = hnswcostestimate;
|
amroutine->amcostestimate = hnswcostestimate;
|
||||||
amroutine->amoptions = hnswoptions;
|
amroutine->amoptions = hnswoptions;
|
||||||
amroutine->amproperty = NULL; /* TODO AMPROP_DISTANCE_ORDERABLE */
|
amroutine->amproperty = NULL; /* TODO AMPROP_DISTANCE_ORDERABLE */
|
||||||
#if PG_VERSION_NUM >= 120000
|
|
||||||
amroutine->ambuildphasename = hnswbuildphasename;
|
amroutine->ambuildphasename = hnswbuildphasename;
|
||||||
#endif
|
|
||||||
amroutine->amvalidate = hnswvalidate;
|
amroutine->amvalidate = hnswvalidate;
|
||||||
#if PG_VERSION_NUM >= 140000
|
#if PG_VERSION_NUM >= 140000
|
||||||
amroutine->amadjustmembers = NULL;
|
amroutine->amadjustmembers = NULL;
|
||||||
|
|||||||
186
src/hnsw.h
186
src/hnsw.h
@@ -3,20 +3,17 @@
|
|||||||
|
|
||||||
#include "postgres.h"
|
#include "postgres.h"
|
||||||
|
|
||||||
#include "access/generic_xlog.h"
|
#include "access/genam.h"
|
||||||
#include "access/parallel.h"
|
#include "access/parallel.h"
|
||||||
#include "access/reloptions.h"
|
#include "lib/pairingheap.h"
|
||||||
#include "nodes/execnodes.h"
|
#include "nodes/execnodes.h"
|
||||||
#include "port.h" /* for random() */
|
#include "port.h" /* for random() */
|
||||||
|
#include "utils/relptr.h"
|
||||||
#include "utils/sampling.h"
|
#include "utils/sampling.h"
|
||||||
#include "vector.h"
|
#include "vector.h"
|
||||||
|
|
||||||
#if PG_VERSION_NUM < 110000
|
|
||||||
#error "Requires PostgreSQL 11+"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if PG_VERSION_NUM < 120000
|
#if PG_VERSION_NUM < 120000
|
||||||
#include "access/relscan.h"
|
#error "Requires PostgreSQL 12+"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#define HNSW_MAX_DIM 2000
|
#define HNSW_MAX_DIM 2000
|
||||||
@@ -72,8 +69,10 @@
|
|||||||
|
|
||||||
#if PG_VERSION_NUM >= 150000
|
#if PG_VERSION_NUM >= 150000
|
||||||
#define RandomDouble() pg_prng_double(&pg_global_prng_state)
|
#define RandomDouble() pg_prng_double(&pg_global_prng_state)
|
||||||
|
#define SeedRandom(seed) pg_prng_seed(&pg_global_prng_state, seed)
|
||||||
#else
|
#else
|
||||||
#define RandomDouble() (((double) random()) / MAX_RANDOM_VALUE)
|
#define RandomDouble() (((double) random()) / MAX_RANDOM_VALUE)
|
||||||
|
#define SeedRandom(seed) srandom(seed)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if PG_VERSION_NUM < 130000
|
#if PG_VERSION_NUM < 130000
|
||||||
@@ -91,32 +90,64 @@
|
|||||||
#define HnswGetMl(m) (1 / log(m))
|
#define HnswGetMl(m) (1 / log(m))
|
||||||
|
|
||||||
/* Ensure fits on page and in uint8 */
|
/* Ensure fits on page and in uint8 */
|
||||||
#define HnswGetMaxLevel(m) Min(((BLCKSZ - MAXALIGN(SizeOfPageHeaderData) - MAXALIGN(sizeof(HnswPageOpaqueData)) - offsetof(HnswNeighborTupleData, indextids) - sizeof(ItemIdData)) / (sizeof(ItemPointerData)) / m) - 2, 255)
|
#define HnswGetMaxLevel(m) Min(((BLCKSZ - MAXALIGN(SizeOfPageHeaderData) - MAXALIGN(sizeof(HnswPageOpaqueData)) - offsetof(HnswNeighborTupleData, indextids) - sizeof(ItemIdData)) / (sizeof(ItemPointerData)) / (m)) - 2, 255)
|
||||||
|
|
||||||
|
#define HnswGetValue(base, element) PointerGetDatum(HnswPtrAccess(base, (element)->value))
|
||||||
|
|
||||||
|
#if PG_VERSION_NUM < 140005
|
||||||
|
#define relptr_offset(rp) ((rp).relptr_off - 1)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Pointer macros */
|
||||||
|
#define HnswPtrAccess(base, hp) ((base) == NULL ? (hp).ptr : relptr_access(base, (hp).relptr))
|
||||||
|
#define HnswPtrStore(base, hp, value) ((base) == NULL ? (void) ((hp).ptr = (value)) : (void) relptr_store(base, (hp).relptr, value))
|
||||||
|
#define HnswPtrIsNull(base, hp) ((base) == NULL ? (hp).ptr == NULL : relptr_is_null((hp).relptr))
|
||||||
|
#define HnswPtrEqual(base, hp1, hp2) ((base) == NULL ? (hp1).ptr == (hp2).ptr : relptr_offset((hp1).relptr) == relptr_offset((hp2).relptr))
|
||||||
|
|
||||||
|
/* For code paths dedicated to each type */
|
||||||
|
#define HnswPtrPointer(hp) (hp).ptr
|
||||||
|
#define HnswPtrOffset(hp) relptr_offset((hp).relptr)
|
||||||
|
|
||||||
/* Variables */
|
/* Variables */
|
||||||
extern int hnsw_ef_search;
|
extern int hnsw_ef_search;
|
||||||
extern bool hnsw_enable_parallel_build;
|
extern int hnsw_lock_tranche_id;
|
||||||
|
|
||||||
|
typedef struct HnswElementData HnswElementData;
|
||||||
typedef struct HnswNeighborArray HnswNeighborArray;
|
typedef struct HnswNeighborArray HnswNeighborArray;
|
||||||
|
|
||||||
|
#define HnswPtrDeclare(type, relptrtype, ptrtype) \
|
||||||
|
relptr_declare(type, relptrtype); \
|
||||||
|
typedef union { type *ptr; relptrtype relptr; } ptrtype;
|
||||||
|
|
||||||
|
/* Pointers that can be absolute or relative */
|
||||||
|
/* Use char for DatumPtr so works with Pointer */
|
||||||
|
HnswPtrDeclare(HnswElementData, HnswElementRelptr, HnswElementPtr);
|
||||||
|
HnswPtrDeclare(HnswNeighborArray, HnswNeighborArrayRelptr, HnswNeighborArrayPtr);
|
||||||
|
HnswPtrDeclare(HnswNeighborArrayPtr, HnswNeighborsRelptr, HnswNeighborsPtr);
|
||||||
|
HnswPtrDeclare(char, DatumRelptr, DatumPtr);
|
||||||
|
|
||||||
typedef struct HnswElementData
|
typedef struct HnswElementData
|
||||||
{
|
{
|
||||||
List *heaptids;
|
HnswElementPtr next;
|
||||||
|
ItemPointerData heaptids[HNSW_HEAPTIDS];
|
||||||
|
uint8 heaptidsLength;
|
||||||
uint8 level;
|
uint8 level;
|
||||||
uint8 deleted;
|
uint8 deleted;
|
||||||
HnswNeighborArray *neighbors;
|
uint32 hash;
|
||||||
|
HnswNeighborsPtr neighbors;
|
||||||
BlockNumber blkno;
|
BlockNumber blkno;
|
||||||
OffsetNumber offno;
|
OffsetNumber offno;
|
||||||
OffsetNumber neighborOffno;
|
OffsetNumber neighborOffno;
|
||||||
BlockNumber neighborPage;
|
BlockNumber neighborPage;
|
||||||
Datum value;
|
DatumPtr value;
|
||||||
|
LWLock lock;
|
||||||
} HnswElementData;
|
} HnswElementData;
|
||||||
|
|
||||||
typedef HnswElementData * HnswElement;
|
typedef HnswElementData * HnswElement;
|
||||||
|
|
||||||
typedef struct HnswCandidate
|
typedef struct HnswCandidate
|
||||||
{
|
{
|
||||||
HnswElement element;
|
HnswElementPtr element;
|
||||||
float distance;
|
float distance;
|
||||||
bool closer;
|
bool closer;
|
||||||
} HnswCandidate;
|
} HnswCandidate;
|
||||||
@@ -125,7 +156,7 @@ typedef struct HnswNeighborArray
|
|||||||
{
|
{
|
||||||
int length;
|
int length;
|
||||||
bool closerSet;
|
bool closerSet;
|
||||||
HnswCandidate *items;
|
HnswCandidate items[FLEXIBLE_ARRAY_MEMBER];
|
||||||
} HnswNeighborArray;
|
} HnswNeighborArray;
|
||||||
|
|
||||||
typedef struct HnswPairingHeapNode
|
typedef struct HnswPairingHeapNode
|
||||||
@@ -142,11 +173,26 @@ typedef struct HnswOptions
|
|||||||
int efConstruction; /* size of dynamic candidate list */
|
int efConstruction; /* size of dynamic candidate list */
|
||||||
} HnswOptions;
|
} HnswOptions;
|
||||||
|
|
||||||
typedef struct HnswSpool
|
typedef struct HnswGraph
|
||||||
{
|
{
|
||||||
Relation heap;
|
/* Graph state */
|
||||||
Relation index;
|
slock_t lock;
|
||||||
} HnswSpool;
|
HnswElementPtr head;
|
||||||
|
double indtuples;
|
||||||
|
|
||||||
|
/* Entry state */
|
||||||
|
LWLock entryLock;
|
||||||
|
HnswElementPtr entryPoint;
|
||||||
|
|
||||||
|
/* Allocations state */
|
||||||
|
LWLock allocatorLock;
|
||||||
|
long memoryUsed;
|
||||||
|
long memoryTotal;
|
||||||
|
|
||||||
|
/* Flushed state */
|
||||||
|
LWLock flushLock;
|
||||||
|
bool flushed;
|
||||||
|
} HnswGraph;
|
||||||
|
|
||||||
typedef struct HnswShared
|
typedef struct HnswShared
|
||||||
{
|
{
|
||||||
@@ -154,7 +200,6 @@ typedef struct HnswShared
|
|||||||
Oid heaprelid;
|
Oid heaprelid;
|
||||||
Oid indexrelid;
|
Oid indexrelid;
|
||||||
bool isconcurrent;
|
bool isconcurrent;
|
||||||
int scantuplesortstates;
|
|
||||||
|
|
||||||
/* Worker progress */
|
/* Worker progress */
|
||||||
ConditionVariable workersdonecv;
|
ConditionVariable workersdonecv;
|
||||||
@@ -165,17 +210,11 @@ typedef struct HnswShared
|
|||||||
/* Mutable state */
|
/* Mutable state */
|
||||||
int nparticipantsdone;
|
int nparticipantsdone;
|
||||||
double reltuples;
|
double reltuples;
|
||||||
double indtuples;
|
HnswGraph graphData;
|
||||||
|
|
||||||
#if PG_VERSION_NUM < 120000
|
|
||||||
ParallelHeapScanDescData heapdesc; /* must come last */
|
|
||||||
#endif
|
|
||||||
} HnswShared;
|
} HnswShared;
|
||||||
|
|
||||||
#if PG_VERSION_NUM >= 120000
|
|
||||||
#define ParallelTableScanFromHnswShared(shared) \
|
#define ParallelTableScanFromHnswShared(shared) \
|
||||||
(ParallelTableScanDesc) ((char *) (shared) + BUFFERALIGN(sizeof(HnswShared)))
|
(ParallelTableScanDesc) ((char *) (shared) + BUFFERALIGN(sizeof(HnswShared)))
|
||||||
#endif
|
|
||||||
|
|
||||||
typedef struct HnswLeader
|
typedef struct HnswLeader
|
||||||
{
|
{
|
||||||
@@ -183,8 +222,15 @@ typedef struct HnswLeader
|
|||||||
int nparticipanttuplesorts;
|
int nparticipanttuplesorts;
|
||||||
HnswShared *hnswshared;
|
HnswShared *hnswshared;
|
||||||
Snapshot snapshot;
|
Snapshot snapshot;
|
||||||
|
char *hnswarea;
|
||||||
} HnswLeader;
|
} HnswLeader;
|
||||||
|
|
||||||
|
typedef struct HnswAllocator
|
||||||
|
{
|
||||||
|
void *(*alloc) (Size size, void *state);
|
||||||
|
void *state;
|
||||||
|
} HnswAllocator;
|
||||||
|
|
||||||
typedef struct HnswBuildState
|
typedef struct HnswBuildState
|
||||||
{
|
{
|
||||||
/* Info */
|
/* Info */
|
||||||
@@ -208,20 +254,21 @@ typedef struct HnswBuildState
|
|||||||
Oid collation;
|
Oid collation;
|
||||||
|
|
||||||
/* Variables */
|
/* Variables */
|
||||||
List *elements;
|
HnswGraph graphData;
|
||||||
HnswElement entryPoint;
|
HnswGraph *graph;
|
||||||
double ml;
|
double ml;
|
||||||
int maxLevel;
|
int maxLevel;
|
||||||
long memoryLeft;
|
|
||||||
bool flushed;
|
|
||||||
Vector *normvec;
|
Vector *normvec;
|
||||||
|
|
||||||
/* Memory */
|
/* Memory */
|
||||||
|
MemoryContext graphCtx;
|
||||||
MemoryContext tmpCtx;
|
MemoryContext tmpCtx;
|
||||||
|
HnswAllocator allocator;
|
||||||
|
|
||||||
/* Parallel builds */
|
/* Parallel builds */
|
||||||
HnswLeader *hnswleader;
|
HnswLeader *hnswleader;
|
||||||
HnswShared *hnswshared;
|
HnswShared *hnswshared;
|
||||||
|
char *hnswarea;
|
||||||
} HnswBuildState;
|
} HnswBuildState;
|
||||||
|
|
||||||
typedef struct HnswMetaPageData
|
typedef struct HnswMetaPageData
|
||||||
@@ -303,7 +350,7 @@ typedef struct HnswVacuumState
|
|||||||
Oid collation;
|
Oid collation;
|
||||||
|
|
||||||
/* Variables */
|
/* Variables */
|
||||||
HTAB *deleted;
|
struct tidhash_hash *deleted;
|
||||||
BufferAccessStrategy bas;
|
BufferAccessStrategy bas;
|
||||||
HnswNeighborTuple ntup;
|
HnswNeighborTuple ntup;
|
||||||
HnswElementData highestPoint;
|
HnswElementData highestPoint;
|
||||||
@@ -317,30 +364,27 @@ int HnswGetM(Relation index);
|
|||||||
int HnswGetEfConstruction(Relation index);
|
int HnswGetEfConstruction(Relation index);
|
||||||
FmgrInfo *HnswOptionalProcInfo(Relation index, uint16 procnum);
|
FmgrInfo *HnswOptionalProcInfo(Relation index, uint16 procnum);
|
||||||
bool HnswNormValue(FmgrInfo *procinfo, Oid collation, Datum *value, Vector * result);
|
bool HnswNormValue(FmgrInfo *procinfo, Oid collation, Datum *value, Vector * result);
|
||||||
void HnswCommitBuffer(Buffer buf, GenericXLogState *state);
|
|
||||||
Buffer HnswNewBuffer(Relation index, ForkNumber forkNum);
|
Buffer HnswNewBuffer(Relation index, ForkNumber forkNum);
|
||||||
void HnswInitPage(Buffer buf, Page page);
|
void HnswInitPage(Buffer buf, Page page);
|
||||||
void HnswInitRegisterPage(Relation index, Buffer *buf, Page *page, GenericXLogState **state);
|
|
||||||
void HnswInit(void);
|
void HnswInit(void);
|
||||||
List *HnswSearchLayer(Datum q, List *ep, int ef, int lc, Relation index, FmgrInfo *procinfo, Oid collation, int m, bool inserting, HnswElement skipElement);
|
List *HnswSearchLayer(char *base, Datum q, List *ep, int ef, int lc, Relation index, FmgrInfo *procinfo, Oid collation, int m, bool inserting, HnswElement skipElement);
|
||||||
HnswElement HnswGetEntryPoint(Relation index);
|
HnswElement HnswGetEntryPoint(Relation index);
|
||||||
void HnswGetMetaPageInfo(Relation index, int *m, HnswElement * entryPoint);
|
void HnswGetMetaPageInfo(Relation index, int *m, HnswElement * entryPoint);
|
||||||
HnswElement HnswInitElement(ItemPointer tid, int m, double ml, int maxLevel);
|
void *HnswAlloc(HnswAllocator * allocator, Size size);
|
||||||
void HnswFreeElement(HnswElement element);
|
HnswElement HnswInitElement(char *base, ItemPointer tid, int m, double ml, int maxLevel, HnswAllocator * alloc);
|
||||||
HnswElement HnswInitElementFromBlock(BlockNumber blkno, OffsetNumber offno);
|
HnswElement HnswInitElementFromBlock(BlockNumber blkno, OffsetNumber offno);
|
||||||
void HnswInsertElement(HnswElement element, HnswElement entryPoint, Relation index, FmgrInfo *procinfo, Oid collation, int m, int efConstruction, bool existing);
|
void HnswFindElementNeighbors(char *base, HnswElement element, HnswElement entryPoint, Relation index, FmgrInfo *procinfo, Oid collation, int m, int efConstruction, bool existing);
|
||||||
HnswElement HnswFindDuplicate(HnswElement e);
|
HnswCandidate *HnswEntryCandidate(char *base, HnswElement em, Datum q, Relation rel, FmgrInfo *procinfo, Oid collation, bool loadVec);
|
||||||
HnswCandidate *HnswEntryCandidate(HnswElement em, Datum q, Relation rel, FmgrInfo *procinfo, Oid collation, bool loadVec);
|
void HnswUpdateMetaPage(Relation index, int updateEntry, HnswElement entryPoint, BlockNumber insertPage, ForkNumber forkNum, bool building);
|
||||||
void HnswUpdateMetaPage(Relation index, int updateEntry, HnswElement entryPoint, BlockNumber insertPage, ForkNumber forkNum);
|
void HnswSetNeighborTuple(char *base, HnswNeighborTuple ntup, HnswElement e, int m);
|
||||||
void HnswSetNeighborTuple(HnswNeighborTuple ntup, HnswElement e, int m);
|
|
||||||
void HnswAddHeapTid(HnswElement element, ItemPointer heaptid);
|
void HnswAddHeapTid(HnswElement element, ItemPointer heaptid);
|
||||||
void HnswInitNeighbors(HnswElement element, int m);
|
void HnswInitNeighbors(char *base, HnswElement element, int m, HnswAllocator * alloc);
|
||||||
bool HnswInsertTuple(Relation index, Datum *values, bool *isnull, ItemPointer heap_tid, Relation heapRel);
|
bool HnswInsertTupleOnDisk(Relation index, Datum value, Datum *values, bool *isnull, ItemPointer heap_tid, Relation heapRel, bool building);
|
||||||
void HnswUpdateNeighborPages(Relation index, FmgrInfo *procinfo, Oid collation, HnswElement e, int m, bool checkExisting);
|
void HnswUpdateNeighborsOnDisk(Relation index, FmgrInfo *procinfo, Oid collation, HnswElement e, int m, bool checkExisting, bool building);
|
||||||
void HnswLoadElementFromTuple(HnswElement element, HnswElementTuple etup, bool loadHeaptids, bool loadVec);
|
void HnswLoadElementFromTuple(HnswElement element, HnswElementTuple etup, bool loadHeaptids, bool loadVec);
|
||||||
void HnswLoadElement(HnswElement element, float *distance, Datum *q, Relation index, FmgrInfo *procinfo, Oid collation, bool loadVec);
|
void HnswLoadElement(HnswElement element, float *distance, Datum *q, Relation index, FmgrInfo *procinfo, Oid collation, bool loadVec);
|
||||||
void HnswSetElementTuple(HnswElementTuple etup, HnswElement element);
|
void HnswSetElementTuple(char *base, HnswElementTuple etup, HnswElement element);
|
||||||
void HnswUpdateConnection(HnswElement element, HnswCandidate * hc, int m, int lc, int *updateIdx, Relation index, FmgrInfo *procinfo, Oid collation);
|
void HnswUpdateConnection(char *base, HnswElement element, HnswCandidate * hc, int lm, int lc, int *updateIdx, Relation index, FmgrInfo *procinfo, Oid collation);
|
||||||
void HnswLoadNeighbors(HnswElement element, Relation index, int m);
|
void HnswLoadNeighbors(HnswElement element, Relation index, int m);
|
||||||
PGDLLEXPORT void HnswParallelBuildMain(dsm_segment *seg, shm_toc *toc);
|
PGDLLEXPORT void HnswParallelBuildMain(dsm_segment *seg, shm_toc *toc);
|
||||||
|
|
||||||
@@ -360,4 +404,54 @@ void hnswrescan(IndexScanDesc scan, ScanKey keys, int nkeys, ScanKey orderbys,
|
|||||||
bool hnswgettuple(IndexScanDesc scan, ScanDirection dir);
|
bool hnswgettuple(IndexScanDesc scan, ScanDirection dir);
|
||||||
void hnswendscan(IndexScanDesc scan);
|
void hnswendscan(IndexScanDesc scan);
|
||||||
|
|
||||||
|
static inline HnswNeighborArray *
|
||||||
|
HnswGetNeighbors(char *base, HnswElement element, int lc)
|
||||||
|
{
|
||||||
|
HnswNeighborArrayPtr *neighborList = HnswPtrAccess(base, element->neighbors);
|
||||||
|
|
||||||
|
Assert(element->level >= lc);
|
||||||
|
|
||||||
|
return HnswPtrAccess(base, neighborList[lc]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hash tables */
|
||||||
|
typedef struct TidHashEntry
|
||||||
|
{
|
||||||
|
ItemPointerData tid;
|
||||||
|
char status;
|
||||||
|
} TidHashEntry;
|
||||||
|
|
||||||
|
#define SH_PREFIX tidhash
|
||||||
|
#define SH_ELEMENT_TYPE TidHashEntry
|
||||||
|
#define SH_KEY_TYPE ItemPointerData
|
||||||
|
#define SH_SCOPE extern
|
||||||
|
#define SH_DECLARE
|
||||||
|
#include "lib/simplehash.h"
|
||||||
|
|
||||||
|
typedef struct PointerHashEntry
|
||||||
|
{
|
||||||
|
uintptr_t ptr;
|
||||||
|
char status;
|
||||||
|
} PointerHashEntry;
|
||||||
|
|
||||||
|
#define SH_PREFIX pointerhash
|
||||||
|
#define SH_ELEMENT_TYPE PointerHashEntry
|
||||||
|
#define SH_KEY_TYPE uintptr_t
|
||||||
|
#define SH_SCOPE extern
|
||||||
|
#define SH_DECLARE
|
||||||
|
#include "lib/simplehash.h"
|
||||||
|
|
||||||
|
typedef struct OffsetHashEntry
|
||||||
|
{
|
||||||
|
Size offset;
|
||||||
|
char status;
|
||||||
|
} OffsetHashEntry;
|
||||||
|
|
||||||
|
#define SH_PREFIX offsethash
|
||||||
|
#define SH_ELEMENT_TYPE OffsetHashEntry
|
||||||
|
#define SH_KEY_TYPE Size
|
||||||
|
#define SH_SCOPE extern
|
||||||
|
#define SH_DECLARE
|
||||||
|
#include "lib/simplehash.h"
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
729
src/hnswbuild.c
729
src/hnswbuild.c
File diff suppressed because it is too large
Load Diff
241
src/hnswinsert.c
241
src/hnswinsert.c
@@ -2,9 +2,11 @@
|
|||||||
|
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
|
|
||||||
|
#include "access/generic_xlog.h"
|
||||||
#include "hnsw.h"
|
#include "hnsw.h"
|
||||||
#include "storage/bufmgr.h"
|
#include "storage/bufmgr.h"
|
||||||
#include "storage/lmgr.h"
|
#include "storage/lmgr.h"
|
||||||
|
#include "utils/datum.h"
|
||||||
#include "utils/memutils.h"
|
#include "utils/memutils.h"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -92,7 +94,7 @@ HnswFreeOffset(Relation index, Buffer buf, Page page, HnswElement element, Size
|
|||||||
* Add a new page
|
* Add a new page
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
HnswInsertAppendPage(Relation index, Buffer *nbuf, Page *npage, GenericXLogState *state, Page page)
|
HnswInsertAppendPage(Relation index, Buffer *nbuf, Page *npage, GenericXLogState *state, Page page, bool building)
|
||||||
{
|
{
|
||||||
/* Add a new page */
|
/* Add a new page */
|
||||||
LockRelationForExtension(index, ExclusiveLock);
|
LockRelationForExtension(index, ExclusiveLock);
|
||||||
@@ -100,7 +102,11 @@ HnswInsertAppendPage(Relation index, Buffer *nbuf, Page *npage, GenericXLogState
|
|||||||
UnlockRelationForExtension(index, ExclusiveLock);
|
UnlockRelationForExtension(index, ExclusiveLock);
|
||||||
|
|
||||||
/* Init new page */
|
/* Init new page */
|
||||||
*npage = GenericXLogRegisterBuffer(state, *nbuf, GENERIC_XLOG_FULL_IMAGE);
|
if (building)
|
||||||
|
*npage = BufferGetPage(*nbuf);
|
||||||
|
else
|
||||||
|
*npage = GenericXLogRegisterBuffer(state, *nbuf, GENERIC_XLOG_FULL_IMAGE);
|
||||||
|
|
||||||
HnswInitPage(*nbuf, *npage);
|
HnswInitPage(*nbuf, *npage);
|
||||||
|
|
||||||
/* Update previous buffer */
|
/* Update previous buffer */
|
||||||
@@ -111,7 +117,7 @@ HnswInsertAppendPage(Relation index, Buffer *nbuf, Page *npage, GenericXLogState
|
|||||||
* Add to element and neighbor pages
|
* Add to element and neighbor pages
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
WriteNewElementPages(Relation index, HnswElement e, int m, BlockNumber insertPage, BlockNumber *updatedInsertPage)
|
AddElementOnDisk(Relation index, HnswElement e, int m, BlockNumber insertPage, BlockNumber *updatedInsertPage, bool building)
|
||||||
{
|
{
|
||||||
Buffer buf;
|
Buffer buf;
|
||||||
Page page;
|
Page page;
|
||||||
@@ -129,9 +135,10 @@ WriteNewElementPages(Relation index, HnswElement e, int m, BlockNumber insertPag
|
|||||||
OffsetNumber freeOffno = InvalidOffsetNumber;
|
OffsetNumber freeOffno = InvalidOffsetNumber;
|
||||||
OffsetNumber freeNeighborOffno = InvalidOffsetNumber;
|
OffsetNumber freeNeighborOffno = InvalidOffsetNumber;
|
||||||
BlockNumber newInsertPage = InvalidBlockNumber;
|
BlockNumber newInsertPage = InvalidBlockNumber;
|
||||||
|
char *base = NULL;
|
||||||
|
|
||||||
/* Calculate sizes */
|
/* Calculate sizes */
|
||||||
etupSize = HNSW_ELEMENT_TUPLE_SIZE(VARSIZE_ANY(DatumGetPointer(e->value)));
|
etupSize = HNSW_ELEMENT_TUPLE_SIZE(VARSIZE_ANY(HnswPtrAccess(base, e->value)));
|
||||||
ntupSize = HNSW_NEIGHBOR_TUPLE_SIZE(e->level, m);
|
ntupSize = HNSW_NEIGHBOR_TUPLE_SIZE(e->level, m);
|
||||||
combinedSize = etupSize + ntupSize + sizeof(ItemIdData);
|
combinedSize = etupSize + ntupSize + sizeof(ItemIdData);
|
||||||
maxSize = HNSW_MAX_SIZE;
|
maxSize = HNSW_MAX_SIZE;
|
||||||
@@ -139,11 +146,11 @@ WriteNewElementPages(Relation index, HnswElement e, int m, BlockNumber insertPag
|
|||||||
|
|
||||||
/* Prepare element tuple */
|
/* Prepare element tuple */
|
||||||
etup = palloc0(etupSize);
|
etup = palloc0(etupSize);
|
||||||
HnswSetElementTuple(etup, e);
|
HnswSetElementTuple(base, etup, e);
|
||||||
|
|
||||||
/* Prepare neighbor tuple */
|
/* Prepare neighbor tuple */
|
||||||
ntup = palloc0(ntupSize);
|
ntup = palloc0(ntupSize);
|
||||||
HnswSetNeighborTuple(ntup, e, m);
|
HnswSetNeighborTuple(base, ntup, e, m);
|
||||||
|
|
||||||
/* Find a page (or two if needed) to insert the tuples */
|
/* Find a page (or two if needed) to insert the tuples */
|
||||||
for (;;)
|
for (;;)
|
||||||
@@ -151,8 +158,16 @@ WriteNewElementPages(Relation index, HnswElement e, int m, BlockNumber insertPag
|
|||||||
buf = ReadBuffer(index, currentPage);
|
buf = ReadBuffer(index, currentPage);
|
||||||
LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
|
LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
|
||||||
|
|
||||||
state = GenericXLogStart(index);
|
if (building)
|
||||||
page = GenericXLogRegisterBuffer(state, buf, 0);
|
{
|
||||||
|
state = NULL;
|
||||||
|
page = BufferGetPage(buf);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
state = GenericXLogStart(index);
|
||||||
|
page = GenericXLogRegisterBuffer(state, buf, 0);
|
||||||
|
}
|
||||||
|
|
||||||
/* Keep track of first page where element at level 0 can fit */
|
/* Keep track of first page where element at level 0 can fit */
|
||||||
if (!BlockNumberIsValid(newInsertPage) && PageGetFreeSpace(page) >= minCombinedSize)
|
if (!BlockNumberIsValid(newInsertPage) && PageGetFreeSpace(page) >= minCombinedSize)
|
||||||
@@ -172,7 +187,12 @@ WriteNewElementPages(Relation index, HnswElement e, int m, BlockNumber insertPag
|
|||||||
if (HnswFreeOffset(index, buf, page, e, ntupSize, &nbuf, &npage, &freeOffno, &freeNeighborOffno, &newInsertPage))
|
if (HnswFreeOffset(index, buf, page, e, ntupSize, &nbuf, &npage, &freeOffno, &freeNeighborOffno, &newInsertPage))
|
||||||
{
|
{
|
||||||
if (nbuf != buf)
|
if (nbuf != buf)
|
||||||
npage = GenericXLogRegisterBuffer(state, nbuf, 0);
|
{
|
||||||
|
if (building)
|
||||||
|
npage = BufferGetPage(nbuf);
|
||||||
|
else
|
||||||
|
npage = GenericXLogRegisterBuffer(state, nbuf, 0);
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -181,7 +201,7 @@ WriteNewElementPages(Relation index, HnswElement e, int m, BlockNumber insertPag
|
|||||||
/* Skip if both tuples can fit on the same page */
|
/* Skip if both tuples can fit on the same page */
|
||||||
if (combinedSize > maxSize && PageGetFreeSpace(page) >= etupSize && !BlockNumberIsValid(HnswPageGetOpaque(page)->nextblkno))
|
if (combinedSize > maxSize && PageGetFreeSpace(page) >= etupSize && !BlockNumberIsValid(HnswPageGetOpaque(page)->nextblkno))
|
||||||
{
|
{
|
||||||
HnswInsertAppendPage(index, &nbuf, &npage, state, page);
|
HnswInsertAppendPage(index, &nbuf, &npage, state, page, building);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -190,7 +210,8 @@ WriteNewElementPages(Relation index, HnswElement e, int m, BlockNumber insertPag
|
|||||||
if (BlockNumberIsValid(currentPage))
|
if (BlockNumberIsValid(currentPage))
|
||||||
{
|
{
|
||||||
/* Move to next page */
|
/* Move to next page */
|
||||||
GenericXLogAbort(state);
|
if (!building)
|
||||||
|
GenericXLogAbort(state);
|
||||||
UnlockReleaseBuffer(buf);
|
UnlockReleaseBuffer(buf);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -198,22 +219,33 @@ WriteNewElementPages(Relation index, HnswElement e, int m, BlockNumber insertPag
|
|||||||
Buffer newbuf;
|
Buffer newbuf;
|
||||||
Page newpage;
|
Page newpage;
|
||||||
|
|
||||||
HnswInsertAppendPage(index, &newbuf, &newpage, state, page);
|
HnswInsertAppendPage(index, &newbuf, &newpage, state, page, building);
|
||||||
|
|
||||||
/* Commit */
|
/* Commit */
|
||||||
GenericXLogFinish(state);
|
if (building)
|
||||||
|
MarkBufferDirty(buf);
|
||||||
|
else
|
||||||
|
GenericXLogFinish(state);
|
||||||
|
|
||||||
/* Unlock previous buffer */
|
/* Unlock previous buffer */
|
||||||
UnlockReleaseBuffer(buf);
|
UnlockReleaseBuffer(buf);
|
||||||
|
|
||||||
/* Prepare new buffer */
|
/* Prepare new buffer */
|
||||||
state = GenericXLogStart(index);
|
|
||||||
buf = newbuf;
|
buf = newbuf;
|
||||||
page = GenericXLogRegisterBuffer(state, buf, 0);
|
if (building)
|
||||||
|
{
|
||||||
|
state = NULL;
|
||||||
|
page = BufferGetPage(buf);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
state = GenericXLogStart(index);
|
||||||
|
page = GenericXLogRegisterBuffer(state, buf, 0);
|
||||||
|
}
|
||||||
|
|
||||||
/* Create new page for neighbors if needed */
|
/* Create new page for neighbors if needed */
|
||||||
if (PageGetFreeSpace(page) < combinedSize)
|
if (PageGetFreeSpace(page) < combinedSize)
|
||||||
HnswInsertAppendPage(index, &nbuf, &npage, state, page);
|
HnswInsertAppendPage(index, &nbuf, &npage, state, page, building);
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
nbuf = buf;
|
nbuf = buf;
|
||||||
@@ -267,7 +299,14 @@ WriteNewElementPages(Relation index, HnswElement e, int m, BlockNumber insertPag
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Commit */
|
/* Commit */
|
||||||
GenericXLogFinish(state);
|
if (building)
|
||||||
|
{
|
||||||
|
MarkBufferDirty(buf);
|
||||||
|
if (nbuf != buf)
|
||||||
|
MarkBufferDirty(nbuf);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
GenericXLogFinish(state);
|
||||||
UnlockReleaseBuffer(buf);
|
UnlockReleaseBuffer(buf);
|
||||||
if (nbuf != buf)
|
if (nbuf != buf)
|
||||||
UnlockReleaseBuffer(nbuf);
|
UnlockReleaseBuffer(nbuf);
|
||||||
@@ -301,12 +340,14 @@ ConnectionExists(HnswElement e, HnswNeighborTuple ntup, int startIdx, int lm)
|
|||||||
* Update neighbors
|
* Update neighbors
|
||||||
*/
|
*/
|
||||||
void
|
void
|
||||||
HnswUpdateNeighborPages(Relation index, FmgrInfo *procinfo, Oid collation, HnswElement e, int m, bool checkExisting)
|
HnswUpdateNeighborsOnDisk(Relation index, FmgrInfo *procinfo, Oid collation, HnswElement e, int m, bool checkExisting, bool building)
|
||||||
{
|
{
|
||||||
|
char *base = NULL;
|
||||||
|
|
||||||
for (int lc = e->level; lc >= 0; lc--)
|
for (int lc = e->level; lc >= 0; lc--)
|
||||||
{
|
{
|
||||||
int lm = HnswGetLayerM(m, lc);
|
int lm = HnswGetLayerM(m, lc);
|
||||||
HnswNeighborArray *neighbors = &e->neighbors[lc];
|
HnswNeighborArray *neighbors = HnswGetNeighbors(base, e, lc);
|
||||||
|
|
||||||
for (int i = 0; i < neighbors->length; i++)
|
for (int i = 0; i < neighbors->length; i++)
|
||||||
{
|
{
|
||||||
@@ -319,11 +360,12 @@ HnswUpdateNeighborPages(Relation index, FmgrInfo *procinfo, Oid collation, HnswE
|
|||||||
Size ntupSize;
|
Size ntupSize;
|
||||||
int idx = -1;
|
int idx = -1;
|
||||||
int startIdx;
|
int startIdx;
|
||||||
OffsetNumber offno = hc->element->neighborOffno;
|
HnswElement neighborElement = HnswPtrAccess(base, hc->element);
|
||||||
|
OffsetNumber offno = neighborElement->neighborOffno;
|
||||||
|
|
||||||
/* Get latest neighbors since they may have changed */
|
/* Get latest neighbors since they may have changed */
|
||||||
/* Do not lock yet since selecting neighbors can take time */
|
/* Do not lock yet since selecting neighbors can take time */
|
||||||
HnswLoadNeighbors(hc->element, index, m);
|
HnswLoadNeighbors(neighborElement, index, m);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Could improve performance for vacuuming by checking neighbors
|
* Could improve performance for vacuuming by checking neighbors
|
||||||
@@ -333,17 +375,25 @@ HnswUpdateNeighborPages(Relation index, FmgrInfo *procinfo, Oid collation, HnswE
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/* Select neighbors */
|
/* Select neighbors */
|
||||||
HnswUpdateConnection(e, hc, lm, lc, &idx, index, procinfo, collation);
|
HnswUpdateConnection(NULL, e, hc, lm, lc, &idx, index, procinfo, collation);
|
||||||
|
|
||||||
/* New element was not selected as a neighbor */
|
/* New element was not selected as a neighbor */
|
||||||
if (idx == -1)
|
if (idx == -1)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
/* Register page */
|
/* Register page */
|
||||||
buf = ReadBuffer(index, hc->element->neighborPage);
|
buf = ReadBuffer(index, neighborElement->neighborPage);
|
||||||
LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
|
LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
|
||||||
state = GenericXLogStart(index);
|
if (building)
|
||||||
page = GenericXLogRegisterBuffer(state, buf, 0);
|
{
|
||||||
|
state = NULL;
|
||||||
|
page = BufferGetPage(buf);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
state = GenericXLogStart(index);
|
||||||
|
page = GenericXLogRegisterBuffer(state, buf, 0);
|
||||||
|
}
|
||||||
|
|
||||||
/* Get tuple */
|
/* Get tuple */
|
||||||
itemid = PageGetItemId(page, offno);
|
itemid = PageGetItemId(page, offno);
|
||||||
@@ -351,7 +401,7 @@ HnswUpdateNeighborPages(Relation index, FmgrInfo *procinfo, Oid collation, HnswE
|
|||||||
ntupSize = ItemIdGetLength(itemid);
|
ntupSize = ItemIdGetLength(itemid);
|
||||||
|
|
||||||
/* Calculate index for update */
|
/* Calculate index for update */
|
||||||
startIdx = (hc->element->level - lc) * m;
|
startIdx = (neighborElement->level - lc) * m;
|
||||||
|
|
||||||
/* Check for existing connection */
|
/* Check for existing connection */
|
||||||
if (checkExisting && ConnectionExists(e, ntup, startIdx, lm))
|
if (checkExisting && ConnectionExists(e, ntup, startIdx, lm))
|
||||||
@@ -385,9 +435,12 @@ HnswUpdateNeighborPages(Relation index, FmgrInfo *procinfo, Oid collation, HnswE
|
|||||||
elog(ERROR, "failed to add index item to \"%s\"", RelationGetRelationName(index));
|
elog(ERROR, "failed to add index item to \"%s\"", RelationGetRelationName(index));
|
||||||
|
|
||||||
/* Commit */
|
/* Commit */
|
||||||
GenericXLogFinish(state);
|
if (building)
|
||||||
|
MarkBufferDirty(buf);
|
||||||
|
else
|
||||||
|
GenericXLogFinish(state);
|
||||||
}
|
}
|
||||||
else
|
else if (!building)
|
||||||
GenericXLogAbort(state);
|
GenericXLogAbort(state);
|
||||||
|
|
||||||
UnlockReleaseBuffer(buf);
|
UnlockReleaseBuffer(buf);
|
||||||
@@ -399,7 +452,7 @@ HnswUpdateNeighborPages(Relation index, FmgrInfo *procinfo, Oid collation, HnswE
|
|||||||
* Add a heap TID to an existing element
|
* Add a heap TID to an existing element
|
||||||
*/
|
*/
|
||||||
static bool
|
static bool
|
||||||
HnswAddDuplicate(Relation index, HnswElement element, HnswElement dup)
|
AddDuplicateOnDisk(Relation index, HnswElement element, HnswElement dup, bool building)
|
||||||
{
|
{
|
||||||
Buffer buf;
|
Buffer buf;
|
||||||
Page page;
|
Page page;
|
||||||
@@ -412,8 +465,16 @@ HnswAddDuplicate(Relation index, HnswElement element, HnswElement dup)
|
|||||||
/* Read page */
|
/* Read page */
|
||||||
buf = ReadBuffer(index, dup->blkno);
|
buf = ReadBuffer(index, dup->blkno);
|
||||||
LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
|
LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
|
||||||
state = GenericXLogStart(index);
|
if (building)
|
||||||
page = GenericXLogRegisterBuffer(state, buf, 0);
|
{
|
||||||
|
state = NULL;
|
||||||
|
page = BufferGetPage(buf);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
state = GenericXLogStart(index);
|
||||||
|
page = GenericXLogRegisterBuffer(state, buf, 0);
|
||||||
|
}
|
||||||
|
|
||||||
/* Find space */
|
/* Find space */
|
||||||
itemid = PageGetItemId(page, dup->offno);
|
itemid = PageGetItemId(page, dup->offno);
|
||||||
@@ -428,82 +489,97 @@ HnswAddDuplicate(Relation index, HnswElement element, HnswElement dup)
|
|||||||
/* Either being deleted or we lost our chance to another backend */
|
/* Either being deleted or we lost our chance to another backend */
|
||||||
if (i == 0 || i == HNSW_HEAPTIDS)
|
if (i == 0 || i == HNSW_HEAPTIDS)
|
||||||
{
|
{
|
||||||
GenericXLogAbort(state);
|
if (!building)
|
||||||
|
GenericXLogAbort(state);
|
||||||
UnlockReleaseBuffer(buf);
|
UnlockReleaseBuffer(buf);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Add heap TID */
|
/* Add heap TID */
|
||||||
etup->heaptids[i] = *((ItemPointer) linitial(element->heaptids));
|
etup->heaptids[i] = element->heaptids[0];
|
||||||
|
|
||||||
/* Overwrite tuple */
|
/* Overwrite tuple */
|
||||||
if (!PageIndexTupleOverwrite(page, dup->offno, (Item) etup, etupSize))
|
if (!PageIndexTupleOverwrite(page, dup->offno, (Item) etup, etupSize))
|
||||||
elog(ERROR, "failed to add index item to \"%s\"", RelationGetRelationName(index));
|
elog(ERROR, "failed to add index item to \"%s\"", RelationGetRelationName(index));
|
||||||
|
|
||||||
/* Commit */
|
/* Commit */
|
||||||
GenericXLogFinish(state);
|
if (building)
|
||||||
|
MarkBufferDirty(buf);
|
||||||
|
else
|
||||||
|
GenericXLogFinish(state);
|
||||||
UnlockReleaseBuffer(buf);
|
UnlockReleaseBuffer(buf);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Write changes to disk
|
* Find duplicate element
|
||||||
|
*/
|
||||||
|
static bool
|
||||||
|
FindDuplicateOnDisk(Relation index, HnswElement element, bool building)
|
||||||
|
{
|
||||||
|
char *base = NULL;
|
||||||
|
HnswNeighborArray *neighbors = HnswGetNeighbors(base, element, 0);
|
||||||
|
Datum value = HnswGetValue(base, element);
|
||||||
|
|
||||||
|
for (int i = 0; i < neighbors->length; i++)
|
||||||
|
{
|
||||||
|
HnswCandidate *neighbor = &neighbors->items[i];
|
||||||
|
HnswElement neighborElement = HnswPtrAccess(base, neighbor->element);
|
||||||
|
Datum neighborValue = HnswGetValue(base, neighborElement);
|
||||||
|
|
||||||
|
/* Exit early since ordered by distance */
|
||||||
|
if (!datumIsEqual(value, neighborValue, false, -1))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (AddDuplicateOnDisk(index, element, neighborElement, building))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Update graph on disk
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
WriteElement(Relation index, FmgrInfo *procinfo, Oid collation, HnswElement element, int m, int efConstruction, HnswElement dup, HnswElement entryPoint)
|
UpdateGraphOnDisk(Relation index, FmgrInfo *procinfo, Oid collation, HnswElement element, int m, int efConstruction, HnswElement entryPoint, bool building)
|
||||||
{
|
{
|
||||||
BlockNumber newInsertPage = InvalidBlockNumber;
|
BlockNumber newInsertPage = InvalidBlockNumber;
|
||||||
|
|
||||||
/* Try to add to existing page */
|
/* Look for duplicate */
|
||||||
if (dup != NULL)
|
if (FindDuplicateOnDisk(index, element, building))
|
||||||
{
|
return;
|
||||||
if (HnswAddDuplicate(index, element, dup))
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Write element and neighbor tuples */
|
/* Add element */
|
||||||
WriteNewElementPages(index, element, m, GetInsertPage(index), &newInsertPage);
|
AddElementOnDisk(index, element, m, GetInsertPage(index), &newInsertPage, building);
|
||||||
|
|
||||||
/* Update insert page if needed */
|
/* Update insert page if needed */
|
||||||
if (BlockNumberIsValid(newInsertPage))
|
if (BlockNumberIsValid(newInsertPage))
|
||||||
HnswUpdateMetaPage(index, 0, NULL, newInsertPage, MAIN_FORKNUM);
|
HnswUpdateMetaPage(index, 0, NULL, newInsertPage, MAIN_FORKNUM, building);
|
||||||
|
|
||||||
/* Update neighbors */
|
/* Update neighbors */
|
||||||
HnswUpdateNeighborPages(index, procinfo, collation, element, m, false);
|
HnswUpdateNeighborsOnDisk(index, procinfo, collation, element, m, false, building);
|
||||||
|
|
||||||
/* Update metapage if needed */
|
/* Update entry point if needed */
|
||||||
if (entryPoint == NULL || element->level > entryPoint->level)
|
if (entryPoint == NULL || element->level > entryPoint->level)
|
||||||
HnswUpdateMetaPage(index, HNSW_UPDATE_ENTRY_GREATER, element, InvalidBlockNumber, MAIN_FORKNUM);
|
HnswUpdateMetaPage(index, HNSW_UPDATE_ENTRY_GREATER, element, InvalidBlockNumber, MAIN_FORKNUM, building);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Insert a tuple into the index
|
* Insert a tuple into the index
|
||||||
*/
|
*/
|
||||||
bool
|
bool
|
||||||
HnswInsertTuple(Relation index, Datum *values, bool *isnull, ItemPointer heap_tid, Relation heapRel)
|
HnswInsertTupleOnDisk(Relation index, Datum value, Datum *values, bool *isnull, ItemPointer heap_tid, Relation heapRel, bool building)
|
||||||
{
|
{
|
||||||
Datum value;
|
|
||||||
FmgrInfo *normprocinfo;
|
|
||||||
HnswElement entryPoint;
|
HnswElement entryPoint;
|
||||||
HnswElement element;
|
HnswElement element;
|
||||||
int m;
|
int m;
|
||||||
int efConstruction = HnswGetEfConstruction(index);
|
int efConstruction = HnswGetEfConstruction(index);
|
||||||
FmgrInfo *procinfo = index_getprocinfo(index, 1, HNSW_DISTANCE_PROC);
|
FmgrInfo *procinfo = index_getprocinfo(index, 1, HNSW_DISTANCE_PROC);
|
||||||
Oid collation = index->rd_indcollation[0];
|
Oid collation = index->rd_indcollation[0];
|
||||||
HnswElement dup;
|
|
||||||
LOCKMODE lockmode = ShareLock;
|
LOCKMODE lockmode = ShareLock;
|
||||||
|
char *base = NULL;
|
||||||
/* Detoast once for all calls */
|
|
||||||
value = PointerGetDatum(PG_DETOAST_DATUM(values[0]));
|
|
||||||
|
|
||||||
/* Normalize if needed */
|
|
||||||
normprocinfo = HnswOptionalProcInfo(index, HNSW_NORM_PROC);
|
|
||||||
if (normprocinfo != NULL)
|
|
||||||
{
|
|
||||||
if (!HnswNormValue(normprocinfo, collation, &value, NULL))
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Get a shared lock. This allows vacuum to ensure no in-flight inserts
|
* Get a shared lock. This allows vacuum to ensure no in-flight inserts
|
||||||
@@ -516,8 +592,8 @@ HnswInsertTuple(Relation index, Datum *values, bool *isnull, ItemPointer heap_ti
|
|||||||
HnswGetMetaPageInfo(index, &m, &entryPoint);
|
HnswGetMetaPageInfo(index, &m, &entryPoint);
|
||||||
|
|
||||||
/* Create an element */
|
/* Create an element */
|
||||||
element = HnswInitElement(heap_tid, m, HnswGetMl(m), HnswGetMaxLevel(m));
|
element = HnswInitElement(base, heap_tid, m, HnswGetMl(m), HnswGetMaxLevel(m), NULL);
|
||||||
element->value = value;
|
HnswPtrStore(base, element->value, DatumGetPointer(value));
|
||||||
|
|
||||||
/* Prevent concurrent inserts when likely updating entry point */
|
/* Prevent concurrent inserts when likely updating entry point */
|
||||||
if (entryPoint == NULL || element->level > entryPoint->level)
|
if (entryPoint == NULL || element->level > entryPoint->level)
|
||||||
@@ -533,14 +609,11 @@ HnswInsertTuple(Relation index, Datum *values, bool *isnull, ItemPointer heap_ti
|
|||||||
entryPoint = HnswGetEntryPoint(index);
|
entryPoint = HnswGetEntryPoint(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Insert element in graph */
|
/* Find neighbors for element */
|
||||||
HnswInsertElement(element, entryPoint, index, procinfo, collation, m, efConstruction, false);
|
HnswFindElementNeighbors(base, element, entryPoint, index, procinfo, collation, m, efConstruction, false);
|
||||||
|
|
||||||
/* Look for duplicate */
|
/* Update graph on disk */
|
||||||
dup = HnswFindDuplicate(element);
|
UpdateGraphOnDisk(index, procinfo, collation, element, m, efConstruction, entryPoint, building);
|
||||||
|
|
||||||
/* Write to disk */
|
|
||||||
WriteElement(index, procinfo, collation, element, m, efConstruction, dup, entryPoint);
|
|
||||||
|
|
||||||
/* Release lock */
|
/* Release lock */
|
||||||
UnlockPage(index, HNSW_UPDATE_LOCK, lockmode);
|
UnlockPage(index, HNSW_UPDATE_LOCK, lockmode);
|
||||||
@@ -548,6 +621,30 @@ HnswInsertTuple(Relation index, Datum *values, bool *isnull, ItemPointer heap_ti
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Insert a tuple into the index
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
HnswInsertTuple(Relation index, Datum *values, bool *isnull, ItemPointer heap_tid, Relation heapRel)
|
||||||
|
{
|
||||||
|
Datum value;
|
||||||
|
FmgrInfo *normprocinfo;
|
||||||
|
Oid collation = index->rd_indcollation[0];
|
||||||
|
|
||||||
|
/* Detoast once for all calls */
|
||||||
|
value = PointerGetDatum(PG_DETOAST_DATUM(values[0]));
|
||||||
|
|
||||||
|
/* Normalize if needed */
|
||||||
|
normprocinfo = HnswOptionalProcInfo(index, HNSW_NORM_PROC);
|
||||||
|
if (normprocinfo != NULL)
|
||||||
|
{
|
||||||
|
if (!HnswNormValue(normprocinfo, collation, &value, NULL))
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
HnswInsertTupleOnDisk(index, value, values, isnull, heap_tid, heapRel, false);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Insert a tuple into the index
|
* Insert a tuple into the index
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ GetScanItems(IndexScanDesc scan, Datum q)
|
|||||||
List *w;
|
List *w;
|
||||||
int m;
|
int m;
|
||||||
HnswElement entryPoint;
|
HnswElement entryPoint;
|
||||||
|
char *base = NULL;
|
||||||
|
|
||||||
/* Get m and entry point */
|
/* Get m and entry point */
|
||||||
HnswGetMetaPageInfo(index, &m, &entryPoint);
|
HnswGetMetaPageInfo(index, &m, &entryPoint);
|
||||||
@@ -28,15 +29,15 @@ GetScanItems(IndexScanDesc scan, Datum q)
|
|||||||
if (entryPoint == NULL)
|
if (entryPoint == NULL)
|
||||||
return NIL;
|
return NIL;
|
||||||
|
|
||||||
ep = list_make1(HnswEntryCandidate(entryPoint, q, index, procinfo, collation, false));
|
ep = list_make1(HnswEntryCandidate(base, entryPoint, q, index, procinfo, collation, false));
|
||||||
|
|
||||||
for (int lc = entryPoint->level; lc >= 1; lc--)
|
for (int lc = entryPoint->level; lc >= 1; lc--)
|
||||||
{
|
{
|
||||||
w = HnswSearchLayer(q, ep, 1, lc, index, procinfo, collation, m, false, NULL);
|
w = HnswSearchLayer(base, q, ep, 1, lc, index, procinfo, collation, m, false, NULL);
|
||||||
ep = w;
|
ep = w;
|
||||||
}
|
}
|
||||||
|
|
||||||
return HnswSearchLayer(q, ep, hnsw_ef_search, 0, index, procinfo, collation, m, false, NULL);
|
return HnswSearchLayer(base, q, ep, hnsw_ef_search, 0, index, procinfo, collation, m, false, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -184,28 +185,23 @@ hnswgettuple(IndexScanDesc scan, ScanDirection dir)
|
|||||||
|
|
||||||
while (list_length(so->w) > 0)
|
while (list_length(so->w) > 0)
|
||||||
{
|
{
|
||||||
|
char *base = NULL;
|
||||||
HnswCandidate *hc = llast(so->w);
|
HnswCandidate *hc = llast(so->w);
|
||||||
|
HnswElement element = HnswPtrAccess(base, hc->element);
|
||||||
ItemPointer heaptid;
|
ItemPointer heaptid;
|
||||||
|
|
||||||
/* Move to next element if no valid heap TIDs */
|
/* Move to next element if no valid heap TIDs */
|
||||||
if (list_length(hc->element->heaptids) == 0)
|
if (element->heaptidsLength == 0)
|
||||||
{
|
{
|
||||||
so->w = list_delete_last(so->w);
|
so->w = list_delete_last(so->w);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
heaptid = llast(hc->element->heaptids);
|
heaptid = &element->heaptids[--element->heaptidsLength];
|
||||||
|
|
||||||
hc->element->heaptids = list_delete_last(hc->element->heaptids);
|
|
||||||
|
|
||||||
MemoryContextSwitchTo(oldCtx);
|
MemoryContextSwitchTo(oldCtx);
|
||||||
|
|
||||||
#if PG_VERSION_NUM >= 120000
|
|
||||||
scan->xs_heaptid = *heaptid;
|
scan->xs_heaptid = *heaptid;
|
||||||
#else
|
|
||||||
scan->xs_ctup.t_self = *heaptid;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
scan->xs_recheckorderby = false;
|
scan->xs_recheckorderby = false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
580
src/hnswutils.c
580
src/hnswutils.c
File diff suppressed because it is too large
Load Diff
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
|
|
||||||
|
#include "access/generic_xlog.h"
|
||||||
#include "commands/vacuum.h"
|
#include "commands/vacuum.h"
|
||||||
#include "hnsw.h"
|
#include "hnsw.h"
|
||||||
#include "storage/bufmgr.h"
|
#include "storage/bufmgr.h"
|
||||||
@@ -12,12 +13,9 @@
|
|||||||
* Check if deleted list contains an index TID
|
* Check if deleted list contains an index TID
|
||||||
*/
|
*/
|
||||||
static bool
|
static bool
|
||||||
DeletedContains(HTAB *deleted, ItemPointer indextid)
|
DeletedContains(tidhash_hash * deleted, ItemPointer indextid)
|
||||||
{
|
{
|
||||||
bool found;
|
return tidhash_lookup(deleted, *indextid) != NULL;
|
||||||
|
|
||||||
hash_search(deleted, indextid, HASH_FIND, &found);
|
|
||||||
return found;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -110,11 +108,13 @@ RemoveHeapTids(HnswVacuumState * vacuumstate)
|
|||||||
if (!ItemPointerIsValid(&etup->heaptids[0]))
|
if (!ItemPointerIsValid(&etup->heaptids[0]))
|
||||||
{
|
{
|
||||||
ItemPointerData ip;
|
ItemPointerData ip;
|
||||||
|
bool found;
|
||||||
|
|
||||||
/* Add to deleted list */
|
/* Add to deleted list */
|
||||||
ItemPointerSet(&ip, blkno, offno);
|
ItemPointerSet(&ip, blkno, offno);
|
||||||
|
|
||||||
(void) hash_search(vacuumstate->deleted, &ip, HASH_ENTER, NULL);
|
tidhash_insert(vacuumstate->deleted, ip, &found);
|
||||||
|
Assert(!found);
|
||||||
}
|
}
|
||||||
else if (etup->level > highestLevel && !(entryPoint != NULL && blkno == entryPoint->blkno && offno == entryPoint->offno))
|
else if (etup->level > highestLevel && !(entryPoint != NULL && blkno == entryPoint->blkno && offno == entryPoint->offno))
|
||||||
{
|
{
|
||||||
@@ -200,21 +200,22 @@ RepairGraphElement(HnswVacuumState * vacuumstate, HnswElement element, HnswEleme
|
|||||||
BufferAccessStrategy bas = vacuumstate->bas;
|
BufferAccessStrategy bas = vacuumstate->bas;
|
||||||
HnswNeighborTuple ntup = vacuumstate->ntup;
|
HnswNeighborTuple ntup = vacuumstate->ntup;
|
||||||
Size ntupSize = HNSW_NEIGHBOR_TUPLE_SIZE(element->level, m);
|
Size ntupSize = HNSW_NEIGHBOR_TUPLE_SIZE(element->level, m);
|
||||||
|
char *base = NULL;
|
||||||
|
|
||||||
/* Skip if element is entry point */
|
/* Skip if element is entry point */
|
||||||
if (entryPoint != NULL && element->blkno == entryPoint->blkno && element->offno == entryPoint->offno)
|
if (entryPoint != NULL && element->blkno == entryPoint->blkno && element->offno == entryPoint->offno)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
/* Init fields */
|
/* Init fields */
|
||||||
HnswInitNeighbors(element, m);
|
HnswInitNeighbors(base, element, m, NULL);
|
||||||
element->heaptids = NIL;
|
element->heaptidsLength = 0;
|
||||||
|
|
||||||
/* Add element to graph, skipping itself */
|
/* Find neighbors for element, skipping itself */
|
||||||
HnswInsertElement(element, entryPoint, index, procinfo, collation, m, efConstruction, true);
|
HnswFindElementNeighbors(base, element, entryPoint, index, procinfo, collation, m, efConstruction, true);
|
||||||
|
|
||||||
/* Update neighbor tuple */
|
/* Update neighbor tuple */
|
||||||
/* Do this before getting page to minimize locking */
|
/* Do this before getting page to minimize locking */
|
||||||
HnswSetNeighborTuple(ntup, element, m);
|
HnswSetNeighborTuple(base, ntup, element, m);
|
||||||
|
|
||||||
/* Get neighbor page */
|
/* Get neighbor page */
|
||||||
buf = ReadBufferExtended(index, MAIN_FORKNUM, element->neighborPage, RBM_NORMAL, bas);
|
buf = ReadBufferExtended(index, MAIN_FORKNUM, element->neighborPage, RBM_NORMAL, bas);
|
||||||
@@ -231,7 +232,7 @@ RepairGraphElement(HnswVacuumState * vacuumstate, HnswElement element, HnswEleme
|
|||||||
UnlockReleaseBuffer(buf);
|
UnlockReleaseBuffer(buf);
|
||||||
|
|
||||||
/* Update neighbors */
|
/* Update neighbors */
|
||||||
HnswUpdateNeighborPages(index, procinfo, collation, element, m, true);
|
HnswUpdateNeighborsOnDisk(index, procinfo, collation, element, m, true, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -287,7 +288,7 @@ RepairGraphEntryPoint(HnswVacuumState * vacuumstate)
|
|||||||
* point is outdated and empty, the entry point will be empty
|
* point is outdated and empty, the entry point will be empty
|
||||||
* until an element is repaired.
|
* until an element is repaired.
|
||||||
*/
|
*/
|
||||||
HnswUpdateMetaPage(index, HNSW_UPDATE_ENTRY_ALWAYS, highestPoint, InvalidBlockNumber, MAIN_FORKNUM);
|
HnswUpdateMetaPage(index, HNSW_UPDATE_ENTRY_ALWAYS, highestPoint, InvalidBlockNumber, MAIN_FORKNUM, false);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -302,7 +303,7 @@ RepairGraphEntryPoint(HnswVacuumState * vacuumstate)
|
|||||||
{
|
{
|
||||||
/* Reset neighbors from previous update */
|
/* Reset neighbors from previous update */
|
||||||
if (highestPoint != NULL)
|
if (highestPoint != NULL)
|
||||||
highestPoint->neighbors = NULL;
|
HnswPtrStore((char *) NULL, highestPoint->neighbors, (HnswNeighborArrayPtr *) NULL);
|
||||||
|
|
||||||
RepairGraphElement(vacuumstate, entryPoint, highestPoint);
|
RepairGraphElement(vacuumstate, entryPoint, highestPoint);
|
||||||
}
|
}
|
||||||
@@ -420,7 +421,7 @@ RepairGraph(HnswVacuumState * vacuumstate)
|
|||||||
* was replaced and highest point was outdated.
|
* was replaced and highest point was outdated.
|
||||||
*/
|
*/
|
||||||
if (entryPoint == NULL || element->level > entryPoint->level)
|
if (entryPoint == NULL || element->level > entryPoint->level)
|
||||||
HnswUpdateMetaPage(index, HNSW_UPDATE_ENTRY_GREATER, element, InvalidBlockNumber, MAIN_FORKNUM);
|
HnswUpdateMetaPage(index, HNSW_UPDATE_ENTRY_GREATER, element, InvalidBlockNumber, MAIN_FORKNUM, false);
|
||||||
|
|
||||||
/* Release lock */
|
/* Release lock */
|
||||||
UnlockPage(index, HNSW_UPDATE_LOCK, lockmode);
|
UnlockPage(index, HNSW_UPDATE_LOCK, lockmode);
|
||||||
@@ -565,7 +566,7 @@ MarkDeleted(HnswVacuumState * vacuumstate)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Update insert page last, after everything has been marked as deleted */
|
/* Update insert page last, after everything has been marked as deleted */
|
||||||
HnswUpdateMetaPage(index, 0, NULL, insertPage, MAIN_FORKNUM);
|
HnswUpdateMetaPage(index, 0, NULL, insertPage, MAIN_FORKNUM, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -575,7 +576,6 @@ static void
|
|||||||
InitVacuumState(HnswVacuumState * vacuumstate, IndexVacuumInfo *info, IndexBulkDeleteResult *stats, IndexBulkDeleteCallback callback, void *callback_state)
|
InitVacuumState(HnswVacuumState * vacuumstate, IndexVacuumInfo *info, IndexBulkDeleteResult *stats, IndexBulkDeleteCallback callback, void *callback_state)
|
||||||
{
|
{
|
||||||
Relation index = info->index;
|
Relation index = info->index;
|
||||||
HASHCTL hash_ctl;
|
|
||||||
|
|
||||||
if (stats == NULL)
|
if (stats == NULL)
|
||||||
stats = (IndexBulkDeleteResult *) palloc0(sizeof(IndexBulkDeleteResult));
|
stats = (IndexBulkDeleteResult *) palloc0(sizeof(IndexBulkDeleteResult));
|
||||||
@@ -597,10 +597,7 @@ InitVacuumState(HnswVacuumState * vacuumstate, IndexVacuumInfo *info, IndexBulkD
|
|||||||
HnswGetMetaPageInfo(index, &vacuumstate->m, NULL);
|
HnswGetMetaPageInfo(index, &vacuumstate->m, NULL);
|
||||||
|
|
||||||
/* Create hash table */
|
/* Create hash table */
|
||||||
hash_ctl.keysize = sizeof(ItemPointerData);
|
vacuumstate->deleted = tidhash_create(CurrentMemoryContext, 256, NULL);
|
||||||
hash_ctl.entrysize = sizeof(ItemPointerData);
|
|
||||||
hash_ctl.hcxt = CurrentMemoryContext;
|
|
||||||
vacuumstate->deleted = hash_create("hnswbulkdelete indextids", 256, &hash_ctl, HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -609,7 +606,7 @@ InitVacuumState(HnswVacuumState * vacuumstate, IndexVacuumInfo *info, IndexBulkD
|
|||||||
static void
|
static void
|
||||||
FreeVacuumState(HnswVacuumState * vacuumstate)
|
FreeVacuumState(HnswVacuumState * vacuumstate)
|
||||||
{
|
{
|
||||||
hash_destroy(vacuumstate->deleted);
|
tidhash_destroy(vacuumstate->deleted);
|
||||||
FreeAccessStrategy(vacuumstate->bas);
|
FreeAccessStrategy(vacuumstate->bas);
|
||||||
pfree(vacuumstate->ntup);
|
pfree(vacuumstate->ntup);
|
||||||
MemoryContextDelete(vacuumstate->tmpCtx);
|
MemoryContextDelete(vacuumstate->tmpCtx);
|
||||||
|
|||||||
@@ -2,30 +2,25 @@
|
|||||||
|
|
||||||
#include <float.h>
|
#include <float.h>
|
||||||
|
|
||||||
|
#include "access/table.h"
|
||||||
|
#include "access/tableam.h"
|
||||||
#include "access/parallel.h"
|
#include "access/parallel.h"
|
||||||
#include "access/xact.h"
|
#include "access/xact.h"
|
||||||
#include "catalog/index.h"
|
#include "catalog/index.h"
|
||||||
#include "catalog/pg_operator_d.h"
|
#include "catalog/pg_operator_d.h"
|
||||||
#include "catalog/pg_type_d.h"
|
#include "catalog/pg_type_d.h"
|
||||||
|
#include "commands/progress.h"
|
||||||
#include "ivfflat.h"
|
#include "ivfflat.h"
|
||||||
#include "miscadmin.h"
|
#include "miscadmin.h"
|
||||||
|
#include "optimizer/optimizer.h"
|
||||||
#include "storage/bufmgr.h"
|
#include "storage/bufmgr.h"
|
||||||
#include "tcop/tcopprot.h"
|
#include "tcop/tcopprot.h"
|
||||||
#include "utils/memutils.h"
|
#include "utils/memutils.h"
|
||||||
|
|
||||||
#if PG_VERSION_NUM >= 140000
|
#if PG_VERSION_NUM >= 140000
|
||||||
#include "utils/backend_progress.h"
|
#include "utils/backend_progress.h"
|
||||||
#elif PG_VERSION_NUM >= 120000
|
|
||||||
#include "pgstat.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if PG_VERSION_NUM >= 120000
|
|
||||||
#include "access/tableam.h"
|
|
||||||
#include "commands/progress.h"
|
|
||||||
#else
|
#else
|
||||||
#define PROGRESS_CREATEIDX_SUBPHASE 0
|
#include "pgstat.h"
|
||||||
#define PROGRESS_CREATEIDX_TUPLES_TOTAL 0
|
|
||||||
#define PROGRESS_CREATEIDX_TUPLES_DONE 0
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if PG_VERSION_NUM >= 130000
|
#if PG_VERSION_NUM >= 130000
|
||||||
@@ -34,26 +29,13 @@
|
|||||||
#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)
|
#define UpdateProgress(index, val) pgstat_progress_update_param(index, val)
|
||||||
#else
|
|
||||||
#define UpdateProgress(index, val) ((void)val)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if PG_VERSION_NUM >= 140000
|
#if PG_VERSION_NUM >= 140000
|
||||||
#include "utils/backend_status.h"
|
#include "utils/backend_status.h"
|
||||||
#include "utils/wait_event.h"
|
#include "utils/wait_event.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if PG_VERSION_NUM >= 120000
|
|
||||||
#include "access/table.h"
|
|
||||||
#include "optimizer/optimizer.h"
|
|
||||||
#else
|
|
||||||
#include "access/heapam.h"
|
|
||||||
#include "optimizer/planner.h"
|
|
||||||
#include "pgstat.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define PARALLEL_KEY_IVFFLAT_SHARED UINT64CONST(0xA000000000000001)
|
#define PARALLEL_KEY_IVFFLAT_SHARED UINT64CONST(0xA000000000000001)
|
||||||
#define PARALLEL_KEY_TUPLESORT UINT64CONST(0xA000000000000002)
|
#define PARALLEL_KEY_TUPLESORT UINT64CONST(0xA000000000000002)
|
||||||
#define PARALLEL_KEY_IVFFLAT_CENTERS UINT64CONST(0xA000000000000003)
|
#define PARALLEL_KEY_IVFFLAT_CENTERS UINT64CONST(0xA000000000000003)
|
||||||
@@ -150,13 +132,8 @@ SampleRows(IvfflatBuildState * buildstate)
|
|||||||
{
|
{
|
||||||
BlockNumber targblock = BlockSampler_Next(&buildstate->bs);
|
BlockNumber targblock = BlockSampler_Next(&buildstate->bs);
|
||||||
|
|
||||||
#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, false, targblock, 1, SampleCallback, (void *) buildstate, NULL);
|
false, true, false, targblock, 1, SampleCallback, (void *) buildstate, NULL);
|
||||||
#else
|
|
||||||
IndexBuildHeapRangeScan(buildstate->heap, buildstate->index, buildstate->indexInfo,
|
|
||||||
false, true, targblock, 1, SampleCallback, (void *) buildstate, NULL);
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -282,11 +259,7 @@ InsertTuples(Relation index, IvfflatBuildState * buildstate, ForkNumber forkNum)
|
|||||||
IndexTuple itup = NULL; /* silence compiler warning */
|
IndexTuple itup = NULL; /* silence compiler warning */
|
||||||
int64 inserted = 0;
|
int64 inserted = 0;
|
||||||
|
|
||||||
#if PG_VERSION_NUM >= 120000
|
|
||||||
TupleTableSlot *slot = MakeSingleTupleTableSlot(buildstate->tupdesc, &TTSOpsMinimalTuple);
|
TupleTableSlot *slot = MakeSingleTupleTableSlot(buildstate->tupdesc, &TTSOpsMinimalTuple);
|
||||||
#else
|
|
||||||
TupleTableSlot *slot = MakeSingleTupleTableSlot(buildstate->tupdesc);
|
|
||||||
#endif
|
|
||||||
TupleDesc tupdesc = RelationGetDescr(index);
|
TupleDesc tupdesc = RelationGetDescr(index);
|
||||||
|
|
||||||
UpdateProgress(PROGRESS_CREATEIDX_SUBPHASE, PROGRESS_IVFFLAT_PHASE_LOAD);
|
UpdateProgress(PROGRESS_CREATEIDX_SUBPHASE, PROGRESS_IVFFLAT_PHASE_LOAD);
|
||||||
@@ -375,20 +348,12 @@ InitBuildState(IvfflatBuildState * buildstate, Relation heap, Relation index, In
|
|||||||
elog(ERROR, "dimensions must be greater than one for this opclass");
|
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
|
|
||||||
buildstate->tupdesc = CreateTemplateTupleDesc(3);
|
buildstate->tupdesc = CreateTemplateTupleDesc(3);
|
||||||
#else
|
|
||||||
buildstate->tupdesc = CreateTemplateTupleDesc(3, false);
|
|
||||||
#endif
|
|
||||||
TupleDescInitEntry(buildstate->tupdesc, (AttrNumber) 1, "list", INT4OID, -1, 0);
|
TupleDescInitEntry(buildstate->tupdesc, (AttrNumber) 1, "list", INT4OID, -1, 0);
|
||||||
TupleDescInitEntry(buildstate->tupdesc, (AttrNumber) 2, "tid", TIDOID, -1, 0);
|
TupleDescInitEntry(buildstate->tupdesc, (AttrNumber) 2, "tid", TIDOID, -1, 0);
|
||||||
TupleDescInitEntry(buildstate->tupdesc, (AttrNumber) 3, "vector", RelationGetDescr(index)->attrs[0].atttypid, -1, 0);
|
TupleDescInitEntry(buildstate->tupdesc, (AttrNumber) 3, "vector", RelationGetDescr(index)->attrs[0].atttypid, -1, 0);
|
||||||
|
|
||||||
#if PG_VERSION_NUM >= 120000
|
|
||||||
buildstate->slot = MakeSingleTupleTableSlot(buildstate->tupdesc, &TTSOpsVirtual);
|
buildstate->slot = MakeSingleTupleTableSlot(buildstate->tupdesc, &TTSOpsVirtual);
|
||||||
#else
|
|
||||||
buildstate->slot = MakeSingleTupleTableSlot(buildstate->tupdesc);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
buildstate->centers = VectorArrayInit(buildstate->lists, buildstate->dimensions);
|
buildstate->centers = VectorArrayInit(buildstate->lists, buildstate->dimensions);
|
||||||
buildstate->listInfo = palloc(sizeof(ListInfo) * buildstate->lists);
|
buildstate->listInfo = palloc(sizeof(ListInfo) * buildstate->lists);
|
||||||
@@ -543,10 +508,10 @@ CreateListPages(Relation index, VectorArray centers, int dimensions,
|
|||||||
pfree(list);
|
pfree(list);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef IVFFLAT_KMEANS_DEBUG
|
||||||
/*
|
/*
|
||||||
* Print k-means metrics
|
* Print k-means metrics
|
||||||
*/
|
*/
|
||||||
#ifdef IVFFLAT_KMEANS_DEBUG
|
|
||||||
static void
|
static void
|
||||||
PrintKmeansMetrics(IvfflatBuildState * buildstate)
|
PrintKmeansMetrics(IvfflatBuildState * buildstate)
|
||||||
{
|
{
|
||||||
@@ -631,11 +596,7 @@ IvfflatParallelScanAndSort(IvfflatSpool * ivfspool, IvfflatShared * ivfshared, S
|
|||||||
{
|
{
|
||||||
SortCoordinate coordinate;
|
SortCoordinate coordinate;
|
||||||
IvfflatBuildState buildstate;
|
IvfflatBuildState buildstate;
|
||||||
#if PG_VERSION_NUM >= 120000
|
|
||||||
TableScanDesc scan;
|
TableScanDesc scan;
|
||||||
#else
|
|
||||||
HeapScanDesc scan;
|
|
||||||
#endif
|
|
||||||
double reltuples;
|
double reltuples;
|
||||||
IndexInfo *indexInfo;
|
IndexInfo *indexInfo;
|
||||||
|
|
||||||
@@ -659,18 +620,11 @@ IvfflatParallelScanAndSort(IvfflatSpool * ivfspool, IvfflatShared * ivfshared, S
|
|||||||
buildstate.centers->length = buildstate.centers->maxlen;
|
buildstate.centers->length = buildstate.centers->maxlen;
|
||||||
ivfspool->sortstate = tuplesort_begin_heap(buildstate.tupdesc, 1, attNums, sortOperators, sortCollations, nullsFirstFlags, sortmem, coordinate, false);
|
ivfspool->sortstate = tuplesort_begin_heap(buildstate.tupdesc, 1, attNums, sortOperators, sortCollations, nullsFirstFlags, sortmem, coordinate, false);
|
||||||
buildstate.sortstate = ivfspool->sortstate;
|
buildstate.sortstate = ivfspool->sortstate;
|
||||||
#if PG_VERSION_NUM >= 120000
|
|
||||||
scan = table_beginscan_parallel(ivfspool->heap,
|
scan = table_beginscan_parallel(ivfspool->heap,
|
||||||
ParallelTableScanFromIvfflatShared(ivfshared));
|
ParallelTableScanFromIvfflatShared(ivfshared));
|
||||||
reltuples = table_index_build_scan(ivfspool->heap, ivfspool->index, indexInfo,
|
reltuples = table_index_build_scan(ivfspool->heap, ivfspool->index, indexInfo,
|
||||||
true, progress, BuildCallback,
|
true, progress, BuildCallback,
|
||||||
(void *) &buildstate, scan);
|
(void *) &buildstate, scan);
|
||||||
#else
|
|
||||||
scan = heap_beginscan_parallel(ivfspool->heap, &ivfshared->heapdesc);
|
|
||||||
reltuples = IndexBuildHeapScan(ivfspool->heap, ivfspool->index, indexInfo,
|
|
||||||
true, BuildCallback,
|
|
||||||
(void *) &buildstate, scan);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/* Execute this worker's part of the sort */
|
/* Execute this worker's part of the sort */
|
||||||
tuplesort_performsort(ivfspool->sortstate);
|
tuplesort_performsort(ivfspool->sortstate);
|
||||||
@@ -740,11 +694,7 @@ IvfflatParallelBuildMain(dsm_segment *seg, shm_toc *toc)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Open relations within worker */
|
/* Open relations within worker */
|
||||||
#if PG_VERSION_NUM >= 120000
|
|
||||||
heapRel = table_open(ivfshared->heaprelid, heapLockmode);
|
heapRel = table_open(ivfshared->heaprelid, heapLockmode);
|
||||||
#else
|
|
||||||
heapRel = heap_open(ivfshared->heaprelid, heapLockmode);
|
|
||||||
#endif
|
|
||||||
indexRel = index_open(ivfshared->indexrelid, indexLockmode);
|
indexRel = index_open(ivfshared->indexrelid, indexLockmode);
|
||||||
|
|
||||||
/* Initialize worker's own spool */
|
/* Initialize worker's own spool */
|
||||||
@@ -764,11 +714,7 @@ IvfflatParallelBuildMain(dsm_segment *seg, shm_toc *toc)
|
|||||||
|
|
||||||
/* Close relations within worker */
|
/* Close relations within worker */
|
||||||
index_close(indexRel, indexLockmode);
|
index_close(indexRel, indexLockmode);
|
||||||
#if PG_VERSION_NUM >= 120000
|
|
||||||
table_close(heapRel, heapLockmode);
|
table_close(heapRel, heapLockmode);
|
||||||
#else
|
|
||||||
heap_close(heapRel, heapLockmode);
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -793,19 +739,7 @@ IvfflatEndParallel(IvfflatLeader * ivfleader)
|
|||||||
static Size
|
static Size
|
||||||
ParallelEstimateShared(Relation heap, Snapshot snapshot)
|
ParallelEstimateShared(Relation heap, Snapshot snapshot)
|
||||||
{
|
{
|
||||||
#if PG_VERSION_NUM >= 120000
|
|
||||||
return add_size(BUFFERALIGN(sizeof(IvfflatShared)), table_parallelscan_estimate(heap, snapshot));
|
return add_size(BUFFERALIGN(sizeof(IvfflatShared)), table_parallelscan_estimate(heap, snapshot));
|
||||||
#else
|
|
||||||
if (!IsMVCCSnapshot(snapshot))
|
|
||||||
{
|
|
||||||
Assert(snapshot == SnapshotAny);
|
|
||||||
return sizeof(IvfflatShared);
|
|
||||||
}
|
|
||||||
|
|
||||||
return add_size(offsetof(IvfflatShared, heapdesc) +
|
|
||||||
offsetof(ParallelHeapScanDescData, phs_snapshot_data),
|
|
||||||
EstimateSnapshotSpace(snapshot));
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -856,11 +790,7 @@ IvfflatBeginParallel(IvfflatBuildState * buildstate, bool isconcurrent, int requ
|
|||||||
/* Enter parallel mode and create context */
|
/* Enter parallel mode and create context */
|
||||||
EnterParallelMode();
|
EnterParallelMode();
|
||||||
Assert(request > 0);
|
Assert(request > 0);
|
||||||
#if PG_VERSION_NUM >= 120000
|
|
||||||
pcxt = CreateParallelContext("vector", "IvfflatParallelBuildMain", request);
|
pcxt = CreateParallelContext("vector", "IvfflatParallelBuildMain", request);
|
||||||
#else
|
|
||||||
pcxt = CreateParallelContext("vector", "IvfflatParallelBuildMain", request, true);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
scantuplesortstates = leaderparticipates ? request + 1 : request;
|
scantuplesortstates = leaderparticipates ? request + 1 : request;
|
||||||
|
|
||||||
@@ -918,13 +848,9 @@ IvfflatBeginParallel(IvfflatBuildState * buildstate, bool isconcurrent, int requ
|
|||||||
#ifdef IVFFLAT_KMEANS_DEBUG
|
#ifdef IVFFLAT_KMEANS_DEBUG
|
||||||
ivfshared->inertia = 0;
|
ivfshared->inertia = 0;
|
||||||
#endif
|
#endif
|
||||||
#if PG_VERSION_NUM >= 120000
|
|
||||||
table_parallelscan_initialize(buildstate->heap,
|
table_parallelscan_initialize(buildstate->heap,
|
||||||
ParallelTableScanFromIvfflatShared(ivfshared),
|
ParallelTableScanFromIvfflatShared(ivfshared),
|
||||||
snapshot);
|
snapshot);
|
||||||
#else
|
|
||||||
heap_parallelscan_initialize(&ivfshared->heapdesc, buildstate->heap, snapshot);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/* Store shared tuplesort-private state, for which we reserved space */
|
/* Store shared tuplesort-private state, for which we reserved space */
|
||||||
sharedsort = (Sharedsort *) shm_toc_allocate(pcxt->toc, estsort);
|
sharedsort = (Sharedsort *) shm_toc_allocate(pcxt->toc, estsort);
|
||||||
@@ -1023,15 +949,8 @@ AssignTuples(IvfflatBuildState * buildstate)
|
|||||||
if (buildstate->ivfleader)
|
if (buildstate->ivfleader)
|
||||||
buildstate->reltuples = ParallelHeapScan(buildstate);
|
buildstate->reltuples = ParallelHeapScan(buildstate);
|
||||||
else
|
else
|
||||||
{
|
|
||||||
#if PG_VERSION_NUM >= 120000
|
|
||||||
buildstate->reltuples = table_index_build_scan(buildstate->heap, buildstate->index, buildstate->indexInfo,
|
buildstate->reltuples = table_index_build_scan(buildstate->heap, buildstate->index, buildstate->indexInfo,
|
||||||
true, true, BuildCallback, (void *) buildstate, NULL);
|
true, true, BuildCallback, (void *) buildstate, NULL);
|
||||||
#else
|
|
||||||
buildstate->reltuples = IndexBuildHeapScan(buildstate->heap, buildstate->index, buildstate->indexInfo,
|
|
||||||
true, BuildCallback, (void *) buildstate, NULL);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef IVFFLAT_KMEANS_DEBUG
|
#ifdef IVFFLAT_KMEANS_DEBUG
|
||||||
PrintKmeansMetrics(buildstate);
|
PrintKmeansMetrics(buildstate);
|
||||||
|
|||||||
@@ -3,14 +3,16 @@
|
|||||||
#include <float.h>
|
#include <float.h>
|
||||||
|
|
||||||
#include "access/amapi.h"
|
#include "access/amapi.h"
|
||||||
|
#include "access/reloptions.h"
|
||||||
|
#include "commands/progress.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"
|
||||||
#include "utils/spccache.h"
|
#include "utils/spccache.h"
|
||||||
|
|
||||||
#if PG_VERSION_NUM >= 120000
|
#if PG_VERSION_NUM < 150000
|
||||||
#include "commands/progress.h"
|
#define MarkGUCPrefixReserved(x) EmitWarningsOnPlaceholders(x)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
int ivfflat_probes;
|
int ivfflat_probes;
|
||||||
@@ -33,12 +35,13 @@ IvfflatInit(void)
|
|||||||
DefineCustomIntVariable("ivfflat.probes", "Sets the number of probes",
|
DefineCustomIntVariable("ivfflat.probes", "Sets the number of probes",
|
||||||
"Valid range is 1..lists.", &ivfflat_probes,
|
"Valid range is 1..lists.", &ivfflat_probes,
|
||||||
IVFFLAT_DEFAULT_PROBES, IVFFLAT_MIN_LISTS, IVFFLAT_MAX_LISTS, PGC_USERSET, 0, NULL, NULL, NULL);
|
IVFFLAT_DEFAULT_PROBES, IVFFLAT_MIN_LISTS, IVFFLAT_MAX_LISTS, PGC_USERSET, 0, NULL, NULL, NULL);
|
||||||
|
|
||||||
|
MarkGUCPrefixReserved("ivfflat");
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Get the name of index build phase
|
* Get the name of index build phase
|
||||||
*/
|
*/
|
||||||
#if PG_VERSION_NUM >= 120000
|
|
||||||
static char *
|
static char *
|
||||||
ivfflatbuildphasename(int64 phasenum)
|
ivfflatbuildphasename(int64 phasenum)
|
||||||
{
|
{
|
||||||
@@ -56,7 +59,6 @@ ivfflatbuildphasename(int64 phasenum)
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Estimate the cost of an index scan
|
* Estimate the cost of an index scan
|
||||||
@@ -72,9 +74,6 @@ ivfflatcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
|
|||||||
double ratio;
|
double ratio;
|
||||||
double spc_seq_page_cost;
|
double spc_seq_page_cost;
|
||||||
Relation index;
|
Relation index;
|
||||||
#if PG_VERSION_NUM < 120000
|
|
||||||
List *qinfos;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/* Never use index without order */
|
/* Never use index without order */
|
||||||
if (path->indexorderbys == NULL)
|
if (path->indexorderbys == NULL)
|
||||||
@@ -105,12 +104,7 @@ ivfflatcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
|
|||||||
*/
|
*/
|
||||||
costs.numIndexTuples = path->indexinfo->tuples * ratio;
|
costs.numIndexTuples = path->indexinfo->tuples * ratio;
|
||||||
|
|
||||||
#if PG_VERSION_NUM >= 120000
|
|
||||||
genericcostestimate(root, path, loop_count, &costs);
|
genericcostestimate(root, path, loop_count, &costs);
|
||||||
#else
|
|
||||||
qinfos = deconstruct_indexquals(path);
|
|
||||||
genericcostestimate(root, path, loop_count, qinfos, &costs);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
get_tablespace_page_costs(path->indexinfo->reltablespace, NULL, &spc_seq_page_cost);
|
get_tablespace_page_costs(path->indexinfo->reltablespace, NULL, &spc_seq_page_cost);
|
||||||
|
|
||||||
@@ -227,9 +221,7 @@ ivfflathandler(PG_FUNCTION_ARGS)
|
|||||||
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
|
|
||||||
amroutine->ambuildphasename = ivfflatbuildphasename;
|
amroutine->ambuildphasename = ivfflatbuildphasename;
|
||||||
#endif
|
|
||||||
amroutine->amvalidate = ivfflatvalidate;
|
amroutine->amvalidate = ivfflatvalidate;
|
||||||
#if PG_VERSION_NUM >= 140000
|
#if PG_VERSION_NUM >= 140000
|
||||||
amroutine->amadjustmembers = NULL;
|
amroutine->amadjustmembers = NULL;
|
||||||
|
|||||||
@@ -3,9 +3,10 @@
|
|||||||
|
|
||||||
#include "postgres.h"
|
#include "postgres.h"
|
||||||
|
|
||||||
|
#include "access/genam.h"
|
||||||
#include "access/generic_xlog.h"
|
#include "access/generic_xlog.h"
|
||||||
#include "access/parallel.h"
|
#include "access/parallel.h"
|
||||||
#include "access/reloptions.h"
|
#include "lib/pairingheap.h"
|
||||||
#include "nodes/execnodes.h"
|
#include "nodes/execnodes.h"
|
||||||
#include "port.h" /* for random() */
|
#include "port.h" /* for random() */
|
||||||
#include "utils/sampling.h"
|
#include "utils/sampling.h"
|
||||||
@@ -16,10 +17,6 @@
|
|||||||
#include "common/pg_prng.h"
|
#include "common/pg_prng.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if PG_VERSION_NUM < 120000
|
|
||||||
#include "access/relscan.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef IVFFLAT_BENCH
|
#ifdef IVFFLAT_BENCH
|
||||||
#include "portability/instr_time.h"
|
#include "portability/instr_time.h"
|
||||||
#endif
|
#endif
|
||||||
@@ -135,16 +132,10 @@ typedef struct IvfflatShared
|
|||||||
#ifdef IVFFLAT_KMEANS_DEBUG
|
#ifdef IVFFLAT_KMEANS_DEBUG
|
||||||
double inertia;
|
double inertia;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if PG_VERSION_NUM < 120000
|
|
||||||
ParallelHeapScanDescData heapdesc; /* must come last */
|
|
||||||
#endif
|
|
||||||
} IvfflatShared;
|
} IvfflatShared;
|
||||||
|
|
||||||
#if PG_VERSION_NUM >= 120000
|
|
||||||
#define ParallelTableScanFromIvfflatShared(shared) \
|
#define ParallelTableScanFromIvfflatShared(shared) \
|
||||||
(ParallelTableScanDesc) ((char *) (shared) + BUFFERALIGN(sizeof(IvfflatShared)))
|
(ParallelTableScanDesc) ((char *) (shared) + BUFFERALIGN(sizeof(IvfflatShared)))
|
||||||
#endif
|
|
||||||
|
|
||||||
typedef struct IvfflatLeader
|
typedef struct IvfflatLeader
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include <float.h>
|
#include <float.h>
|
||||||
|
|
||||||
|
#include "access/generic_xlog.h"
|
||||||
#include "ivfflat.h"
|
#include "ivfflat.h"
|
||||||
#include "storage/bufmgr.h"
|
#include "storage/bufmgr.h"
|
||||||
#include "storage/lmgr.h"
|
#include "storage/lmgr.h"
|
||||||
|
|||||||
@@ -6,6 +6,10 @@
|
|||||||
#include "ivfflat.h"
|
#include "ivfflat.h"
|
||||||
#include "miscadmin.h"
|
#include "miscadmin.h"
|
||||||
|
|
||||||
|
#ifdef IVFFLAT_MEMORY
|
||||||
|
#include "utils/memutils.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Initialize with kmeans++
|
* Initialize with kmeans++
|
||||||
*
|
*
|
||||||
@@ -151,6 +155,23 @@ QuickCenters(Relation index, VectorArray samples, VectorArray centers)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef IVFFLAT_MEMORY
|
||||||
|
/*
|
||||||
|
* Show memory usage
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
ShowMemoryUsage(Size estimatedSize)
|
||||||
|
{
|
||||||
|
#if PG_VERSION_NUM >= 130000
|
||||||
|
elog(INFO, "total memory: %zu MB",
|
||||||
|
MemoryContextMemAllocated(CurrentMemoryContext, true) / (1024 * 1024));
|
||||||
|
#else
|
||||||
|
MemoryContextStats(CurrentMemoryContext);
|
||||||
|
#endif
|
||||||
|
elog(INFO, "estimated memory: %zu MB", estimatedSize / (1024 * 1024));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Use Elkan for performance. This requires distance function to satisfy triangle inequality.
|
* Use Elkan for performance. This requires distance function to satisfy triangle inequality.
|
||||||
*
|
*
|
||||||
@@ -231,6 +252,10 @@ ElkanKmeans(Relation index, VectorArray samples, VectorArray centers)
|
|||||||
vec->dim = dimensions;
|
vec->dim = dimensions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef IVFFLAT_MEMORY
|
||||||
|
ShowMemoryUsage(totalSize);
|
||||||
|
#endif
|
||||||
|
|
||||||
/* Pick initial centers */
|
/* Pick initial centers */
|
||||||
InitCenters(index, samples, centers, lowerBound);
|
InitCenters(index, samples, centers, lowerBound);
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
#include "access/relscan.h"
|
#include "access/relscan.h"
|
||||||
#include "catalog/pg_operator_d.h"
|
#include "catalog/pg_operator_d.h"
|
||||||
#include "catalog/pg_type_d.h"
|
#include "catalog/pg_type_d.h"
|
||||||
|
#include "lib/pairingheap.h"
|
||||||
#include "ivfflat.h"
|
#include "ivfflat.h"
|
||||||
#include "miscadmin.h"
|
#include "miscadmin.h"
|
||||||
#include "pgstat.h"
|
#include "pgstat.h"
|
||||||
@@ -105,12 +106,7 @@ GetScanItems(IndexScanDesc scan, Datum value)
|
|||||||
IvfflatScanOpaque so = (IvfflatScanOpaque) scan->opaque;
|
IvfflatScanOpaque so = (IvfflatScanOpaque) scan->opaque;
|
||||||
TupleDesc tupdesc = RelationGetDescr(scan->indexRelation);
|
TupleDesc tupdesc = RelationGetDescr(scan->indexRelation);
|
||||||
double tuples = 0;
|
double tuples = 0;
|
||||||
|
|
||||||
#if PG_VERSION_NUM >= 120000
|
|
||||||
TupleTableSlot *slot = MakeSingleTupleTableSlot(so->tupdesc, &TTSOpsVirtual);
|
TupleTableSlot *slot = MakeSingleTupleTableSlot(so->tupdesc, &TTSOpsVirtual);
|
||||||
#else
|
|
||||||
TupleTableSlot *slot = MakeSingleTupleTableSlot(so->tupdesc);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Reuse same set of shared buffers for scan
|
* Reuse same set of shared buffers for scan
|
||||||
@@ -216,22 +212,14 @@ ivfflatbeginscan(Relation index, int nkeys, int norderbys)
|
|||||||
so->collation = index->rd_indcollation[0];
|
so->collation = index->rd_indcollation[0];
|
||||||
|
|
||||||
/* Create tuple description for sorting */
|
/* Create tuple description for sorting */
|
||||||
#if PG_VERSION_NUM >= 120000
|
|
||||||
so->tupdesc = CreateTemplateTupleDesc(2);
|
so->tupdesc = CreateTemplateTupleDesc(2);
|
||||||
#else
|
|
||||||
so->tupdesc = CreateTemplateTupleDesc(2, false);
|
|
||||||
#endif
|
|
||||||
TupleDescInitEntry(so->tupdesc, (AttrNumber) 1, "distance", FLOAT8OID, -1, 0);
|
TupleDescInitEntry(so->tupdesc, (AttrNumber) 1, "distance", FLOAT8OID, -1, 0);
|
||||||
TupleDescInitEntry(so->tupdesc, (AttrNumber) 2, "heaptid", TIDOID, -1, 0);
|
TupleDescInitEntry(so->tupdesc, (AttrNumber) 2, "heaptid", TIDOID, -1, 0);
|
||||||
|
|
||||||
/* Prep sort */
|
/* Prep sort */
|
||||||
so->sortstate = tuplesort_begin_heap(so->tupdesc, 1, attNums, sortOperators, sortCollations, nullsFirstFlags, work_mem, NULL, false);
|
so->sortstate = tuplesort_begin_heap(so->tupdesc, 1, attNums, sortOperators, sortCollations, nullsFirstFlags, work_mem, NULL, false);
|
||||||
|
|
||||||
#if PG_VERSION_NUM >= 120000
|
|
||||||
so->slot = MakeSingleTupleTableSlot(so->tupdesc, &TTSOpsMinimalTuple);
|
so->slot = MakeSingleTupleTableSlot(so->tupdesc, &TTSOpsMinimalTuple);
|
||||||
#else
|
|
||||||
so->slot = MakeSingleTupleTableSlot(so->tupdesc);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
so->listQueue = pairingheap_allocate(CompareLists, scan);
|
so->listQueue = pairingheap_allocate(CompareLists, scan);
|
||||||
|
|
||||||
@@ -321,12 +309,7 @@ ivfflatgettuple(IndexScanDesc scan, ScanDirection dir)
|
|||||||
{
|
{
|
||||||
ItemPointer heaptid = (ItemPointer) DatumGetPointer(slot_getattr(so->slot, 2, &so->isnull));
|
ItemPointer heaptid = (ItemPointer) DatumGetPointer(slot_getattr(so->slot, 2, &so->isnull));
|
||||||
|
|
||||||
#if PG_VERSION_NUM >= 120000
|
|
||||||
scan->xs_heaptid = *heaptid;
|
scan->xs_heaptid = *heaptid;
|
||||||
#else
|
|
||||||
scan->xs_ctup.t_self = *heaptid;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
scan->xs_recheckorderby = false;
|
scan->xs_recheckorderby = false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#include "postgres.h"
|
#include "postgres.h"
|
||||||
|
|
||||||
|
#include "access/generic_xlog.h"
|
||||||
#include "ivfflat.h"
|
#include "ivfflat.h"
|
||||||
#include "storage/bufmgr.h"
|
#include "storage/bufmgr.h"
|
||||||
#include "vector.h"
|
#include "vector.h"
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#include "postgres.h"
|
#include "postgres.h"
|
||||||
|
|
||||||
|
#include "access/generic_xlog.h"
|
||||||
#include "commands/vacuum.h"
|
#include "commands/vacuum.h"
|
||||||
#include "ivfflat.h"
|
#include "ivfflat.h"
|
||||||
#include "storage/bufmgr.h"
|
#include "storage/bufmgr.h"
|
||||||
|
|||||||
100
src/vector.c
100
src/vector.c
@@ -3,14 +3,17 @@
|
|||||||
#include <math.h>
|
#include <math.h>
|
||||||
|
|
||||||
#include "catalog/pg_type.h"
|
#include "catalog/pg_type.h"
|
||||||
|
#include "common/shortest_dec.h"
|
||||||
#include "fmgr.h"
|
#include "fmgr.h"
|
||||||
#include "hnsw.h"
|
#include "hnsw.h"
|
||||||
#include "ivfflat.h"
|
#include "ivfflat.h"
|
||||||
#include "lib/stringinfo.h"
|
#include "lib/stringinfo.h"
|
||||||
#include "libpq/pqformat.h"
|
#include "libpq/pqformat.h"
|
||||||
#include "port.h" /* for strtof() */
|
#include "port.h" /* for strtof() */
|
||||||
|
#include "port/pg_bitutils.h"
|
||||||
#include "utils/array.h"
|
#include "utils/array.h"
|
||||||
#include "utils/builtins.h"
|
#include "utils/builtins.h"
|
||||||
|
#include "utils/float.h"
|
||||||
#include "utils/lsyscache.h"
|
#include "utils/lsyscache.h"
|
||||||
#include "utils/numeric.h"
|
#include "utils/numeric.h"
|
||||||
#include "vector.h"
|
#include "vector.h"
|
||||||
@@ -19,13 +22,6 @@
|
|||||||
#include "varatt.h"
|
#include "varatt.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if PG_VERSION_NUM >= 120000
|
|
||||||
#include "common/shortest_dec.h"
|
|
||||||
#include "utils/float.h"
|
|
||||||
#else
|
|
||||||
#include <float.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if PG_VERSION_NUM < 130000
|
#if PG_VERSION_NUM < 130000
|
||||||
#define TYPALIGN_DOUBLE 'd'
|
#define TYPALIGN_DOUBLE 'd'
|
||||||
#define TYPALIGN_INT 'i'
|
#define TYPALIGN_INT 'i'
|
||||||
@@ -177,14 +173,15 @@ 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 *lit = PG_GETARG_CSTRING(0);
|
||||||
int32 typmod = PG_GETARG_INT32(2);
|
int32 typmod = PG_GETARG_INT32(2);
|
||||||
float x[VECTOR_MAX_DIM];
|
float x[VECTOR_MAX_DIM];
|
||||||
int dim = 0;
|
int dim = 0;
|
||||||
char *pt;
|
char *pt;
|
||||||
char *stringEnd;
|
char *stringEnd;
|
||||||
Vector *result;
|
Vector *result;
|
||||||
char *lit = pstrdup(str);
|
char *litcopy = pstrdup(lit);
|
||||||
|
char *str = litcopy;
|
||||||
|
|
||||||
while (vector_isspace(*str))
|
while (vector_isspace(*str))
|
||||||
str++;
|
str++;
|
||||||
@@ -268,7 +265,7 @@ vector_in(PG_FUNCTION_ARGS)
|
|||||||
(errcode(ERRCODE_DATA_EXCEPTION),
|
(errcode(ERRCODE_DATA_EXCEPTION),
|
||||||
errmsg("vector must have at least 1 dimension")));
|
errmsg("vector must have at least 1 dimension")));
|
||||||
|
|
||||||
pfree(lit);
|
pfree(litcopy);
|
||||||
|
|
||||||
CheckExpectedDim(typmod, dim);
|
CheckExpectedDim(typmod, dim);
|
||||||
|
|
||||||
@@ -292,15 +289,6 @@ vector_out(PG_FUNCTION_ARGS)
|
|||||||
char *ptr;
|
char *ptr;
|
||||||
int n;
|
int n;
|
||||||
|
|
||||||
#if PG_VERSION_NUM < 120000
|
|
||||||
int ndig = FLT_DIG + extra_float_digits;
|
|
||||||
|
|
||||||
if (ndig < 1)
|
|
||||||
ndig = 1;
|
|
||||||
|
|
||||||
#define FLOAT_SHORTEST_DECIMAL_LEN (ndig + 10)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Need:
|
* Need:
|
||||||
*
|
*
|
||||||
@@ -324,11 +312,7 @@ vector_out(PG_FUNCTION_ARGS)
|
|||||||
ptr++;
|
ptr++;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if PG_VERSION_NUM >= 120000
|
|
||||||
n = float_to_shortest_decimal_bufn(vector->x[i], ptr);
|
n = float_to_shortest_decimal_bufn(vector->x[i], ptr);
|
||||||
#else
|
|
||||||
n = sprintf(ptr, "%.*g", ndig, vector->x[i]);
|
|
||||||
#endif
|
|
||||||
ptr += n;
|
ptr += n;
|
||||||
}
|
}
|
||||||
*ptr = ']';
|
*ptr = ']';
|
||||||
@@ -724,7 +708,7 @@ vector_spherical_distance(PG_FUNCTION_ARGS)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Get the L1 distance between vectors
|
* Get the L1 distance between two vectors
|
||||||
*/
|
*/
|
||||||
PGDLLEXPORT PG_FUNCTION_INFO_V1(l1_distance);
|
PGDLLEXPORT PG_FUNCTION_INFO_V1(l1_distance);
|
||||||
Datum
|
Datum
|
||||||
@@ -903,8 +887,8 @@ PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_lt);
|
|||||||
Datum
|
Datum
|
||||||
vector_lt(PG_FUNCTION_ARGS)
|
vector_lt(PG_FUNCTION_ARGS)
|
||||||
{
|
{
|
||||||
Vector *a = (Vector *) PG_GETARG_VECTOR_P(0);
|
Vector *a = PG_GETARG_VECTOR_P(0);
|
||||||
Vector *b = (Vector *) PG_GETARG_VECTOR_P(1);
|
Vector *b = PG_GETARG_VECTOR_P(1);
|
||||||
|
|
||||||
PG_RETURN_BOOL(vector_cmp_internal(a, b) < 0);
|
PG_RETURN_BOOL(vector_cmp_internal(a, b) < 0);
|
||||||
}
|
}
|
||||||
@@ -916,8 +900,8 @@ PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_le);
|
|||||||
Datum
|
Datum
|
||||||
vector_le(PG_FUNCTION_ARGS)
|
vector_le(PG_FUNCTION_ARGS)
|
||||||
{
|
{
|
||||||
Vector *a = (Vector *) PG_GETARG_VECTOR_P(0);
|
Vector *a = PG_GETARG_VECTOR_P(0);
|
||||||
Vector *b = (Vector *) PG_GETARG_VECTOR_P(1);
|
Vector *b = PG_GETARG_VECTOR_P(1);
|
||||||
|
|
||||||
PG_RETURN_BOOL(vector_cmp_internal(a, b) <= 0);
|
PG_RETURN_BOOL(vector_cmp_internal(a, b) <= 0);
|
||||||
}
|
}
|
||||||
@@ -929,8 +913,8 @@ PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_eq);
|
|||||||
Datum
|
Datum
|
||||||
vector_eq(PG_FUNCTION_ARGS)
|
vector_eq(PG_FUNCTION_ARGS)
|
||||||
{
|
{
|
||||||
Vector *a = (Vector *) PG_GETARG_VECTOR_P(0);
|
Vector *a = PG_GETARG_VECTOR_P(0);
|
||||||
Vector *b = (Vector *) PG_GETARG_VECTOR_P(1);
|
Vector *b = PG_GETARG_VECTOR_P(1);
|
||||||
|
|
||||||
PG_RETURN_BOOL(vector_cmp_internal(a, b) == 0);
|
PG_RETURN_BOOL(vector_cmp_internal(a, b) == 0);
|
||||||
}
|
}
|
||||||
@@ -942,8 +926,8 @@ PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_ne);
|
|||||||
Datum
|
Datum
|
||||||
vector_ne(PG_FUNCTION_ARGS)
|
vector_ne(PG_FUNCTION_ARGS)
|
||||||
{
|
{
|
||||||
Vector *a = (Vector *) PG_GETARG_VECTOR_P(0);
|
Vector *a = PG_GETARG_VECTOR_P(0);
|
||||||
Vector *b = (Vector *) PG_GETARG_VECTOR_P(1);
|
Vector *b = PG_GETARG_VECTOR_P(1);
|
||||||
|
|
||||||
PG_RETURN_BOOL(vector_cmp_internal(a, b) != 0);
|
PG_RETURN_BOOL(vector_cmp_internal(a, b) != 0);
|
||||||
}
|
}
|
||||||
@@ -955,8 +939,8 @@ PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_ge);
|
|||||||
Datum
|
Datum
|
||||||
vector_ge(PG_FUNCTION_ARGS)
|
vector_ge(PG_FUNCTION_ARGS)
|
||||||
{
|
{
|
||||||
Vector *a = (Vector *) PG_GETARG_VECTOR_P(0);
|
Vector *a = PG_GETARG_VECTOR_P(0);
|
||||||
Vector *b = (Vector *) PG_GETARG_VECTOR_P(1);
|
Vector *b = PG_GETARG_VECTOR_P(1);
|
||||||
|
|
||||||
PG_RETURN_BOOL(vector_cmp_internal(a, b) >= 0);
|
PG_RETURN_BOOL(vector_cmp_internal(a, b) >= 0);
|
||||||
}
|
}
|
||||||
@@ -968,8 +952,8 @@ PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_gt);
|
|||||||
Datum
|
Datum
|
||||||
vector_gt(PG_FUNCTION_ARGS)
|
vector_gt(PG_FUNCTION_ARGS)
|
||||||
{
|
{
|
||||||
Vector *a = (Vector *) PG_GETARG_VECTOR_P(0);
|
Vector *a = PG_GETARG_VECTOR_P(0);
|
||||||
Vector *b = (Vector *) PG_GETARG_VECTOR_P(1);
|
Vector *b = PG_GETARG_VECTOR_P(1);
|
||||||
|
|
||||||
PG_RETURN_BOOL(vector_cmp_internal(a, b) > 0);
|
PG_RETURN_BOOL(vector_cmp_internal(a, b) > 0);
|
||||||
}
|
}
|
||||||
@@ -981,8 +965,8 @@ PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_cmp);
|
|||||||
Datum
|
Datum
|
||||||
vector_cmp(PG_FUNCTION_ARGS)
|
vector_cmp(PG_FUNCTION_ARGS)
|
||||||
{
|
{
|
||||||
Vector *a = (Vector *) PG_GETARG_VECTOR_P(0);
|
Vector *a = PG_GETARG_VECTOR_P(0);
|
||||||
Vector *b = (Vector *) PG_GETARG_VECTOR_P(1);
|
Vector *b = PG_GETARG_VECTOR_P(1);
|
||||||
|
|
||||||
PG_RETURN_INT32(vector_cmp_internal(a, b));
|
PG_RETURN_INT32(vector_cmp_internal(a, b));
|
||||||
}
|
}
|
||||||
@@ -1151,3 +1135,43 @@ vector_avg(PG_FUNCTION_ARGS)
|
|||||||
|
|
||||||
PG_RETURN_POINTER(result);
|
PG_RETURN_POINTER(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Ensure same number of bytes
|
||||||
|
*/
|
||||||
|
static inline void
|
||||||
|
CheckByteLengths(uint32 aLen, uint32 bLen)
|
||||||
|
{
|
||||||
|
if (aLen != bLen)
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_DATA_EXCEPTION),
|
||||||
|
errmsg("different byte lengths %u and %u", aLen, bLen)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Get the Hamming distance between two binary strings
|
||||||
|
*/
|
||||||
|
PGDLLEXPORT PG_FUNCTION_INFO_V1(hamming_distance);
|
||||||
|
Datum
|
||||||
|
hamming_distance(PG_FUNCTION_ARGS)
|
||||||
|
{
|
||||||
|
bytea *a = PG_GETARG_BYTEA_PP(0);
|
||||||
|
bytea *b = PG_GETARG_BYTEA_PP(1);
|
||||||
|
char *ax = VARDATA_ANY(a);
|
||||||
|
char *bx = VARDATA_ANY(b);
|
||||||
|
uint32 aLen = VARSIZE_ANY_EXHDR(a);
|
||||||
|
uint32 bLen = VARSIZE_ANY_EXHDR(b);
|
||||||
|
uint64 distance = 0;
|
||||||
|
|
||||||
|
CheckByteLengths(aLen, bLen);
|
||||||
|
|
||||||
|
for (uint32 i = 0; i < aLen; i++)
|
||||||
|
{
|
||||||
|
unsigned char diff = (unsigned char) (ax[i] ^ bx[i]);
|
||||||
|
|
||||||
|
distance += pg_number_of_ones[diff];
|
||||||
|
}
|
||||||
|
|
||||||
|
/* TODO Decide on return type */
|
||||||
|
PG_RETURN_FLOAT8((double) distance);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
CREATE TABLE t (val vector(3), val2 half[]);
|
CREATE TABLE t (val vector(3));
|
||||||
INSERT INTO t (val, val2) VALUES ('[0,0,0]', '{0,0,0}'), ('[1,2,3]', '{1,2,3}'), ('[1,1,1]', '{1,1,1}'), (NULL, NULL);
|
INSERT INTO t (val) VALUES ('[0,0,0]'), ('[1,2,3]'), ('[1,1,1]'), (NULL);
|
||||||
CREATE TABLE t2 (val vector(3), val2 half[]);
|
CREATE TABLE t2 (val vector(3));
|
||||||
\copy t TO 'results/data.bin' WITH (FORMAT binary)
|
\copy t TO 'results/data.bin' WITH (FORMAT binary)
|
||||||
\copy t2 FROM 'results/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 | val2
|
val
|
||||||
---------+---------
|
---------
|
||||||
[0,0,0] | {0,0,0}
|
[0,0,0]
|
||||||
[1,1,1] | {1,1,1}
|
[1,1,1]
|
||||||
[1,2,3] | {1,2,3}
|
[1,2,3]
|
||||||
|
|
|
||||||
(4 rows)
|
(4 rows)
|
||||||
|
|
||||||
DROP TABLE t;
|
DROP TABLE t;
|
||||||
|
|||||||
@@ -54,110 +54,136 @@ SELECT vector_norm('[3e37,4e37]')::real;
|
|||||||
5e+37
|
5e+37
|
||||||
(1 row)
|
(1 row)
|
||||||
|
|
||||||
SELECT l2_distance('[0,0]'::vector, '[3,4]');
|
SELECT l2_distance('[0,0]', '[3,4]');
|
||||||
l2_distance
|
l2_distance
|
||||||
-------------
|
-------------
|
||||||
5
|
5
|
||||||
(1 row)
|
(1 row)
|
||||||
|
|
||||||
SELECT l2_distance('[0,0]'::vector, '[0,1]');
|
SELECT l2_distance('[0,0]', '[0,1]');
|
||||||
l2_distance
|
l2_distance
|
||||||
-------------
|
-------------
|
||||||
1
|
1
|
||||||
(1 row)
|
(1 row)
|
||||||
|
|
||||||
SELECT l2_distance('[1,2]'::vector, '[3]');
|
SELECT l2_distance('[1,2]', '[3]');
|
||||||
ERROR: different vector dimensions 2 and 1
|
ERROR: different vector dimensions 2 and 1
|
||||||
SELECT l2_distance('[3e38]'::vector, '[-3e38]');
|
SELECT l2_distance('[3e38]', '[-3e38]');
|
||||||
l2_distance
|
l2_distance
|
||||||
-------------
|
-------------
|
||||||
Infinity
|
Infinity
|
||||||
(1 row)
|
(1 row)
|
||||||
|
|
||||||
SELECT inner_product('[1,2]'::vector, '[3,4]');
|
SELECT inner_product('[1,2]', '[3,4]');
|
||||||
inner_product
|
inner_product
|
||||||
---------------
|
---------------
|
||||||
11
|
11
|
||||||
(1 row)
|
(1 row)
|
||||||
|
|
||||||
SELECT inner_product('[1,2]'::vector, '[3]');
|
SELECT inner_product('[1,2]', '[3]');
|
||||||
ERROR: different vector dimensions 2 and 1
|
ERROR: different vector dimensions 2 and 1
|
||||||
SELECT inner_product('[3e38]'::vector, '[3e38]');
|
SELECT inner_product('[3e38]', '[3e38]');
|
||||||
inner_product
|
inner_product
|
||||||
---------------
|
---------------
|
||||||
Infinity
|
Infinity
|
||||||
(1 row)
|
(1 row)
|
||||||
|
|
||||||
SELECT cosine_distance('[1,2]'::vector, '[2,4]');
|
SELECT cosine_distance('[1,2]', '[2,4]');
|
||||||
cosine_distance
|
cosine_distance
|
||||||
-----------------
|
-----------------
|
||||||
0
|
0
|
||||||
(1 row)
|
(1 row)
|
||||||
|
|
||||||
SELECT cosine_distance('[1,2]'::vector, '[0,0]');
|
SELECT cosine_distance('[1,2]', '[0,0]');
|
||||||
cosine_distance
|
cosine_distance
|
||||||
-----------------
|
-----------------
|
||||||
NaN
|
NaN
|
||||||
(1 row)
|
(1 row)
|
||||||
|
|
||||||
SELECT cosine_distance('[1,1]'::vector, '[1,1]');
|
SELECT cosine_distance('[1,1]', '[1,1]');
|
||||||
cosine_distance
|
cosine_distance
|
||||||
-----------------
|
-----------------
|
||||||
0
|
0
|
||||||
(1 row)
|
(1 row)
|
||||||
|
|
||||||
SELECT cosine_distance('[1,0]'::vector, '[0,2]');
|
SELECT cosine_distance('[1,0]', '[0,2]');
|
||||||
cosine_distance
|
cosine_distance
|
||||||
-----------------
|
-----------------
|
||||||
1
|
1
|
||||||
(1 row)
|
(1 row)
|
||||||
|
|
||||||
SELECT cosine_distance('[1,1]'::vector, '[-1,-1]');
|
SELECT cosine_distance('[1,1]', '[-1,-1]');
|
||||||
cosine_distance
|
cosine_distance
|
||||||
-----------------
|
-----------------
|
||||||
2
|
2
|
||||||
(1 row)
|
(1 row)
|
||||||
|
|
||||||
SELECT cosine_distance('[1,2]'::vector, '[3]');
|
SELECT cosine_distance('[1,2]', '[3]');
|
||||||
ERROR: different vector dimensions 2 and 1
|
ERROR: different vector dimensions 2 and 1
|
||||||
SELECT cosine_distance('[1,1]'::vector, '[1.1,1.1]');
|
SELECT cosine_distance('[1,1]', '[1.1,1.1]');
|
||||||
cosine_distance
|
cosine_distance
|
||||||
-----------------
|
-----------------
|
||||||
0
|
0
|
||||||
(1 row)
|
(1 row)
|
||||||
|
|
||||||
SELECT cosine_distance('[1,1]'::vector, '[-1.1,-1.1]');
|
SELECT cosine_distance('[1,1]', '[-1.1,-1.1]');
|
||||||
cosine_distance
|
cosine_distance
|
||||||
-----------------
|
-----------------
|
||||||
2
|
2
|
||||||
(1 row)
|
(1 row)
|
||||||
|
|
||||||
SELECT cosine_distance('[3e38]'::vector, '[3e38]');
|
SELECT cosine_distance('[3e38]', '[3e38]');
|
||||||
cosine_distance
|
cosine_distance
|
||||||
-----------------
|
-----------------
|
||||||
NaN
|
NaN
|
||||||
(1 row)
|
(1 row)
|
||||||
|
|
||||||
SELECT l1_distance('[0,0]'::vector, '[3,4]');
|
SELECT l1_distance('[0,0]', '[3,4]');
|
||||||
l1_distance
|
l1_distance
|
||||||
-------------
|
-------------
|
||||||
7
|
7
|
||||||
(1 row)
|
(1 row)
|
||||||
|
|
||||||
SELECT l1_distance('[0,0]'::vector, '[0,1]');
|
SELECT l1_distance('[0,0]', '[0,1]');
|
||||||
l1_distance
|
l1_distance
|
||||||
-------------
|
-------------
|
||||||
1
|
1
|
||||||
(1 row)
|
(1 row)
|
||||||
|
|
||||||
SELECT l1_distance('[1,2]'::vector, '[3]');
|
SELECT l1_distance('[1,2]', '[3]');
|
||||||
ERROR: different vector dimensions 2 and 1
|
ERROR: different vector dimensions 2 and 1
|
||||||
SELECT l1_distance('[3e38]'::vector, '[-3e38]');
|
SELECT l1_distance('[3e38]', '[-3e38]');
|
||||||
l1_distance
|
l1_distance
|
||||||
-------------
|
-------------
|
||||||
Infinity
|
Infinity
|
||||||
(1 row)
|
(1 row)
|
||||||
|
|
||||||
|
SELECT hamming_distance('\xFFFF', '\xFFFF');
|
||||||
|
hamming_distance
|
||||||
|
------------------
|
||||||
|
0
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT hamming_distance('\xFFFF', '\xFFFE');
|
||||||
|
hamming_distance
|
||||||
|
------------------
|
||||||
|
1
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT hamming_distance('\xFFFF', '\xFFFC');
|
||||||
|
hamming_distance
|
||||||
|
------------------
|
||||||
|
2
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT hamming_distance('\xFFFF', '\x0000');
|
||||||
|
hamming_distance
|
||||||
|
------------------
|
||||||
|
16
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT hamming_distance('\xFFFF', '\x00');
|
||||||
|
ERROR: different byte lengths 2 and 1
|
||||||
SELECT avg(v) FROM unnest(ARRAY['[1,2,3]'::vector, '[3,5,7]']) v;
|
SELECT avg(v) FROM unnest(ARRAY['[1,2,3]'::vector, '[3,5,7]']) v;
|
||||||
avg
|
avg
|
||||||
-----------
|
-----------
|
||||||
|
|||||||
@@ -1,226 +0,0 @@
|
|||||||
SELECT '1.5'::half;
|
|
||||||
half
|
|
||||||
------
|
|
||||||
1.5
|
|
||||||
(1 row)
|
|
||||||
|
|
||||||
SELECT '65504'::half;
|
|
||||||
half
|
|
||||||
-------
|
|
||||||
65504
|
|
||||||
(1 row)
|
|
||||||
|
|
||||||
SELECT '65505'::half;
|
|
||||||
ERROR: "65505" is out of range for type half
|
|
||||||
LINE 1: SELECT '65505'::half;
|
|
||||||
^
|
|
||||||
SELECT '-65504'::half;
|
|
||||||
half
|
|
||||||
--------
|
|
||||||
-65504
|
|
||||||
(1 row)
|
|
||||||
|
|
||||||
SELECT '-65505'::half;
|
|
||||||
ERROR: "-65505" is out of range for type half
|
|
||||||
LINE 1: SELECT '-65505'::half;
|
|
||||||
^
|
|
||||||
SELECT ''::half;
|
|
||||||
ERROR: invalid input syntax for type half: ""
|
|
||||||
LINE 1: SELECT ''::half;
|
|
||||||
^
|
|
||||||
SELECT ' '::half;
|
|
||||||
ERROR: invalid input syntax for type half: " "
|
|
||||||
LINE 1: SELECT ' '::half;
|
|
||||||
^
|
|
||||||
SELECT '-'::half;
|
|
||||||
ERROR: invalid input syntax for type half: "-"
|
|
||||||
LINE 1: SELECT '-'::half;
|
|
||||||
^
|
|
||||||
SELECT ' 1.5'::half;
|
|
||||||
half
|
|
||||||
------
|
|
||||||
1.5
|
|
||||||
(1 row)
|
|
||||||
|
|
||||||
SELECT '1.5 '::half;
|
|
||||||
half
|
|
||||||
------
|
|
||||||
1.5
|
|
||||||
(1 row)
|
|
||||||
|
|
||||||
SELECT '1.5a'::half;
|
|
||||||
ERROR: invalid input syntax for type half: "1.5a"
|
|
||||||
LINE 1: SELECT '1.5a'::half;
|
|
||||||
^
|
|
||||||
SELECT '{1,2,3}'::half[];
|
|
||||||
half
|
|
||||||
---------
|
|
||||||
{1,2,3}
|
|
||||||
(1 row)
|
|
||||||
|
|
||||||
SELECT '{1,2,3}'::half[]::real[];
|
|
||||||
float4
|
|
||||||
---------
|
|
||||||
{1,2,3}
|
|
||||||
(1 row)
|
|
||||||
|
|
||||||
SELECT '65505'::integer::half;
|
|
||||||
half
|
|
||||||
-------
|
|
||||||
65504
|
|
||||||
(1 row)
|
|
||||||
|
|
||||||
SELECT 'NaN'::real::half;
|
|
||||||
half
|
|
||||||
------
|
|
||||||
NaN
|
|
||||||
(1 row)
|
|
||||||
|
|
||||||
SELECT 'Infinity'::real::half;
|
|
||||||
half
|
|
||||||
----------
|
|
||||||
Infinity
|
|
||||||
(1 row)
|
|
||||||
|
|
||||||
SELECT '1e-38'::real::half;
|
|
||||||
ERROR: value out of range: underflow
|
|
||||||
SELECT '1.5'::half::real;
|
|
||||||
float4
|
|
||||||
--------
|
|
||||||
1.5
|
|
||||||
(1 row)
|
|
||||||
|
|
||||||
SELECT '1.5'::real::half;
|
|
||||||
half
|
|
||||||
------
|
|
||||||
1.5
|
|
||||||
(1 row)
|
|
||||||
|
|
||||||
SELECT '1.5'::half::double precision;
|
|
||||||
float8
|
|
||||||
--------
|
|
||||||
1.5
|
|
||||||
(1 row)
|
|
||||||
|
|
||||||
SELECT '1.5'::double precision::half;
|
|
||||||
half
|
|
||||||
------
|
|
||||||
1.5
|
|
||||||
(1 row)
|
|
||||||
|
|
||||||
SELECT '1.5'::half::numeric;
|
|
||||||
numeric
|
|
||||||
---------
|
|
||||||
1.5
|
|
||||||
(1 row)
|
|
||||||
|
|
||||||
SELECT '1.5'::numeric::half;
|
|
||||||
half
|
|
||||||
------
|
|
||||||
1.5
|
|
||||||
(1 row)
|
|
||||||
|
|
||||||
SELECT l2_distance('{0,0}'::half[], '{3,4}'::half[]);
|
|
||||||
l2_distance
|
|
||||||
-------------
|
|
||||||
5
|
|
||||||
(1 row)
|
|
||||||
|
|
||||||
SELECT l2_distance('{0,0}'::half[], '{0,1}'::half[]);
|
|
||||||
l2_distance
|
|
||||||
-------------
|
|
||||||
1
|
|
||||||
(1 row)
|
|
||||||
|
|
||||||
SELECT l2_distance('{1,2}'::half[], '{3}'::half[]);
|
|
||||||
ERROR: different dimensions 2 and 1
|
|
||||||
SELECT '{0,0}'::half[] <-> '{3,4}'::half[];
|
|
||||||
?column?
|
|
||||||
----------
|
|
||||||
5
|
|
||||||
(1 row)
|
|
||||||
|
|
||||||
SELECT inner_product('{1,2}'::half[], '{3,4}'::half[]);
|
|
||||||
inner_product
|
|
||||||
---------------
|
|
||||||
11
|
|
||||||
(1 row)
|
|
||||||
|
|
||||||
SELECT inner_product('{1,2}'::half[], '{3}'::half[]);
|
|
||||||
ERROR: different dimensions 2 and 1
|
|
||||||
SELECT inner_product('{65504}'::half[], '{65504}'::half[]);
|
|
||||||
inner_product
|
|
||||||
---------------
|
|
||||||
4290774016
|
|
||||||
(1 row)
|
|
||||||
|
|
||||||
SELECT '{1,2}'::half[] <#> '{3,4}'::half[];
|
|
||||||
?column?
|
|
||||||
----------
|
|
||||||
-11
|
|
||||||
(1 row)
|
|
||||||
|
|
||||||
SELECT cosine_distance('{1,2}'::half[], '{2,4}'::half[]);
|
|
||||||
cosine_distance
|
|
||||||
-----------------
|
|
||||||
0
|
|
||||||
(1 row)
|
|
||||||
|
|
||||||
SELECT cosine_distance('{1,2}'::half[], '{0,0}'::half[]);
|
|
||||||
cosine_distance
|
|
||||||
-----------------
|
|
||||||
NaN
|
|
||||||
(1 row)
|
|
||||||
|
|
||||||
SELECT cosine_distance('{1,1}'::half[], '{1,1}'::half[]);
|
|
||||||
cosine_distance
|
|
||||||
-----------------
|
|
||||||
0
|
|
||||||
(1 row)
|
|
||||||
|
|
||||||
SELECT cosine_distance('{1,0}'::half[], '{0,2}'::half[]);
|
|
||||||
cosine_distance
|
|
||||||
-----------------
|
|
||||||
1
|
|
||||||
(1 row)
|
|
||||||
|
|
||||||
SELECT cosine_distance('{1,1}'::half[], '{-1,-1}'::half[]);
|
|
||||||
cosine_distance
|
|
||||||
-----------------
|
|
||||||
2
|
|
||||||
(1 row)
|
|
||||||
|
|
||||||
SELECT cosine_distance('{1,2}'::half[], '{3}'::half[]);
|
|
||||||
ERROR: different dimensions 2 and 1
|
|
||||||
SELECT cosine_distance('{1,1}'::half[], '{1.1,1.1}'::half[]);
|
|
||||||
cosine_distance
|
|
||||||
-----------------
|
|
||||||
0
|
|
||||||
(1 row)
|
|
||||||
|
|
||||||
SELECT cosine_distance('{1,1}'::half[], '{-1.1,-1.1}'::half[]);
|
|
||||||
cosine_distance
|
|
||||||
-----------------
|
|
||||||
2
|
|
||||||
(1 row)
|
|
||||||
|
|
||||||
SELECT '{1,2}'::half[] <=> '{2,4}'::half[];
|
|
||||||
?column?
|
|
||||||
----------
|
|
||||||
0
|
|
||||||
(1 row)
|
|
||||||
|
|
||||||
SELECT l1_distance('{0,0}'::half[], '{3,4}');
|
|
||||||
l1_distance
|
|
||||||
-------------
|
|
||||||
7
|
|
||||||
(1 row)
|
|
||||||
|
|
||||||
SELECT l1_distance('{0,0}'::half[], '{0,1}');
|
|
||||||
l1_distance
|
|
||||||
-------------
|
|
||||||
1
|
|
||||||
(1 row)
|
|
||||||
|
|
||||||
SELECT l1_distance('{1,2}'::half[], '{3}');
|
|
||||||
ERROR: different dimensions 2 and 1
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
CREATE TABLE t (val vector(3), val2 half[]);
|
CREATE TABLE t (val vector(3));
|
||||||
INSERT INTO t (val, val2) VALUES ('[0,0,0]', '{0,0,0}'), ('[1,2,3]', '{1,2,3}'), ('[1,1,1]', '{1,1,1}'), (NULL, NULL);
|
INSERT INTO t (val) VALUES ('[0,0,0]'), ('[1,2,3]'), ('[1,1,1]'), (NULL);
|
||||||
|
|
||||||
CREATE TABLE t2 (val vector(3), val2 half[]);
|
CREATE TABLE t2 (val vector(3));
|
||||||
|
|
||||||
\copy t TO 'results/data.bin' WITH (FORMAT binary)
|
\copy t TO 'results/data.bin' WITH (FORMAT binary)
|
||||||
\copy t2 FROM 'results/data.bin' WITH (FORMAT binary)
|
\copy t2 FROM 'results/data.bin' WITH (FORMAT binary)
|
||||||
|
|||||||
@@ -13,29 +13,35 @@ SELECT vector_norm('[3,4]');
|
|||||||
SELECT vector_norm('[0,1]');
|
SELECT vector_norm('[0,1]');
|
||||||
SELECT vector_norm('[3e37,4e37]')::real;
|
SELECT vector_norm('[3e37,4e37]')::real;
|
||||||
|
|
||||||
SELECT l2_distance('[0,0]'::vector, '[3,4]');
|
SELECT l2_distance('[0,0]', '[3,4]');
|
||||||
SELECT l2_distance('[0,0]'::vector, '[0,1]');
|
SELECT l2_distance('[0,0]', '[0,1]');
|
||||||
SELECT l2_distance('[1,2]'::vector, '[3]');
|
SELECT l2_distance('[1,2]', '[3]');
|
||||||
SELECT l2_distance('[3e38]'::vector, '[-3e38]');
|
SELECT l2_distance('[3e38]', '[-3e38]');
|
||||||
|
|
||||||
SELECT inner_product('[1,2]'::vector, '[3,4]');
|
SELECT inner_product('[1,2]', '[3,4]');
|
||||||
SELECT inner_product('[1,2]'::vector, '[3]');
|
SELECT inner_product('[1,2]', '[3]');
|
||||||
SELECT inner_product('[3e38]'::vector, '[3e38]');
|
SELECT inner_product('[3e38]', '[3e38]');
|
||||||
|
|
||||||
SELECT cosine_distance('[1,2]'::vector, '[2,4]');
|
SELECT cosine_distance('[1,2]', '[2,4]');
|
||||||
SELECT cosine_distance('[1,2]'::vector, '[0,0]');
|
SELECT cosine_distance('[1,2]', '[0,0]');
|
||||||
SELECT cosine_distance('[1,1]'::vector, '[1,1]');
|
SELECT cosine_distance('[1,1]', '[1,1]');
|
||||||
SELECT cosine_distance('[1,0]'::vector, '[0,2]');
|
SELECT cosine_distance('[1,0]', '[0,2]');
|
||||||
SELECT cosine_distance('[1,1]'::vector, '[-1,-1]');
|
SELECT cosine_distance('[1,1]', '[-1,-1]');
|
||||||
SELECT cosine_distance('[1,2]'::vector, '[3]');
|
SELECT cosine_distance('[1,2]', '[3]');
|
||||||
SELECT cosine_distance('[1,1]'::vector, '[1.1,1.1]');
|
SELECT cosine_distance('[1,1]', '[1.1,1.1]');
|
||||||
SELECT cosine_distance('[1,1]'::vector, '[-1.1,-1.1]');
|
SELECT cosine_distance('[1,1]', '[-1.1,-1.1]');
|
||||||
SELECT cosine_distance('[3e38]'::vector, '[3e38]');
|
SELECT cosine_distance('[3e38]', '[3e38]');
|
||||||
|
|
||||||
SELECT l1_distance('[0,0]'::vector, '[3,4]');
|
SELECT l1_distance('[0,0]', '[3,4]');
|
||||||
SELECT l1_distance('[0,0]'::vector, '[0,1]');
|
SELECT l1_distance('[0,0]', '[0,1]');
|
||||||
SELECT l1_distance('[1,2]'::vector, '[3]');
|
SELECT l1_distance('[1,2]', '[3]');
|
||||||
SELECT l1_distance('[3e38]'::vector, '[-3e38]');
|
SELECT l1_distance('[3e38]', '[-3e38]');
|
||||||
|
|
||||||
|
SELECT hamming_distance('\xFFFF', '\xFFFF');
|
||||||
|
SELECT hamming_distance('\xFFFF', '\xFFFE');
|
||||||
|
SELECT hamming_distance('\xFFFF', '\xFFFC');
|
||||||
|
SELECT hamming_distance('\xFFFF', '\x0000');
|
||||||
|
SELECT hamming_distance('\xFFFF', '\x00');
|
||||||
|
|
||||||
SELECT avg(v) FROM unnest(ARRAY['[1,2,3]'::vector, '[3,5,7]']) v;
|
SELECT avg(v) FROM unnest(ARRAY['[1,2,3]'::vector, '[3,5,7]']) v;
|
||||||
SELECT avg(v) FROM unnest(ARRAY['[1,2,3]'::vector, '[3,5,7]', NULL]) v;
|
SELECT avg(v) FROM unnest(ARRAY['[1,2,3]'::vector, '[3,5,7]', NULL]) v;
|
||||||
|
|||||||
@@ -1,53 +0,0 @@
|
|||||||
SELECT '1.5'::half;
|
|
||||||
SELECT '65504'::half;
|
|
||||||
SELECT '65505'::half;
|
|
||||||
SELECT '-65504'::half;
|
|
||||||
SELECT '-65505'::half;
|
|
||||||
|
|
||||||
SELECT ''::half;
|
|
||||||
SELECT ' '::half;
|
|
||||||
SELECT '-'::half;
|
|
||||||
SELECT ' 1.5'::half;
|
|
||||||
SELECT '1.5 '::half;
|
|
||||||
SELECT '1.5a'::half;
|
|
||||||
|
|
||||||
SELECT '{1,2,3}'::half[];
|
|
||||||
SELECT '{1,2,3}'::half[]::real[];
|
|
||||||
|
|
||||||
SELECT '65505'::integer::half;
|
|
||||||
SELECT 'NaN'::real::half;
|
|
||||||
SELECT 'Infinity'::real::half;
|
|
||||||
SELECT '1e-38'::real::half;
|
|
||||||
|
|
||||||
SELECT '1.5'::half::real;
|
|
||||||
SELECT '1.5'::real::half;
|
|
||||||
|
|
||||||
SELECT '1.5'::half::double precision;
|
|
||||||
SELECT '1.5'::double precision::half;
|
|
||||||
|
|
||||||
SELECT '1.5'::half::numeric;
|
|
||||||
SELECT '1.5'::numeric::half;
|
|
||||||
|
|
||||||
SELECT l2_distance('{0,0}'::half[], '{3,4}'::half[]);
|
|
||||||
SELECT l2_distance('{0,0}'::half[], '{0,1}'::half[]);
|
|
||||||
SELECT l2_distance('{1,2}'::half[], '{3}'::half[]);
|
|
||||||
SELECT '{0,0}'::half[] <-> '{3,4}'::half[];
|
|
||||||
|
|
||||||
SELECT inner_product('{1,2}'::half[], '{3,4}'::half[]);
|
|
||||||
SELECT inner_product('{1,2}'::half[], '{3}'::half[]);
|
|
||||||
SELECT inner_product('{65504}'::half[], '{65504}'::half[]);
|
|
||||||
SELECT '{1,2}'::half[] <#> '{3,4}'::half[];
|
|
||||||
|
|
||||||
SELECT cosine_distance('{1,2}'::half[], '{2,4}'::half[]);
|
|
||||||
SELECT cosine_distance('{1,2}'::half[], '{0,0}'::half[]);
|
|
||||||
SELECT cosine_distance('{1,1}'::half[], '{1,1}'::half[]);
|
|
||||||
SELECT cosine_distance('{1,0}'::half[], '{0,2}'::half[]);
|
|
||||||
SELECT cosine_distance('{1,1}'::half[], '{-1,-1}'::half[]);
|
|
||||||
SELECT cosine_distance('{1,2}'::half[], '{3}'::half[]);
|
|
||||||
SELECT cosine_distance('{1,1}'::half[], '{1.1,1.1}'::half[]);
|
|
||||||
SELECT cosine_distance('{1,1}'::half[], '{-1.1,-1.1}'::half[]);
|
|
||||||
SELECT '{1,2}'::half[] <=> '{2,4}'::half[];
|
|
||||||
|
|
||||||
SELECT l1_distance('{0,0}'::half[], '{3,4}');
|
|
||||||
SELECT l1_distance('{0,0}'::half[], '{0,1}');
|
|
||||||
SELECT l1_distance('{1,2}'::half[], '{3}');
|
|
||||||
@@ -95,11 +95,10 @@ for my $i (0 .. $#operators)
|
|||||||
|
|
||||||
$node->safe_psql("postgres", "DROP INDEX idx;");
|
$node->safe_psql("postgres", "DROP INDEX idx;");
|
||||||
|
|
||||||
# Build index in parallel
|
# Build index in parallel in memory
|
||||||
my ($ret, $stdout, $stderr) = $node->psql("postgres", qq(
|
my ($ret, $stdout, $stderr) = $node->psql("postgres", qq(
|
||||||
SET client_min_messages = DEBUG;
|
SET client_min_messages = DEBUG;
|
||||||
SET min_parallel_table_scan_size = 1;
|
SET min_parallel_table_scan_size = 1;
|
||||||
SET hnsw.enable_parallel_build = on;
|
|
||||||
CREATE INDEX idx ON tst USING hnsw (v $opclass);
|
CREATE INDEX idx ON tst USING hnsw (v $opclass);
|
||||||
));
|
));
|
||||||
is($ret, 0, $stderr);
|
is($ret, 0, $stderr);
|
||||||
@@ -109,6 +108,21 @@ for my $i (0 .. $#operators)
|
|||||||
test_recall($min, $operator);
|
test_recall($min, $operator);
|
||||||
|
|
||||||
$node->safe_psql("postgres", "DROP INDEX idx;");
|
$node->safe_psql("postgres", "DROP INDEX idx;");
|
||||||
|
|
||||||
|
# Build index in parallel on disk
|
||||||
|
# Set parallel_workers on table to use workers with low maintenance_work_mem
|
||||||
|
($ret, $stdout, $stderr) = $node->psql("postgres", qq(
|
||||||
|
ALTER TABLE tst SET (parallel_workers = 2);
|
||||||
|
SET client_min_messages = DEBUG;
|
||||||
|
SET maintenance_work_mem = '4MB';
|
||||||
|
CREATE INDEX idx ON tst USING hnsw (v $opclass);
|
||||||
|
ALTER TABLE tst RESET (parallel_workers);
|
||||||
|
));
|
||||||
|
is($ret, 0, $stderr);
|
||||||
|
like($stderr, qr/using \d+ parallel workers/);
|
||||||
|
like($stderr, qr/hnsw graph no longer fits into maintenance_work_mem/);
|
||||||
|
|
||||||
|
$node->safe_psql("postgres", "DROP INDEX idx;");
|
||||||
}
|
}
|
||||||
|
|
||||||
done_testing();
|
done_testing();
|
||||||
|
|||||||
114
test/t/018_hnsw_filtering.pl
Normal file
114
test/t/018_hnsw_filtering.pl
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
use PostgresNode;
|
||||||
|
use TestLib;
|
||||||
|
use Test::More;
|
||||||
|
|
||||||
|
my $dim = 3;
|
||||||
|
my $nc = 50;
|
||||||
|
my $limit = 20;
|
||||||
|
|
||||||
|
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 (i int4, v vector($dim), c int4, t text);");
|
||||||
|
$node->safe_psql("postgres",
|
||||||
|
"INSERT INTO tst SELECT i, ARRAY[$array_sql], i % $nc, 'test ' || i FROM generate_series(1, 10000) i;"
|
||||||
|
);
|
||||||
|
$node->safe_psql("postgres", "CREATE INDEX idx ON tst USING hnsw (v vector_l2_ops);");
|
||||||
|
$node->safe_psql("postgres", "ANALYZE tst;");
|
||||||
|
|
||||||
|
# Generate query
|
||||||
|
my @r = ();
|
||||||
|
for (1 .. $dim)
|
||||||
|
{
|
||||||
|
push(@r, rand());
|
||||||
|
}
|
||||||
|
my $query = "[" . join(",", @r) . "]";
|
||||||
|
my $c = int(rand() * $nc);
|
||||||
|
|
||||||
|
# Test attribute filtering
|
||||||
|
my $explain = $node->safe_psql("postgres", qq(
|
||||||
|
EXPLAIN ANALYZE SELECT i FROM tst WHERE c = $c ORDER BY v <-> '$query' LIMIT $limit;
|
||||||
|
));
|
||||||
|
# TODO Do not use index
|
||||||
|
like($explain, qr/Index Scan using idx/);
|
||||||
|
|
||||||
|
# Test attribute filtering with few rows removed
|
||||||
|
$explain = $node->safe_psql("postgres", qq(
|
||||||
|
EXPLAIN ANALYZE SELECT i FROM tst WHERE c != $c ORDER BY v <-> '$query' LIMIT $limit;
|
||||||
|
));
|
||||||
|
like($explain, qr/Index Scan using idx/);
|
||||||
|
|
||||||
|
# Test attribute filtering with few rows removed comparison
|
||||||
|
$explain = $node->safe_psql("postgres", qq(
|
||||||
|
EXPLAIN ANALYZE SELECT i FROM tst WHERE c >= 1 ORDER BY v <-> '$query' LIMIT $limit;
|
||||||
|
));
|
||||||
|
like($explain, qr/Index Scan using idx/);
|
||||||
|
|
||||||
|
# Test attribute filtering with many rows removed comparison
|
||||||
|
$explain = $node->safe_psql("postgres", qq(
|
||||||
|
EXPLAIN ANALYZE SELECT i FROM tst WHERE c < 1 ORDER BY v <-> '$query' LIMIT $limit;
|
||||||
|
));
|
||||||
|
# TODO Do not use index
|
||||||
|
like($explain, qr/Index Scan using idx/);
|
||||||
|
|
||||||
|
# Test attribute filtering with few rows removed like
|
||||||
|
$explain = $node->safe_psql("postgres", qq(
|
||||||
|
EXPLAIN ANALYZE SELECT i FROM tst WHERE t LIKE '%%test%%' ORDER BY v <-> '$query' LIMIT $limit;
|
||||||
|
));
|
||||||
|
like($explain, qr/Index Scan using idx/);
|
||||||
|
|
||||||
|
# Test attribute filtering with many rows removed like
|
||||||
|
$explain = $node->safe_psql("postgres", qq(
|
||||||
|
EXPLAIN ANALYZE SELECT i FROM tst WHERE t LIKE '%%other%%' ORDER BY v <-> '$query' LIMIT $limit;
|
||||||
|
));
|
||||||
|
like($explain, qr/Seq Scan/);
|
||||||
|
|
||||||
|
# Test distance filtering
|
||||||
|
$explain = $node->safe_psql("postgres", qq(
|
||||||
|
EXPLAIN ANALYZE SELECT i FROM tst WHERE v <-> '$query' < 1 ORDER BY v <-> '$query' LIMIT $limit;
|
||||||
|
));
|
||||||
|
like($explain, qr/Index Scan using idx/);
|
||||||
|
|
||||||
|
# Test distance filtering greater than distance
|
||||||
|
$explain = $node->safe_psql("postgres", qq(
|
||||||
|
EXPLAIN ANALYZE SELECT i FROM tst WHERE v <-> '$query' > 1 ORDER BY v <-> '$query' LIMIT $limit;
|
||||||
|
));
|
||||||
|
# TODO Do not use index
|
||||||
|
like($explain, qr/Index Scan using idx/);
|
||||||
|
|
||||||
|
# Test distance filtering without order
|
||||||
|
$explain = $node->safe_psql("postgres", qq(
|
||||||
|
EXPLAIN ANALYZE SELECT i FROM tst WHERE v <-> '$query' < 1;
|
||||||
|
));
|
||||||
|
like($explain, qr/Seq Scan/);
|
||||||
|
|
||||||
|
# Test distance filtering without limit
|
||||||
|
$explain = $node->safe_psql("postgres", qq(
|
||||||
|
EXPLAIN ANALYZE SELECT i FROM tst WHERE v <-> '$query' < 1 ORDER BY v <-> '$query';
|
||||||
|
));
|
||||||
|
like($explain, qr/Seq Scan/);
|
||||||
|
|
||||||
|
# Test attribute index
|
||||||
|
$node->safe_psql("postgres", "CREATE INDEX attribute_idx ON tst (c);");
|
||||||
|
$explain = $node->safe_psql("postgres", qq(
|
||||||
|
EXPLAIN ANALYZE SELECT i FROM tst WHERE c = $c ORDER BY v <-> '$query' LIMIT $limit;
|
||||||
|
));
|
||||||
|
# TODO Use attribute index
|
||||||
|
like($explain, qr/Index Scan using idx/);
|
||||||
|
|
||||||
|
# Test partial index
|
||||||
|
$node->safe_psql("postgres", "CREATE INDEX partial_idx ON tst USING hnsw (v vector_l2_ops) WHERE (c = $c);");
|
||||||
|
$explain = $node->safe_psql("postgres", qq(
|
||||||
|
EXPLAIN ANALYZE SELECT i FROM tst WHERE c = $c ORDER BY v <-> '$query' LIMIT $limit;
|
||||||
|
));
|
||||||
|
like($explain, qr/Index Scan using partial_idx/);
|
||||||
|
|
||||||
|
done_testing();
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
use strict;
|
|
||||||
use warnings;
|
|
||||||
use PostgresNode;
|
|
||||||
use TestLib;
|
|
||||||
use Test::More;
|
|
||||||
|
|
||||||
my $dim = 3;
|
|
||||||
|
|
||||||
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 (i serial, v vector($dim));");
|
|
||||||
$node->safe_psql("postgres",
|
|
||||||
"INSERT INTO tst (v) SELECT ARRAY[$array_sql] FROM generate_series(1, 10000) i;"
|
|
||||||
);
|
|
||||||
$node->safe_psql("postgres", "CREATE INDEX ON tst USING ivfflat (v vector_l2_ops);");
|
|
||||||
|
|
||||||
# Delete data
|
|
||||||
$node->safe_psql("postgres", "DELETE FROM tst WHERE i % 100 != 0;");
|
|
||||||
|
|
||||||
my $exp = $node->safe_psql("postgres", qq(
|
|
||||||
SET enable_indexscan = off;
|
|
||||||
SELECT i FROM tst ORDER BY v <-> '[0,0,0]';
|
|
||||||
));
|
|
||||||
|
|
||||||
# Run twice to make sure correct tuples marked as dead
|
|
||||||
for (1 .. 2)
|
|
||||||
{
|
|
||||||
my $res = $node->safe_psql("postgres", qq(
|
|
||||||
SET enable_seqscan = off;
|
|
||||||
SET ivfflat.probes = 100;
|
|
||||||
SELECT i FROM tst ORDER BY v <-> '[0,0,0]';
|
|
||||||
));
|
|
||||||
is($res, $exp);
|
|
||||||
}
|
|
||||||
|
|
||||||
done_testing();
|
|
||||||
116
test/t/019_ivfflat_filtering.pl
Normal file
116
test/t/019_ivfflat_filtering.pl
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
use PostgresNode;
|
||||||
|
use TestLib;
|
||||||
|
use Test::More;
|
||||||
|
|
||||||
|
my $dim = 3;
|
||||||
|
my $nc = 50;
|
||||||
|
my $limit = 20;
|
||||||
|
|
||||||
|
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 (i int4, v vector($dim), c int4, t text);");
|
||||||
|
$node->safe_psql("postgres",
|
||||||
|
"INSERT INTO tst SELECT i, ARRAY[$array_sql], i % $nc, 'test ' || i FROM generate_series(1, 10000) i;"
|
||||||
|
);
|
||||||
|
$node->safe_psql("postgres", "CREATE INDEX idx ON tst USING ivfflat (v vector_l2_ops) WITH (lists = 100);");
|
||||||
|
$node->safe_psql("postgres", "ANALYZE tst;");
|
||||||
|
|
||||||
|
# Generate query
|
||||||
|
my @r = ();
|
||||||
|
for (1 .. $dim)
|
||||||
|
{
|
||||||
|
push(@r, rand());
|
||||||
|
}
|
||||||
|
my $query = "[" . join(",", @r) . "]";
|
||||||
|
my $c = int(rand() * $nc);
|
||||||
|
|
||||||
|
# Test attribute filtering
|
||||||
|
my $explain = $node->safe_psql("postgres", qq(
|
||||||
|
EXPLAIN ANALYZE SELECT i FROM tst WHERE c = $c ORDER BY v <-> '$query' LIMIT $limit;
|
||||||
|
));
|
||||||
|
# TODO Do not use index
|
||||||
|
like($explain, qr/Index Scan using idx/);
|
||||||
|
|
||||||
|
# Test attribute filtering with few rows removed
|
||||||
|
$explain = $node->safe_psql("postgres", qq(
|
||||||
|
EXPLAIN ANALYZE SELECT i FROM tst WHERE c != $c ORDER BY v <-> '$query' LIMIT $limit;
|
||||||
|
));
|
||||||
|
like($explain, qr/Index Scan using idx/);
|
||||||
|
|
||||||
|
# Test attribute filtering with few rows removed comparison
|
||||||
|
$explain = $node->safe_psql("postgres", qq(
|
||||||
|
EXPLAIN ANALYZE SELECT i FROM tst WHERE c >= 1 ORDER BY v <-> '$query' LIMIT $limit;
|
||||||
|
));
|
||||||
|
like($explain, qr/Index Scan using idx/);
|
||||||
|
|
||||||
|
# Test attribute filtering with many rows removed comparison
|
||||||
|
$explain = $node->safe_psql("postgres", qq(
|
||||||
|
EXPLAIN ANALYZE SELECT i FROM tst WHERE c < 1 ORDER BY v <-> '$query' LIMIT $limit;
|
||||||
|
));
|
||||||
|
# TODO Do not use index
|
||||||
|
like($explain, qr/Index Scan using idx/);
|
||||||
|
|
||||||
|
# Test attribute filtering with few rows removed like
|
||||||
|
$explain = $node->safe_psql("postgres", qq(
|
||||||
|
EXPLAIN ANALYZE SELECT i FROM tst WHERE t LIKE '%%test%%' ORDER BY v <-> '$query' LIMIT $limit;
|
||||||
|
));
|
||||||
|
like($explain, qr/Index Scan using idx/);
|
||||||
|
|
||||||
|
# Test attribute filtering with many rows removed like
|
||||||
|
$explain = $node->safe_psql("postgres", qq(
|
||||||
|
EXPLAIN ANALYZE SELECT i FROM tst WHERE t LIKE '%%other%%' ORDER BY v <-> '$query' LIMIT $limit;
|
||||||
|
));
|
||||||
|
like($explain, qr/Seq Scan/);
|
||||||
|
|
||||||
|
# Test distance filtering
|
||||||
|
$explain = $node->safe_psql("postgres", qq(
|
||||||
|
EXPLAIN ANALYZE SELECT i FROM tst WHERE v <-> '$query' < 1 ORDER BY v <-> '$query' LIMIT $limit;
|
||||||
|
));
|
||||||
|
like($explain, qr/Index Scan using idx/);
|
||||||
|
|
||||||
|
# Test distance filtering greater than distance
|
||||||
|
$explain = $node->safe_psql("postgres", qq(
|
||||||
|
EXPLAIN ANALYZE SELECT i FROM tst WHERE v <-> '$query' > 1 ORDER BY v <-> '$query' LIMIT $limit;
|
||||||
|
));
|
||||||
|
# TODO Do not use index
|
||||||
|
like($explain, qr/Index Scan using idx/);
|
||||||
|
|
||||||
|
# Test distance filtering without order
|
||||||
|
$explain = $node->safe_psql("postgres", qq(
|
||||||
|
EXPLAIN ANALYZE SELECT i FROM tst WHERE v <-> '$query' < 1;
|
||||||
|
));
|
||||||
|
like($explain, qr/Seq Scan/);
|
||||||
|
|
||||||
|
# Test distance filtering without limit
|
||||||
|
$explain = $node->safe_psql("postgres", qq(
|
||||||
|
EXPLAIN ANALYZE SELECT i FROM tst WHERE v <-> '$query' < 1 ORDER BY v <-> '$query';
|
||||||
|
));
|
||||||
|
# TODO Do not use index
|
||||||
|
like($explain, qr/Index Scan using idx/);
|
||||||
|
|
||||||
|
# Test attribute index
|
||||||
|
$node->safe_psql("postgres", "CREATE INDEX attribute_idx ON tst (c);");
|
||||||
|
$explain = $node->safe_psql("postgres", qq(
|
||||||
|
EXPLAIN ANALYZE SELECT i FROM tst WHERE c = $c ORDER BY v <-> '$query' LIMIT $limit;
|
||||||
|
));
|
||||||
|
# TODO Use attribute index
|
||||||
|
like($explain, qr/Index Scan using idx/);
|
||||||
|
|
||||||
|
# Test partial index
|
||||||
|
$node->safe_psql("postgres", "CREATE INDEX partial_idx ON tst USING ivfflat (v vector_l2_ops) WITH (lists = 5) WHERE (c = $c);");
|
||||||
|
$explain = $node->safe_psql("postgres", qq(
|
||||||
|
EXPLAIN ANALYZE SELECT i FROM tst WHERE c = $c ORDER BY v <-> '$query' LIMIT $limit;
|
||||||
|
));
|
||||||
|
# TODO Use partial index
|
||||||
|
like($explain, qr/Index Scan using idx/);
|
||||||
|
|
||||||
|
done_testing();
|
||||||
Reference in New Issue
Block a user