mirror of
https://github.com/pgvector/pgvector.git
synced 2026-07-24 04:45:45 +08:00
Compare commits
115 Commits
hnsw-print
...
svector
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f90d52d562 | ||
|
|
22a1b06924 | ||
|
|
b17b9c1ca2 | ||
|
|
492ae1225c | ||
|
|
a01a72d812 | ||
|
|
0c2fc18a80 | ||
|
|
e860042d3c | ||
|
|
5986862bd2 | ||
|
|
5d24f5d09a | ||
|
|
7c43b0d8ee | ||
|
|
7be40036f4 | ||
|
|
9b5a1a69db | ||
|
|
04b96506f5 | ||
|
|
35cd7b63cb | ||
|
|
b5416d6f10 | ||
|
|
f361bf2704 | ||
|
|
3d8c1921aa | ||
|
|
154207bc17 | ||
|
|
8e507f3bf5 | ||
|
|
e115773a55 | ||
|
|
9333bef046 | ||
|
|
4851e47d9f | ||
|
|
12aecfb4f5 | ||
|
|
800697fb14 | ||
|
|
de1f2b09dd | ||
|
|
bcccb7f5a5 | ||
|
|
bec3d30d68 | ||
|
|
588de60445 | ||
|
|
c599f92b52 | ||
|
|
2a17b335da | ||
|
|
6ede6ac301 | ||
|
|
3f49b95f01 | ||
|
|
ef1bea7163 | ||
|
|
e630efd195 | ||
|
|
b5b912906b | ||
|
|
4b5db94307 | ||
|
|
65e70326b8 | ||
|
|
71641ed84e | ||
|
|
f3dba25036 | ||
|
|
5588ba6410 | ||
|
|
ec9fac5456 | ||
|
|
8085d3e538 | ||
|
|
cae162ffc6 | ||
|
|
62482e3760 | ||
|
|
c81302b835 | ||
|
|
a713e2acaa | ||
|
|
6e1312ddbe | ||
|
|
4ef5bca275 | ||
|
|
1ecf6ada76 | ||
|
|
564a3d45fc | ||
|
|
8d7abb6590 | ||
|
|
b247b688a8 | ||
|
|
9672446a4c | ||
|
|
334614b7f7 | ||
|
|
643eacd9dc | ||
|
|
bca50a03fa | ||
|
|
d87833cacc | ||
|
|
4c01073ac1 | ||
|
|
6fed8f1e78 | ||
|
|
611f5b1047 | ||
|
|
e367155732 | ||
|
|
466c556b1a | ||
|
|
75e09265d6 | ||
|
|
77c5070fb6 | ||
|
|
1006fdf3f2 | ||
|
|
4209c9b3af | ||
|
|
ec0bb4e4ff | ||
|
|
b164833933 | ||
|
|
30fb4dd602 | ||
|
|
d032726976 | ||
|
|
8fa9001474 | ||
|
|
3431acef94 | ||
|
|
41bdf24cb7 | ||
|
|
3469a0e74c | ||
|
|
0fe43ca675 | ||
|
|
bbbe1db72b | ||
|
|
bab5fea9e7 | ||
|
|
b1f9519689 | ||
|
|
4571fccc60 | ||
|
|
db747e5aa0 | ||
|
|
2179414c05 | ||
|
|
8426ee61d2 | ||
|
|
c98c4e13aa | ||
|
|
04312f6638 | ||
|
|
72ea3c1210 | ||
|
|
b0801b8833 | ||
|
|
d05d6ee83d | ||
|
|
4022bb66a0 | ||
|
|
034d4acaea | ||
|
|
01f58e470a | ||
|
|
dbef8d1ad1 | ||
|
|
5c005cf57c | ||
|
|
5665a11a05 | ||
|
|
6b2e215447 | ||
|
|
0d86191eaf | ||
|
|
cf9f7aeea9 | ||
|
|
0b0e542ce6 | ||
|
|
a4590d2d9d | ||
|
|
9ebec1529b | ||
|
|
77ff4c18f0 | ||
|
|
88dabaa41c | ||
|
|
1809ffa52b | ||
|
|
024f283ee8 | ||
|
|
da3b2fab46 | ||
|
|
884026a23c | ||
|
|
4d352e6c30 | ||
|
|
a8e257e1f1 | ||
|
|
3913432303 | ||
|
|
baeebec244 | ||
|
|
d578a4cccc | ||
|
|
1e18e19496 | ||
|
|
39f973dab2 | ||
|
|
453fa15f28 | ||
|
|
e50a79108f | ||
|
|
cfaa2ecd7f |
8
.github/workflows/build.yml
vendored
8
.github/workflows/build.yml
vendored
@@ -8,6 +8,8 @@ jobs:
|
|||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
|
- postgres: 17
|
||||||
|
os: ubuntu-22.04
|
||||||
- postgres: 16
|
- postgres: 16
|
||||||
os: ubuntu-22.04
|
os: ubuntu-22.04
|
||||||
- postgres: 15
|
- postgres: 15
|
||||||
@@ -21,7 +23,7 @@ jobs:
|
|||||||
- postgres: 11
|
- postgres: 11
|
||||||
os: ubuntu-20.04
|
os: ubuntu-20.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: ankane/setup-postgres@v1
|
- uses: ankane/setup-postgres@v1
|
||||||
with:
|
with:
|
||||||
postgres-version: ${{ matrix.postgres }}
|
postgres-version: ${{ matrix.postgres }}
|
||||||
@@ -43,7 +45,7 @@ jobs:
|
|||||||
runs-on: macos-latest
|
runs-on: macos-latest
|
||||||
if: ${{ !startsWith(github.ref_name, 'windows') }}
|
if: ${{ !startsWith(github.ref_name, 'windows') }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: ankane/setup-postgres@v1
|
- uses: ankane/setup-postgres@v1
|
||||||
with:
|
with:
|
||||||
postgres-version: 14
|
postgres-version: 14
|
||||||
@@ -65,7 +67,7 @@ jobs:
|
|||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
if: ${{ !startsWith(github.ref_name, 'mac') }}
|
if: ${{ !startsWith(github.ref_name, 'mac') }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: ankane/setup-postgres@v1
|
- uses: ankane/setup-postgres@v1
|
||||||
with:
|
with:
|
||||||
postgres-version: 14
|
postgres-version: 14
|
||||||
|
|||||||
15
CHANGELOG.md
15
CHANGELOG.md
@@ -1,13 +1,22 @@
|
|||||||
## 0.5.0 (unreleased)
|
## 0.6.0 (unreleased)
|
||||||
|
|
||||||
|
- Added support for sparse vectors
|
||||||
|
|
||||||
|
## 0.5.1 (2023-10-10)
|
||||||
|
|
||||||
|
- Improved performance of HNSW index builds
|
||||||
|
- Added check for MVCC-compliant snapshot for index scans
|
||||||
|
|
||||||
|
## 0.5.0 (2023-08-28)
|
||||||
|
|
||||||
- Added HNSW index type
|
- Added HNSW index type
|
||||||
- Added support for parallel index builds
|
- Added support for parallel index builds for IVFFlat
|
||||||
- Added `l1_distance` function
|
- Added `l1_distance` function
|
||||||
- Added element-wise multiplication for vectors
|
- Added element-wise multiplication for vectors
|
||||||
- Added `sum` aggregate
|
- Added `sum` aggregate
|
||||||
- Improved performance of distance functions
|
- Improved performance of distance functions
|
||||||
- Fixed out of range results for cosine distance
|
- Fixed out of range results for cosine distance
|
||||||
- Fixed results for NULL and NaN distances
|
- Fixed results for NULL and NaN distances for IVFFlat
|
||||||
|
|
||||||
## 0.4.4 (2023-06-12)
|
## 0.4.4 (2023-06-12)
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"name": "vector",
|
"name": "vector",
|
||||||
"abstract": "Open-source vector similarity search for Postgres",
|
"abstract": "Open-source vector similarity search for Postgres",
|
||||||
"description": "Supports L2 distance, inner product, and cosine distance",
|
"description": "Supports L2 distance, inner product, and cosine distance",
|
||||||
"version": "0.4.4",
|
"version": "0.5.1",
|
||||||
"maintainer": [
|
"maintainer": [
|
||||||
"Andrew Kane <andrew@ankane.org>"
|
"Andrew Kane <andrew@ankane.org>"
|
||||||
],
|
],
|
||||||
@@ -20,7 +20,7 @@
|
|||||||
"vector": {
|
"vector": {
|
||||||
"file": "sql/vector.sql",
|
"file": "sql/vector.sql",
|
||||||
"docfile": "README.md",
|
"docfile": "README.md",
|
||||||
"version": "0.4.4",
|
"version": "0.5.1",
|
||||||
"abstract": "Open-source vector similarity search for Postgres"
|
"abstract": "Open-source vector similarity search for Postgres"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
8
Makefile
8
Makefile
@@ -1,14 +1,14 @@
|
|||||||
EXTENSION = vector
|
EXTENSION = vector
|
||||||
EXTVERSION = 0.4.4
|
EXTVERSION = 0.5.1
|
||||||
|
|
||||||
MODULE_big = vector
|
MODULE_big = vector
|
||||||
DATA = $(wildcard sql/*--*.sql)
|
DATA = $(wildcard sql/*--*.sql)
|
||||||
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
|
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/svector.o src/vector.o
|
||||||
HEADERS = src/vector.h
|
HEADERS = src/svector.h 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))
|
||||||
REGRESS_OPTS = --inputdir=test --load-extension=vector
|
REGRESS_OPTS = --inputdir=test --load-extension=$(EXTENSION)
|
||||||
|
|
||||||
OPTFLAGS = -march=native
|
OPTFLAGS = -march=native
|
||||||
|
|
||||||
|
|||||||
10
Makefile.win
10
Makefile.win
@@ -1,11 +1,11 @@
|
|||||||
EXTENSION = vector
|
EXTENSION = vector
|
||||||
EXTVERSION = 0.4.4
|
EXTVERSION = 0.5.1
|
||||||
|
|
||||||
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
|
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\svector.obj src\vector.obj
|
||||||
HEADERS = src\vector.h
|
HEADERS = src\svector.h 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=vector
|
REGRESS_OPTS = --inputdir=test --load-extension=$(EXTENSION)
|
||||||
|
|
||||||
# For /arch flags
|
# For /arch flags
|
||||||
# https://learn.microsoft.com/en-us/cpp/build/reference/arch-minimum-cpu-architecture
|
# https://learn.microsoft.com/en-us/cpp/build/reference/arch-minimum-cpu-architecture
|
||||||
@@ -56,7 +56,7 @@ install:
|
|||||||
copy $(EXTENSION).control "$(SHAREDIR)\extension"
|
copy $(EXTENSION).control "$(SHAREDIR)\extension"
|
||||||
copy sql\$(EXTENSION)--*.sql "$(SHAREDIR)\extension"
|
copy sql\$(EXTENSION)--*.sql "$(SHAREDIR)\extension"
|
||||||
mkdir "$(INCLUDEDIR_SERVER)\extension\$(EXTENSION)"
|
mkdir "$(INCLUDEDIR_SERVER)\extension\$(EXTENSION)"
|
||||||
copy $(HEADERS) "$(INCLUDEDIR_SERVER)\extension\$(EXTENSION)"
|
for %f in ($(HEADERS)) do copy %f "$(INCLUDEDIR_SERVER)\extension\$(EXTENSION)"
|
||||||
|
|
||||||
installcheck:
|
installcheck:
|
||||||
"$(BINDIR)\pg_regress" --bindir="$(BINDIR)" $(REGRESS_OPTS) $(REGRESS)
|
"$(BINDIR)\pg_regress" --bindir="$(BINDIR)" $(REGRESS_OPTS) $(REGRESS)
|
||||||
|
|||||||
231
README.md
231
README.md
@@ -18,7 +18,7 @@ Compile and install the extension (supports Postgres 11+)
|
|||||||
|
|
||||||
```sh
|
```sh
|
||||||
cd /tmp
|
cd /tmp
|
||||||
git clone --branch v0.4.4 https://github.com/pgvector/pgvector.git
|
git clone --branch v0.5.1 https://github.com/pgvector/pgvector.git
|
||||||
cd pgvector
|
cd pgvector
|
||||||
make
|
make
|
||||||
make install # may need sudo
|
make install # may need sudo
|
||||||
@@ -26,7 +26,7 @@ make install # may need sudo
|
|||||||
|
|
||||||
See the [installation notes](#installation-notes) if you run into issues
|
See the [installation notes](#installation-notes) if you run into issues
|
||||||
|
|
||||||
You can also install it with [Docker](#docker), [Homebrew](#homebrew), [PGXN](#pgxn), [APT](#apt), [Yum](#yum), or [conda-forge](#conda-forge), and it comes preinstalled with [Postgres.app](#postgresapp) and many [hosted providers](#hosted-postgres)
|
You can also install it with [Docker](#docker), [Homebrew](#homebrew), [PGXN](#pgxn), [APT](#apt), [Yum](#yum), or [conda-forge](#conda-forge), and it comes preinstalled with [Postgres.app](#postgresapp) and many [hosted providers](#hosted-postgres). There are also instructions for [GitHub Actions](https://github.com/pgvector/setup-pgvector).
|
||||||
|
|
||||||
## Getting Started
|
## Getting Started
|
||||||
|
|
||||||
@@ -157,7 +157,16 @@ SELECT category_id, AVG(embedding) FROM items GROUP BY category_id;
|
|||||||
|
|
||||||
By default, pgvector performs exact nearest neighbor search, which provides perfect recall.
|
By default, pgvector performs exact nearest neighbor search, which provides perfect recall.
|
||||||
|
|
||||||
You can add an index to use approximate nearest neighbor search, which trades some recall for performance. Unlike typical indexes, you will see different results for queries after adding an approximate index.
|
You can add an index to use approximate nearest neighbor search, which trades some recall for speed. Unlike typical indexes, you will see different results for queries after adding an approximate index.
|
||||||
|
|
||||||
|
Supported index types are:
|
||||||
|
|
||||||
|
- [IVFFlat](#ivfflat)
|
||||||
|
- [HNSW](#hnsw) - added in 0.5.0
|
||||||
|
|
||||||
|
## 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:
|
Three keys to achieving good recall are:
|
||||||
|
|
||||||
@@ -211,17 +220,86 @@ COMMIT;
|
|||||||
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+
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
SELECT phase, tuples_done, tuples_total FROM pg_stat_progress_create_index;
|
SELECT phase, round(100.0 * tuples_done / nullif(tuples_total, 0), 1) AS "%" FROM pg_stat_progress_create_index;
|
||||||
```
|
```
|
||||||
|
|
||||||
The phases are:
|
The phases for IVFFlat are:
|
||||||
|
|
||||||
1. `initializing`
|
1. `initializing`
|
||||||
2. `performing k-means`
|
2. `performing k-means`
|
||||||
3. `sorting tuples`
|
3. `assigning tuples`
|
||||||
4. `loading tuples`
|
4. `loading tuples`
|
||||||
|
|
||||||
Note: `tuples_done` and `tuples_total` are only populated during the `loading tuples` phase
|
Note: `%` is only populated during the `loading tuples` phase
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
|
||||||
|
Add an index for each distance function you want to use.
|
||||||
|
|
||||||
|
L2 distance
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE INDEX ON items USING hnsw (embedding vector_l2_ops);
|
||||||
|
```
|
||||||
|
|
||||||
|
Inner product
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE INDEX ON items USING hnsw (embedding vector_ip_ops);
|
||||||
|
```
|
||||||
|
|
||||||
|
Cosine distance
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE INDEX ON items USING hnsw (embedding vector_cosine_ops);
|
||||||
|
```
|
||||||
|
|
||||||
|
Vectors with up to 2,000 dimensions can be indexed.
|
||||||
|
|
||||||
|
### Index Options
|
||||||
|
|
||||||
|
Specify HNSW parameters
|
||||||
|
|
||||||
|
- `m` - the max number of connections per layer (16 by default)
|
||||||
|
- `ef_construction` - the size of the dynamic candidate list for constructing the graph (64 by default)
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE INDEX ON items USING hnsw (embedding vector_l2_ops) WITH (m = 16, ef_construction = 64);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Query Options
|
||||||
|
|
||||||
|
Specify the size of the dynamic candidate list for search (40 by default)
|
||||||
|
|
||||||
|
```sql
|
||||||
|
SET hnsw.ef_search = 100;
|
||||||
|
```
|
||||||
|
|
||||||
|
A higher value provides better recall at the cost of speed.
|
||||||
|
|
||||||
|
Use `SET LOCAL` inside a transaction to set it for a single query
|
||||||
|
|
||||||
|
```sql
|
||||||
|
BEGIN;
|
||||||
|
SET LOCAL hnsw.ef_search = 100;
|
||||||
|
SELECT ...
|
||||||
|
COMMIT;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Indexing Progress
|
||||||
|
|
||||||
|
Check [indexing progress](https://www.postgresql.org/docs/current/progress-reporting.html#CREATE-INDEX-PROGRESS-REPORTING) with Postgres 12+
|
||||||
|
|
||||||
|
```sql
|
||||||
|
SELECT phase, round(100.0 * blocks_done / nullif(blocks_total, 0), 1) AS "%" FROM pg_stat_progress_create_index;
|
||||||
|
```
|
||||||
|
|
||||||
|
The phases for HNSW are:
|
||||||
|
|
||||||
|
1. `initializing`
|
||||||
|
2. `loading tuples`
|
||||||
|
|
||||||
## Filtering
|
## Filtering
|
||||||
|
|
||||||
@@ -252,13 +330,15 @@ CREATE TABLE items (embedding vector(3), category_id int) PARTITION BY LIST(cate
|
|||||||
|
|
||||||
## Hybrid Search
|
## Hybrid Search
|
||||||
|
|
||||||
Use together with Postgres [full-text search](https://www.postgresql.org/docs/current/textsearch-intro.html) for hybrid search ([Python example](https://github.com/pgvector/pgvector-python/blob/master/examples/hybrid_search.py)).
|
Use together with Postgres [full-text search](https://www.postgresql.org/docs/current/textsearch-intro.html) for hybrid search.
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
SELECT id, content FROM items, plainto_tsquery('hello search') query
|
SELECT id, content FROM items, plainto_tsquery('hello search') query
|
||||||
WHERE textsearch @@ query ORDER BY ts_rank_cd(textsearch, query) DESC LIMIT 5;
|
WHERE textsearch @@ query ORDER BY ts_rank_cd(textsearch, query) DESC LIMIT 5;
|
||||||
```
|
```
|
||||||
|
|
||||||
|
You can use [Reciprocal Rank Fusion](https://github.com/pgvector/pgvector-python/blob/master/examples/hybrid_search_rrf.py) or a [cross-encoder](https://github.com/pgvector/pgvector-python/blob/master/examples/hybrid_search.py) to combine results.
|
||||||
|
|
||||||
## Performance
|
## Performance
|
||||||
|
|
||||||
Use `EXPLAIN ANALYZE` to debug performance.
|
Use `EXPLAIN ANALYZE` to debug performance.
|
||||||
@@ -283,18 +363,39 @@ SELECT * FROM items ORDER BY embedding <#> '[3,1,2]' LIMIT 5;
|
|||||||
|
|
||||||
### Approximate Search
|
### Approximate Search
|
||||||
|
|
||||||
To speed up queries with an index, increase the number of inverted lists (at the expense of recall).
|
To speed up queries with an IVFFlat index, increase the number of inverted lists (at the expense of recall).
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
CREATE INDEX ON items USING ivfflat (embedding vector_l2_ops) WITH (lists = 1000);
|
CREATE INDEX ON items USING ivfflat (embedding vector_l2_ops) WITH (lists = 1000);
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Sparse Vectors
|
||||||
|
|
||||||
|
Create a sparse vector column with 10 dimensions
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE TABLE items (id bigserial PRIMARY KEY, embedding svector(10));
|
||||||
|
```
|
||||||
|
|
||||||
|
Insert vectors
|
||||||
|
|
||||||
|
```sql
|
||||||
|
INSERT INTO items (embedding) VALUES ('(0,1),(1,2),(2,3)|10|'), ('(0,4),(1,5),(4,6)|10|');
|
||||||
|
```
|
||||||
|
|
||||||
|
Get the nearest neighbors by L2 distance
|
||||||
|
|
||||||
|
```sql
|
||||||
|
SELECT * FROM items ORDER BY embedding <-> '(0,3),(1,1),(2,2)|10|' LIMIT 5;
|
||||||
|
```
|
||||||
|
|
||||||
## Languages
|
## Languages
|
||||||
|
|
||||||
Use pgvector from any language with a Postgres client. You can even generate and store vectors in one language and query them in another.
|
Use pgvector from any language with a Postgres client. You can even generate and store vectors in one language and query them in another.
|
||||||
|
|
||||||
Language | Libraries / Examples
|
Language | Libraries / Examples
|
||||||
--- | ---
|
--- | ---
|
||||||
|
C | [pgvector-c](https://github.com/pgvector/pgvector-c)
|
||||||
C++ | [pgvector-cpp](https://github.com/pgvector/pgvector-cpp)
|
C++ | [pgvector-cpp](https://github.com/pgvector/pgvector-cpp)
|
||||||
C# | [pgvector-dotnet](https://github.com/pgvector/pgvector-dotnet)
|
C# | [pgvector-dotnet](https://github.com/pgvector/pgvector-dotnet)
|
||||||
Crystal | [pgvector-crystal](https://github.com/pgvector/pgvector-crystal)
|
Crystal | [pgvector-crystal](https://github.com/pgvector/pgvector-crystal)
|
||||||
@@ -302,10 +403,11 @@ Dart | [pgvector-dart](https://github.com/pgvector/pgvector-dart)
|
|||||||
Elixir | [pgvector-elixir](https://github.com/pgvector/pgvector-elixir)
|
Elixir | [pgvector-elixir](https://github.com/pgvector/pgvector-elixir)
|
||||||
Go | [pgvector-go](https://github.com/pgvector/pgvector-go)
|
Go | [pgvector-go](https://github.com/pgvector/pgvector-go)
|
||||||
Haskell | [pgvector-haskell](https://github.com/pgvector/pgvector-haskell)
|
Haskell | [pgvector-haskell](https://github.com/pgvector/pgvector-haskell)
|
||||||
Java, Scala | [pgvector-java](https://github.com/pgvector/pgvector-java)
|
Java, Kotlin, Groovy, Scala | [pgvector-java](https://github.com/pgvector/pgvector-java)
|
||||||
|
JavaScript, TypeScript | [pgvector-node](https://github.com/pgvector/pgvector-node)
|
||||||
Julia | [pgvector-julia](https://github.com/pgvector/pgvector-julia)
|
Julia | [pgvector-julia](https://github.com/pgvector/pgvector-julia)
|
||||||
Lua | [pgvector-lua](https://github.com/pgvector/pgvector-lua)
|
Lua | [pgvector-lua](https://github.com/pgvector/pgvector-lua)
|
||||||
Node.js | [pgvector-node](https://github.com/pgvector/pgvector-node)
|
Nim | [pgvector-nim](https://github.com/pgvector/pgvector-nim)
|
||||||
Perl | [pgvector-perl](https://github.com/pgvector/pgvector-perl)
|
Perl | [pgvector-perl](https://github.com/pgvector/pgvector-perl)
|
||||||
PHP | [pgvector-php](https://github.com/pgvector/pgvector-php)
|
PHP | [pgvector-php](https://github.com/pgvector/pgvector-php)
|
||||||
Python | [pgvector-python](https://github.com/pgvector/pgvector-python)
|
Python | [pgvector-python](https://github.com/pgvector/pgvector-python)
|
||||||
@@ -313,6 +415,7 @@ R | [pgvector-r](https://github.com/pgvector/pgvector-r)
|
|||||||
Ruby | [pgvector-ruby](https://github.com/pgvector/pgvector-ruby), [Neighbor](https://github.com/ankane/neighbor)
|
Ruby | [pgvector-ruby](https://github.com/pgvector/pgvector-ruby), [Neighbor](https://github.com/ankane/neighbor)
|
||||||
Rust | [pgvector-rust](https://github.com/pgvector/pgvector-rust)
|
Rust | [pgvector-rust](https://github.com/pgvector/pgvector-rust)
|
||||||
Swift | [pgvector-swift](https://github.com/pgvector/pgvector-swift)
|
Swift | [pgvector-swift](https://github.com/pgvector/pgvector-swift)
|
||||||
|
Zig | [pgvector-zig](https://github.com/pgvector/pgvector-zig)
|
||||||
|
|
||||||
## Frequently Asked Questions
|
## Frequently Asked Questions
|
||||||
|
|
||||||
@@ -328,6 +431,55 @@ Yes, pgvector uses the write-ahead log (WAL), which allows for replication and p
|
|||||||
|
|
||||||
You’ll need to use [dimensionality reduction](https://en.wikipedia.org/wiki/Dimensionality_reduction) at the moment.
|
You’ll need to use [dimensionality reduction](https://en.wikipedia.org/wiki/Dimensionality_reduction) at the moment.
|
||||||
|
|
||||||
|
#### Can I store vectors with different dimensions in the same column?
|
||||||
|
|
||||||
|
You can use `vector` as the type (instead of `vector(3)`).
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE TABLE embeddings (model_id bigint, item_id bigint, embedding vector, PRIMARY KEY (model_id, item_id));
|
||||||
|
```
|
||||||
|
|
||||||
|
However, you can only create indexes on rows with the same number of dimensions (using [expression](https://www.postgresql.org/docs/current/indexes-expressional.html) and [partial](https://www.postgresql.org/docs/current/indexes-partial.html) indexing):
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE INDEX ON embeddings USING hnsw ((embedding::vector(3)) vector_l2_ops) WHERE (model_id = 123);
|
||||||
|
```
|
||||||
|
|
||||||
|
and query with:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
SELECT * FROM embeddings WHERE model_id = 123 ORDER BY embedding::vector(3) <-> '[3,1,2]' LIMIT 5;
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Can I store vectors with more precision?
|
||||||
|
|
||||||
|
You can use the `double precision[]` or `numeric[]` type to store vectors with more precision.
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE TABLE items (id bigserial PRIMARY KEY, embedding double precision[]);
|
||||||
|
|
||||||
|
-- use {} instead of [] for Postgres arrays
|
||||||
|
INSERT INTO items (embedding) VALUES ('{1,2,3}'), ('{4,5,6}');
|
||||||
|
```
|
||||||
|
|
||||||
|
Optionally, add a [check constraint](https://www.postgresql.org/docs/current/ddl-constraints.html) to ensure data can be converted to the `vector` type and has the expected dimensions.
|
||||||
|
|
||||||
|
```sql
|
||||||
|
ALTER TABLE items ADD CHECK (vector_dims(embedding::vector) = 3);
|
||||||
|
```
|
||||||
|
|
||||||
|
Use [expression indexing](https://www.postgresql.org/docs/current/indexes-expressional.html) to index (at a lower precision):
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE INDEX ON items USING hnsw ((embedding::vector(3)) vector_l2_ops);
|
||||||
|
```
|
||||||
|
|
||||||
|
and query with:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
SELECT * FROM items ORDER BY embedding::vector(3) <-> '[3,1,2]' LIMIT 5;
|
||||||
|
```
|
||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
|
||||||
#### Why isn’t a query using an index?
|
#### Why isn’t a query using an index?
|
||||||
@@ -341,6 +493,8 @@ SELECT ...
|
|||||||
COMMIT;
|
COMMIT;
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Also, if the table is small, a table scan may be faster.
|
||||||
|
|
||||||
#### Why isn’t a query using a parallel table scan?
|
#### Why isn’t a query using a parallel table scan?
|
||||||
|
|
||||||
The planner doesn’t consider [out-of-line storage](https://www.postgresql.org/docs/current/storage-toast.html) in cost estimates, which can make a serial scan look cheaper. You can reduce the cost of a parallel scan for a query with:
|
The planner doesn’t consider [out-of-line storage](https://www.postgresql.org/docs/current/storage-toast.html) in cost estimates, which can make a serial scan look cheaper. You can reduce the cost of a parallel scan for a query with:
|
||||||
@@ -359,7 +513,7 @@ or choose to store vectors inline:
|
|||||||
ALTER TABLE items ALTER COLUMN embedding SET STORAGE PLAIN;
|
ALTER TABLE items ALTER COLUMN embedding SET STORAGE PLAIN;
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Why are there less results for a query after adding an index?
|
#### Why are there less results for a query after adding an IVFFlat index?
|
||||||
|
|
||||||
The index was likely created with too little data for the number of lists. Drop the index until the table has more data.
|
The index was likely created with too little data for the number of lists. Drop the index until the table has more data.
|
||||||
|
|
||||||
@@ -375,32 +529,32 @@ Each vector takes `4 * dimensions + 8` bytes of storage. Each element is a singl
|
|||||||
|
|
||||||
### Vector Operators
|
### Vector Operators
|
||||||
|
|
||||||
Operator | Description
|
Operator | Description | Added
|
||||||
--- | ---
|
--- | --- | ---
|
||||||
\+ | element-wise addition
|
\+ | element-wise addition |
|
||||||
\- | element-wise subtraction
|
\- | element-wise subtraction |
|
||||||
\* | element-wise multiplication [unreleased]
|
\* | element-wise multiplication | 0.5.0
|
||||||
<-> | Euclidean distance
|
<-> | Euclidean distance |
|
||||||
<#> | negative inner product
|
<#> | negative inner product |
|
||||||
<=> | cosine distance
|
<=> | cosine distance |
|
||||||
|
|
||||||
### Vector Functions
|
### Vector Functions
|
||||||
|
|
||||||
Function | Description
|
Function | Description | Added
|
||||||
--- | ---
|
--- | --- | ---
|
||||||
cosine_distance(vector, vector) → double precision | cosine distance
|
cosine_distance(vector, vector) → double precision | cosine distance |
|
||||||
inner_product(vector, vector) → double precision | inner product
|
inner_product(vector, vector) → double precision | inner product |
|
||||||
l2_distance(vector, vector) → double precision | Euclidean distance
|
l2_distance(vector, vector) → double precision | Euclidean distance |
|
||||||
l1_distance(vector, vector) → double precision | taxicab distance [unreleased]
|
l1_distance(vector, vector) → double precision | taxicab distance | 0.5.0
|
||||||
vector_dims(vector) → integer | number of dimensions
|
vector_dims(vector) → integer | number of dimensions |
|
||||||
vector_norm(vector) → double precision | Euclidean norm
|
vector_norm(vector) → double precision | Euclidean norm |
|
||||||
|
|
||||||
### Aggregate Functions
|
### Aggregate Functions
|
||||||
|
|
||||||
Function | Description
|
Function | Description | Added
|
||||||
--- | ---
|
--- | --- | ---
|
||||||
avg(vector) → vector | arithmetic mean
|
avg(vector) → vector | average |
|
||||||
sum(vector) → vector | sum [unreleased]
|
sum(vector) → vector | sum | 0.5.0
|
||||||
|
|
||||||
## Installation Notes
|
## Installation Notes
|
||||||
|
|
||||||
@@ -444,7 +598,7 @@ Then use `nmake` to build:
|
|||||||
|
|
||||||
```cmd
|
```cmd
|
||||||
set "PGROOT=C:\Program Files\PostgreSQL\15"
|
set "PGROOT=C:\Program Files\PostgreSQL\15"
|
||||||
git clone --branch v0.4.4 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
|
||||||
nmake /F Makefile.win install
|
nmake /F Makefile.win install
|
||||||
@@ -465,9 +619,8 @@ This adds pgvector to the [Postgres image](https://hub.docker.com/_/postgres) (r
|
|||||||
You can also build the image manually:
|
You can also build the image manually:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
git clone --branch v0.4.4 https://github.com/pgvector/pgvector.git
|
git clone --branch v0.5.1 https://github.com/pgvector/pgvector.git
|
||||||
cd pgvector
|
cd pgvector
|
||||||
git cherry-pick 237a6df
|
|
||||||
docker build --build-arg PG_MAJOR=15 -t myuser/pgvector .
|
docker build --build-arg PG_MAJOR=15 -t myuser/pgvector .
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -531,12 +684,18 @@ pgvector is available on [these providers](https://github.com/pgvector/pgvector/
|
|||||||
|
|
||||||
## Upgrading
|
## Upgrading
|
||||||
|
|
||||||
Install the latest version and run:
|
[Install](#installation) the latest version (use the same method as the original installation). Then in each database you want to upgrade, run:
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
ALTER EXTENSION vector UPDATE;
|
ALTER EXTENSION vector UPDATE;
|
||||||
```
|
```
|
||||||
|
|
||||||
|
You can check the version in the current database with:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
SELECT extversion FROM pg_extension WHERE extname = 'vector';
|
||||||
|
```
|
||||||
|
|
||||||
## Upgrade Notes
|
## Upgrade Notes
|
||||||
|
|
||||||
### 0.4.0
|
### 0.4.0
|
||||||
|
|||||||
2
sql/vector--0.5.0--0.5.1.sql
Normal file
2
sql/vector--0.5.0--0.5.1.sql
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
-- complain if script is sourced in psql, rather than via CREATE EXTENSION
|
||||||
|
\echo Use "ALTER EXTENSION vector UPDATE TO '0.5.1'" to load this file. \quit
|
||||||
79
sql/vector--0.5.1--0.6.0.sql
Normal file
79
sql/vector--0.5.1--0.6.0.sql
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
-- 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
|
||||||
|
|
||||||
|
CREATE TYPE svector;
|
||||||
|
|
||||||
|
CREATE FUNCTION svector_in(cstring, oid, integer) RETURNS svector
|
||||||
|
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||||
|
|
||||||
|
CREATE FUNCTION svector_out(svector) RETURNS cstring
|
||||||
|
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||||
|
|
||||||
|
CREATE FUNCTION svector_typmod_in(cstring[]) RETURNS integer
|
||||||
|
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||||
|
|
||||||
|
CREATE FUNCTION svector_recv(internal, oid, integer) RETURNS svector
|
||||||
|
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||||
|
|
||||||
|
CREATE FUNCTION svector_send(svector) RETURNS bytea
|
||||||
|
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||||
|
|
||||||
|
CREATE TYPE svector (
|
||||||
|
INPUT = svector_in,
|
||||||
|
OUTPUT = svector_out,
|
||||||
|
TYPMOD_IN = svector_typmod_in,
|
||||||
|
RECEIVE = svector_recv,
|
||||||
|
SEND = svector_send,
|
||||||
|
STORAGE = external
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE FUNCTION l2_distance(svector, svector) RETURNS float8
|
||||||
|
AS 'MODULE_PATHNAME', 'svector_l2_distance' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||||
|
|
||||||
|
CREATE FUNCTION inner_product(svector, svector) RETURNS float8
|
||||||
|
AS 'MODULE_PATHNAME', 'svector_inner_product' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||||
|
|
||||||
|
CREATE FUNCTION cosine_distance(svector, svector) RETURNS float8
|
||||||
|
AS 'MODULE_PATHNAME', 'svector_cosine_distance' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||||
|
|
||||||
|
CREATE FUNCTION jaccard_distance(svector, svector) RETURNS float8
|
||||||
|
AS 'MODULE_PATHNAME', 'svector_jaccard_distance' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||||
|
|
||||||
|
CREATE FUNCTION svector_l2_squared_distance(svector, svector) RETURNS float8
|
||||||
|
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||||
|
|
||||||
|
CREATE FUNCTION svector_negative_inner_product(svector, svector) RETURNS float8
|
||||||
|
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||||
|
|
||||||
|
CREATE FUNCTION svector(svector, integer, boolean) RETURNS svector
|
||||||
|
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||||
|
|
||||||
|
CREATE FUNCTION vector_to_svector(vector, integer, boolean) RETURNS svector
|
||||||
|
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||||
|
|
||||||
|
CREATE FUNCTION svector_to_vector(svector, integer, boolean) RETURNS vector
|
||||||
|
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||||
|
|
||||||
|
CREATE CAST (svector AS svector)
|
||||||
|
WITH FUNCTION svector(svector, integer, boolean) AS IMPLICIT;
|
||||||
|
|
||||||
|
CREATE CAST (svector AS vector)
|
||||||
|
WITH FUNCTION svector_to_vector(svector, integer, boolean) AS IMPLICIT;
|
||||||
|
|
||||||
|
CREATE CAST (vector AS svector)
|
||||||
|
WITH FUNCTION vector_to_svector(vector, integer, boolean) AS IMPLICIT;
|
||||||
|
|
||||||
|
CREATE OPERATOR <-> (
|
||||||
|
LEFTARG = svector, RIGHTARG = svector, PROCEDURE = l2_distance,
|
||||||
|
COMMUTATOR = '<->'
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE OPERATOR <#> (
|
||||||
|
LEFTARG = svector, RIGHTARG = svector, PROCEDURE = svector_negative_inner_product,
|
||||||
|
COMMUTATOR = '<#>'
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE OPERATOR <=> (
|
||||||
|
LEFTARG = svector, RIGHTARG = svector, PROCEDURE = cosine_distance,
|
||||||
|
COMMUTATOR = '<=>'
|
||||||
|
);
|
||||||
@@ -290,3 +290,92 @@ CREATE OPERATOR CLASS vector_cosine_ops
|
|||||||
OPERATOR 1 <=> (vector, vector) FOR ORDER BY float_ops,
|
OPERATOR 1 <=> (vector, vector) FOR ORDER BY float_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);
|
||||||
|
|
||||||
|
--- svector type
|
||||||
|
|
||||||
|
CREATE TYPE svector;
|
||||||
|
|
||||||
|
CREATE FUNCTION svector_in(cstring, oid, integer) RETURNS svector
|
||||||
|
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||||
|
|
||||||
|
CREATE FUNCTION svector_out(svector) RETURNS cstring
|
||||||
|
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||||
|
|
||||||
|
CREATE FUNCTION svector_typmod_in(cstring[]) RETURNS integer
|
||||||
|
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||||
|
|
||||||
|
CREATE FUNCTION svector_recv(internal, oid, integer) RETURNS svector
|
||||||
|
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||||
|
|
||||||
|
CREATE FUNCTION svector_send(svector) RETURNS bytea
|
||||||
|
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||||
|
|
||||||
|
CREATE TYPE svector (
|
||||||
|
INPUT = svector_in,
|
||||||
|
OUTPUT = svector_out,
|
||||||
|
TYPMOD_IN = svector_typmod_in,
|
||||||
|
RECEIVE = svector_recv,
|
||||||
|
SEND = svector_send,
|
||||||
|
STORAGE = external
|
||||||
|
);
|
||||||
|
|
||||||
|
-- svector functions
|
||||||
|
|
||||||
|
CREATE FUNCTION l2_distance(svector, svector) RETURNS float8
|
||||||
|
AS 'MODULE_PATHNAME', 'svector_l2_distance' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||||
|
|
||||||
|
CREATE FUNCTION inner_product(svector, svector) RETURNS float8
|
||||||
|
AS 'MODULE_PATHNAME', 'svector_inner_product' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||||
|
|
||||||
|
CREATE FUNCTION cosine_distance(svector, svector) RETURNS float8
|
||||||
|
AS 'MODULE_PATHNAME', 'svector_cosine_distance' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||||
|
|
||||||
|
CREATE FUNCTION jaccard_distance(svector, svector) RETURNS float8
|
||||||
|
AS 'MODULE_PATHNAME', 'svector_jaccard_distance' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||||
|
|
||||||
|
-- svector private functions
|
||||||
|
|
||||||
|
CREATE FUNCTION svector_l2_squared_distance(svector, svector) RETURNS float8
|
||||||
|
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||||
|
|
||||||
|
CREATE FUNCTION svector_negative_inner_product(svector, svector) RETURNS float8
|
||||||
|
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||||
|
|
||||||
|
-- svector cast functions
|
||||||
|
|
||||||
|
CREATE FUNCTION svector(svector, integer, boolean) RETURNS svector
|
||||||
|
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||||
|
|
||||||
|
CREATE FUNCTION vector_to_svector(vector, integer, boolean) RETURNS svector
|
||||||
|
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||||
|
|
||||||
|
CREATE FUNCTION svector_to_vector(svector, integer, boolean) RETURNS vector
|
||||||
|
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||||
|
|
||||||
|
-- svector casts
|
||||||
|
|
||||||
|
CREATE CAST (svector AS svector)
|
||||||
|
WITH FUNCTION svector(svector, integer, boolean) AS IMPLICIT;
|
||||||
|
|
||||||
|
CREATE CAST (svector AS vector)
|
||||||
|
WITH FUNCTION svector_to_vector(svector, integer, boolean) AS IMPLICIT;
|
||||||
|
|
||||||
|
CREATE CAST (vector AS svector)
|
||||||
|
WITH FUNCTION vector_to_svector(vector, integer, boolean) AS IMPLICIT;
|
||||||
|
|
||||||
|
-- svector operators
|
||||||
|
|
||||||
|
CREATE OPERATOR <-> (
|
||||||
|
LEFTARG = svector, RIGHTARG = svector, PROCEDURE = l2_distance,
|
||||||
|
COMMUTATOR = '<->'
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE OPERATOR <#> (
|
||||||
|
LEFTARG = svector, RIGHTARG = svector, PROCEDURE = svector_negative_inner_product,
|
||||||
|
COMMUTATOR = '<#>'
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE OPERATOR <=> (
|
||||||
|
LEFTARG = svector, RIGHTARG = svector, PROCEDURE = cosine_distance,
|
||||||
|
COMMUTATOR = '<=>'
|
||||||
|
);
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ hnswcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
|
|||||||
MemSet(&costs, 0, sizeof(costs));
|
MemSet(&costs, 0, sizeof(costs));
|
||||||
|
|
||||||
index = index_open(path->indexinfo->indexoid, NoLock);
|
index = index_open(path->indexinfo->indexoid, NoLock);
|
||||||
m = HnswGetM(index);
|
HnswGetMetaPageInfo(index, &m, NULL);
|
||||||
index_close(index, NoLock);
|
index_close(index, NoLock);
|
||||||
|
|
||||||
/* Approximate entry level */
|
/* Approximate entry level */
|
||||||
@@ -196,7 +196,7 @@ hnswhandler(PG_FUNCTION_ARGS)
|
|||||||
amroutine->aminsert = hnswinsert;
|
amroutine->aminsert = hnswinsert;
|
||||||
amroutine->ambulkdelete = hnswbulkdelete;
|
amroutine->ambulkdelete = hnswbulkdelete;
|
||||||
amroutine->amvacuumcleanup = hnswvacuumcleanup;
|
amroutine->amvacuumcleanup = hnswvacuumcleanup;
|
||||||
amroutine->amcanreturn = NULL; /* tuple not included in heapsort */
|
amroutine->amcanreturn = NULL;
|
||||||
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 */
|
||||||
|
|||||||
14
src/hnsw.h
14
src/hnsw.h
@@ -36,7 +36,7 @@
|
|||||||
#define HNSW_DEFAULT_M 16
|
#define HNSW_DEFAULT_M 16
|
||||||
#define HNSW_MIN_M 2
|
#define HNSW_MIN_M 2
|
||||||
#define HNSW_MAX_M 100
|
#define HNSW_MAX_M 100
|
||||||
#define HNSW_DEFAULT_EF_CONSTRUCTION 40
|
#define HNSW_DEFAULT_EF_CONSTRUCTION 64
|
||||||
#define HNSW_MIN_EF_CONSTRUCTION 4
|
#define HNSW_MIN_EF_CONSTRUCTION 4
|
||||||
#define HNSW_MAX_EF_CONSTRUCTION 1000
|
#define HNSW_MAX_EF_CONSTRUCTION 1000
|
||||||
#define HNSW_DEFAULT_EF_SEARCH 40
|
#define HNSW_DEFAULT_EF_SEARCH 40
|
||||||
@@ -57,6 +57,8 @@
|
|||||||
/* PROGRESS_CREATEIDX_SUBPHASE_INITIALIZE is 1 */
|
/* PROGRESS_CREATEIDX_SUBPHASE_INITIALIZE is 1 */
|
||||||
#define PROGRESS_HNSW_PHASE_LOAD 2
|
#define PROGRESS_HNSW_PHASE_LOAD 2
|
||||||
|
|
||||||
|
#define HNSW_MAX_SIZE (BLCKSZ - MAXALIGN(SizeOfPageHeaderData) - MAXALIGN(sizeof(HnswPageOpaqueData)) - sizeof(ItemIdData))
|
||||||
|
|
||||||
#define HNSW_ELEMENT_TUPLE_SIZE(_dim) MAXALIGN(offsetof(HnswElementTupleData, vec) + VECTOR_SIZE(_dim))
|
#define HNSW_ELEMENT_TUPLE_SIZE(_dim) MAXALIGN(offsetof(HnswElementTupleData, vec) + VECTOR_SIZE(_dim))
|
||||||
#define HNSW_NEIGHBOR_TUPLE_SIZE(level, m) MAXALIGN(offsetof(HnswNeighborTupleData, indextids) + ((level) + 2) * (m) * sizeof(ItemPointerData))
|
#define HNSW_NEIGHBOR_TUPLE_SIZE(level, m) MAXALIGN(offsetof(HnswNeighborTupleData, indextids) + ((level) + 2) * (m) * sizeof(ItemPointerData))
|
||||||
|
|
||||||
@@ -110,11 +112,13 @@ typedef struct HnswCandidate
|
|||||||
{
|
{
|
||||||
HnswElement element;
|
HnswElement element;
|
||||||
float distance;
|
float distance;
|
||||||
|
bool closer;
|
||||||
} HnswCandidate;
|
} HnswCandidate;
|
||||||
|
|
||||||
typedef struct HnswNeighborArray
|
typedef struct HnswNeighborArray
|
||||||
{
|
{
|
||||||
int length;
|
int length;
|
||||||
|
bool closerSet;
|
||||||
HnswCandidate *items;
|
HnswCandidate *items;
|
||||||
} HnswNeighborArray;
|
} HnswNeighborArray;
|
||||||
|
|
||||||
@@ -218,7 +222,6 @@ typedef HnswNeighborTupleData * HnswNeighborTuple;
|
|||||||
typedef struct HnswScanOpaqueData
|
typedef struct HnswScanOpaqueData
|
||||||
{
|
{
|
||||||
bool first;
|
bool first;
|
||||||
Buffer buf;
|
|
||||||
List *w;
|
List *w;
|
||||||
MemoryContext tmpCtx;
|
MemoryContext tmpCtx;
|
||||||
|
|
||||||
@@ -259,15 +262,16 @@ typedef struct HnswVacuumState
|
|||||||
/* Methods */
|
/* Methods */
|
||||||
int HnswGetM(Relation index);
|
int HnswGetM(Relation index);
|
||||||
int HnswGetEfConstruction(Relation index);
|
int HnswGetEfConstruction(Relation index);
|
||||||
FmgrInfo *HnswOptionalProcInfo(Relation rel, 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);
|
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 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, bool inserting, HnswElement skipElement);
|
List *HnswSearchLayer(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);
|
||||||
HnswElement HnswInitElement(ItemPointer tid, int m, double ml, int maxLevel);
|
HnswElement HnswInitElement(ItemPointer tid, int m, double ml, int maxLevel);
|
||||||
void HnswFreeElement(HnswElement element);
|
void HnswFreeElement(HnswElement element);
|
||||||
HnswElement HnswInitElementFromBlock(BlockNumber blkno, OffsetNumber offno);
|
HnswElement HnswInitElementFromBlock(BlockNumber blkno, OffsetNumber offno);
|
||||||
@@ -284,7 +288,7 @@ void HnswLoadElementFromTuple(HnswElement element, HnswElementTuple etup, bool
|
|||||||
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(HnswElementTuple etup, HnswElement element);
|
||||||
void HnswUpdateConnection(HnswElement element, HnswCandidate * hc, int m, int lc, int *updateIdx, Relation index, FmgrInfo *procinfo, Oid collation);
|
void HnswUpdateConnection(HnswElement element, HnswCandidate * hc, int m, int lc, int *updateIdx, Relation index, FmgrInfo *procinfo, Oid collation);
|
||||||
void HnswLoadNeighbors(HnswElement element, Relation index);
|
void HnswLoadNeighbors(HnswElement element, Relation index, int m);
|
||||||
|
|
||||||
/* Index access methods */
|
/* Index access methods */
|
||||||
IndexBuildResult *hnswbuild(Relation heap, Relation index, IndexInfo *indexInfo);
|
IndexBuildResult *hnswbuild(Relation heap, Relation index, IndexInfo *indexInfo);
|
||||||
|
|||||||
@@ -81,7 +81,6 @@ HnswBuildAppendPage(Relation index, Buffer *buf, Page *page, GenericXLogState **
|
|||||||
HnswPageGetOpaque(*page)->nextblkno = BufferGetBlockNumber(newbuf);
|
HnswPageGetOpaque(*page)->nextblkno = BufferGetBlockNumber(newbuf);
|
||||||
|
|
||||||
/* Commit */
|
/* Commit */
|
||||||
MarkBufferDirty(*buf);
|
|
||||||
GenericXLogFinish(*state);
|
GenericXLogFinish(*state);
|
||||||
UnlockReleaseBuffer(*buf);
|
UnlockReleaseBuffer(*buf);
|
||||||
|
|
||||||
@@ -118,12 +117,12 @@ CreateElementPages(HnswBuildState * buildstate)
|
|||||||
ListCell *lc;
|
ListCell *lc;
|
||||||
|
|
||||||
/* Calculate sizes */
|
/* Calculate sizes */
|
||||||
maxSize = BLCKSZ - MAXALIGN(SizeOfPageHeaderData) - MAXALIGN(sizeof(HnswPageOpaqueData));
|
maxSize = HNSW_MAX_SIZE;
|
||||||
etupSize = HNSW_ELEMENT_TUPLE_SIZE(dimensions);
|
etupSize = HNSW_ELEMENT_TUPLE_SIZE(dimensions);
|
||||||
|
|
||||||
/* Allocate once */
|
/* Allocate once */
|
||||||
etup = palloc0(etupSize);
|
etup = palloc0(etupSize);
|
||||||
ntup = palloc0(maxSize);
|
ntup = palloc0(BLCKSZ);
|
||||||
|
|
||||||
/* Prepare first page */
|
/* Prepare first page */
|
||||||
buf = HnswNewBuffer(index, forkNum);
|
buf = HnswNewBuffer(index, forkNum);
|
||||||
@@ -179,7 +178,6 @@ CreateElementPages(HnswBuildState * buildstate)
|
|||||||
insertPage = BufferGetBlockNumber(buf);
|
insertPage = BufferGetBlockNumber(buf);
|
||||||
|
|
||||||
/* Commit */
|
/* Commit */
|
||||||
MarkBufferDirty(buf);
|
|
||||||
GenericXLogFinish(state);
|
GenericXLogFinish(state);
|
||||||
UnlockReleaseBuffer(buf);
|
UnlockReleaseBuffer(buf);
|
||||||
|
|
||||||
@@ -227,7 +225,6 @@ CreateNeighborPages(HnswBuildState * buildstate)
|
|||||||
elog(ERROR, "failed to add index item to \"%s\"", RelationGetRelationName(index));
|
elog(ERROR, "failed to add index item to \"%s\"", RelationGetRelationName(index));
|
||||||
|
|
||||||
/* Commit */
|
/* Commit */
|
||||||
MarkBufferDirty(buf);
|
|
||||||
GenericXLogFinish(state);
|
GenericXLogFinish(state);
|
||||||
UnlockReleaseBuffer(buf);
|
UnlockReleaseBuffer(buf);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -135,7 +135,7 @@ WriteNewElementPages(Relation index, HnswElement e, int m, BlockNumber insertPag
|
|||||||
etupSize = HNSW_ELEMENT_TUPLE_SIZE(dimensions);
|
etupSize = HNSW_ELEMENT_TUPLE_SIZE(dimensions);
|
||||||
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 = BLCKSZ - MAXALIGN(SizeOfPageHeaderData) - MAXALIGN(sizeof(HnswPageOpaqueData));
|
maxSize = HNSW_MAX_SIZE;
|
||||||
minCombinedSize = etupSize + HNSW_NEIGHBOR_TUPLE_SIZE(0, m) + sizeof(ItemIdData);
|
minCombinedSize = etupSize + HNSW_NEIGHBOR_TUPLE_SIZE(0, m) + sizeof(ItemIdData);
|
||||||
|
|
||||||
/* Prepare element tuple */
|
/* Prepare element tuple */
|
||||||
@@ -202,8 +202,6 @@ WriteNewElementPages(Relation index, HnswElement e, int m, BlockNumber insertPag
|
|||||||
HnswInsertAppendPage(index, &newbuf, &newpage, state, page);
|
HnswInsertAppendPage(index, &newbuf, &newpage, state, page);
|
||||||
|
|
||||||
/* Commit */
|
/* Commit */
|
||||||
MarkBufferDirty(newbuf);
|
|
||||||
MarkBufferDirty(buf);
|
|
||||||
GenericXLogFinish(state);
|
GenericXLogFinish(state);
|
||||||
|
|
||||||
/* Unlock previous buffer */
|
/* Unlock previous buffer */
|
||||||
@@ -270,9 +268,6 @@ WriteNewElementPages(Relation index, HnswElement e, int m, BlockNumber insertPag
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Commit */
|
/* Commit */
|
||||||
MarkBufferDirty(buf);
|
|
||||||
if (nbuf != buf)
|
|
||||||
MarkBufferDirty(nbuf);
|
|
||||||
GenericXLogFinish(state);
|
GenericXLogFinish(state);
|
||||||
UnlockReleaseBuffer(buf);
|
UnlockReleaseBuffer(buf);
|
||||||
if (nbuf != buf)
|
if (nbuf != buf)
|
||||||
@@ -329,7 +324,7 @@ HnswUpdateNeighborPages(Relation index, FmgrInfo *procinfo, Oid collation, HnswE
|
|||||||
|
|
||||||
/* 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);
|
HnswLoadNeighbors(hc->element, index, m);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Could improve performance for vacuuming by checking neighbors
|
* Could improve performance for vacuuming by checking neighbors
|
||||||
@@ -391,7 +386,6 @@ 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 */
|
||||||
MarkBufferDirty(buf);
|
|
||||||
GenericXLogFinish(state);
|
GenericXLogFinish(state);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -445,7 +439,6 @@ HnswAddDuplicate(Relation index, HnswElement element, HnswElement dup)
|
|||||||
elog(ERROR, "failed to add index item to \"%s\"", RelationGetRelationName(index));
|
elog(ERROR, "failed to add index item to \"%s\"", RelationGetRelationName(index));
|
||||||
|
|
||||||
/* Commit */
|
/* Commit */
|
||||||
MarkBufferDirty(buf);
|
|
||||||
GenericXLogFinish(state);
|
GenericXLogFinish(state);
|
||||||
UnlockReleaseBuffer(buf);
|
UnlockReleaseBuffer(buf);
|
||||||
|
|
||||||
@@ -492,9 +485,8 @@ HnswInsertTuple(Relation index, Datum *values, bool *isnull, ItemPointer heap_ti
|
|||||||
FmgrInfo *normprocinfo;
|
FmgrInfo *normprocinfo;
|
||||||
HnswElement entryPoint;
|
HnswElement entryPoint;
|
||||||
HnswElement element;
|
HnswElement element;
|
||||||
int m = HnswGetM(index);
|
int m;
|
||||||
int efConstruction = HnswGetEfConstruction(index);
|
int efConstruction = HnswGetEfConstruction(index);
|
||||||
double ml = HnswGetMl(m);
|
|
||||||
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;
|
HnswElement dup;
|
||||||
@@ -511,10 +503,6 @@ HnswInsertTuple(Relation index, Datum *values, bool *isnull, ItemPointer heap_ti
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Create an element */
|
|
||||||
element = HnswInitElement(heap_tid, m, ml, HnswGetMaxLevel(m));
|
|
||||||
element->vec = DatumGetVector(value);
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* 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
|
||||||
* before repairing graph. Use a page lock so it does not interfere with
|
* before repairing graph. Use a page lock so it does not interfere with
|
||||||
@@ -522,8 +510,12 @@ HnswInsertTuple(Relation index, Datum *values, bool *isnull, ItemPointer heap_ti
|
|||||||
*/
|
*/
|
||||||
LockPage(index, HNSW_UPDATE_LOCK, lockmode);
|
LockPage(index, HNSW_UPDATE_LOCK, lockmode);
|
||||||
|
|
||||||
/* Get entry point */
|
/* Get m and entry point */
|
||||||
entryPoint = HnswGetEntryPoint(index);
|
HnswGetMetaPageInfo(index, &m, &entryPoint);
|
||||||
|
|
||||||
|
/* Create an element */
|
||||||
|
element = HnswInitElement(heap_tid, m, HnswGetMl(m), HnswGetMaxLevel(m));
|
||||||
|
element->vec = DatumGetVector(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)
|
||||||
|
|||||||
@@ -19,7 +19,11 @@ GetScanItems(IndexScanDesc scan, Datum q)
|
|||||||
Oid collation = so->collation;
|
Oid collation = so->collation;
|
||||||
List *ep;
|
List *ep;
|
||||||
List *w;
|
List *w;
|
||||||
HnswElement entryPoint = HnswGetEntryPoint(index);
|
int m;
|
||||||
|
HnswElement entryPoint;
|
||||||
|
|
||||||
|
/* Get m and entry point */
|
||||||
|
HnswGetMetaPageInfo(index, &m, &entryPoint);
|
||||||
|
|
||||||
if (entryPoint == NULL)
|
if (entryPoint == NULL)
|
||||||
return NIL;
|
return NIL;
|
||||||
@@ -28,11 +32,11 @@ GetScanItems(IndexScanDesc scan, Datum q)
|
|||||||
|
|
||||||
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, false, NULL);
|
w = HnswSearchLayer(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, false, NULL);
|
return HnswSearchLayer(q, ep, hnsw_ef_search, 0, index, procinfo, collation, m, false, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -58,6 +62,33 @@ GetDimensions(Relation index)
|
|||||||
return dimensions;
|
return dimensions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Get scan value
|
||||||
|
*/
|
||||||
|
static Datum
|
||||||
|
GetScanValue(IndexScanDesc scan)
|
||||||
|
{
|
||||||
|
HnswScanOpaque so = (HnswScanOpaque) scan->opaque;
|
||||||
|
Datum value;
|
||||||
|
|
||||||
|
if (scan->orderByData->sk_flags & SK_ISNULL)
|
||||||
|
value = PointerGetDatum(InitVector(GetDimensions(scan->indexRelation)));
|
||||||
|
else
|
||||||
|
{
|
||||||
|
value = scan->orderByData->sk_argument;
|
||||||
|
|
||||||
|
/* Value should not be compressed or toasted */
|
||||||
|
Assert(!VARATT_IS_COMPRESSED(DatumGetPointer(value)));
|
||||||
|
Assert(!VARATT_IS_EXTENDED(DatumGetPointer(value)));
|
||||||
|
|
||||||
|
/* Fine if normalization fails */
|
||||||
|
if (so->normprocinfo != NULL)
|
||||||
|
HnswNormValue(so->normprocinfo, so->collation, &value, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Prepare for an index scan
|
* Prepare for an index scan
|
||||||
*/
|
*/
|
||||||
@@ -70,7 +101,6 @@ hnswbeginscan(Relation index, int nkeys, int norderbys)
|
|||||||
scan = RelationGetIndexScan(index, nkeys, norderbys);
|
scan = RelationGetIndexScan(index, nkeys, norderbys);
|
||||||
|
|
||||||
so = (HnswScanOpaque) palloc(sizeof(HnswScanOpaqueData));
|
so = (HnswScanOpaque) palloc(sizeof(HnswScanOpaqueData));
|
||||||
so->buf = InvalidBuffer;
|
|
||||||
so->first = true;
|
so->first = true;
|
||||||
so->tmpCtx = AllocSetContextCreate(CurrentMemoryContext,
|
so->tmpCtx = AllocSetContextCreate(CurrentMemoryContext,
|
||||||
"Hnsw scan temporary context",
|
"Hnsw scan temporary context",
|
||||||
@@ -130,20 +160,13 @@ hnswgettuple(IndexScanDesc scan, ScanDirection dir)
|
|||||||
if (scan->orderByData == NULL)
|
if (scan->orderByData == NULL)
|
||||||
elog(ERROR, "cannot scan hnsw index without order");
|
elog(ERROR, "cannot scan hnsw index without order");
|
||||||
|
|
||||||
if (scan->orderByData->sk_flags & SK_ISNULL)
|
/* Requires MVCC-compliant snapshot as not able to maintain a pin */
|
||||||
value = PointerGetDatum(InitVector(GetDimensions(scan->indexRelation)));
|
/* https://www.postgresql.org/docs/current/index-locking.html */
|
||||||
else
|
if (!IsMVCCSnapshot(scan->xs_snapshot))
|
||||||
{
|
elog(ERROR, "non-MVCC snapshots are not supported with hnsw");
|
||||||
value = scan->orderByData->sk_argument;
|
|
||||||
|
|
||||||
/* Value should not be compressed or toasted */
|
/* Get scan value */
|
||||||
Assert(!VARATT_IS_COMPRESSED(DatumGetPointer(value)));
|
value = GetScanValue(scan);
|
||||||
Assert(!VARATT_IS_EXTENDED(DatumGetPointer(value)));
|
|
||||||
|
|
||||||
/* Fine if normalization fails */
|
|
||||||
if (so->normprocinfo != NULL)
|
|
||||||
HnswNormValue(so->normprocinfo, so->collation, &value, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Get a shared lock. This allows vacuum to ensure no in-flight scans
|
* Get a shared lock. This allows vacuum to ensure no in-flight scans
|
||||||
@@ -162,40 +185,27 @@ hnswgettuple(IndexScanDesc scan, ScanDirection dir)
|
|||||||
while (list_length(so->w) > 0)
|
while (list_length(so->w) > 0)
|
||||||
{
|
{
|
||||||
HnswCandidate *hc = llast(so->w);
|
HnswCandidate *hc = llast(so->w);
|
||||||
ItemPointer tid;
|
ItemPointer heaptid;
|
||||||
BlockNumber indexblkno;
|
|
||||||
|
|
||||||
/* 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 (list_length(hc->element->heaptids) == 0)
|
||||||
{
|
{
|
||||||
so->w = list_delete_last(so->w);
|
so->w = list_delete_last(so->w);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
tid = llast(hc->element->heaptids);
|
heaptid = llast(hc->element->heaptids);
|
||||||
indexblkno = hc->element->blkno;
|
|
||||||
|
|
||||||
hc->element->heaptids = list_delete_last(hc->element->heaptids);
|
hc->element->heaptids = list_delete_last(hc->element->heaptids);
|
||||||
|
|
||||||
MemoryContextSwitchTo(oldCtx);
|
MemoryContextSwitchTo(oldCtx);
|
||||||
|
|
||||||
#if PG_VERSION_NUM >= 120000
|
#if PG_VERSION_NUM >= 120000
|
||||||
scan->xs_heaptid = *tid;
|
scan->xs_heaptid = *heaptid;
|
||||||
#else
|
#else
|
||||||
scan->xs_ctup.t_self = *tid;
|
scan->xs_ctup.t_self = *heaptid;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (BufferIsValid(so->buf))
|
|
||||||
ReleaseBuffer(so->buf);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* An index scan must maintain a pin on the index page holding the
|
|
||||||
* item last returned by amgettuple
|
|
||||||
*
|
|
||||||
* https://www.postgresql.org/docs/current/index-locking.html
|
|
||||||
*/
|
|
||||||
so->buf = ReadBuffer(scan->indexRelation, indexblkno);
|
|
||||||
|
|
||||||
scan->xs_recheckorderby = false;
|
scan->xs_recheckorderby = false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -212,10 +222,6 @@ hnswendscan(IndexScanDesc scan)
|
|||||||
{
|
{
|
||||||
HnswScanOpaque so = (HnswScanOpaque) scan->opaque;
|
HnswScanOpaque so = (HnswScanOpaque) scan->opaque;
|
||||||
|
|
||||||
/* Release pin */
|
|
||||||
if (BufferIsValid(so->buf))
|
|
||||||
ReleaseBuffer(so->buf);
|
|
||||||
|
|
||||||
MemoryContextDelete(so->tmpCtx);
|
MemoryContextDelete(so->tmpCtx);
|
||||||
|
|
||||||
pfree(so);
|
pfree(so);
|
||||||
|
|||||||
205
src/hnswutils.c
205
src/hnswutils.c
@@ -38,12 +38,12 @@ HnswGetEfConstruction(Relation index)
|
|||||||
* Get proc
|
* Get proc
|
||||||
*/
|
*/
|
||||||
FmgrInfo *
|
FmgrInfo *
|
||||||
HnswOptionalProcInfo(Relation rel, uint16 procnum)
|
HnswOptionalProcInfo(Relation index, uint16 procnum)
|
||||||
{
|
{
|
||||||
if (!OidIsValid(index_getprocid(rel, 1, procnum)))
|
if (!OidIsValid(index_getprocid(index, 1, procnum)))
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
return index_getprocinfo(rel, 1, procnum);
|
return index_getprocinfo(index, 1, procnum);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -117,7 +117,6 @@ HnswInitRegisterPage(Relation index, Buffer *buf, Page *page, GenericXLogState *
|
|||||||
void
|
void
|
||||||
HnswCommitBuffer(Buffer buf, GenericXLogState *state)
|
HnswCommitBuffer(Buffer buf, GenericXLogState *state)
|
||||||
{
|
{
|
||||||
MarkBufferDirty(buf);
|
|
||||||
GenericXLogFinish(state);
|
GenericXLogFinish(state);
|
||||||
UnlockReleaseBuffer(buf);
|
UnlockReleaseBuffer(buf);
|
||||||
}
|
}
|
||||||
@@ -140,9 +139,21 @@ HnswInitNeighbors(HnswElement element, int m)
|
|||||||
a = &element->neighbors[lc];
|
a = &element->neighbors[lc];
|
||||||
a->length = 0;
|
a->length = 0;
|
||||||
a->items = palloc(sizeof(HnswCandidate) * lm);
|
a->items = palloc(sizeof(HnswCandidate) * lm);
|
||||||
|
a->closerSet = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Free neighbors
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
HnswFreeNeighbors(HnswElement element)
|
||||||
|
{
|
||||||
|
for (int lc = 0; lc <= element->level; lc++)
|
||||||
|
pfree(element->neighbors[lc].items);
|
||||||
|
pfree(element->neighbors);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Allocate an element
|
* Allocate an element
|
||||||
*/
|
*/
|
||||||
@@ -174,10 +185,8 @@ HnswInitElement(ItemPointer heaptid, int m, double ml, int maxLevel)
|
|||||||
void
|
void
|
||||||
HnswFreeElement(HnswElement element)
|
HnswFreeElement(HnswElement element)
|
||||||
{
|
{
|
||||||
|
HnswFreeNeighbors(element);
|
||||||
list_free_deep(element->heaptids);
|
list_free_deep(element->heaptids);
|
||||||
for (int lc = 0; lc <= element->level; lc++)
|
|
||||||
pfree(element->neighbors[lc].items);
|
|
||||||
pfree(element->neighbors);
|
|
||||||
pfree(element->vec);
|
pfree(element->vec);
|
||||||
pfree(element);
|
pfree(element);
|
||||||
}
|
}
|
||||||
@@ -210,25 +219,43 @@ HnswInitElementFromBlock(BlockNumber blkno, OffsetNumber offno)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Get the entry point
|
* Get the metapage info
|
||||||
*/
|
*/
|
||||||
HnswElement
|
void
|
||||||
HnswGetEntryPoint(Relation index)
|
HnswGetMetaPageInfo(Relation index, int *m, HnswElement * entryPoint)
|
||||||
{
|
{
|
||||||
Buffer buf;
|
Buffer buf;
|
||||||
Page page;
|
Page page;
|
||||||
HnswMetaPage metap;
|
HnswMetaPage metap;
|
||||||
HnswElement entryPoint = NULL;
|
|
||||||
|
|
||||||
buf = ReadBuffer(index, HNSW_METAPAGE_BLKNO);
|
buf = ReadBuffer(index, HNSW_METAPAGE_BLKNO);
|
||||||
LockBuffer(buf, BUFFER_LOCK_SHARE);
|
LockBuffer(buf, BUFFER_LOCK_SHARE);
|
||||||
page = BufferGetPage(buf);
|
page = BufferGetPage(buf);
|
||||||
metap = HnswPageGetMeta(page);
|
metap = HnswPageGetMeta(page);
|
||||||
|
|
||||||
if (BlockNumberIsValid(metap->entryBlkno))
|
if (m != NULL)
|
||||||
entryPoint = HnswInitElementFromBlock(metap->entryBlkno, metap->entryOffno);
|
*m = metap->m;
|
||||||
|
|
||||||
|
if (entryPoint != NULL)
|
||||||
|
{
|
||||||
|
if (BlockNumberIsValid(metap->entryBlkno))
|
||||||
|
*entryPoint = HnswInitElementFromBlock(metap->entryBlkno, metap->entryOffno);
|
||||||
|
else
|
||||||
|
*entryPoint = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
UnlockReleaseBuffer(buf);
|
UnlockReleaseBuffer(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Get the entry point
|
||||||
|
*/
|
||||||
|
HnswElement
|
||||||
|
HnswGetEntryPoint(Relation index)
|
||||||
|
{
|
||||||
|
HnswElement entryPoint;
|
||||||
|
|
||||||
|
HnswGetMetaPageInfo(index, NULL, &entryPoint);
|
||||||
|
|
||||||
return entryPoint;
|
return entryPoint;
|
||||||
}
|
}
|
||||||
@@ -337,10 +364,9 @@ HnswSetNeighborTuple(HnswNeighborTuple ntup, HnswElement e, int m)
|
|||||||
* Load neighbors from page
|
* Load neighbors from page
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
LoadNeighborsFromPage(HnswElement element, Relation index, Page page)
|
LoadNeighborsFromPage(HnswElement element, Relation index, Page page, int m)
|
||||||
{
|
{
|
||||||
HnswNeighborTuple ntup = (HnswNeighborTuple) PageGetItem(page, PageGetItemId(page, element->neighborOffno));
|
HnswNeighborTuple ntup = (HnswNeighborTuple) PageGetItem(page, PageGetItemId(page, element->neighborOffno));
|
||||||
int m = HnswGetM(index);
|
|
||||||
int neighborCount = (element->level + 2) * m;
|
int neighborCount = (element->level + 2) * m;
|
||||||
|
|
||||||
Assert(HnswIsNeighborTuple(ntup));
|
Assert(HnswIsNeighborTuple(ntup));
|
||||||
@@ -381,7 +407,7 @@ LoadNeighborsFromPage(HnswElement element, Relation index, Page page)
|
|||||||
* Load neighbors
|
* Load neighbors
|
||||||
*/
|
*/
|
||||||
void
|
void
|
||||||
HnswLoadNeighbors(HnswElement element, Relation index)
|
HnswLoadNeighbors(HnswElement element, Relation index, int m)
|
||||||
{
|
{
|
||||||
Buffer buf;
|
Buffer buf;
|
||||||
Page page;
|
Page page;
|
||||||
@@ -390,7 +416,7 @@ HnswLoadNeighbors(HnswElement element, Relation index)
|
|||||||
LockBuffer(buf, BUFFER_LOCK_SHARE);
|
LockBuffer(buf, BUFFER_LOCK_SHARE);
|
||||||
page = BufferGetPage(buf);
|
page = BufferGetPage(buf);
|
||||||
|
|
||||||
LoadNeighborsFromPage(element, index, page);
|
LoadNeighborsFromPage(element, index, page, m);
|
||||||
|
|
||||||
UnlockReleaseBuffer(buf);
|
UnlockReleaseBuffer(buf);
|
||||||
}
|
}
|
||||||
@@ -543,7 +569,7 @@ AddToVisited(HTAB *v, HnswCandidate * hc, Relation index, bool *found)
|
|||||||
* Algorithm 2 from paper
|
* Algorithm 2 from paper
|
||||||
*/
|
*/
|
||||||
List *
|
List *
|
||||||
HnswSearchLayer(Datum q, List *ep, int ef, int lc, Relation index, FmgrInfo *procinfo, Oid collation, bool inserting, HnswElement skipElement)
|
HnswSearchLayer(Datum q, List *ep, int ef, int lc, Relation index, FmgrInfo *procinfo, Oid collation, int m, bool inserting, HnswElement skipElement)
|
||||||
{
|
{
|
||||||
ListCell *lc2;
|
ListCell *lc2;
|
||||||
|
|
||||||
@@ -598,7 +624,7 @@ HnswSearchLayer(Datum q, List *ep, int ef, int lc, Relation index, FmgrInfo *pro
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
if (c->element->neighbors == NULL)
|
if (c->element->neighbors == NULL)
|
||||||
HnswLoadNeighbors(c->element, index);
|
HnswLoadNeighbors(c->element, index, m);
|
||||||
|
|
||||||
/* Get the neighborhood at layer lc */
|
/* Get the neighborhood at layer lc */
|
||||||
neighborhood = &c->element->neighbors[lc];
|
neighborhood = &c->element->neighbors[lc];
|
||||||
@@ -623,10 +649,6 @@ HnswSearchLayer(Datum q, List *ep, int ef, int lc, Relation index, FmgrInfo *pro
|
|||||||
|
|
||||||
Assert(!e->element->deleted);
|
Assert(!e->element->deleted);
|
||||||
|
|
||||||
/* Skip self for vacuuming update */
|
|
||||||
if (skipElement != NULL && e->element->blkno == skipElement->blkno && e->element->offno == skipElement->offno)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
/* Make robust to issues */
|
/* Make robust to issues */
|
||||||
if (e->element->level < lc)
|
if (e->element->level < lc)
|
||||||
continue;
|
continue;
|
||||||
@@ -671,6 +693,34 @@ HnswSearchLayer(Datum q, List *ep, int ef, int lc, Relation index, FmgrInfo *pro
|
|||||||
return w;
|
return w;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Compare candidate distances
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
#if PG_VERSION_NUM >= 130000
|
||||||
|
CompareCandidateDistances(const ListCell *a, const ListCell *b)
|
||||||
|
#else
|
||||||
|
CompareCandidateDistances(const void *a, const void *b)
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
HnswCandidate *hca = lfirst((ListCell *) a);
|
||||||
|
HnswCandidate *hcb = lfirst((ListCell *) b);
|
||||||
|
|
||||||
|
if (hca->distance < hcb->distance)
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
if (hca->distance > hcb->distance)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
if (hca->element < hcb->element)
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
if (hca->element > hcb->element)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Calculate the distance between elements
|
* Calculate the distance between elements
|
||||||
*/
|
*/
|
||||||
@@ -727,33 +777,77 @@ CheckElementCloser(HnswCandidate * e, List *r, int lc, FmgrInfo *procinfo, Oid c
|
|||||||
* Algorithm 4 from paper
|
* Algorithm 4 from paper
|
||||||
*/
|
*/
|
||||||
static List *
|
static List *
|
||||||
SelectNeighbors(List *c, int m, int lc, FmgrInfo *procinfo, Oid collation, HnswCandidate * *pruned)
|
SelectNeighbors(List *c, int m, int lc, FmgrInfo *procinfo, Oid collation, HnswElement e2, HnswCandidate * newCandidate, HnswCandidate * *pruned, bool sortCandidates)
|
||||||
{
|
{
|
||||||
List *r = NIL;
|
List *r = NIL;
|
||||||
List *w = list_copy(c);
|
List *w = list_copy(c);
|
||||||
pairingheap *wd;
|
pairingheap *wd;
|
||||||
|
bool mustCalculate = !e2->neighbors[lc].closerSet;
|
||||||
|
List *added = NIL;
|
||||||
|
bool removedAny = false;
|
||||||
|
|
||||||
if (list_length(w) <= m)
|
if (list_length(w) <= m)
|
||||||
return w;
|
return w;
|
||||||
|
|
||||||
wd = pairingheap_allocate(CompareNearestCandidates, NULL);
|
wd = pairingheap_allocate(CompareNearestCandidates, NULL);
|
||||||
|
|
||||||
|
/* Ensure order of candidates is deterministic for closer caching */
|
||||||
|
if (sortCandidates)
|
||||||
|
list_sort(w, CompareCandidateDistances);
|
||||||
|
|
||||||
while (list_length(w) > 0 && list_length(r) < m)
|
while (list_length(w) > 0 && list_length(r) < m)
|
||||||
{
|
{
|
||||||
/* Assumes w is already ordered desc */
|
/* Assumes w is already ordered desc */
|
||||||
HnswCandidate *e = llast(w);
|
HnswCandidate *e = llast(w);
|
||||||
bool closer;
|
|
||||||
|
|
||||||
w = list_delete_last(w);
|
w = list_delete_last(w);
|
||||||
|
|
||||||
closer = CheckElementCloser(e, r, lc, procinfo, collation);
|
/* Use previous state of r and wd to skip work when possible */
|
||||||
|
if (mustCalculate)
|
||||||
|
e->closer = CheckElementCloser(e, r, lc, procinfo, collation);
|
||||||
|
else if (list_length(added) > 0)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* If the current candidate was closer, we only need to compare it
|
||||||
|
* with the other candidates that we have added.
|
||||||
|
*/
|
||||||
|
if (e->closer)
|
||||||
|
{
|
||||||
|
e->closer = CheckElementCloser(e, added, lc, procinfo, collation);
|
||||||
|
|
||||||
if (closer)
|
if (!e->closer)
|
||||||
|
removedAny = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* If we have removed any candidates from closer, a candidate
|
||||||
|
* that was not closer earlier might now be.
|
||||||
|
*/
|
||||||
|
if (removedAny)
|
||||||
|
{
|
||||||
|
e->closer = CheckElementCloser(e, r, lc, procinfo, collation);
|
||||||
|
if (e->closer)
|
||||||
|
added = lappend(added, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (e == newCandidate)
|
||||||
|
{
|
||||||
|
e->closer = CheckElementCloser(e, r, lc, procinfo, collation);
|
||||||
|
if (e->closer)
|
||||||
|
added = lappend(added, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e->closer)
|
||||||
r = lappend(r, e);
|
r = lappend(r, e);
|
||||||
else
|
else
|
||||||
pairingheap_add(wd, &(CreatePairingHeapNode(e)->ph_node));
|
pairingheap_add(wd, &(CreatePairingHeapNode(e)->ph_node));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Cached value can only be used in future if sorted deterministically */
|
||||||
|
e2->neighbors[lc].closerSet = sortCandidates;
|
||||||
|
|
||||||
/* Keep pruned connections */
|
/* Keep pruned connections */
|
||||||
while (!pairingheap_is_empty(wd) && list_length(r) < m)
|
while (!pairingheap_is_empty(wd) && list_length(r) < m)
|
||||||
r = lappend(r, ((HnswPairingHeapNode *) pairingheap_remove_first(wd))->inner);
|
r = lappend(r, ((HnswPairingHeapNode *) pairingheap_remove_first(wd))->inner);
|
||||||
@@ -807,28 +901,6 @@ AddConnections(HnswElement element, List *neighbors, int m, int lc)
|
|||||||
a->items[a->length++] = *((HnswCandidate *) lfirst(lc2));
|
a->items[a->length++] = *((HnswCandidate *) lfirst(lc2));
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* Compare candidate distances
|
|
||||||
*/
|
|
||||||
static int
|
|
||||||
#if PG_VERSION_NUM >= 130000
|
|
||||||
CompareCandidateDistances(const ListCell *a, const ListCell *b)
|
|
||||||
#else
|
|
||||||
CompareCandidateDistances(const void *a, const void *b)
|
|
||||||
#endif
|
|
||||||
{
|
|
||||||
HnswCandidate *hca = lfirst((ListCell *) a);
|
|
||||||
HnswCandidate *hcb = lfirst((ListCell *) b);
|
|
||||||
|
|
||||||
if (hca->distance < hcb->distance)
|
|
||||||
return 1;
|
|
||||||
|
|
||||||
if (hca->distance > hcb->distance)
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Update connections
|
* Update connections
|
||||||
*/
|
*/
|
||||||
@@ -882,13 +954,12 @@ HnswUpdateConnection(HnswElement element, HnswCandidate * hc, int m, int lc, int
|
|||||||
{
|
{
|
||||||
List *c = NIL;
|
List *c = NIL;
|
||||||
|
|
||||||
/* Add and sort candidates */
|
/* Add candidates */
|
||||||
for (int i = 0; i < currentNeighbors->length; i++)
|
for (int i = 0; i < currentNeighbors->length; i++)
|
||||||
c = lappend(c, ¤tNeighbors->items[i]);
|
c = lappend(c, ¤tNeighbors->items[i]);
|
||||||
c = lappend(c, &hc2);
|
c = lappend(c, &hc2);
|
||||||
list_sort(c, CompareCandidateDistances);
|
|
||||||
|
|
||||||
SelectNeighbors(c, m, lc, procinfo, collation, &pruned);
|
SelectNeighbors(c, m, lc, procinfo, collation, hc->element, &hc2, &pruned, true);
|
||||||
|
|
||||||
/* Should not happen */
|
/* Should not happen */
|
||||||
if (pruned == NULL)
|
if (pruned == NULL)
|
||||||
@@ -913,10 +984,10 @@ HnswUpdateConnection(HnswElement element, HnswCandidate * hc, int m, int lc, int
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Remove elements being deleted
|
* Remove elements being deleted or skipped
|
||||||
*/
|
*/
|
||||||
static List *
|
static List *
|
||||||
RemoveElementsBeingDeleted(List *w)
|
RemoveElements(List *w, HnswElement skipElement)
|
||||||
{
|
{
|
||||||
ListCell *lc2;
|
ListCell *lc2;
|
||||||
List *w2 = NIL;
|
List *w2 = NIL;
|
||||||
@@ -925,6 +996,10 @@ RemoveElementsBeingDeleted(List *w)
|
|||||||
{
|
{
|
||||||
HnswCandidate *hc = (HnswCandidate *) lfirst(lc2);
|
HnswCandidate *hc = (HnswCandidate *) lfirst(lc2);
|
||||||
|
|
||||||
|
/* Skip self for vacuuming update */
|
||||||
|
if (skipElement != NULL && hc->element->blkno == skipElement->blkno && hc->element->offno == skipElement->offno)
|
||||||
|
continue;
|
||||||
|
|
||||||
if (list_length(hc->element->heaptids) != 0)
|
if (list_length(hc->element->heaptids) != 0)
|
||||||
w2 = lappend(w2, hc);
|
w2 = lappend(w2, hc);
|
||||||
}
|
}
|
||||||
@@ -956,27 +1031,39 @@ HnswInsertElement(HnswElement element, HnswElement entryPoint, Relation index, F
|
|||||||
/* 1st phase: greedy search to insert level */
|
/* 1st phase: greedy search to insert level */
|
||||||
for (int lc = entryLevel; lc >= level + 1; lc--)
|
for (int lc = entryLevel; lc >= level + 1; lc--)
|
||||||
{
|
{
|
||||||
w = HnswSearchLayer(q, ep, 1, lc, index, procinfo, collation, true, skipElement);
|
w = HnswSearchLayer(q, ep, 1, lc, index, procinfo, collation, m, true, skipElement);
|
||||||
ep = w;
|
ep = w;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (level > entryLevel)
|
if (level > entryLevel)
|
||||||
level = entryLevel;
|
level = entryLevel;
|
||||||
|
|
||||||
|
/* Add one for existing element */
|
||||||
|
if (existing)
|
||||||
|
efConstruction++;
|
||||||
|
|
||||||
/* 2nd phase */
|
/* 2nd phase */
|
||||||
for (int lc = level; lc >= 0; lc--)
|
for (int lc = level; lc >= 0; lc--)
|
||||||
{
|
{
|
||||||
int lm = HnswGetLayerM(m, lc);
|
int lm = HnswGetLayerM(m, lc);
|
||||||
List *neighbors;
|
List *neighbors;
|
||||||
|
List *lw;
|
||||||
|
|
||||||
w = HnswSearchLayer(q, ep, efConstruction, lc, index, procinfo, collation, true, skipElement);
|
w = HnswSearchLayer(q, ep, efConstruction, lc, index, procinfo, collation, m, true, skipElement);
|
||||||
|
|
||||||
/* Elements being deleted can help with search */
|
/* Elements being deleted or skipped can help with search */
|
||||||
/* but should be removed before selecting neighbors */
|
/* but should be removed before selecting neighbors */
|
||||||
if (index != NULL)
|
if (index != NULL)
|
||||||
w = RemoveElementsBeingDeleted(w);
|
lw = RemoveElements(w, skipElement);
|
||||||
|
else
|
||||||
|
lw = w;
|
||||||
|
|
||||||
neighbors = SelectNeighbors(w, lm, lc, procinfo, collation, NULL);
|
/*
|
||||||
|
* Candidates are sorted, but not deterministically. Could set
|
||||||
|
* sortCandidates to true for in-memory builds to enable closer
|
||||||
|
* caching, but there does not seem to be a difference in performance.
|
||||||
|
*/
|
||||||
|
neighbors = SelectNeighbors(lw, lm, lc, procinfo, collation, element, NULL, NULL, false);
|
||||||
|
|
||||||
AddConnections(element, neighbors, lm, lc);
|
AddConnections(element, neighbors, lm, lc);
|
||||||
|
|
||||||
|
|||||||
@@ -128,10 +128,7 @@ RemoveHeapTids(HnswVacuumState * vacuumstate)
|
|||||||
blkno = HnswPageGetOpaque(page)->nextblkno;
|
blkno = HnswPageGetOpaque(page)->nextblkno;
|
||||||
|
|
||||||
if (updated)
|
if (updated)
|
||||||
{
|
|
||||||
MarkBufferDirty(buf);
|
|
||||||
GenericXLogFinish(state);
|
GenericXLogFinish(state);
|
||||||
}
|
|
||||||
else
|
else
|
||||||
GenericXLogAbort(state);
|
GenericXLogAbort(state);
|
||||||
|
|
||||||
@@ -229,7 +226,6 @@ RepairGraphElement(HnswVacuumState * vacuumstate, HnswElement element, HnswEleme
|
|||||||
elog(ERROR, "failed to add index item to \"%s\"", RelationGetRelationName(index));
|
elog(ERROR, "failed to add index item to \"%s\"", RelationGetRelationName(index));
|
||||||
|
|
||||||
/* Commit */
|
/* Commit */
|
||||||
MarkBufferDirty(buf);
|
|
||||||
GenericXLogFinish(state);
|
GenericXLogFinish(state);
|
||||||
UnlockReleaseBuffer(buf);
|
UnlockReleaseBuffer(buf);
|
||||||
|
|
||||||
@@ -330,7 +326,10 @@ RepairGraph(HnswVacuumState * vacuumstate)
|
|||||||
BufferAccessStrategy bas = vacuumstate->bas;
|
BufferAccessStrategy bas = vacuumstate->bas;
|
||||||
BlockNumber blkno = HNSW_HEAD_BLKNO;
|
BlockNumber blkno = HNSW_HEAD_BLKNO;
|
||||||
|
|
||||||
/* Wait for inserts to complete */
|
/*
|
||||||
|
* Wait for inserts to complete. Inserts before this point may have
|
||||||
|
* neighbors about to be deleted. Inserts after this point will not.
|
||||||
|
*/
|
||||||
LockPage(index, HNSW_UPDATE_LOCK, ExclusiveLock);
|
LockPage(index, HNSW_UPDATE_LOCK, ExclusiveLock);
|
||||||
UnlockPage(index, HNSW_UPDATE_LOCK, ExclusiveLock);
|
UnlockPage(index, HNSW_UPDATE_LOCK, ExclusiveLock);
|
||||||
|
|
||||||
@@ -443,7 +442,11 @@ MarkDeleted(HnswVacuumState * vacuumstate)
|
|||||||
Relation index = vacuumstate->index;
|
Relation index = vacuumstate->index;
|
||||||
BufferAccessStrategy bas = vacuumstate->bas;
|
BufferAccessStrategy bas = vacuumstate->bas;
|
||||||
|
|
||||||
/* Wait for selects to complete */
|
/*
|
||||||
|
* Wait for index scans to complete. Scans before this point may contain
|
||||||
|
* tuples about to be deleted. Scans after this point will not, since the
|
||||||
|
* graph has been repaired.
|
||||||
|
*/
|
||||||
LockPage(index, HNSW_SCAN_LOCK, ExclusiveLock);
|
LockPage(index, HNSW_SCAN_LOCK, ExclusiveLock);
|
||||||
UnlockPage(index, HNSW_SCAN_LOCK, ExclusiveLock);
|
UnlockPage(index, HNSW_SCAN_LOCK, ExclusiveLock);
|
||||||
|
|
||||||
@@ -540,9 +543,6 @@ MarkDeleted(HnswVacuumState * vacuumstate)
|
|||||||
elog(ERROR, "failed to add index item to \"%s\"", RelationGetRelationName(index));
|
elog(ERROR, "failed to add index item to \"%s\"", RelationGetRelationName(index));
|
||||||
|
|
||||||
/* Commit */
|
/* Commit */
|
||||||
MarkBufferDirty(buf);
|
|
||||||
if (nbuf != buf)
|
|
||||||
MarkBufferDirty(nbuf);
|
|
||||||
GenericXLogFinish(state);
|
GenericXLogFinish(state);
|
||||||
if (nbuf != buf)
|
if (nbuf != buf)
|
||||||
UnlockReleaseBuffer(nbuf);
|
UnlockReleaseBuffer(nbuf);
|
||||||
@@ -582,7 +582,6 @@ InitVacuumState(HnswVacuumState * vacuumstate, IndexVacuumInfo *info, IndexBulkD
|
|||||||
vacuumstate->stats = stats;
|
vacuumstate->stats = stats;
|
||||||
vacuumstate->callback = callback;
|
vacuumstate->callback = callback;
|
||||||
vacuumstate->callback_state = callback_state;
|
vacuumstate->callback_state = callback_state;
|
||||||
vacuumstate->m = HnswGetM(index);
|
|
||||||
vacuumstate->efConstruction = HnswGetEfConstruction(index);
|
vacuumstate->efConstruction = HnswGetEfConstruction(index);
|
||||||
vacuumstate->bas = GetAccessStrategy(BAS_BULKREAD);
|
vacuumstate->bas = GetAccessStrategy(BAS_BULKREAD);
|
||||||
vacuumstate->procinfo = index_getprocinfo(index, 1, HNSW_DISTANCE_PROC);
|
vacuumstate->procinfo = index_getprocinfo(index, 1, HNSW_DISTANCE_PROC);
|
||||||
@@ -592,6 +591,9 @@ InitVacuumState(HnswVacuumState * vacuumstate, IndexVacuumInfo *info, IndexBulkD
|
|||||||
"Hnsw vacuum temporary context",
|
"Hnsw vacuum temporary context",
|
||||||
ALLOCSET_DEFAULT_SIZES);
|
ALLOCSET_DEFAULT_SIZES);
|
||||||
|
|
||||||
|
/* Get m from metapage */
|
||||||
|
HnswGetMetaPageInfo(index, &vacuumstate->m, NULL);
|
||||||
|
|
||||||
/* Create hash table */
|
/* Create hash table */
|
||||||
hash_ctl.keysize = sizeof(ItemPointerData);
|
hash_ctl.keysize = sizeof(ItemPointerData);
|
||||||
hash_ctl.entrysize = sizeof(ItemPointerData);
|
hash_ctl.entrysize = sizeof(ItemPointerData);
|
||||||
@@ -611,67 +613,6 @@ FreeVacuumState(HnswVacuumState * vacuumstate)
|
|||||||
MemoryContextDelete(vacuumstate->tmpCtx);
|
MemoryContextDelete(vacuumstate->tmpCtx);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* Print graph
|
|
||||||
*/
|
|
||||||
#ifdef HNSW_DEBUG
|
|
||||||
static void
|
|
||||||
PrintGraph(HnswVacuumState * vacuumstate)
|
|
||||||
{
|
|
||||||
BlockNumber blkno = HNSW_HEAD_BLKNO;
|
|
||||||
Relation index = vacuumstate->index;
|
|
||||||
|
|
||||||
while (BlockNumberIsValid(blkno))
|
|
||||||
{
|
|
||||||
Buffer buf;
|
|
||||||
Page page;
|
|
||||||
OffsetNumber offno;
|
|
||||||
OffsetNumber maxoffno;
|
|
||||||
|
|
||||||
buf = ReadBuffer(index, blkno);
|
|
||||||
LockBuffer(buf, BUFFER_LOCK_SHARE);
|
|
||||||
page = BufferGetPage(buf);
|
|
||||||
maxoffno = PageGetMaxOffsetNumber(page);
|
|
||||||
|
|
||||||
for (offno = FirstOffsetNumber; offno <= maxoffno; offno = OffsetNumberNext(offno))
|
|
||||||
{
|
|
||||||
HnswElementTuple etup = (HnswElementTuple) PageGetItem(page, PageGetItemId(page, offno));
|
|
||||||
HnswElement element;
|
|
||||||
|
|
||||||
/* Skip neighbor tuples */
|
|
||||||
if (!HnswIsElementTuple(etup))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
/* Skip deleted tuples */
|
|
||||||
if (etup->deleted)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
element = HnswInitElementFromBlock(blkno, offno);
|
|
||||||
HnswLoadElementFromTuple(element, etup, false, true);
|
|
||||||
HnswLoadNeighbors(element, index);
|
|
||||||
|
|
||||||
elog(INFO, "element (%d,%d)", element->blkno, element->offno);
|
|
||||||
|
|
||||||
for (int lc = element->level; lc >= 0; lc--)
|
|
||||||
{
|
|
||||||
HnswNeighborArray *neighbors = &element->neighbors[lc];
|
|
||||||
|
|
||||||
for (int i = 0; i < neighbors->length; i++)
|
|
||||||
{
|
|
||||||
HnswElement e = neighbors->items[i].element;
|
|
||||||
|
|
||||||
elog(INFO, "%d: (%d,%d)", lc, e->blkno, e->offno);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
blkno = HnswPageGetOpaque(page)->nextblkno;
|
|
||||||
|
|
||||||
UnlockReleaseBuffer(buf);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Bulk delete tuples from the index
|
* Bulk delete tuples from the index
|
||||||
*/
|
*/
|
||||||
@@ -692,10 +633,6 @@ hnswbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
|
|||||||
/* Pass 3: Mark as deleted */
|
/* Pass 3: Mark as deleted */
|
||||||
MarkDeleted(&vacuumstate);
|
MarkDeleted(&vacuumstate);
|
||||||
|
|
||||||
#ifdef HNSW_DEBUG
|
|
||||||
PrintGraph(&vacuumstate);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
FreeVacuumState(&vacuumstate);
|
FreeVacuumState(&vacuumstate);
|
||||||
|
|
||||||
return vacuumstate.stats;
|
return vacuumstate.stats;
|
||||||
|
|||||||
@@ -10,8 +10,8 @@
|
|||||||
#include "ivfflat.h"
|
#include "ivfflat.h"
|
||||||
#include "miscadmin.h"
|
#include "miscadmin.h"
|
||||||
#include "storage/bufmgr.h"
|
#include "storage/bufmgr.h"
|
||||||
#include "utils/memutils.h"
|
|
||||||
#include "tcop/tcopprot.h"
|
#include "tcop/tcopprot.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"
|
||||||
@@ -506,29 +506,30 @@ CreateListPages(Relation index, VectorArray centers, int dimensions,
|
|||||||
Buffer buf;
|
Buffer buf;
|
||||||
Page page;
|
Page page;
|
||||||
GenericXLogState *state;
|
GenericXLogState *state;
|
||||||
OffsetNumber offno;
|
Size listSize;
|
||||||
Size itemsz;
|
|
||||||
IvfflatList list;
|
IvfflatList list;
|
||||||
|
|
||||||
itemsz = MAXALIGN(IVFFLAT_LIST_SIZE(dimensions));
|
listSize = MAXALIGN(IVFFLAT_LIST_SIZE(dimensions));
|
||||||
list = palloc(itemsz);
|
list = palloc(listSize);
|
||||||
|
|
||||||
buf = IvfflatNewBuffer(index, forkNum);
|
buf = IvfflatNewBuffer(index, forkNum);
|
||||||
IvfflatInitRegisterPage(index, &buf, &page, &state);
|
IvfflatInitRegisterPage(index, &buf, &page, &state);
|
||||||
|
|
||||||
for (int i = 0; i < lists; i++)
|
for (int i = 0; i < lists; i++)
|
||||||
{
|
{
|
||||||
|
OffsetNumber offno;
|
||||||
|
|
||||||
/* Load list */
|
/* Load list */
|
||||||
list->startPage = InvalidBlockNumber;
|
list->startPage = InvalidBlockNumber;
|
||||||
list->insertPage = InvalidBlockNumber;
|
list->insertPage = InvalidBlockNumber;
|
||||||
memcpy(&list->center, VectorArrayGet(centers, i), VECTOR_SIZE(dimensions));
|
memcpy(&list->center, VectorArrayGet(centers, i), VECTOR_SIZE(dimensions));
|
||||||
|
|
||||||
/* Ensure free space */
|
/* Ensure free space */
|
||||||
if (PageGetFreeSpace(page) < itemsz)
|
if (PageGetFreeSpace(page) < listSize)
|
||||||
IvfflatAppendPage(index, &buf, &page, &state, forkNum);
|
IvfflatAppendPage(index, &buf, &page, &state, forkNum);
|
||||||
|
|
||||||
/* Add the item */
|
/* Add the item */
|
||||||
offno = PageAddItem(page, (Item) list, itemsz, InvalidOffsetNumber, false, false);
|
offno = PageAddItem(page, (Item) list, listSize, InvalidOffsetNumber, false, false);
|
||||||
if (offno == InvalidOffsetNumber)
|
if (offno == InvalidOffsetNumber)
|
||||||
elog(ERROR, "failed to add index item to \"%s\"", RelationGetRelationName(index));
|
elog(ERROR, "failed to add index item to \"%s\"", RelationGetRelationName(index));
|
||||||
|
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ ivfflatcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
|
|||||||
int lists;
|
int lists;
|
||||||
double ratio;
|
double ratio;
|
||||||
double spc_seq_page_cost;
|
double spc_seq_page_cost;
|
||||||
Relation indexRel;
|
Relation index;
|
||||||
#if PG_VERSION_NUM < 120000
|
#if PG_VERSION_NUM < 120000
|
||||||
List *qinfos;
|
List *qinfos;
|
||||||
#endif
|
#endif
|
||||||
@@ -89,9 +89,9 @@ ivfflatcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
|
|||||||
|
|
||||||
MemSet(&costs, 0, sizeof(costs));
|
MemSet(&costs, 0, sizeof(costs));
|
||||||
|
|
||||||
indexRel = index_open(path->indexinfo->indexoid, NoLock);
|
index = index_open(path->indexinfo->indexoid, NoLock);
|
||||||
lists = IvfflatGetLists(indexRel);
|
IvfflatGetMetaPageInfo(index, &lists, NULL);
|
||||||
index_close(indexRel, NoLock);
|
index_close(index, NoLock);
|
||||||
|
|
||||||
/* Get the ratio of lists that we need to visit */
|
/* Get the ratio of lists that we need to visit */
|
||||||
ratio = ((double) ivfflat_probes) / lists;
|
ratio = ((double) ivfflat_probes) / lists;
|
||||||
|
|||||||
@@ -244,8 +244,8 @@ typedef struct IvfflatScanList
|
|||||||
typedef struct IvfflatScanOpaqueData
|
typedef struct IvfflatScanOpaqueData
|
||||||
{
|
{
|
||||||
int probes;
|
int probes;
|
||||||
|
int dimensions;
|
||||||
bool first;
|
bool first;
|
||||||
Buffer buf;
|
|
||||||
|
|
||||||
/* Sorting */
|
/* Sorting */
|
||||||
Tuplesortstate *sortstate;
|
Tuplesortstate *sortstate;
|
||||||
@@ -275,9 +275,10 @@ VectorArray VectorArrayInit(int maxlen, int dimensions);
|
|||||||
void VectorArrayFree(VectorArray arr);
|
void VectorArrayFree(VectorArray arr);
|
||||||
void PrintVectorArray(char *msg, VectorArray arr);
|
void PrintVectorArray(char *msg, VectorArray arr);
|
||||||
void IvfflatKmeans(Relation index, VectorArray samples, VectorArray centers);
|
void IvfflatKmeans(Relation index, VectorArray samples, VectorArray centers);
|
||||||
FmgrInfo *IvfflatOptionalProcInfo(Relation rel, uint16 procnum);
|
FmgrInfo *IvfflatOptionalProcInfo(Relation index, uint16 procnum);
|
||||||
bool IvfflatNormValue(FmgrInfo *procinfo, Oid collation, Datum *value, Vector * result);
|
bool IvfflatNormValue(FmgrInfo *procinfo, Oid collation, Datum *value, Vector * result);
|
||||||
int IvfflatGetLists(Relation index);
|
int IvfflatGetLists(Relation index);
|
||||||
|
void IvfflatGetMetaPageInfo(Relation index, int *lists, int *dimensions);
|
||||||
void IvfflatUpdateList(Relation index, ListInfo listInfo, BlockNumber insertPage, BlockNumber originalInsertPage, BlockNumber startPage, ForkNumber forkNum);
|
void IvfflatUpdateList(Relation index, ListInfo listInfo, BlockNumber insertPage, BlockNumber originalInsertPage, BlockNumber startPage, ForkNumber forkNum);
|
||||||
void IvfflatCommitBuffer(Buffer buf, GenericXLogState *state);
|
void IvfflatCommitBuffer(Buffer buf, GenericXLogState *state);
|
||||||
void IvfflatAppendPage(Relation index, Buffer *buf, Page *page, GenericXLogState **state, ForkNumber forkNum);
|
void IvfflatAppendPage(Relation index, Buffer *buf, Page *page, GenericXLogState **state, ForkNumber forkNum);
|
||||||
|
|||||||
@@ -11,36 +11,37 @@
|
|||||||
* Find the list that minimizes the distance function
|
* Find the list that minimizes the distance function
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
FindInsertPage(Relation rel, Datum *values, BlockNumber *insertPage, ListInfo * listInfo)
|
FindInsertPage(Relation index, Datum *values, BlockNumber *insertPage, ListInfo * listInfo)
|
||||||
{
|
{
|
||||||
Buffer cbuf;
|
|
||||||
Page cpage;
|
|
||||||
IvfflatList list;
|
|
||||||
double distance;
|
|
||||||
double minDistance = DBL_MAX;
|
double minDistance = DBL_MAX;
|
||||||
BlockNumber nextblkno = IVFFLAT_HEAD_BLKNO;
|
BlockNumber nextblkno = IVFFLAT_HEAD_BLKNO;
|
||||||
FmgrInfo *procinfo;
|
FmgrInfo *procinfo;
|
||||||
Oid collation;
|
Oid collation;
|
||||||
OffsetNumber offno;
|
|
||||||
OffsetNumber maxoffno;
|
|
||||||
|
|
||||||
/* Avoid compiler warning */
|
/* Avoid compiler warning */
|
||||||
listInfo->blkno = nextblkno;
|
listInfo->blkno = nextblkno;
|
||||||
listInfo->offno = FirstOffsetNumber;
|
listInfo->offno = FirstOffsetNumber;
|
||||||
|
|
||||||
procinfo = index_getprocinfo(rel, 1, IVFFLAT_DISTANCE_PROC);
|
procinfo = index_getprocinfo(index, 1, IVFFLAT_DISTANCE_PROC);
|
||||||
collation = rel->rd_indcollation[0];
|
collation = index->rd_indcollation[0];
|
||||||
|
|
||||||
/* Search all list pages */
|
/* Search all list pages */
|
||||||
while (BlockNumberIsValid(nextblkno))
|
while (BlockNumberIsValid(nextblkno))
|
||||||
{
|
{
|
||||||
cbuf = ReadBuffer(rel, nextblkno);
|
Buffer cbuf;
|
||||||
|
Page cpage;
|
||||||
|
OffsetNumber maxoffno;
|
||||||
|
|
||||||
|
cbuf = ReadBuffer(index, nextblkno);
|
||||||
LockBuffer(cbuf, BUFFER_LOCK_SHARE);
|
LockBuffer(cbuf, BUFFER_LOCK_SHARE);
|
||||||
cpage = BufferGetPage(cbuf);
|
cpage = BufferGetPage(cbuf);
|
||||||
maxoffno = PageGetMaxOffsetNumber(cpage);
|
maxoffno = PageGetMaxOffsetNumber(cpage);
|
||||||
|
|
||||||
for (offno = FirstOffsetNumber; offno <= maxoffno; offno = OffsetNumberNext(offno))
|
for (OffsetNumber offno = FirstOffsetNumber; offno <= maxoffno; offno = OffsetNumberNext(offno))
|
||||||
{
|
{
|
||||||
|
IvfflatList list;
|
||||||
|
double distance;
|
||||||
|
|
||||||
list = (IvfflatList) PageGetItem(cpage, PageGetItemId(cpage, offno));
|
list = (IvfflatList) PageGetItem(cpage, PageGetItemId(cpage, offno));
|
||||||
distance = DatumGetFloat8(FunctionCall2Coll(procinfo, collation, values[0], PointerGetDatum(&list->center)));
|
distance = DatumGetFloat8(FunctionCall2Coll(procinfo, collation, values[0], PointerGetDatum(&list->center)));
|
||||||
|
|
||||||
@@ -63,7 +64,7 @@ FindInsertPage(Relation rel, Datum *values, BlockNumber *insertPage, ListInfo *
|
|||||||
* Insert a tuple into the index
|
* Insert a tuple into the index
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
InsertTuple(Relation rel, Datum *values, bool *isnull, ItemPointer heap_tid, Relation heapRel)
|
InsertTuple(Relation index, Datum *values, bool *isnull, ItemPointer heap_tid, Relation heapRel)
|
||||||
{
|
{
|
||||||
IndexTuple itup;
|
IndexTuple itup;
|
||||||
Datum value;
|
Datum value;
|
||||||
@@ -80,33 +81,33 @@ InsertTuple(Relation rel, Datum *values, bool *isnull, ItemPointer heap_tid, Rel
|
|||||||
value = PointerGetDatum(PG_DETOAST_DATUM(values[0]));
|
value = PointerGetDatum(PG_DETOAST_DATUM(values[0]));
|
||||||
|
|
||||||
/* Normalize if needed */
|
/* Normalize if needed */
|
||||||
normprocinfo = IvfflatOptionalProcInfo(rel, IVFFLAT_NORM_PROC);
|
normprocinfo = IvfflatOptionalProcInfo(index, IVFFLAT_NORM_PROC);
|
||||||
if (normprocinfo != NULL)
|
if (normprocinfo != NULL)
|
||||||
{
|
{
|
||||||
if (!IvfflatNormValue(normprocinfo, rel->rd_indcollation[0], &value, NULL))
|
if (!IvfflatNormValue(normprocinfo, index->rd_indcollation[0], &value, NULL))
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Find the insert page - sets the page and list info */
|
/* Find the insert page - sets the page and list info */
|
||||||
FindInsertPage(rel, values, &insertPage, &listInfo);
|
FindInsertPage(index, values, &insertPage, &listInfo);
|
||||||
Assert(BlockNumberIsValid(insertPage));
|
Assert(BlockNumberIsValid(insertPage));
|
||||||
originalInsertPage = insertPage;
|
originalInsertPage = insertPage;
|
||||||
|
|
||||||
/* Form tuple */
|
/* Form tuple */
|
||||||
itup = index_form_tuple(RelationGetDescr(rel), &value, isnull);
|
itup = index_form_tuple(RelationGetDescr(index), &value, isnull);
|
||||||
itup->t_tid = *heap_tid;
|
itup->t_tid = *heap_tid;
|
||||||
|
|
||||||
/* Get tuple size */
|
/* Get tuple size */
|
||||||
itemsz = MAXALIGN(IndexTupleSize(itup));
|
itemsz = MAXALIGN(IndexTupleSize(itup));
|
||||||
Assert(itemsz <= BLCKSZ - MAXALIGN(SizeOfPageHeaderData) - MAXALIGN(sizeof(IvfflatPageOpaqueData)));
|
Assert(itemsz <= BLCKSZ - MAXALIGN(SizeOfPageHeaderData) - MAXALIGN(sizeof(IvfflatPageOpaqueData)) - sizeof(ItemIdData));
|
||||||
|
|
||||||
/* Find a page to insert the item */
|
/* Find a page to insert the item */
|
||||||
for (;;)
|
for (;;)
|
||||||
{
|
{
|
||||||
buf = ReadBuffer(rel, insertPage);
|
buf = ReadBuffer(index, insertPage);
|
||||||
LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
|
LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
|
||||||
|
|
||||||
state = GenericXLogStart(rel);
|
state = GenericXLogStart(index);
|
||||||
page = GenericXLogRegisterBuffer(state, buf, 0);
|
page = GenericXLogRegisterBuffer(state, buf, 0);
|
||||||
|
|
||||||
if (PageGetFreeSpace(page) >= itemsz)
|
if (PageGetFreeSpace(page) >= itemsz)
|
||||||
@@ -126,9 +127,9 @@ InsertTuple(Relation rel, Datum *values, bool *isnull, ItemPointer heap_tid, Rel
|
|||||||
Page newpage;
|
Page newpage;
|
||||||
|
|
||||||
/* Add a new page */
|
/* Add a new page */
|
||||||
LockRelationForExtension(rel, ExclusiveLock);
|
LockRelationForExtension(index, ExclusiveLock);
|
||||||
newbuf = IvfflatNewBuffer(rel, MAIN_FORKNUM);
|
newbuf = IvfflatNewBuffer(index, MAIN_FORKNUM);
|
||||||
UnlockRelationForExtension(rel, ExclusiveLock);
|
UnlockRelationForExtension(index, ExclusiveLock);
|
||||||
|
|
||||||
/* Init new page */
|
/* Init new page */
|
||||||
newpage = GenericXLogRegisterBuffer(state, newbuf, GENERIC_XLOG_FULL_IMAGE);
|
newpage = GenericXLogRegisterBuffer(state, newbuf, GENERIC_XLOG_FULL_IMAGE);
|
||||||
@@ -141,15 +142,13 @@ InsertTuple(Relation rel, Datum *values, bool *isnull, ItemPointer heap_tid, Rel
|
|||||||
IvfflatPageGetOpaque(page)->nextblkno = insertPage;
|
IvfflatPageGetOpaque(page)->nextblkno = insertPage;
|
||||||
|
|
||||||
/* Commit */
|
/* Commit */
|
||||||
MarkBufferDirty(newbuf);
|
|
||||||
MarkBufferDirty(buf);
|
|
||||||
GenericXLogFinish(state);
|
GenericXLogFinish(state);
|
||||||
|
|
||||||
/* Unlock previous buffer */
|
/* Unlock previous buffer */
|
||||||
UnlockReleaseBuffer(buf);
|
UnlockReleaseBuffer(buf);
|
||||||
|
|
||||||
/* Prepare new buffer */
|
/* Prepare new buffer */
|
||||||
state = GenericXLogStart(rel);
|
state = GenericXLogStart(index);
|
||||||
buf = newbuf;
|
buf = newbuf;
|
||||||
page = GenericXLogRegisterBuffer(state, buf, 0);
|
page = GenericXLogRegisterBuffer(state, buf, 0);
|
||||||
break;
|
break;
|
||||||
@@ -158,13 +157,13 @@ InsertTuple(Relation rel, Datum *values, bool *isnull, ItemPointer heap_tid, Rel
|
|||||||
|
|
||||||
/* Add to next offset */
|
/* Add to next offset */
|
||||||
if (PageAddItem(page, (Item) itup, itemsz, InvalidOffsetNumber, false, false) == InvalidOffsetNumber)
|
if (PageAddItem(page, (Item) itup, itemsz, InvalidOffsetNumber, false, false) == InvalidOffsetNumber)
|
||||||
elog(ERROR, "failed to add index item to \"%s\"", RelationGetRelationName(rel));
|
elog(ERROR, "failed to add index item to \"%s\"", RelationGetRelationName(index));
|
||||||
|
|
||||||
IvfflatCommitBuffer(buf, state);
|
IvfflatCommitBuffer(buf, state);
|
||||||
|
|
||||||
/* Update the insert page */
|
/* Update the insert page */
|
||||||
if (insertPage != originalInsertPage)
|
if (insertPage != originalInsertPage)
|
||||||
IvfflatUpdateList(rel, listInfo, insertPage, originalInsertPage, InvalidBlockNumber, MAIN_FORKNUM);
|
IvfflatUpdateList(index, listInfo, insertPage, originalInsertPage, InvalidBlockNumber, MAIN_FORKNUM);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -17,10 +17,6 @@ InitCenters(Relation index, VectorArray samples, VectorArray centers, float *low
|
|||||||
FmgrInfo *procinfo;
|
FmgrInfo *procinfo;
|
||||||
Oid collation;
|
Oid collation;
|
||||||
int64 j;
|
int64 j;
|
||||||
double distance;
|
|
||||||
double sum;
|
|
||||||
double choice;
|
|
||||||
Vector *vec;
|
|
||||||
float *weight = palloc(samples->length * sizeof(float));
|
float *weight = palloc(samples->length * sizeof(float));
|
||||||
int numCenters = centers->maxlen;
|
int numCenters = centers->maxlen;
|
||||||
int numSamples = samples->length;
|
int numSamples = samples->length;
|
||||||
@@ -33,17 +29,21 @@ InitCenters(Relation index, VectorArray samples, VectorArray centers, float *low
|
|||||||
centers->length++;
|
centers->length++;
|
||||||
|
|
||||||
for (j = 0; j < numSamples; j++)
|
for (j = 0; j < numSamples; j++)
|
||||||
weight[j] = DBL_MAX;
|
weight[j] = FLT_MAX;
|
||||||
|
|
||||||
for (int i = 0; i < numCenters; i++)
|
for (int i = 0; i < numCenters; i++)
|
||||||
{
|
{
|
||||||
|
double sum;
|
||||||
|
double choice;
|
||||||
|
|
||||||
CHECK_FOR_INTERRUPTS();
|
CHECK_FOR_INTERRUPTS();
|
||||||
|
|
||||||
sum = 0.0;
|
sum = 0.0;
|
||||||
|
|
||||||
for (j = 0; j < numSamples; j++)
|
for (j = 0; j < numSamples; j++)
|
||||||
{
|
{
|
||||||
vec = VectorArrayGet(samples, j);
|
Vector *vec = VectorArrayGet(samples, j);
|
||||||
|
double distance;
|
||||||
|
|
||||||
/* Only need to compute distance for new center */
|
/* Only need to compute distance for new center */
|
||||||
/* TODO Use triangle inequality to reduce distance calculations */
|
/* TODO Use triangle inequality to reduce distance calculations */
|
||||||
@@ -112,7 +112,6 @@ CompareVectors(const void *a, const void *b)
|
|||||||
static void
|
static void
|
||||||
QuickCenters(Relation index, VectorArray samples, VectorArray centers)
|
QuickCenters(Relation index, VectorArray samples, VectorArray centers)
|
||||||
{
|
{
|
||||||
Vector *vec;
|
|
||||||
int dimensions = centers->dim;
|
int dimensions = centers->dim;
|
||||||
Oid collation = index->rd_indcollation[0];
|
Oid collation = index->rd_indcollation[0];
|
||||||
FmgrInfo *normprocinfo = IvfflatOptionalProcInfo(index, IVFFLAT_KMEANS_NORM_PROC);
|
FmgrInfo *normprocinfo = IvfflatOptionalProcInfo(index, IVFFLAT_KMEANS_NORM_PROC);
|
||||||
@@ -123,7 +122,7 @@ QuickCenters(Relation index, VectorArray samples, VectorArray centers)
|
|||||||
qsort(samples->items, samples->length, VECTOR_SIZE(samples->dim), CompareVectors);
|
qsort(samples->items, samples->length, VECTOR_SIZE(samples->dim), CompareVectors);
|
||||||
for (int i = 0; i < samples->length; i++)
|
for (int i = 0; i < samples->length; i++)
|
||||||
{
|
{
|
||||||
vec = VectorArrayGet(samples, i);
|
Vector *vec = VectorArrayGet(samples, i);
|
||||||
|
|
||||||
if (i == 0 || CompareVectors(vec, VectorArrayGet(samples, i - 1)) != 0)
|
if (i == 0 || CompareVectors(vec, VectorArrayGet(samples, i - 1)) != 0)
|
||||||
{
|
{
|
||||||
@@ -136,7 +135,7 @@ QuickCenters(Relation index, VectorArray samples, VectorArray centers)
|
|||||||
/* Fill remaining with random data */
|
/* Fill remaining with random data */
|
||||||
while (centers->length < centers->maxlen)
|
while (centers->length < centers->maxlen)
|
||||||
{
|
{
|
||||||
vec = VectorArrayGet(centers, centers->length);
|
Vector *vec = VectorArrayGet(centers, centers->length);
|
||||||
|
|
||||||
SET_VARSIZE(vec, VECTOR_SIZE(dimensions));
|
SET_VARSIZE(vec, VECTOR_SIZE(dimensions));
|
||||||
vec->dim = dimensions;
|
vec->dim = dimensions;
|
||||||
@@ -168,7 +167,6 @@ ElkanKmeans(Relation index, VectorArray samples, VectorArray centers)
|
|||||||
Oid collation;
|
Oid collation;
|
||||||
Vector *vec;
|
Vector *vec;
|
||||||
Vector *newCenter;
|
Vector *newCenter;
|
||||||
int iteration;
|
|
||||||
int64 j;
|
int64 j;
|
||||||
int64 k;
|
int64 k;
|
||||||
int dimensions = centers->dim;
|
int dimensions = centers->dim;
|
||||||
@@ -182,14 +180,6 @@ ElkanKmeans(Relation index, VectorArray samples, VectorArray centers)
|
|||||||
float *s;
|
float *s;
|
||||||
float *halfcdist;
|
float *halfcdist;
|
||||||
float *newcdist;
|
float *newcdist;
|
||||||
int changes;
|
|
||||||
double minDistance;
|
|
||||||
int closestCenter;
|
|
||||||
double distance;
|
|
||||||
bool rj;
|
|
||||||
bool rjreset;
|
|
||||||
double dxcx;
|
|
||||||
double dxc;
|
|
||||||
|
|
||||||
/* Calculate allocation sizes */
|
/* Calculate allocation sizes */
|
||||||
Size samplesSize = VECTOR_ARRAY_SIZE(samples->maxlen, samples->dim);
|
Size samplesSize = VECTOR_ARRAY_SIZE(samples->maxlen, samples->dim);
|
||||||
@@ -247,14 +237,14 @@ ElkanKmeans(Relation index, VectorArray samples, VectorArray centers)
|
|||||||
/* Assign each x to its closest initial center c(x) = argmin d(x,c) */
|
/* Assign each x to its closest initial center c(x) = argmin d(x,c) */
|
||||||
for (j = 0; j < numSamples; j++)
|
for (j = 0; j < numSamples; j++)
|
||||||
{
|
{
|
||||||
minDistance = DBL_MAX;
|
float minDistance = FLT_MAX;
|
||||||
closestCenter = 0;
|
int closestCenter = 0;
|
||||||
|
|
||||||
/* Find closest center */
|
/* Find closest center */
|
||||||
for (k = 0; k < numCenters; k++)
|
for (k = 0; k < numCenters; k++)
|
||||||
{
|
{
|
||||||
/* TODO Use Lemma 1 in k-means++ initialization */
|
/* TODO Use Lemma 1 in k-means++ initialization */
|
||||||
distance = lowerBound[j * numCenters + k];
|
float distance = lowerBound[j * numCenters + k];
|
||||||
|
|
||||||
if (distance < minDistance)
|
if (distance < minDistance)
|
||||||
{
|
{
|
||||||
@@ -268,13 +258,14 @@ ElkanKmeans(Relation index, VectorArray samples, VectorArray centers)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Give 500 iterations to converge */
|
/* Give 500 iterations to converge */
|
||||||
for (iteration = 0; iteration < 500; iteration++)
|
for (int iteration = 0; iteration < 500; iteration++)
|
||||||
{
|
{
|
||||||
|
int changes = 0;
|
||||||
|
bool rjreset;
|
||||||
|
|
||||||
/* Can take a while, so ensure we can interrupt */
|
/* Can take a while, so ensure we can interrupt */
|
||||||
CHECK_FOR_INTERRUPTS();
|
CHECK_FOR_INTERRUPTS();
|
||||||
|
|
||||||
changes = 0;
|
|
||||||
|
|
||||||
/* Step 1: For all centers, compute distance */
|
/* Step 1: For all centers, compute distance */
|
||||||
for (j = 0; j < numCenters; j++)
|
for (j = 0; j < numCenters; j++)
|
||||||
{
|
{
|
||||||
@@ -282,7 +273,8 @@ ElkanKmeans(Relation index, VectorArray samples, VectorArray centers)
|
|||||||
|
|
||||||
for (k = j + 1; k < numCenters; k++)
|
for (k = j + 1; k < numCenters; k++)
|
||||||
{
|
{
|
||||||
distance = 0.5 * DatumGetFloat8(FunctionCall2Coll(procinfo, collation, PointerGetDatum(vec), PointerGetDatum(VectorArrayGet(centers, k))));
|
float distance = 0.5 * DatumGetFloat8(FunctionCall2Coll(procinfo, collation, PointerGetDatum(vec), PointerGetDatum(VectorArrayGet(centers, k))));
|
||||||
|
|
||||||
halfcdist[j * numCenters + k] = distance;
|
halfcdist[j * numCenters + k] = distance;
|
||||||
halfcdist[k * numCenters + j] = distance;
|
halfcdist[k * numCenters + j] = distance;
|
||||||
}
|
}
|
||||||
@@ -291,10 +283,12 @@ ElkanKmeans(Relation index, VectorArray samples, VectorArray centers)
|
|||||||
/* For all centers c, compute s(c) */
|
/* For all centers c, compute s(c) */
|
||||||
for (j = 0; j < numCenters; j++)
|
for (j = 0; j < numCenters; j++)
|
||||||
{
|
{
|
||||||
minDistance = DBL_MAX;
|
float minDistance = FLT_MAX;
|
||||||
|
|
||||||
for (k = 0; k < numCenters; k++)
|
for (k = 0; k < numCenters; k++)
|
||||||
{
|
{
|
||||||
|
float distance;
|
||||||
|
|
||||||
if (j == k)
|
if (j == k)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
@@ -310,6 +304,8 @@ ElkanKmeans(Relation index, VectorArray samples, VectorArray centers)
|
|||||||
|
|
||||||
for (j = 0; j < numSamples; j++)
|
for (j = 0; j < numSamples; j++)
|
||||||
{
|
{
|
||||||
|
bool rj;
|
||||||
|
|
||||||
/* Step 2: Identify all points x such that u(x) <= s(c(x)) */
|
/* Step 2: Identify all points x such that u(x) <= s(c(x)) */
|
||||||
if (upperBound[j] <= s[closestCenters[j]])
|
if (upperBound[j] <= s[closestCenters[j]])
|
||||||
continue;
|
continue;
|
||||||
@@ -318,6 +314,8 @@ ElkanKmeans(Relation index, VectorArray samples, VectorArray centers)
|
|||||||
|
|
||||||
for (k = 0; k < numCenters; k++)
|
for (k = 0; k < numCenters; k++)
|
||||||
{
|
{
|
||||||
|
float dxcx;
|
||||||
|
|
||||||
/* Step 3: For all remaining points x and centers c */
|
/* Step 3: For all remaining points x and centers c */
|
||||||
if (k == closestCenters[j])
|
if (k == closestCenters[j])
|
||||||
continue;
|
continue;
|
||||||
@@ -347,7 +345,7 @@ ElkanKmeans(Relation index, VectorArray samples, VectorArray centers)
|
|||||||
/* Step 3b */
|
/* Step 3b */
|
||||||
if (dxcx > lowerBound[j * numCenters + k] || dxcx > halfcdist[closestCenters[j] * numCenters + k])
|
if (dxcx > lowerBound[j * numCenters + k] || dxcx > halfcdist[closestCenters[j] * numCenters + k])
|
||||||
{
|
{
|
||||||
dxc = DatumGetFloat8(FunctionCall2Coll(procinfo, collation, PointerGetDatum(vec), PointerGetDatum(VectorArrayGet(centers, k))));
|
float dxc = DatumGetFloat8(FunctionCall2Coll(procinfo, collation, PointerGetDatum(vec), PointerGetDatum(VectorArrayGet(centers, k))));
|
||||||
|
|
||||||
/* d(x,c) calculated */
|
/* d(x,c) calculated */
|
||||||
lowerBound[j * numCenters + k] = dxc;
|
lowerBound[j * numCenters + k] = dxc;
|
||||||
@@ -361,7 +359,6 @@ ElkanKmeans(Relation index, VectorArray samples, VectorArray centers)
|
|||||||
|
|
||||||
changes++;
|
changes++;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -378,6 +375,8 @@ ElkanKmeans(Relation index, VectorArray samples, VectorArray centers)
|
|||||||
|
|
||||||
for (j = 0; j < numSamples; j++)
|
for (j = 0; j < numSamples; j++)
|
||||||
{
|
{
|
||||||
|
int closestCenter;
|
||||||
|
|
||||||
vec = VectorArrayGet(samples, j);
|
vec = VectorArrayGet(samples, j);
|
||||||
closestCenter = closestCenters[j];
|
closestCenter = closestCenters[j];
|
||||||
|
|
||||||
@@ -426,7 +425,7 @@ ElkanKmeans(Relation index, VectorArray samples, VectorArray centers)
|
|||||||
{
|
{
|
||||||
for (k = 0; k < numCenters; k++)
|
for (k = 0; k < numCenters; k++)
|
||||||
{
|
{
|
||||||
distance = lowerBound[j * numCenters + k] - newcdist[k];
|
float distance = lowerBound[j * numCenters + k] - newcdist[k];
|
||||||
|
|
||||||
if (distance < 0)
|
if (distance < 0)
|
||||||
distance = 0;
|
distance = 0;
|
||||||
@@ -442,7 +441,7 @@ ElkanKmeans(Relation index, VectorArray samples, VectorArray centers)
|
|||||||
|
|
||||||
/* Step 7 */
|
/* Step 7 */
|
||||||
for (j = 0; j < numCenters; j++)
|
for (j = 0; j < numCenters; j++)
|
||||||
memcpy(VectorArrayGet(centers, j), VectorArrayGet(newCenters, j), VECTOR_SIZE(dimensions));
|
VectorArraySet(centers, j, VectorArrayGet(newCenters, j));
|
||||||
|
|
||||||
if (changes == 0 && iteration != 0)
|
if (changes == 0 && iteration != 0)
|
||||||
break;
|
break;
|
||||||
@@ -465,9 +464,6 @@ static void
|
|||||||
CheckCenters(Relation index, VectorArray centers)
|
CheckCenters(Relation index, VectorArray centers)
|
||||||
{
|
{
|
||||||
FmgrInfo *normprocinfo;
|
FmgrInfo *normprocinfo;
|
||||||
Oid collation;
|
|
||||||
Vector *vec;
|
|
||||||
double norm;
|
|
||||||
|
|
||||||
if (centers->length != centers->maxlen)
|
if (centers->length != centers->maxlen)
|
||||||
elog(ERROR, "Not enough centers. Please report a bug.");
|
elog(ERROR, "Not enough centers. Please report a bug.");
|
||||||
@@ -475,7 +471,7 @@ CheckCenters(Relation index, VectorArray centers)
|
|||||||
/* Ensure no NaN or infinite values */
|
/* Ensure no NaN or infinite values */
|
||||||
for (int i = 0; i < centers->length; i++)
|
for (int i = 0; i < centers->length; i++)
|
||||||
{
|
{
|
||||||
vec = VectorArrayGet(centers, i);
|
Vector *vec = VectorArrayGet(centers, i);
|
||||||
|
|
||||||
for (int j = 0; j < vec->dim; j++)
|
for (int j = 0; j < vec->dim; j++)
|
||||||
{
|
{
|
||||||
@@ -501,11 +497,12 @@ CheckCenters(Relation index, VectorArray centers)
|
|||||||
normprocinfo = IvfflatOptionalProcInfo(index, IVFFLAT_NORM_PROC);
|
normprocinfo = IvfflatOptionalProcInfo(index, IVFFLAT_NORM_PROC);
|
||||||
if (normprocinfo != NULL)
|
if (normprocinfo != NULL)
|
||||||
{
|
{
|
||||||
collation = index->rd_indcollation[0];
|
Oid collation = index->rd_indcollation[0];
|
||||||
|
|
||||||
for (int i = 0; i < centers->length; i++)
|
for (int i = 0; i < centers->length; i++)
|
||||||
{
|
{
|
||||||
norm = DatumGetFloat8(FunctionCall1Coll(normprocinfo, collation, PointerGetDatum(VectorArrayGet(centers, i))));
|
double norm = DatumGetFloat8(FunctionCall1Coll(normprocinfo, collation, PointerGetDatum(VectorArrayGet(centers, i))));
|
||||||
|
|
||||||
if (norm == 0)
|
if (norm == 0)
|
||||||
elog(ERROR, "Zero norm detected. Please report a bug.");
|
elog(ERROR, "Zero norm detected. Please report a bug.");
|
||||||
}
|
}
|
||||||
|
|||||||
113
src/ivfscan.c
113
src/ivfscan.c
@@ -31,36 +31,36 @@ CompareLists(const pairingheap_node *a, const pairingheap_node *b, void *arg)
|
|||||||
static void
|
static void
|
||||||
GetScanLists(IndexScanDesc scan, Datum value)
|
GetScanLists(IndexScanDesc scan, Datum value)
|
||||||
{
|
{
|
||||||
Buffer cbuf;
|
IvfflatScanOpaque so = (IvfflatScanOpaque) scan->opaque;
|
||||||
Page cpage;
|
|
||||||
IvfflatList list;
|
|
||||||
OffsetNumber offno;
|
|
||||||
OffsetNumber maxoffno;
|
|
||||||
BlockNumber nextblkno = IVFFLAT_HEAD_BLKNO;
|
BlockNumber nextblkno = IVFFLAT_HEAD_BLKNO;
|
||||||
int listCount = 0;
|
int listCount = 0;
|
||||||
IvfflatScanOpaque so = (IvfflatScanOpaque) scan->opaque;
|
|
||||||
double distance;
|
|
||||||
IvfflatScanList *scanlist;
|
|
||||||
double maxDistance = DBL_MAX;
|
double maxDistance = DBL_MAX;
|
||||||
|
|
||||||
/* Search all list pages */
|
/* Search all list pages */
|
||||||
while (BlockNumberIsValid(nextblkno))
|
while (BlockNumberIsValid(nextblkno))
|
||||||
{
|
{
|
||||||
|
Buffer cbuf;
|
||||||
|
Page cpage;
|
||||||
|
OffsetNumber maxoffno;
|
||||||
|
|
||||||
cbuf = ReadBuffer(scan->indexRelation, nextblkno);
|
cbuf = ReadBuffer(scan->indexRelation, nextblkno);
|
||||||
LockBuffer(cbuf, BUFFER_LOCK_SHARE);
|
LockBuffer(cbuf, BUFFER_LOCK_SHARE);
|
||||||
cpage = BufferGetPage(cbuf);
|
cpage = BufferGetPage(cbuf);
|
||||||
|
|
||||||
maxoffno = PageGetMaxOffsetNumber(cpage);
|
maxoffno = PageGetMaxOffsetNumber(cpage);
|
||||||
|
|
||||||
for (offno = FirstOffsetNumber; offno <= maxoffno; offno = OffsetNumberNext(offno))
|
for (OffsetNumber offno = FirstOffsetNumber; offno <= maxoffno; offno = OffsetNumberNext(offno))
|
||||||
{
|
{
|
||||||
list = (IvfflatList) PageGetItem(cpage, PageGetItemId(cpage, offno));
|
IvfflatList list = (IvfflatList) PageGetItem(cpage, PageGetItemId(cpage, offno));
|
||||||
|
double distance;
|
||||||
|
|
||||||
/* Use procinfo from the index instead of scan key for performance */
|
/* Use procinfo from the index instead of scan key for performance */
|
||||||
distance = DatumGetFloat8(FunctionCall2Coll(so->procinfo, so->collation, PointerGetDatum(&list->center), value));
|
distance = DatumGetFloat8(FunctionCall2Coll(so->procinfo, so->collation, PointerGetDatum(&list->center), value));
|
||||||
|
|
||||||
if (listCount < so->probes)
|
if (listCount < so->probes)
|
||||||
{
|
{
|
||||||
|
IvfflatScanList *scanlist;
|
||||||
|
|
||||||
scanlist = &so->lists[listCount];
|
scanlist = &so->lists[listCount];
|
||||||
scanlist->startPage = list->startPage;
|
scanlist->startPage = list->startPage;
|
||||||
scanlist->distance = distance;
|
scanlist->distance = distance;
|
||||||
@@ -75,6 +75,8 @@ GetScanLists(IndexScanDesc scan, Datum value)
|
|||||||
}
|
}
|
||||||
else if (distance < maxDistance)
|
else if (distance < maxDistance)
|
||||||
{
|
{
|
||||||
|
IvfflatScanList *scanlist;
|
||||||
|
|
||||||
/* Remove */
|
/* Remove */
|
||||||
scanlist = (IvfflatScanList *) pairingheap_remove_first(so->listQueue);
|
scanlist = (IvfflatScanList *) pairingheap_remove_first(so->listQueue);
|
||||||
|
|
||||||
@@ -101,14 +103,6 @@ static void
|
|||||||
GetScanItems(IndexScanDesc scan, Datum value)
|
GetScanItems(IndexScanDesc scan, Datum value)
|
||||||
{
|
{
|
||||||
IvfflatScanOpaque so = (IvfflatScanOpaque) scan->opaque;
|
IvfflatScanOpaque so = (IvfflatScanOpaque) scan->opaque;
|
||||||
Buffer buf;
|
|
||||||
Page page;
|
|
||||||
IndexTuple itup;
|
|
||||||
BlockNumber searchPage;
|
|
||||||
OffsetNumber offno;
|
|
||||||
OffsetNumber maxoffno;
|
|
||||||
Datum datum;
|
|
||||||
bool isnull;
|
|
||||||
TupleDesc tupdesc = RelationGetDescr(scan->indexRelation);
|
TupleDesc tupdesc = RelationGetDescr(scan->indexRelation);
|
||||||
double tuples = 0;
|
double tuples = 0;
|
||||||
|
|
||||||
@@ -128,19 +122,28 @@ GetScanItems(IndexScanDesc scan, Datum value)
|
|||||||
/* Search closest probes lists */
|
/* Search closest probes lists */
|
||||||
while (!pairingheap_is_empty(so->listQueue))
|
while (!pairingheap_is_empty(so->listQueue))
|
||||||
{
|
{
|
||||||
searchPage = ((IvfflatScanList *) pairingheap_remove_first(so->listQueue))->startPage;
|
BlockNumber searchPage = ((IvfflatScanList *) pairingheap_remove_first(so->listQueue))->startPage;
|
||||||
|
|
||||||
/* Search all entry pages for list */
|
/* Search all entry pages for list */
|
||||||
while (BlockNumberIsValid(searchPage))
|
while (BlockNumberIsValid(searchPage))
|
||||||
{
|
{
|
||||||
|
Buffer buf;
|
||||||
|
Page page;
|
||||||
|
OffsetNumber maxoffno;
|
||||||
|
|
||||||
buf = ReadBufferExtended(scan->indexRelation, MAIN_FORKNUM, searchPage, RBM_NORMAL, bas);
|
buf = ReadBufferExtended(scan->indexRelation, MAIN_FORKNUM, searchPage, RBM_NORMAL, bas);
|
||||||
LockBuffer(buf, BUFFER_LOCK_SHARE);
|
LockBuffer(buf, BUFFER_LOCK_SHARE);
|
||||||
page = BufferGetPage(buf);
|
page = BufferGetPage(buf);
|
||||||
maxoffno = PageGetMaxOffsetNumber(page);
|
maxoffno = PageGetMaxOffsetNumber(page);
|
||||||
|
|
||||||
for (offno = FirstOffsetNumber; offno <= maxoffno; offno = OffsetNumberNext(offno))
|
for (OffsetNumber offno = FirstOffsetNumber; offno <= maxoffno; offno = OffsetNumberNext(offno))
|
||||||
{
|
{
|
||||||
itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, offno));
|
IndexTuple itup;
|
||||||
|
Datum datum;
|
||||||
|
bool isnull;
|
||||||
|
ItemId itemid = PageGetItemId(page, offno);
|
||||||
|
|
||||||
|
itup = (IndexTuple) PageGetItem(page, itemid);
|
||||||
datum = index_getattr(itup, 1, tupdesc, &isnull);
|
datum = index_getattr(itup, 1, tupdesc, &isnull);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -154,8 +157,6 @@ GetScanItems(IndexScanDesc scan, Datum value)
|
|||||||
slot->tts_isnull[0] = false;
|
slot->tts_isnull[0] = false;
|
||||||
slot->tts_values[1] = PointerGetDatum(&itup->t_tid);
|
slot->tts_values[1] = PointerGetDatum(&itup->t_tid);
|
||||||
slot->tts_isnull[1] = false;
|
slot->tts_isnull[1] = false;
|
||||||
slot->tts_values[2] = Int32GetDatum((int) searchPage);
|
|
||||||
slot->tts_isnull[2] = false;
|
|
||||||
ExecStoreVirtualTuple(slot);
|
ExecStoreVirtualTuple(slot);
|
||||||
|
|
||||||
tuplesort_puttupleslot(so->sortstate, slot);
|
tuplesort_puttupleslot(so->sortstate, slot);
|
||||||
@@ -180,29 +181,6 @@ GetScanItems(IndexScanDesc scan, Datum value)
|
|||||||
tuplesort_performsort(so->sortstate);
|
tuplesort_performsort(so->sortstate);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* Get dimensions from metapage
|
|
||||||
*/
|
|
||||||
static int
|
|
||||||
GetDimensions(Relation index)
|
|
||||||
{
|
|
||||||
Buffer buf;
|
|
||||||
Page page;
|
|
||||||
IvfflatMetaPage metap;
|
|
||||||
int dimensions;
|
|
||||||
|
|
||||||
buf = ReadBuffer(index, IVFFLAT_METAPAGE_BLKNO);
|
|
||||||
LockBuffer(buf, BUFFER_LOCK_SHARE);
|
|
||||||
page = BufferGetPage(buf);
|
|
||||||
metap = IvfflatPageGetMeta(page);
|
|
||||||
|
|
||||||
dimensions = metap->dimensions;
|
|
||||||
|
|
||||||
UnlockReleaseBuffer(buf);
|
|
||||||
|
|
||||||
return dimensions;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Prepare for an index scan
|
* Prepare for an index scan
|
||||||
*/
|
*/
|
||||||
@@ -212,6 +190,7 @@ ivfflatbeginscan(Relation index, int nkeys, int norderbys)
|
|||||||
IndexScanDesc scan;
|
IndexScanDesc scan;
|
||||||
IvfflatScanOpaque so;
|
IvfflatScanOpaque so;
|
||||||
int lists;
|
int lists;
|
||||||
|
int dimensions;
|
||||||
AttrNumber attNums[] = {1};
|
AttrNumber attNums[] = {1};
|
||||||
Oid sortOperators[] = {Float8LessOperator};
|
Oid sortOperators[] = {Float8LessOperator};
|
||||||
Oid sortCollations[] = {InvalidOid};
|
Oid sortCollations[] = {InvalidOid};
|
||||||
@@ -219,15 +198,17 @@ ivfflatbeginscan(Relation index, int nkeys, int norderbys)
|
|||||||
int probes = ivfflat_probes;
|
int probes = ivfflat_probes;
|
||||||
|
|
||||||
scan = RelationGetIndexScan(index, nkeys, norderbys);
|
scan = RelationGetIndexScan(index, nkeys, norderbys);
|
||||||
lists = IvfflatGetLists(scan->indexRelation);
|
|
||||||
|
/* Get lists and dimensions from metapage */
|
||||||
|
IvfflatGetMetaPageInfo(index, &lists, &dimensions);
|
||||||
|
|
||||||
if (probes > lists)
|
if (probes > lists)
|
||||||
probes = lists;
|
probes = lists;
|
||||||
|
|
||||||
so = (IvfflatScanOpaque) palloc(offsetof(IvfflatScanOpaqueData, lists) + probes * sizeof(IvfflatScanList));
|
so = (IvfflatScanOpaque) palloc(offsetof(IvfflatScanOpaqueData, lists) + probes * sizeof(IvfflatScanList));
|
||||||
so->buf = InvalidBuffer;
|
|
||||||
so->first = true;
|
so->first = true;
|
||||||
so->probes = probes;
|
so->probes = probes;
|
||||||
|
so->dimensions = dimensions;
|
||||||
|
|
||||||
/* Set support functions */
|
/* Set support functions */
|
||||||
so->procinfo = index_getprocinfo(index, 1, IVFFLAT_DISTANCE_PROC);
|
so->procinfo = index_getprocinfo(index, 1, IVFFLAT_DISTANCE_PROC);
|
||||||
@@ -236,13 +217,12 @@ ivfflatbeginscan(Relation index, int nkeys, int norderbys)
|
|||||||
|
|
||||||
/* Create tuple description for sorting */
|
/* Create tuple description for sorting */
|
||||||
#if PG_VERSION_NUM >= 120000
|
#if PG_VERSION_NUM >= 120000
|
||||||
so->tupdesc = CreateTemplateTupleDesc(3);
|
so->tupdesc = CreateTemplateTupleDesc(2);
|
||||||
#else
|
#else
|
||||||
so->tupdesc = CreateTemplateTupleDesc(3, false);
|
so->tupdesc = CreateTemplateTupleDesc(2, false);
|
||||||
#endif
|
#endif
|
||||||
TupleDescInitEntry(so->tupdesc, (AttrNumber) 1, "distance", FLOAT8OID, -1, 0);
|
TupleDescInitEntry(so->tupdesc, (AttrNumber) 1, "distance", FLOAT8OID, -1, 0);
|
||||||
TupleDescInitEntry(so->tupdesc, (AttrNumber) 2, "tid", TIDOID, -1, 0);
|
TupleDescInitEntry(so->tupdesc, (AttrNumber) 2, "heaptid", TIDOID, -1, 0);
|
||||||
TupleDescInitEntry(so->tupdesc, (AttrNumber) 3, "indexblkno", INT4OID, -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);
|
||||||
@@ -308,8 +288,13 @@ ivfflatgettuple(IndexScanDesc scan, ScanDirection dir)
|
|||||||
if (scan->orderByData == NULL)
|
if (scan->orderByData == NULL)
|
||||||
elog(ERROR, "cannot scan ivfflat index without order");
|
elog(ERROR, "cannot scan ivfflat index without order");
|
||||||
|
|
||||||
|
/* Requires MVCC-compliant snapshot as not able to pin during sorting */
|
||||||
|
/* https://www.postgresql.org/docs/current/index-locking.html */
|
||||||
|
if (!IsMVCCSnapshot(scan->xs_snapshot))
|
||||||
|
elog(ERROR, "non-MVCC snapshots are not supported with ivfflat");
|
||||||
|
|
||||||
if (scan->orderByData->sk_flags & SK_ISNULL)
|
if (scan->orderByData->sk_flags & SK_ISNULL)
|
||||||
value = PointerGetDatum(InitVector(GetDimensions(scan->indexRelation)));
|
value = PointerGetDatum(InitVector(so->dimensions));
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
value = scan->orderByData->sk_argument;
|
value = scan->orderByData->sk_argument;
|
||||||
@@ -334,26 +319,14 @@ ivfflatgettuple(IndexScanDesc scan, ScanDirection dir)
|
|||||||
|
|
||||||
if (tuplesort_gettupleslot(so->sortstate, true, false, so->slot, NULL))
|
if (tuplesort_gettupleslot(so->sortstate, true, false, so->slot, NULL))
|
||||||
{
|
{
|
||||||
ItemPointer tid = (ItemPointer) DatumGetPointer(slot_getattr(so->slot, 2, &so->isnull));
|
ItemPointer heaptid = (ItemPointer) DatumGetPointer(slot_getattr(so->slot, 2, &so->isnull));
|
||||||
BlockNumber indexblkno = DatumGetInt32(slot_getattr(so->slot, 3, &so->isnull));
|
|
||||||
|
|
||||||
#if PG_VERSION_NUM >= 120000
|
#if PG_VERSION_NUM >= 120000
|
||||||
scan->xs_heaptid = *tid;
|
scan->xs_heaptid = *heaptid;
|
||||||
#else
|
#else
|
||||||
scan->xs_ctup.t_self = *tid;
|
scan->xs_ctup.t_self = *heaptid;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (BufferIsValid(so->buf))
|
|
||||||
ReleaseBuffer(so->buf);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* An index scan must maintain a pin on the index page holding the
|
|
||||||
* item last returned by amgettuple
|
|
||||||
*
|
|
||||||
* https://www.postgresql.org/docs/current/index-locking.html
|
|
||||||
*/
|
|
||||||
so->buf = ReadBuffer(scan->indexRelation, indexblkno);
|
|
||||||
|
|
||||||
scan->xs_recheckorderby = false;
|
scan->xs_recheckorderby = false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -369,10 +342,6 @@ ivfflatendscan(IndexScanDesc scan)
|
|||||||
{
|
{
|
||||||
IvfflatScanOpaque so = (IvfflatScanOpaque) scan->opaque;
|
IvfflatScanOpaque so = (IvfflatScanOpaque) scan->opaque;
|
||||||
|
|
||||||
/* Release pin */
|
|
||||||
if (BufferIsValid(so->buf))
|
|
||||||
ReleaseBuffer(so->buf);
|
|
||||||
|
|
||||||
pairingheap_free(so->listQueue);
|
pairingheap_free(so->listQueue);
|
||||||
tuplesort_end(so->sortstate);
|
tuplesort_end(so->sortstate);
|
||||||
|
|
||||||
|
|||||||
@@ -57,12 +57,12 @@ IvfflatGetLists(Relation index)
|
|||||||
* Get proc
|
* Get proc
|
||||||
*/
|
*/
|
||||||
FmgrInfo *
|
FmgrInfo *
|
||||||
IvfflatOptionalProcInfo(Relation rel, uint16 procnum)
|
IvfflatOptionalProcInfo(Relation index, uint16 procnum)
|
||||||
{
|
{
|
||||||
if (!OidIsValid(index_getprocid(rel, 1, procnum)))
|
if (!OidIsValid(index_getprocid(index, 1, procnum)))
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
return index_getprocinfo(rel, 1, procnum);
|
return index_getprocinfo(index, 1, procnum);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -136,7 +136,6 @@ IvfflatInitRegisterPage(Relation index, Buffer *buf, Page *page, GenericXLogStat
|
|||||||
void
|
void
|
||||||
IvfflatCommitBuffer(Buffer buf, GenericXLogState *state)
|
IvfflatCommitBuffer(Buffer buf, GenericXLogState *state)
|
||||||
{
|
{
|
||||||
MarkBufferDirty(buf);
|
|
||||||
GenericXLogFinish(state);
|
GenericXLogFinish(state);
|
||||||
UnlockReleaseBuffer(buf);
|
UnlockReleaseBuffer(buf);
|
||||||
}
|
}
|
||||||
@@ -160,8 +159,6 @@ IvfflatAppendPage(Relation index, Buffer *buf, Page *page, GenericXLogState **st
|
|||||||
IvfflatInitPage(newbuf, newpage);
|
IvfflatInitPage(newbuf, newpage);
|
||||||
|
|
||||||
/* Commit */
|
/* Commit */
|
||||||
MarkBufferDirty(*buf);
|
|
||||||
MarkBufferDirty(newbuf);
|
|
||||||
GenericXLogFinish(*state);
|
GenericXLogFinish(*state);
|
||||||
|
|
||||||
/* Unlock */
|
/* Unlock */
|
||||||
@@ -172,6 +169,29 @@ IvfflatAppendPage(Relation index, Buffer *buf, Page *page, GenericXLogState **st
|
|||||||
*buf = newbuf;
|
*buf = newbuf;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Get the metapage info
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
IvfflatGetMetaPageInfo(Relation index, int *lists, int *dimensions)
|
||||||
|
{
|
||||||
|
Buffer buf;
|
||||||
|
Page page;
|
||||||
|
IvfflatMetaPage metap;
|
||||||
|
|
||||||
|
buf = ReadBuffer(index, IVFFLAT_METAPAGE_BLKNO);
|
||||||
|
LockBuffer(buf, BUFFER_LOCK_SHARE);
|
||||||
|
page = BufferGetPage(buf);
|
||||||
|
metap = IvfflatPageGetMeta(page);
|
||||||
|
|
||||||
|
*lists = metap->lists;
|
||||||
|
|
||||||
|
if (dimensions != NULL)
|
||||||
|
*dimensions = metap->dimensions;
|
||||||
|
|
||||||
|
UnlockReleaseBuffer(buf);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Update the start or insert page of a list
|
* Update the start or insert page of a list
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -107,7 +107,6 @@ ivfflatbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
|
|||||||
{
|
{
|
||||||
/* Delete tuples */
|
/* Delete tuples */
|
||||||
PageIndexMultiDelete(page, deletable, ndeletable);
|
PageIndexMultiDelete(page, deletable, ndeletable);
|
||||||
MarkBufferDirty(buf);
|
|
||||||
GenericXLogFinish(state);
|
GenericXLogFinish(state);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|||||||
705
src/svector.c
Normal file
705
src/svector.c
Normal file
@@ -0,0 +1,705 @@
|
|||||||
|
#include "postgres.h"
|
||||||
|
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
|
#include "fmgr.h"
|
||||||
|
#include "libpq/pqformat.h"
|
||||||
|
#include "svector.h"
|
||||||
|
#include "utils/array.h"
|
||||||
|
#include "vector.h"
|
||||||
|
|
||||||
|
#if PG_VERSION_NUM >= 120000
|
||||||
|
#include "common/shortest_dec.h"
|
||||||
|
#include "utils/float.h"
|
||||||
|
#else
|
||||||
|
#include <float.h>
|
||||||
|
#include "utils/builtins.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Ensure same dimensions
|
||||||
|
*/
|
||||||
|
static inline void
|
||||||
|
CheckDims(SVector * a, SVector * b)
|
||||||
|
{
|
||||||
|
if (a->dim != b->dim)
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_DATA_EXCEPTION),
|
||||||
|
errmsg("different svector dimensions %d and %d", a->dim, b->dim)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Ensure expected dimensions
|
||||||
|
*/
|
||||||
|
static inline void
|
||||||
|
CheckExpectedDim(int32 typmod, int dim)
|
||||||
|
{
|
||||||
|
if (typmod != -1 && typmod != dim)
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_DATA_EXCEPTION),
|
||||||
|
errmsg("expected %d dimensions, not %d", typmod, dim)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Ensure valid dimensions
|
||||||
|
*/
|
||||||
|
static inline void
|
||||||
|
CheckDim(int dim)
|
||||||
|
{
|
||||||
|
if (dim < 1)
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_DATA_EXCEPTION),
|
||||||
|
errmsg("svector must have at least 1 dimension")));
|
||||||
|
|
||||||
|
if (dim > SVECTOR_MAX_DIM)
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
|
||||||
|
errmsg("svector cannot have more than %d dimensions", SVECTOR_MAX_DIM)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Ensure valid nnz
|
||||||
|
*/
|
||||||
|
static inline void
|
||||||
|
CheckNnz(int nnz, int dim)
|
||||||
|
{
|
||||||
|
if (nnz < 0)
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_DATA_EXCEPTION),
|
||||||
|
errmsg("svector must have at least one element")));
|
||||||
|
|
||||||
|
if (nnz > dim)
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
|
||||||
|
errmsg("svector cannot have more elements than dimensions")));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Ensure valid index
|
||||||
|
*/
|
||||||
|
static inline void
|
||||||
|
CheckIndex(int32 *indices, int i, int dim)
|
||||||
|
{
|
||||||
|
int32 index = indices[i];
|
||||||
|
|
||||||
|
if (index < 0)
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_DATA_EXCEPTION),
|
||||||
|
errmsg("index must not be negative")));
|
||||||
|
|
||||||
|
if (index >= dim)
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_DATA_EXCEPTION),
|
||||||
|
errmsg("index must be less than dimensions")));
|
||||||
|
|
||||||
|
if (i > 0)
|
||||||
|
{
|
||||||
|
if (index < indices[i - 1])
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_DATA_EXCEPTION),
|
||||||
|
errmsg("indexes must be in ascending order")));
|
||||||
|
|
||||||
|
if (index == indices[i - 1])
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_DATA_EXCEPTION),
|
||||||
|
errmsg("indexes must not contain duplicates")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Ensure finite element
|
||||||
|
*/
|
||||||
|
static inline void
|
||||||
|
CheckElement(float value)
|
||||||
|
{
|
||||||
|
if (isnan(value))
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_DATA_EXCEPTION),
|
||||||
|
errmsg("NaN not allowed in svector")));
|
||||||
|
|
||||||
|
if (isinf(value))
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_DATA_EXCEPTION),
|
||||||
|
errmsg("infinite value not allowed in svector")));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Allocate and initialize a new sparse vector
|
||||||
|
*/
|
||||||
|
SVector *
|
||||||
|
InitSVector(int dim, int nnz)
|
||||||
|
{
|
||||||
|
SVector *result;
|
||||||
|
int size;
|
||||||
|
|
||||||
|
size = SVECTOR_SIZE(nnz);
|
||||||
|
result = (SVector *) palloc0(size);
|
||||||
|
SET_VARSIZE(result, size);
|
||||||
|
result->dim = dim;
|
||||||
|
result->nnz = nnz;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Convert textual representation to internal representation
|
||||||
|
*/
|
||||||
|
PGDLLEXPORT PG_FUNCTION_INFO_V1(svector_in);
|
||||||
|
Datum
|
||||||
|
svector_in(PG_FUNCTION_ARGS)
|
||||||
|
{
|
||||||
|
char *str = PG_GETARG_CSTRING(0);
|
||||||
|
int32 typmod = PG_GETARG_INT32(2);
|
||||||
|
int dim;
|
||||||
|
char *pt;
|
||||||
|
SVector *result;
|
||||||
|
float *rvalues;
|
||||||
|
char *lit = pstrdup(str);
|
||||||
|
int n;
|
||||||
|
int32 *indices;
|
||||||
|
float *values;
|
||||||
|
int index;
|
||||||
|
float value;
|
||||||
|
int maxNnz;
|
||||||
|
int nnz = 0;
|
||||||
|
|
||||||
|
/* TODO Improve code and checks after deciding on format */
|
||||||
|
|
||||||
|
maxNnz = 1;
|
||||||
|
pt = str;
|
||||||
|
while (*pt != '\0')
|
||||||
|
{
|
||||||
|
if (*pt == ',')
|
||||||
|
maxNnz++;
|
||||||
|
|
||||||
|
pt++;
|
||||||
|
}
|
||||||
|
maxNnz /= 2;
|
||||||
|
|
||||||
|
indices = palloc(maxNnz * sizeof(int32));
|
||||||
|
values = palloc(maxNnz * sizeof(float));
|
||||||
|
|
||||||
|
while (sscanf(str, "(%d,%f)%n", &index, &value, &n) == 2)
|
||||||
|
{
|
||||||
|
/* TODO Better error */
|
||||||
|
if (nnz == maxNnz)
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
|
||||||
|
errmsg("ran out of buffer: \"%s\"", lit)));
|
||||||
|
|
||||||
|
/* TODO Decide whether to store zero values */
|
||||||
|
indices[nnz] = index;
|
||||||
|
values[nnz] = value;
|
||||||
|
nnz++;
|
||||||
|
|
||||||
|
str += n;
|
||||||
|
|
||||||
|
if (*str == ',')
|
||||||
|
str++;
|
||||||
|
else if (*str == '|')
|
||||||
|
break;
|
||||||
|
else
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
|
||||||
|
errmsg("malformed svector literal: \"%s\"", lit)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sscanf(str, "|%d|%n", &dim, &n) != 1)
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
|
||||||
|
errmsg("malformed svector literal: \"%s\"", lit)));
|
||||||
|
|
||||||
|
str += n;
|
||||||
|
|
||||||
|
if (*str != '\0')
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
|
||||||
|
errmsg("malformed svector literal: \"%s\"", lit),
|
||||||
|
errdetail("Junk after closing pipe.")));
|
||||||
|
|
||||||
|
pfree(lit);
|
||||||
|
|
||||||
|
CheckDim(dim);
|
||||||
|
CheckExpectedDim(typmod, dim);
|
||||||
|
|
||||||
|
result = InitSVector(dim, nnz);
|
||||||
|
rvalues = SVECTOR_VALUES(result);
|
||||||
|
for (int i = 0; i < nnz; i++)
|
||||||
|
{
|
||||||
|
result->indices[i] = indices[i];
|
||||||
|
rvalues[i] = values[i];
|
||||||
|
|
||||||
|
CheckIndex(result->indices, i, dim);
|
||||||
|
CheckElement(rvalues[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
PG_RETURN_POINTER(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Convert internal representation to textual representation
|
||||||
|
*/
|
||||||
|
PGDLLEXPORT PG_FUNCTION_INFO_V1(svector_out);
|
||||||
|
Datum
|
||||||
|
svector_out(PG_FUNCTION_ARGS)
|
||||||
|
{
|
||||||
|
SVector *svector = PG_GETARG_SVECTOR_P(0);
|
||||||
|
float *values = SVECTOR_VALUES(svector);
|
||||||
|
char *buf;
|
||||||
|
char *ptr;
|
||||||
|
int n;
|
||||||
|
|
||||||
|
/* TODO Improve code after deciding on format */
|
||||||
|
|
||||||
|
#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
|
||||||
|
|
||||||
|
/* TODO Move */
|
||||||
|
#define APPEND_CHAR(ptr, ch) (*(ptr)++ = (ch))
|
||||||
|
|
||||||
|
/* TODO Improve */
|
||||||
|
buf = (char *) palloc((FLOAT_SHORTEST_DECIMAL_LEN + 20) * svector->nnz + 20);
|
||||||
|
ptr = buf;
|
||||||
|
|
||||||
|
for (int i = 0; i < svector->nnz; i++)
|
||||||
|
{
|
||||||
|
if (i > 0)
|
||||||
|
APPEND_CHAR(ptr, ',');
|
||||||
|
|
||||||
|
n = sprintf(ptr, "(%d,", svector->indices[i]);
|
||||||
|
ptr += n;
|
||||||
|
|
||||||
|
#if PG_VERSION_NUM >= 120000
|
||||||
|
n = float_to_shortest_decimal_bufn(values[i], ptr);
|
||||||
|
#else
|
||||||
|
n = sprintf(ptr, "%.*g", ndig, values[i]);
|
||||||
|
#endif
|
||||||
|
ptr += n;
|
||||||
|
|
||||||
|
APPEND_CHAR(ptr, ')');
|
||||||
|
}
|
||||||
|
|
||||||
|
n = sprintf(ptr, "|%d|", svector->dim);
|
||||||
|
ptr += n;
|
||||||
|
|
||||||
|
APPEND_CHAR(ptr, '\0');
|
||||||
|
|
||||||
|
PG_FREE_IF_COPY(svector, 0);
|
||||||
|
PG_RETURN_CSTRING(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Convert type modifier
|
||||||
|
*/
|
||||||
|
PGDLLEXPORT PG_FUNCTION_INFO_V1(svector_typmod_in);
|
||||||
|
Datum
|
||||||
|
svector_typmod_in(PG_FUNCTION_ARGS)
|
||||||
|
{
|
||||||
|
ArrayType *ta = PG_GETARG_ARRAYTYPE_P(0);
|
||||||
|
int32 *tl;
|
||||||
|
int n;
|
||||||
|
|
||||||
|
tl = ArrayGetIntegerTypmods(ta, &n);
|
||||||
|
|
||||||
|
if (n != 1)
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
||||||
|
errmsg("invalid type modifier")));
|
||||||
|
|
||||||
|
if (*tl < 1)
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
||||||
|
errmsg("dimensions for type svector must be at least 1")));
|
||||||
|
|
||||||
|
if (*tl > SVECTOR_MAX_DIM)
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
||||||
|
errmsg("dimensions for type svector cannot exceed %d", SVECTOR_MAX_DIM)));
|
||||||
|
|
||||||
|
PG_RETURN_INT32(*tl);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Convert external binary representation to internal representation
|
||||||
|
*/
|
||||||
|
PGDLLEXPORT PG_FUNCTION_INFO_V1(svector_recv);
|
||||||
|
Datum
|
||||||
|
svector_recv(PG_FUNCTION_ARGS)
|
||||||
|
{
|
||||||
|
StringInfo buf = (StringInfo) PG_GETARG_POINTER(0);
|
||||||
|
int32 typmod = PG_GETARG_INT32(2);
|
||||||
|
SVector *result;
|
||||||
|
int32 dim;
|
||||||
|
int32 nnz;
|
||||||
|
int32 unused;
|
||||||
|
float *values;
|
||||||
|
|
||||||
|
dim = pq_getmsgint(buf, sizeof(int32));
|
||||||
|
nnz = pq_getmsgint(buf, sizeof(int32));
|
||||||
|
unused = pq_getmsgint(buf, sizeof(int32));
|
||||||
|
|
||||||
|
CheckDim(dim);
|
||||||
|
CheckNnz(nnz, dim);
|
||||||
|
CheckExpectedDim(typmod, dim);
|
||||||
|
|
||||||
|
if (unused != 0)
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_DATA_EXCEPTION),
|
||||||
|
errmsg("expected unused to be 0, not %d", unused)));
|
||||||
|
|
||||||
|
result = InitSVector(dim, nnz);
|
||||||
|
values = SVECTOR_VALUES(result);
|
||||||
|
|
||||||
|
for (int i = 0; i < nnz; i++)
|
||||||
|
{
|
||||||
|
result->indices[i] = pq_getmsgint(buf, sizeof(int32));
|
||||||
|
CheckIndex(result->indices, i, dim);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < nnz; i++)
|
||||||
|
{
|
||||||
|
values[i] = pq_getmsgfloat4(buf);
|
||||||
|
CheckElement(values[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
PG_RETURN_POINTER(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Convert internal representation to the external binary representation
|
||||||
|
*/
|
||||||
|
PGDLLEXPORT PG_FUNCTION_INFO_V1(svector_send);
|
||||||
|
Datum
|
||||||
|
svector_send(PG_FUNCTION_ARGS)
|
||||||
|
{
|
||||||
|
SVector *svec = PG_GETARG_SVECTOR_P(0);
|
||||||
|
float *values = SVECTOR_VALUES(svec);
|
||||||
|
StringInfoData buf;
|
||||||
|
|
||||||
|
pq_begintypsend(&buf);
|
||||||
|
pq_sendint(&buf, svec->dim, sizeof(int32));
|
||||||
|
pq_sendint(&buf, svec->nnz, sizeof(int32));
|
||||||
|
pq_sendint(&buf, svec->unused, sizeof(int32));
|
||||||
|
for (int i = 0; i < svec->nnz; i++)
|
||||||
|
pq_sendint(&buf, svec->indices[i], sizeof(int32));
|
||||||
|
for (int i = 0; i < svec->nnz; i++)
|
||||||
|
pq_sendfloat4(&buf, values[i]);
|
||||||
|
|
||||||
|
PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Convert sparse vector to sparse vector
|
||||||
|
* This is needed to check the type modifier
|
||||||
|
*/
|
||||||
|
PGDLLEXPORT PG_FUNCTION_INFO_V1(svector);
|
||||||
|
Datum
|
||||||
|
svector(PG_FUNCTION_ARGS)
|
||||||
|
{
|
||||||
|
SVector *svec = PG_GETARG_SVECTOR_P(0);
|
||||||
|
int32 typmod = PG_GETARG_INT32(1);
|
||||||
|
|
||||||
|
CheckExpectedDim(typmod, svec->dim);
|
||||||
|
|
||||||
|
PG_RETURN_POINTER(svec);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Convert dense vector to sparse vector
|
||||||
|
*/
|
||||||
|
PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_to_svector);
|
||||||
|
Datum
|
||||||
|
vector_to_svector(PG_FUNCTION_ARGS)
|
||||||
|
{
|
||||||
|
Vector *vec = PG_GETARG_VECTOR_P(0);
|
||||||
|
int32 typmod = PG_GETARG_INT32(1);
|
||||||
|
SVector *result;
|
||||||
|
int dim = vec->dim;
|
||||||
|
int nnz = 0;
|
||||||
|
float *values;
|
||||||
|
int j = 0;
|
||||||
|
|
||||||
|
CheckDim(dim);
|
||||||
|
CheckExpectedDim(typmod, dim);
|
||||||
|
|
||||||
|
for (int i = 0; i < dim; i++)
|
||||||
|
{
|
||||||
|
if (vec->x[i] != 0)
|
||||||
|
nnz++;
|
||||||
|
}
|
||||||
|
|
||||||
|
result = InitSVector(dim, nnz);
|
||||||
|
values = SVECTOR_VALUES(result);
|
||||||
|
for (int i = 0; i < dim; i++)
|
||||||
|
{
|
||||||
|
if (vec->x[i] != 0)
|
||||||
|
{
|
||||||
|
/* Safety check */
|
||||||
|
if (j == nnz)
|
||||||
|
elog(ERROR, "safety check failed");
|
||||||
|
|
||||||
|
result->indices[j] = i;
|
||||||
|
values[j] = vec->x[i];
|
||||||
|
j++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PG_RETURN_POINTER(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Get the L2 squared distance between sparse vectors
|
||||||
|
*/
|
||||||
|
static double
|
||||||
|
l2_distance_squared_internal(SVector * a, SVector * b)
|
||||||
|
{
|
||||||
|
float *ax = SVECTOR_VALUES(a);
|
||||||
|
float *bx = SVECTOR_VALUES(b);
|
||||||
|
double distance = 0.0;
|
||||||
|
int bpos = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < a->nnz; i++)
|
||||||
|
{
|
||||||
|
int ai = a->indices[i];
|
||||||
|
int bi = -1;
|
||||||
|
|
||||||
|
for (int j = bpos; j < b->nnz; j++)
|
||||||
|
{
|
||||||
|
bi = b->indices[j];
|
||||||
|
|
||||||
|
if (ai == bi)
|
||||||
|
{
|
||||||
|
double diff = ax[i] - bx[j];
|
||||||
|
|
||||||
|
distance += diff * diff;
|
||||||
|
}
|
||||||
|
else if (ai > bi)
|
||||||
|
distance += bx[j] * bx[j];
|
||||||
|
|
||||||
|
/* Update start for next iteration */
|
||||||
|
if (ai >= bi)
|
||||||
|
bpos = j + 1;
|
||||||
|
|
||||||
|
/* Found or passed it */
|
||||||
|
if (bi >= ai)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ai != bi)
|
||||||
|
distance += ax[i] * ax[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int j = bpos; j < b->nnz; j++)
|
||||||
|
distance += bx[j] * bx[j];
|
||||||
|
|
||||||
|
return distance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Get the L2 distance between sparse vectors
|
||||||
|
*/
|
||||||
|
PGDLLEXPORT PG_FUNCTION_INFO_V1(svector_l2_distance);
|
||||||
|
Datum
|
||||||
|
svector_l2_distance(PG_FUNCTION_ARGS)
|
||||||
|
{
|
||||||
|
SVector *a = PG_GETARG_SVECTOR_P(0);
|
||||||
|
SVector *b = PG_GETARG_SVECTOR_P(1);
|
||||||
|
|
||||||
|
CheckDims(a, b);
|
||||||
|
|
||||||
|
PG_RETURN_FLOAT8(sqrt(l2_distance_squared_internal(a, b)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Get the L2 squared distance between sparse vectors
|
||||||
|
* This saves a sqrt calculation
|
||||||
|
*/
|
||||||
|
PGDLLEXPORT PG_FUNCTION_INFO_V1(svector_l2_squared_distance);
|
||||||
|
Datum
|
||||||
|
svector_l2_squared_distance(PG_FUNCTION_ARGS)
|
||||||
|
{
|
||||||
|
SVector *a = PG_GETARG_SVECTOR_P(0);
|
||||||
|
SVector *b = PG_GETARG_SVECTOR_P(1);
|
||||||
|
|
||||||
|
CheckDims(a, b);
|
||||||
|
|
||||||
|
PG_RETURN_FLOAT8(l2_distance_squared_internal(a, b));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Get the inner product of two sparse vectors
|
||||||
|
*/
|
||||||
|
static double
|
||||||
|
inner_product_internal(SVector * a, SVector * b)
|
||||||
|
{
|
||||||
|
float *ax = SVECTOR_VALUES(a);
|
||||||
|
float *bx = SVECTOR_VALUES(b);
|
||||||
|
double distance = 0.0;
|
||||||
|
int bpos = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < a->nnz; i++)
|
||||||
|
{
|
||||||
|
int ai = a->indices[i];
|
||||||
|
|
||||||
|
for (int j = bpos; j < b->nnz; j++)
|
||||||
|
{
|
||||||
|
int bi = b->indices[j];
|
||||||
|
|
||||||
|
/* Only update when the same index */
|
||||||
|
if (ai == bi)
|
||||||
|
distance += ax[i] * bx[j];
|
||||||
|
|
||||||
|
/* Update start for next iteration */
|
||||||
|
if (ai >= bi)
|
||||||
|
bpos = j + 1;
|
||||||
|
|
||||||
|
/* Found or passed it */
|
||||||
|
if (bi >= ai)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return distance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Get the inner product of two sparse vectors
|
||||||
|
*/
|
||||||
|
PGDLLEXPORT PG_FUNCTION_INFO_V1(svector_inner_product);
|
||||||
|
Datum
|
||||||
|
svector_inner_product(PG_FUNCTION_ARGS)
|
||||||
|
{
|
||||||
|
SVector *a = PG_GETARG_SVECTOR_P(0);
|
||||||
|
SVector *b = PG_GETARG_SVECTOR_P(1);
|
||||||
|
|
||||||
|
CheckDims(a, b);
|
||||||
|
|
||||||
|
PG_RETURN_FLOAT8(inner_product_internal(a, b));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Get the negative inner product of two sparse vectors
|
||||||
|
*/
|
||||||
|
PGDLLEXPORT PG_FUNCTION_INFO_V1(svector_negative_inner_product);
|
||||||
|
Datum
|
||||||
|
svector_negative_inner_product(PG_FUNCTION_ARGS)
|
||||||
|
{
|
||||||
|
SVector *a = PG_GETARG_SVECTOR_P(0);
|
||||||
|
SVector *b = PG_GETARG_SVECTOR_P(1);
|
||||||
|
|
||||||
|
CheckDims(a, b);
|
||||||
|
|
||||||
|
PG_RETURN_FLOAT8(-inner_product_internal(a, b));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Get the cosine distance between two sparse vectors
|
||||||
|
*/
|
||||||
|
PGDLLEXPORT PG_FUNCTION_INFO_V1(svector_cosine_distance);
|
||||||
|
Datum
|
||||||
|
svector_cosine_distance(PG_FUNCTION_ARGS)
|
||||||
|
{
|
||||||
|
SVector *a = PG_GETARG_SVECTOR_P(0);
|
||||||
|
SVector *b = PG_GETARG_SVECTOR_P(1);
|
||||||
|
float *ax = SVECTOR_VALUES(a);
|
||||||
|
float *bx = SVECTOR_VALUES(b);
|
||||||
|
float norma = 0.0;
|
||||||
|
float normb = 0.0;
|
||||||
|
double similarity;
|
||||||
|
|
||||||
|
CheckDims(a, b);
|
||||||
|
|
||||||
|
similarity = inner_product_internal(a, b);
|
||||||
|
|
||||||
|
/* Auto-vectorized */
|
||||||
|
for (int i = 0; i < a->nnz; i++)
|
||||||
|
norma += ax[i] * ax[i];
|
||||||
|
|
||||||
|
/* Auto-vectorized */
|
||||||
|
for (int i = 0; i < b->nnz; i++)
|
||||||
|
normb += bx[i] * bx[i];
|
||||||
|
|
||||||
|
/* Use sqrt(a * b) over sqrt(a) * sqrt(b) */
|
||||||
|
similarity /= sqrt((double) norma * (double) normb);
|
||||||
|
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
/* /fp:fast may not propagate NaN */
|
||||||
|
if (isnan(similarity))
|
||||||
|
PG_RETURN_FLOAT8(NAN);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Keep in range */
|
||||||
|
if (similarity > 1)
|
||||||
|
similarity = 1.0;
|
||||||
|
else if (similarity < -1)
|
||||||
|
similarity = -1.0;
|
||||||
|
|
||||||
|
PG_RETURN_FLOAT8(1.0 - similarity);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Get the weighted Jaccard distance between two sparse vectors
|
||||||
|
*/
|
||||||
|
PGDLLEXPORT PG_FUNCTION_INFO_V1(svector_jaccard_distance);
|
||||||
|
Datum
|
||||||
|
svector_jaccard_distance(PG_FUNCTION_ARGS)
|
||||||
|
{
|
||||||
|
SVector *a = PG_GETARG_SVECTOR_P(0);
|
||||||
|
SVector *b = PG_GETARG_SVECTOR_P(1);
|
||||||
|
float *ax = SVECTOR_VALUES(a);
|
||||||
|
float *bx = SVECTOR_VALUES(b);
|
||||||
|
double num = 0.0;
|
||||||
|
double denom = 0.0;
|
||||||
|
int bpos = 0;
|
||||||
|
|
||||||
|
CheckDims(a, b);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Weighted Jaccard distance is not defined for vectors with negative
|
||||||
|
* values. Could check and return NaN if minimal impact on performance.
|
||||||
|
*/
|
||||||
|
|
||||||
|
for (int i = 0; i < a->nnz; i++)
|
||||||
|
{
|
||||||
|
int ai = a->indices[i];
|
||||||
|
int bi = -1;
|
||||||
|
|
||||||
|
for (int j = bpos; j < b->nnz; j++)
|
||||||
|
{
|
||||||
|
bi = b->indices[j];
|
||||||
|
|
||||||
|
if (ai == bi)
|
||||||
|
{
|
||||||
|
num += ax[i] < bx[j] ? ax[i] : bx[j];
|
||||||
|
denom += ax[i] > bx[j] ? ax[i] : bx[j];
|
||||||
|
}
|
||||||
|
else if (ai > bi)
|
||||||
|
denom += bx[j];
|
||||||
|
|
||||||
|
/* Update start for next iteration */
|
||||||
|
if (ai >= bi)
|
||||||
|
bpos = j + 1;
|
||||||
|
|
||||||
|
/* Found or passed it */
|
||||||
|
if (bi >= ai)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ai != bi)
|
||||||
|
denom += ax[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int j = bpos; j < b->nnz; j++)
|
||||||
|
denom += bx[j];
|
||||||
|
|
||||||
|
if (denom > 0)
|
||||||
|
PG_RETURN_FLOAT8(1.0 - (num / denom));
|
||||||
|
else
|
||||||
|
PG_RETURN_FLOAT8(NAN);
|
||||||
|
}
|
||||||
23
src/svector.h
Normal file
23
src/svector.h
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
#ifndef SVECTOR_H
|
||||||
|
#define SVECTOR_H
|
||||||
|
|
||||||
|
#define SVECTOR_MAX_DIM 100000
|
||||||
|
|
||||||
|
#define SVECTOR_SIZE(_nnz) (offsetof(SVector, indices) + (_nnz) * sizeof(int32) + (_nnz * sizeof(float)))
|
||||||
|
#define SVECTOR_VALUES(x) ((float *) (((char *) (x)) + offsetof(SVector, indices) + (x)->nnz * sizeof(int32)))
|
||||||
|
#define DatumGetSVector(x) ((SVector *) PG_DETOAST_DATUM(x))
|
||||||
|
#define PG_GETARG_SVECTOR_P(x) DatumGetSVector(PG_GETARG_DATUM(x))
|
||||||
|
#define PG_RETURN_SVECTOR_P(x) PG_RETURN_POINTER(x)
|
||||||
|
|
||||||
|
typedef struct SVector
|
||||||
|
{
|
||||||
|
int32 vl_len_; /* varlena header (do not touch directly!) */
|
||||||
|
int32 dim; /* number of dimensions */
|
||||||
|
int32 nnz;
|
||||||
|
int32 unused;
|
||||||
|
int32 indices[FLEXIBLE_ARRAY_MEMBER];
|
||||||
|
} SVector;
|
||||||
|
|
||||||
|
SVector *InitSVector(int dim, int nnz);
|
||||||
|
|
||||||
|
#endif
|
||||||
46
src/vector.c
46
src/vector.c
@@ -9,6 +9,7 @@
|
|||||||
#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 "svector.h"
|
||||||
#include "utils/array.h"
|
#include "utils/array.h"
|
||||||
#include "utils/builtins.h"
|
#include "utils/builtins.h"
|
||||||
#include "utils/lsyscache.h"
|
#include "utils/lsyscache.h"
|
||||||
@@ -89,7 +90,7 @@ CheckDim(int dim)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Ensure finite elements
|
* Ensure finite element
|
||||||
*/
|
*/
|
||||||
static inline void
|
static inline void
|
||||||
CheckElement(float value)
|
CheckElement(float value)
|
||||||
@@ -437,17 +438,18 @@ vector_send(PG_FUNCTION_ARGS)
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* Convert vector to vector
|
* Convert vector to vector
|
||||||
|
* This is needed to check the type modifier
|
||||||
*/
|
*/
|
||||||
PGDLLEXPORT PG_FUNCTION_INFO_V1(vector);
|
PGDLLEXPORT PG_FUNCTION_INFO_V1(vector);
|
||||||
Datum
|
Datum
|
||||||
vector(PG_FUNCTION_ARGS)
|
vector(PG_FUNCTION_ARGS)
|
||||||
{
|
{
|
||||||
Vector *arg = PG_GETARG_VECTOR_P(0);
|
Vector *vec = PG_GETARG_VECTOR_P(0);
|
||||||
int32 typmod = PG_GETARG_INT32(1);
|
int32 typmod = PG_GETARG_INT32(1);
|
||||||
|
|
||||||
CheckExpectedDim(typmod, arg->dim);
|
CheckExpectedDim(typmod, vec->dim);
|
||||||
|
|
||||||
PG_RETURN_POINTER(arg);
|
PG_RETURN_POINTER(vec);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -464,7 +466,6 @@ array_to_vector(PG_FUNCTION_ARGS)
|
|||||||
bool typbyval;
|
bool typbyval;
|
||||||
char typalign;
|
char typalign;
|
||||||
Datum *elemsp;
|
Datum *elemsp;
|
||||||
bool *nullsp;
|
|
||||||
int nelemsp;
|
int nelemsp;
|
||||||
|
|
||||||
if (ARR_NDIM(array) > 1)
|
if (ARR_NDIM(array) > 1)
|
||||||
@@ -478,7 +479,7 @@ array_to_vector(PG_FUNCTION_ARGS)
|
|||||||
errmsg("array must not contain nulls")));
|
errmsg("array must not contain nulls")));
|
||||||
|
|
||||||
get_typlenbyvalalign(ARR_ELEMTYPE(array), &typlen, &typbyval, &typalign);
|
get_typlenbyvalalign(ARR_ELEMTYPE(array), &typlen, &typbyval, &typalign);
|
||||||
deconstruct_array(array, ARR_ELEMTYPE(array), typlen, typbyval, typalign, &elemsp, &nullsp, &nelemsp);
|
deconstruct_array(array, ARR_ELEMTYPE(array), typlen, typbyval, typalign, &elemsp, NULL, &nelemsp);
|
||||||
|
|
||||||
CheckDim(nelemsp);
|
CheckDim(nelemsp);
|
||||||
CheckExpectedDim(typmod, nelemsp);
|
CheckExpectedDim(typmod, nelemsp);
|
||||||
@@ -512,6 +513,12 @@ array_to_vector(PG_FUNCTION_ARGS)
|
|||||||
errmsg("unsupported array type")));
|
errmsg("unsupported array type")));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Free allocation from deconstruct_array. Do not free individual elements
|
||||||
|
* when pass-by-reference since they point to original array.
|
||||||
|
*/
|
||||||
|
pfree(elemsp);
|
||||||
|
|
||||||
/* Check elements */
|
/* Check elements */
|
||||||
for (int i = 0; i < result->dim; i++)
|
for (int i = 0; i < result->dim; i++)
|
||||||
CheckElement(result->x[i]);
|
CheckElement(result->x[i]);
|
||||||
@@ -695,6 +702,8 @@ vector_spherical_distance(PG_FUNCTION_ARGS)
|
|||||||
{
|
{
|
||||||
Vector *a = PG_GETARG_VECTOR_P(0);
|
Vector *a = PG_GETARG_VECTOR_P(0);
|
||||||
Vector *b = PG_GETARG_VECTOR_P(1);
|
Vector *b = PG_GETARG_VECTOR_P(1);
|
||||||
|
float *ax = a->x;
|
||||||
|
float *bx = b->x;
|
||||||
float dp = 0.0;
|
float dp = 0.0;
|
||||||
double distance;
|
double distance;
|
||||||
|
|
||||||
@@ -702,7 +711,7 @@ vector_spherical_distance(PG_FUNCTION_ARGS)
|
|||||||
|
|
||||||
/* Auto-vectorized */
|
/* Auto-vectorized */
|
||||||
for (int i = 0; i < a->dim; i++)
|
for (int i = 0; i < a->dim; i++)
|
||||||
dp += a->x[i] * b->x[i];
|
dp += ax[i] * bx[i];
|
||||||
|
|
||||||
distance = (double) dp;
|
distance = (double) dp;
|
||||||
|
|
||||||
@@ -1143,3 +1152,26 @@ vector_avg(PG_FUNCTION_ARGS)
|
|||||||
|
|
||||||
PG_RETURN_POINTER(result);
|
PG_RETURN_POINTER(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Convert sparse vector to dense vector
|
||||||
|
*/
|
||||||
|
PGDLLEXPORT PG_FUNCTION_INFO_V1(svector_to_vector);
|
||||||
|
Datum
|
||||||
|
svector_to_vector(PG_FUNCTION_ARGS)
|
||||||
|
{
|
||||||
|
SVector *svec = PG_GETARG_SVECTOR_P(0);
|
||||||
|
int32 typmod = PG_GETARG_INT32(1);
|
||||||
|
Vector *result;
|
||||||
|
int dim = svec->dim;
|
||||||
|
float *values = SVECTOR_VALUES(svec);
|
||||||
|
|
||||||
|
CheckDim(dim);
|
||||||
|
CheckExpectedDim(typmod, dim);
|
||||||
|
|
||||||
|
result = InitVector(dim);
|
||||||
|
for (int i = 0; i < svec->nnz; i++)
|
||||||
|
result->x[svec->indices[i]] = values[i];
|
||||||
|
|
||||||
|
PG_RETURN_POINTER(result);
|
||||||
|
}
|
||||||
|
|||||||
@@ -54,79 +54,85 @@ SELECT vector_norm('[3e37,4e37]')::real;
|
|||||||
5e+37
|
5e+37
|
||||||
(1 row)
|
(1 row)
|
||||||
|
|
||||||
SELECT l2_distance('[0,0]', '[3,4]');
|
SELECT l2_distance('[0,0]'::vector, '[3,4]');
|
||||||
l2_distance
|
l2_distance
|
||||||
-------------
|
-------------
|
||||||
5
|
5
|
||||||
(1 row)
|
(1 row)
|
||||||
|
|
||||||
SELECT l2_distance('[0,0]', '[0,1]');
|
SELECT l2_distance('[0,0]'::vector, '[0,1]');
|
||||||
l2_distance
|
l2_distance
|
||||||
-------------
|
-------------
|
||||||
1
|
1
|
||||||
(1 row)
|
(1 row)
|
||||||
|
|
||||||
SELECT l2_distance('[1,2]', '[3]');
|
SELECT l2_distance('[1,2]'::vector, '[3]');
|
||||||
ERROR: different vector dimensions 2 and 1
|
ERROR: different vector dimensions 2 and 1
|
||||||
SELECT l2_distance('[3e38]', '[-3e38]');
|
SELECT l2_distance('[3e38]'::vector, '[-3e38]');
|
||||||
l2_distance
|
l2_distance
|
||||||
-------------
|
-------------
|
||||||
Infinity
|
Infinity
|
||||||
(1 row)
|
(1 row)
|
||||||
|
|
||||||
SELECT inner_product('[1,2]', '[3,4]');
|
SELECT inner_product('[1,2]'::vector, '[3,4]');
|
||||||
inner_product
|
inner_product
|
||||||
---------------
|
---------------
|
||||||
11
|
11
|
||||||
(1 row)
|
(1 row)
|
||||||
|
|
||||||
SELECT inner_product('[1,2]', '[3]');
|
SELECT inner_product('[1,2]'::vector, '[3]');
|
||||||
ERROR: different vector dimensions 2 and 1
|
ERROR: different vector dimensions 2 and 1
|
||||||
SELECT inner_product('[3e38]', '[3e38]');
|
SELECT inner_product('[3e38]'::vector, '[3e38]');
|
||||||
inner_product
|
inner_product
|
||||||
---------------
|
---------------
|
||||||
Infinity
|
Infinity
|
||||||
(1 row)
|
(1 row)
|
||||||
|
|
||||||
SELECT cosine_distance('[1,2]', '[2,4]');
|
SELECT cosine_distance('[1,2]'::vector, '[2,4]');
|
||||||
cosine_distance
|
cosine_distance
|
||||||
-----------------
|
-----------------
|
||||||
0
|
0
|
||||||
(1 row)
|
(1 row)
|
||||||
|
|
||||||
SELECT cosine_distance('[1,2]', '[0,0]');
|
SELECT cosine_distance('[1,2]'::vector, '[0,0]');
|
||||||
cosine_distance
|
cosine_distance
|
||||||
-----------------
|
-----------------
|
||||||
NaN
|
NaN
|
||||||
(1 row)
|
(1 row)
|
||||||
|
|
||||||
SELECT cosine_distance('[1,1]', '[1,1]');
|
SELECT cosine_distance('[1,1]'::vector, '[1,1]');
|
||||||
cosine_distance
|
cosine_distance
|
||||||
-----------------
|
-----------------
|
||||||
0
|
0
|
||||||
(1 row)
|
(1 row)
|
||||||
|
|
||||||
SELECT cosine_distance('[1,1]', '[-1,-1]');
|
SELECT cosine_distance('[1,0]'::vector, '[0,2]');
|
||||||
|
cosine_distance
|
||||||
|
-----------------
|
||||||
|
1
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT cosine_distance('[1,1]'::vector, '[-1,-1]');
|
||||||
cosine_distance
|
cosine_distance
|
||||||
-----------------
|
-----------------
|
||||||
2
|
2
|
||||||
(1 row)
|
(1 row)
|
||||||
|
|
||||||
SELECT cosine_distance('[1,2]', '[3]');
|
SELECT cosine_distance('[1,2]'::vector, '[3]');
|
||||||
ERROR: different vector dimensions 2 and 1
|
ERROR: different vector dimensions 2 and 1
|
||||||
SELECT cosine_distance('[1,1]', '[1.1,1.1]');
|
SELECT cosine_distance('[1,1]'::vector, '[1.1,1.1]');
|
||||||
cosine_distance
|
cosine_distance
|
||||||
-----------------
|
-----------------
|
||||||
0
|
0
|
||||||
(1 row)
|
(1 row)
|
||||||
|
|
||||||
SELECT cosine_distance('[1,1]', '[-1.1,-1.1]');
|
SELECT cosine_distance('[1,1]'::vector, '[-1.1,-1.1]');
|
||||||
cosine_distance
|
cosine_distance
|
||||||
-----------------
|
-----------------
|
||||||
2
|
2
|
||||||
(1 row)
|
(1 row)
|
||||||
|
|
||||||
SELECT cosine_distance('[3e38]', '[3e38]');
|
SELECT cosine_distance('[3e38]'::vector, '[3e38]');
|
||||||
cosine_distance
|
cosine_distance
|
||||||
-----------------
|
-----------------
|
||||||
NaN
|
NaN
|
||||||
|
|||||||
140
test/expected/svector.out
Normal file
140
test/expected/svector.out
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
SELECT '(0,1.5),(2,3.5)|5|'::svector;
|
||||||
|
svector
|
||||||
|
--------------------
|
||||||
|
(0,1.5),(2,3.5)|5|
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT '(0,1.5),(2,3.5)|5|'::svector::vector;
|
||||||
|
vector
|
||||||
|
-----------------
|
||||||
|
[1.5,0,3.5,0,0]
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT '(0,1.5),(2,3.5)|5|'::svector::vector(5);
|
||||||
|
vector
|
||||||
|
-----------------
|
||||||
|
[1.5,0,3.5,0,0]
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT '(0,1.5),(2,3.5)|5|'::svector::vector(4);
|
||||||
|
ERROR: expected 4 dimensions, not 5
|
||||||
|
SELECT '[0,1.5,0,3.5,0]'::vector::svector;
|
||||||
|
svector
|
||||||
|
--------------------
|
||||||
|
(1,1.5),(3,3.5)|5|
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT '(0,0),(1,1),(2,0)|3|'::svector;
|
||||||
|
svector
|
||||||
|
----------------------
|
||||||
|
(0,0),(1,1),(2,0)|3|
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT '|5|'::svector;
|
||||||
|
svector
|
||||||
|
---------
|
||||||
|
|5|
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT '|-1|'::svector;
|
||||||
|
ERROR: svector must have at least 1 dimension
|
||||||
|
LINE 1: SELECT '|-1|'::svector;
|
||||||
|
^
|
||||||
|
SELECT '|100001|'::svector;
|
||||||
|
ERROR: svector cannot have more than 100000 dimensions
|
||||||
|
LINE 1: SELECT '|100001|'::svector;
|
||||||
|
^
|
||||||
|
SELECT '|16001|'::svector::vector;
|
||||||
|
ERROR: vector cannot have more than 16000 dimensions
|
||||||
|
SELECT '(-1,1)|1|'::svector;
|
||||||
|
ERROR: index must not be negative
|
||||||
|
LINE 1: SELECT '(-1,1)|1|'::svector;
|
||||||
|
^
|
||||||
|
SELECT '(1,1)|1|'::svector;
|
||||||
|
ERROR: index must be less than dimensions
|
||||||
|
LINE 1: SELECT '(1,1)|1|'::svector;
|
||||||
|
^
|
||||||
|
SELECT '|1|'::svector(2);
|
||||||
|
ERROR: expected 2 dimensions, not 1
|
||||||
|
SELECT l2_distance('|2|'::svector, '(0,3),(1,4)|2|');
|
||||||
|
l2_distance
|
||||||
|
-------------
|
||||||
|
5
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT l2_distance('|2|'::svector, '(1,1)|2|');
|
||||||
|
l2_distance
|
||||||
|
-------------
|
||||||
|
1
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT '|2|'::svector <-> '(0,3),(1,4)|2|';
|
||||||
|
?column?
|
||||||
|
----------
|
||||||
|
5
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT inner_product('(0,1),(1,2)|2|'::svector, '(0,2),(1,4)|2|');
|
||||||
|
inner_product
|
||||||
|
---------------
|
||||||
|
10
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT svector_negative_inner_product('(0,1),(1,2)|2|', '(0,2),(1,4)|2|');
|
||||||
|
svector_negative_inner_product
|
||||||
|
--------------------------------
|
||||||
|
-10
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT cosine_distance('(0,1),(1,2)|2|'::svector, '(0,2),(1,4)|2|');
|
||||||
|
cosine_distance
|
||||||
|
-----------------
|
||||||
|
0
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT cosine_distance('(0,1),(1,2)|2|'::svector, '|2|');
|
||||||
|
cosine_distance
|
||||||
|
-----------------
|
||||||
|
NaN
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT cosine_distance('(0,1),(1,1)|2|'::svector, '(0,-1),(1,-1)|2|');
|
||||||
|
cosine_distance
|
||||||
|
-----------------
|
||||||
|
2
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT cosine_distance('(0,1)|2|'::svector, '(1,2)|2|');
|
||||||
|
cosine_distance
|
||||||
|
-----------------
|
||||||
|
1
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT cosine_distance('|1|'::svector, '|1|');
|
||||||
|
cosine_distance
|
||||||
|
-----------------
|
||||||
|
NaN
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT cosine_distance('(0,1)|2|'::svector, '(0,1)|3|');
|
||||||
|
ERROR: different svector dimensions 2 and 3
|
||||||
|
SELECT jaccard_distance('(0,1)|2|', '(0,1)|2|');
|
||||||
|
jaccard_distance
|
||||||
|
------------------
|
||||||
|
0
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT jaccard_distance('(0,1)|2|', '(1,1)|2|');
|
||||||
|
jaccard_distance
|
||||||
|
------------------
|
||||||
|
1
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT jaccard_distance('|1|', '|1|');
|
||||||
|
jaccard_distance
|
||||||
|
------------------
|
||||||
|
NaN
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT jaccard_distance('(0,1)|2|', '(0,1)|3|');
|
||||||
|
ERROR: different svector dimensions 2 and 3
|
||||||
@@ -13,23 +13,24 @@ 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]', '[3,4]');
|
SELECT l2_distance('[0,0]'::vector, '[3,4]');
|
||||||
SELECT l2_distance('[0,0]', '[0,1]');
|
SELECT l2_distance('[0,0]'::vector, '[0,1]');
|
||||||
SELECT l2_distance('[1,2]', '[3]');
|
SELECT l2_distance('[1,2]'::vector, '[3]');
|
||||||
SELECT l2_distance('[3e38]', '[-3e38]');
|
SELECT l2_distance('[3e38]'::vector, '[-3e38]');
|
||||||
|
|
||||||
SELECT inner_product('[1,2]', '[3,4]');
|
SELECT inner_product('[1,2]'::vector, '[3,4]');
|
||||||
SELECT inner_product('[1,2]', '[3]');
|
SELECT inner_product('[1,2]'::vector, '[3]');
|
||||||
SELECT inner_product('[3e38]', '[3e38]');
|
SELECT inner_product('[3e38]'::vector, '[3e38]');
|
||||||
|
|
||||||
SELECT cosine_distance('[1,2]', '[2,4]');
|
SELECT cosine_distance('[1,2]'::vector, '[2,4]');
|
||||||
SELECT cosine_distance('[1,2]', '[0,0]');
|
SELECT cosine_distance('[1,2]'::vector, '[0,0]');
|
||||||
SELECT cosine_distance('[1,1]', '[1,1]');
|
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,2]', '[3]');
|
SELECT cosine_distance('[1,1]'::vector, '[-1,-1]');
|
||||||
SELECT cosine_distance('[1,1]', '[1.1,1.1]');
|
SELECT cosine_distance('[1,2]'::vector, '[3]');
|
||||||
SELECT cosine_distance('[1,1]', '[-1.1,-1.1]');
|
SELECT cosine_distance('[1,1]'::vector, '[1.1,1.1]');
|
||||||
SELECT cosine_distance('[3e38]', '[3e38]');
|
SELECT cosine_distance('[1,1]'::vector, '[-1.1,-1.1]');
|
||||||
|
SELECT cosine_distance('[3e38]'::vector, '[3e38]');
|
||||||
|
|
||||||
SELECT l1_distance('[0,0]', '[3,4]');
|
SELECT l1_distance('[0,0]', '[3,4]');
|
||||||
SELECT l1_distance('[0,0]', '[0,1]');
|
SELECT l1_distance('[0,0]', '[0,1]');
|
||||||
|
|||||||
36
test/sql/svector.sql
Normal file
36
test/sql/svector.sql
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
SELECT '(0,1.5),(2,3.5)|5|'::svector;
|
||||||
|
SELECT '(0,1.5),(2,3.5)|5|'::svector::vector;
|
||||||
|
SELECT '(0,1.5),(2,3.5)|5|'::svector::vector(5);
|
||||||
|
SELECT '(0,1.5),(2,3.5)|5|'::svector::vector(4);
|
||||||
|
SELECT '[0,1.5,0,3.5,0]'::vector::svector;
|
||||||
|
|
||||||
|
SELECT '(0,0),(1,1),(2,0)|3|'::svector;
|
||||||
|
|
||||||
|
SELECT '|5|'::svector;
|
||||||
|
SELECT '|-1|'::svector;
|
||||||
|
SELECT '|100001|'::svector;
|
||||||
|
SELECT '|16001|'::svector::vector;
|
||||||
|
|
||||||
|
SELECT '(-1,1)|1|'::svector;
|
||||||
|
SELECT '(1,1)|1|'::svector;
|
||||||
|
|
||||||
|
SELECT '|1|'::svector(2);
|
||||||
|
|
||||||
|
SELECT l2_distance('|2|'::svector, '(0,3),(1,4)|2|');
|
||||||
|
SELECT l2_distance('|2|'::svector, '(1,1)|2|');
|
||||||
|
SELECT '|2|'::svector <-> '(0,3),(1,4)|2|';
|
||||||
|
|
||||||
|
SELECT inner_product('(0,1),(1,2)|2|'::svector, '(0,2),(1,4)|2|');
|
||||||
|
SELECT svector_negative_inner_product('(0,1),(1,2)|2|', '(0,2),(1,4)|2|');
|
||||||
|
|
||||||
|
SELECT cosine_distance('(0,1),(1,2)|2|'::svector, '(0,2),(1,4)|2|');
|
||||||
|
SELECT cosine_distance('(0,1),(1,2)|2|'::svector, '|2|');
|
||||||
|
SELECT cosine_distance('(0,1),(1,1)|2|'::svector, '(0,-1),(1,-1)|2|');
|
||||||
|
SELECT cosine_distance('(0,1)|2|'::svector, '(1,2)|2|');
|
||||||
|
SELECT cosine_distance('|1|'::svector, '|1|');
|
||||||
|
SELECT cosine_distance('(0,1)|2|'::svector, '(0,1)|3|');
|
||||||
|
|
||||||
|
SELECT jaccard_distance('(0,1)|2|', '(0,1)|2|');
|
||||||
|
SELECT jaccard_distance('(0,1)|2|', '(1,1)|2|');
|
||||||
|
SELECT jaccard_distance('|1|', '|1|');
|
||||||
|
SELECT jaccard_distance('(0,1)|2|', '(0,1)|3|');
|
||||||
@@ -19,8 +19,6 @@ sub test_index_replay
|
|||||||
|
|
||||||
# Wait for replica to catch up
|
# Wait for replica to catch up
|
||||||
my $applname = $node_replica->name;
|
my $applname = $node_replica->name;
|
||||||
|
|
||||||
my $server_version_num = $node_primary->safe_psql("postgres", "SHOW server_version_num");
|
|
||||||
my $caughtup_query = "SELECT pg_current_wal_lsn() <= replay_lsn FROM pg_stat_replication WHERE application_name = '$applname';";
|
my $caughtup_query = "SELECT pg_current_wal_lsn() <= replay_lsn FROM pg_stat_replication WHERE application_name = '$applname';";
|
||||||
$node_primary->poll_query_until('postgres', $caughtup_query)
|
$node_primary->poll_query_until('postgres', $caughtup_query)
|
||||||
or die "Timed out while waiting for replica 1 to catch up";
|
or die "Timed out while waiting for replica 1 to catch up";
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ for my $i (0 .. $#operators)
|
|||||||
# Test approximate results
|
# Test approximate results
|
||||||
if ($operator ne "<#>")
|
if ($operator ne "<#>")
|
||||||
{
|
{
|
||||||
# TODO fix test
|
# TODO Fix test (uniform random vectors all have similar inner product)
|
||||||
test_recall(1, 0.71, $operator);
|
test_recall(1, 0.71, $operator);
|
||||||
test_recall(10, 0.95, $operator);
|
test_recall(10, 0.95, $operator);
|
||||||
}
|
}
|
||||||
@@ -115,7 +115,7 @@ for my $i (0 .. $#operators)
|
|||||||
# Test approximate results
|
# Test approximate results
|
||||||
if ($operator ne "<#>")
|
if ($operator ne "<#>")
|
||||||
{
|
{
|
||||||
# TODO fix test
|
# TODO Fix test (uniform random vectors all have similar inner product)
|
||||||
test_recall(1, 0.71, $operator);
|
test_recall(1, 0.71, $operator);
|
||||||
test_recall(10, 0.95, $operator);
|
test_recall(10, 0.95, $operator);
|
||||||
}
|
}
|
||||||
@@ -19,8 +19,6 @@ sub test_index_replay
|
|||||||
|
|
||||||
# Wait for replica to catch up
|
# Wait for replica to catch up
|
||||||
my $applname = $node_replica->name;
|
my $applname = $node_replica->name;
|
||||||
|
|
||||||
my $server_version_num = $node_primary->safe_psql("postgres", "SHOW server_version_num");
|
|
||||||
my $caughtup_query = "SELECT pg_current_wal_lsn() <= replay_lsn FROM pg_stat_replication WHERE application_name = '$applname';";
|
my $caughtup_query = "SELECT pg_current_wal_lsn() <= replay_lsn FROM pg_stat_replication WHERE application_name = '$applname';";
|
||||||
$node_primary->poll_query_until('postgres', $caughtup_query)
|
$node_primary->poll_query_until('postgres', $caughtup_query)
|
||||||
or die "Timed out while waiting for replica 1 to catch up";
|
or die "Timed out while waiting for replica 1 to catch up";
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ foreach (@queries)
|
|||||||
test_recall(0.20, $limit, "before vacuum");
|
test_recall(0.20, $limit, "before vacuum");
|
||||||
test_recall(0.95, 100, "before vacuum");
|
test_recall(0.95, 100, "before vacuum");
|
||||||
|
|
||||||
# TODO test concurrent inserts with vacuum
|
# TODO Test concurrent inserts with vacuum
|
||||||
$node->safe_psql("postgres", "VACUUM tst;");
|
$node->safe_psql("postgres", "VACUUM tst;");
|
||||||
|
|
||||||
test_recall(0.95, $limit, "after vacuum");
|
test_recall(0.95, $limit, "after vacuum");
|
||||||
|
|||||||
117
test/t/017_ivfflat_insert_recall.pl
Normal file
117
test/t/017_ivfflat_insert_recall.pl
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
use PostgresNode;
|
||||||
|
use TestLib;
|
||||||
|
use Test::More;
|
||||||
|
|
||||||
|
my $node;
|
||||||
|
my @queries = ();
|
||||||
|
my @expected;
|
||||||
|
my $limit = 20;
|
||||||
|
|
||||||
|
sub test_recall
|
||||||
|
{
|
||||||
|
my ($probes, $min, $operator) = @_;
|
||||||
|
my $correct = 0;
|
||||||
|
my $total = 0;
|
||||||
|
|
||||||
|
my $explain = $node->safe_psql("postgres", qq(
|
||||||
|
SET enable_seqscan = off;
|
||||||
|
SET ivfflat.probes = $probes;
|
||||||
|
EXPLAIN ANALYZE SELECT i FROM tst ORDER BY v $operator '$queries[0]' LIMIT $limit;
|
||||||
|
));
|
||||||
|
like($explain, qr/Index Scan using idx on tst/);
|
||||||
|
|
||||||
|
for my $i (0 .. $#queries)
|
||||||
|
{
|
||||||
|
my $actual = $node->safe_psql("postgres", qq(
|
||||||
|
SET enable_seqscan = off;
|
||||||
|
SET ivfflat.probes = $probes;
|
||||||
|
SELECT i FROM tst ORDER BY v $operator '$queries[$i]' LIMIT $limit;
|
||||||
|
));
|
||||||
|
my @actual_ids = split("\n", $actual);
|
||||||
|
my %actual_set = map { $_ => 1 } @actual_ids;
|
||||||
|
|
||||||
|
my @expected_ids = split("\n", $expected[$i]);
|
||||||
|
|
||||||
|
foreach (@expected_ids)
|
||||||
|
{
|
||||||
|
if (exists($actual_set{$_}))
|
||||||
|
{
|
||||||
|
$correct++;
|
||||||
|
}
|
||||||
|
$total++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cmp_ok($correct / $total, ">=", $min, $operator);
|
||||||
|
}
|
||||||
|
|
||||||
|
# Initialize node
|
||||||
|
$node = get_new_node('node');
|
||||||
|
$node->init;
|
||||||
|
$node->start;
|
||||||
|
|
||||||
|
# Create table
|
||||||
|
$node->safe_psql("postgres", "CREATE EXTENSION vector;");
|
||||||
|
$node->safe_psql("postgres", "CREATE TABLE tst (i serial, v vector(3));");
|
||||||
|
|
||||||
|
# Generate queries
|
||||||
|
for (1 .. 20)
|
||||||
|
{
|
||||||
|
my $r1 = rand();
|
||||||
|
my $r2 = rand();
|
||||||
|
my $r3 = rand();
|
||||||
|
push(@queries, "[$r1,$r2,$r3]");
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check each index type
|
||||||
|
my @operators = ("<->", "<#>", "<=>");
|
||||||
|
my @opclasses = ("vector_l2_ops", "vector_ip_ops", "vector_cosine_ops");
|
||||||
|
|
||||||
|
for my $i (0 .. $#operators)
|
||||||
|
{
|
||||||
|
my $operator = $operators[$i];
|
||||||
|
my $opclass = $opclasses[$i];
|
||||||
|
|
||||||
|
# Add index
|
||||||
|
$node->safe_psql("postgres", "CREATE INDEX idx ON tst USING ivfflat (v $opclass);");
|
||||||
|
|
||||||
|
# Use concurrent inserts
|
||||||
|
$node->pgbench(
|
||||||
|
"--no-vacuum --client=10 --transactions=1000",
|
||||||
|
0,
|
||||||
|
[qr{actually processed}],
|
||||||
|
[qr{^$}],
|
||||||
|
"concurrent INSERTs",
|
||||||
|
{
|
||||||
|
"017_ivfflat_insert_recall_$opclass" => "INSERT INTO tst (v) SELECT ARRAY[random(), random(), random()] FROM generate_series(1, 10) i;"
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
# Get exact results
|
||||||
|
@expected = ();
|
||||||
|
foreach (@queries)
|
||||||
|
{
|
||||||
|
my $res = $node->safe_psql("postgres", qq(
|
||||||
|
SET enable_indexscan = off;
|
||||||
|
SELECT i FROM tst ORDER BY v $operator '$_' LIMIT $limit;
|
||||||
|
));
|
||||||
|
push(@expected, $res);
|
||||||
|
}
|
||||||
|
|
||||||
|
# Test approximate results
|
||||||
|
if ($operator ne "<#>")
|
||||||
|
{
|
||||||
|
# TODO Fix test (uniform random vectors all have similar inner product)
|
||||||
|
test_recall(1, 0.71, $operator);
|
||||||
|
test_recall(10, 0.95, $operator);
|
||||||
|
}
|
||||||
|
# Account for equal distances
|
||||||
|
test_recall(100, 0.9925, $operator);
|
||||||
|
|
||||||
|
$node->safe_psql("postgres", "DROP INDEX idx;");
|
||||||
|
$node->safe_psql("postgres", "TRUNCATE tst;");
|
||||||
|
}
|
||||||
|
|
||||||
|
done_testing();
|
||||||
43
test/t/018_ivfflat_deletes.pl
Normal file
43
test/t/018_ivfflat_deletes.pl
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
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();
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
comment = 'vector data type and ivfflat access method'
|
comment = 'vector data type and ivfflat and hnsw access methods'
|
||||||
default_version = '0.4.4'
|
default_version = '0.5.1'
|
||||||
module_pathname = '$libdir/vector'
|
module_pathname = '$libdir/vector'
|
||||||
relocatable = true
|
relocatable = true
|
||||||
|
|||||||
Reference in New Issue
Block a user