Compare commits

..

16 Commits

Author SHA1 Message Date
Andrew Kane
cd8d04359a fp16 2024-03-30 16:15:11 -07:00
Andrew Kane
eeeacc771b Test 2024-03-30 16:00:19 -07:00
Andrew Kane
56622e73fb Fixed underflow error 2024-03-30 15:04:33 -07:00
Andrew Kane
968173690e Changed casting to implicit 2024-03-30 10:28:38 -07:00
Andrew Kane
d82f6634fe Added dimension checks [skip ci] 2024-03-30 10:23:56 -07:00
Andrew Kane
4d041d9282 Fixed CI 2024-03-30 10:19:48 -07:00
Andrew Kane
e68db812aa Fixed underflow error 2024-03-30 10:06:21 -07:00
Andrew Kane
49bc34caf3 Make max dimensions consistent across types [skip ci] 2024-03-28 16:41:13 -07:00
Andrew Kane
4f1a379638 Added casting between vector and halfvec 2024-03-28 14:04:59 -07:00
Andrew Kane
45ef8f8a45 Improved code [skip ci] 2024-03-27 17:28:15 -07:00
Andrew Kane
6eedf78c25 Improved HnswNormValue [skip ci] 2024-03-27 17:19:58 -07:00
Andrew Kane
e2978d1365 Updated test [skip ci] 2024-03-27 17:09:38 -07:00
Andrew Kane
ccf44136dd Updated readme [skip ci] 2024-03-27 14:03:59 -07:00
Andrew Kane
daf27d5bd4 Fixed upgrade file [skip ci] 2024-03-27 13:51:12 -07:00
Andrew Kane
868498abec Fixed CI 2024-03-27 13:49:37 -07:00
Andrew Kane
264494b3e2 Added halfvec type 2024-03-27 13:47:19 -07:00
65 changed files with 199 additions and 2424 deletions

View File

@@ -8,16 +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
os: ubuntu-22.04
- postgres: 14
os: ubuntu-22.04
- postgres: 13
os: ubuntu-20.04
- postgres: 12 - postgres: 12
os: ubuntu-20.04 os: ubuntu-20.04
steps: steps:
@@ -32,51 +24,6 @@ jobs:
- run: | - run: |
export PG_CONFIG=`which pg_config` export PG_CONFIG=`which pg_config`
sudo --preserve-env=PG_CONFIG make install sudo --preserve-env=PG_CONFIG make install
- run: make installcheck
- if: ${{ failure() }}
run: cat regression.diffs
- run: |
sudo apt-get update
sudo apt-get install libipc-run-perl
- run: make prove_installcheck
mac:
runs-on: ${{ matrix.os }}
if: ${{ !startsWith(github.ref_name, 'windows') }}
strategy:
fail-fast: false
matrix:
include:
- postgres: 16
os: macos-14
- postgres: 14
os: macos-12
steps:
- uses: actions/checkout@v4
- uses: ankane/setup-postgres@v1
with:
postgres-version: ${{ matrix.postgres }}
- run: make
env:
PG_CFLAGS: -DUSE_ASSERT_CHECKING -Wall -Wextra -Werror -Wno-unused-parameter
- run: make install
- run: make installcheck
- if: ${{ failure() }}
run: cat regression.diffs
# Homebrew Postgres does not enable TAP tests, so need to download
- run: |
brew install cpanm
cpanm --notest IPC::Run
wget -q https://github.com/postgres/postgres/archive/refs/tags/$TAG.tar.gz
tar xf $TAG.tar.gz
mv postgres-$TAG postgres
env:
TAG: ${{ matrix.postgres == 16 && 'REL_16_2' || 'REL_14_11' }}
- run: make prove_installcheck PROVE_FLAGS="-I ./postgres/src/test/perl -I ./test/perl"
env:
PERL5LIB: /Users/runner/perl5/lib/perl5
- run: make clean && $(brew --prefix llvm@15)/bin/scan-build --status-bugs make
env:
PG_CFLAGS: -DUSE_ASSERT_CHECKING
windows: windows:
runs-on: windows-latest runs-on: windows-latest
if: ${{ !startsWith(github.ref_name, 'mac') }} if: ${{ !startsWith(github.ref_name, 'mac') }}
@@ -109,20 +56,5 @@ jobs:
git fetch origin ${{ github.ref }} git fetch origin ${{ github.ref }}
git reset --hard FETCH_HEAD git reset --hard FETCH_HEAD
make make
make install
chown -R postgres .
sudo -u postgres make installcheck
sudo -u postgres make prove_installcheck
env: env:
PG_CFLAGS: -DUSE_ASSERT_CHECKING -Wall -Wextra -Werror -Wno-unused-parameter -Wno-sign-compare PG_CFLAGS: -DUSE_ASSERT_CHECKING -Wall -Wextra -Werror -Wno-unused-parameter -Wno-sign-compare
valgrind:
if: ${{ !startsWith(github.ref_name, 'mac') && !startsWith(github.ref_name, 'windows') }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: ankane/setup-postgres-valgrind@v1
with:
postgres-version: 16
- run: make
- run: sudo --preserve-env=PG_CONFIG make install
- run: make installcheck

View File

@@ -1,13 +1,6 @@
## 0.7.0 (unreleased) ## 0.7.0 (unreleased)
- Added `halfvec` type - Added `halfvec` type
- Added `sparsevec` type
- Added support for `bit` vectors to HNSW
- Added `hamming_distance` function
- Added `jaccard_distance` function
- Added `quantize_binary` function
- Added `subvector` function
- Updated comparison operators to support vectors with different dimensions
## 0.6.2 (2024-03-18) ## 0.6.2 (2024-03-18)

View File

@@ -3,8 +3,8 @@ EXTVERSION = 0.6.2
MODULE_big = vector MODULE_big = vector
DATA = $(wildcard sql/*--*.sql) DATA = $(wildcard sql/*--*.sql)
OBJS = src/bitvector.o src/halfvec.o src/hnsw.o src/hnswbuild.o src/hnswinsert.o src/hnswscan.o src/hnswutils.o src/hnswvacuum.o src/ivfbuild.o src/ivfflat.o src/ivfinsert.o src/ivfkmeans.o src/ivfscan.o src/ivfutils.o src/ivfvacuum.o src/sparsevec.o src/vector.o OBJS = src/halfvec.o src/hnsw.o src/hnswbuild.o src/hnswinsert.o src/hnswscan.o src/hnswutils.o src/hnswvacuum.o src/ivfbuild.o src/ivfflat.o src/ivfinsert.o src/ivfkmeans.o src/ivfscan.o src/ivfutils.o src/ivfvacuum.o src/vector.o
HEADERS = src/halfvec.h src/sparsevec.h src/vector.h HEADERS = src/halfvec.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))
@@ -12,7 +12,7 @@ REGRESS_OPTS = --inputdir=test --load-extension=$(EXTENSION)
OPTFLAGS = -march=native OPTFLAGS = -march=native
# Mac ARM doesn't always support -march=native # Mac ARM doesn't support -march=native
ifeq ($(shell uname -s), Darwin) ifeq ($(shell uname -s), Darwin)
ifeq ($(shell uname -p), arm) ifeq ($(shell uname -p), arm)
# no difference with -march=armv8.5-a # no difference with -march=armv8.5-a

View File

@@ -1,10 +1,10 @@
EXTENSION = vector EXTENSION = vector
EXTVERSION = 0.6.2 EXTVERSION = 0.6.2
OBJS = src\bitvector.obj src\halfvec.obj src\hnsw.obj src\hnswbuild.obj src\hnswinsert.obj src\hnswscan.obj src\hnswutils.obj src\hnswvacuum.obj src\ivfbuild.obj src\ivfflat.obj src\ivfinsert.obj src\ivfkmeans.obj src\ivfscan.obj src\ivfutils.obj src\ivfvacuum.obj src\sparsevec.obj src\vector.obj OBJS = src\halfvec.obj src\hnsw.obj src\hnswbuild.obj src\hnswinsert.obj src\hnswscan.obj src\hnswutils.obj src\hnswvacuum.obj src\ivfbuild.obj src\ivfflat.obj src\ivfinsert.obj src\ivfkmeans.obj src\ivfscan.obj src\ivfutils.obj src\ivfvacuum.obj src\vector.obj
HEADERS = src\halfvec.h src\sparsevec.h src\vector.h HEADERS = src\halfvec.h src\vector.h
REGRESS = bit_functions btree cast copy halfvec_functions halfvec_input hnsw_bit_hamming hnsw_bit_jaccard hnsw_halfvec_cosine hnsw_halfvec_ip hnsw_halfvec_l2 hnsw_options hnsw_sparsevec_cosine hnsw_sparsevec_ip hnsw_sparsevec_l2 hnsw_unlogged hnsw_vector_cosine hnsw_vector_ip hnsw_vector_l2 ivfflat_options ivfflat_unlogged ivfflat_vector_cosine ivfflat_vector_ip ivfflat_vector_l2 sparsevec_functions sparsevec_input vector_functions vector_input REGRESS = btree cast copy functions input ivfflat_cosine ivfflat_ip ivfflat_l2 ivfflat_options ivfflat_unlogged
REGRESS_OPTS = --inputdir=test --load-extension=$(EXTENSION) REGRESS_OPTS = --inputdir=test --load-extension=$(EXTENSION)
# For /arch flags # For /arch flags

103
README.md
View File

@@ -221,24 +221,7 @@ Cosine distance
CREATE INDEX ON items USING hnsw (embedding vector_cosine_ops); CREATE INDEX ON items USING hnsw (embedding vector_cosine_ops);
``` ```
Hamming distance - unreleased Vectors with up to 2,000 dimensions can be indexed.
```sql
CREATE INDEX ON items USING hnsw (embedding bit_hamming_ops);
```
Jaccard distance - unreleased
```sql
CREATE INDEX ON items USING hnsw (embedding bit_jaccard_ops);
```
Supported types are:
- `vector` - up to 2,000 dimensions
- `halfvec` - up to 4,000 dimensions (unreleased)
- `bit` - up to 64,000 dimensions (unreleased)
- `sparsevec` - up to 1,000 non-zero elements (unreleased)
### Index Options ### Index Options
@@ -343,9 +326,7 @@ Cosine distance
CREATE INDEX ON items USING ivfflat (embedding vector_cosine_ops) WITH (lists = 100); CREATE INDEX ON items USING ivfflat (embedding vector_cosine_ops) WITH (lists = 100);
``` ```
Supported types are: Vectors with up to 2,000 dimensions can be indexed.
- `vector` - up to 2,000 dimensions
### Query Options ### Query Options
@@ -434,19 +415,7 @@ You can use [Reciprocal Rank Fusion](https://github.com/pgvector/pgvector-python
### Tuning ### Tuning
Use a tool like [PgTune](https://pgtune.leopard.in.ua/) to set initial values for Postgres server parameters. For instance, `shared_buffers` should typically be 25% of the servers memory. You can find the config file with: Use a tool like [PgTune](https://pgtune.leopard.in.ua/) to set initial values for Postgres server parameters.
```sql
SHOW config_file;
```
And check individual settings with:
```sql
SHOW shared_buffers;
```
Be sure to restart Postgres for changes to take effect.
### Loading ### Loading
@@ -718,14 +687,9 @@ Also, note that `NULL` vectors are not indexed (as well as zero vectors for cosi
## Reference ## Reference
- [Vector](#vector-type)
- [Halfvec](#halfvec-type)
- [Bit](#bit-type)
- [Sparsevec](#sparsevec-type)
### Vector Type ### Vector Type
Each vector takes `4 * dimensions + 8` bytes of storage. Each element is a single-precision floating-point number (like the `real` type in Postgres), and all elements must be finite (no `NaN`, `Infinity` or `-Infinity`). Vectors can have up to 16,000 dimensions. Each vector takes `4 * dimensions + 8` bytes of storage. Each element is a single precision floating-point number (like the `real` type in Postgres), and all elements must be finite (no `NaN`, `Infinity` or `-Infinity`). Vectors can have up to 16,000 dimensions.
### Vector Operators ### Vector Operators
@@ -746,12 +710,10 @@ 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 | 0.5.0 l1_distance(vector, vector) → double precision | taxicab distance | 0.5.0
quantize_binary(vector) → bit | quantize | unreleased
subvector(vector, integer, integer) → vector | subvector | unreleased
vector_dims(vector) → integer | number of dimensions | vector_dims(vector) → integer | number of dimensions |
vector_norm(vector) → double precision | Euclidean norm | vector_norm(vector) → double precision | Euclidean norm |
### Vector Aggregate Functions ### Aggregate Functions
Function | Description | Added Function | Description | Added
--- | --- | --- --- | --- | ---
@@ -766,59 +728,18 @@ Each half vector takes `2 * dimensions + 8` bytes of storage. Each element is a
Operator | Description | Added Operator | Description | Added
--- | --- | --- --- | --- | ---
<-> | Euclidean distance | unreleased <-> | Euclidean distance | 0.7.0
<#> | negative inner product | unreleased <#> | negative inner product | 0.7.0
<=> | cosine distance | unreleased <=> | cosine distance | 0.7.0
### Halfvec Functions ### Halfvec Functions
Function | Description | Added Function | Description | Added
--- | --- | --- --- | --- | ---
cosine_distance(halfvec, halfvec) → double precision | cosine distance | unreleased cosine_distance(halfvec, halfvec) → double precision | cosine distance | 0.7.0
inner_product(halfvec, halfvec) → double precision | inner product | unreleased inner_product(halfvec, halfvec) → double precision | inner product | 0.7.0
l2_distance(halfvec, halfvec) → double precision | Euclidean distance | unreleased l2_distance(halfvec, halfvec) → double precision | Euclidean distance | 0.7.0
l1_distance(halfvec, halfvec) → double precision | taxicab distance | unreleased l1_distance(halfvec, halfvec) → double precision | taxicab distance | 0.7.0
quantize_binary(halfvec) → bit | quantize | unreleased
subvector(halfvec, integer, integer) → halfvec | subvector | unreleased
### Bit Type
Each bit vector takes `dimensions / 8 + 8` bytes of storage. See the [Postgres docs](https://www.postgresql.org/docs/current/datatype-bit.html) for more info.
### Bit Operators
Operator | Description | Added
--- | --- | ---
<~> | Hamming distance | unreleased
<%> | Jaccard distance | unreleased
### Bit Functions
Function | Description | Added
--- | --- | ---
hamming_distance(bit, bit) → double precision | Hamming distance | unreleased
jaccard_distance(bit, bit) → double precision | Jaccard distance | unreleased
### Sparsevec Type
Each sparse vector takes `8 * non-zero elements + 16` bytes of storage. Each element is a single-precision floating-point number, and all elements must be finite (no `NaN`, `Infinity` or `-Infinity`). Sparse vectors can have up to 100,000 dimensions.
### Sparsevec Operators
Operator | Description | Added
--- | --- | ---
<-> | Euclidean distance | unreleased
<#> | negative inner product | unreleased
<=> | cosine distance | unreleased
### Sparsevec Functions
Function | Description | Added
--- | --- | ---
cosine_distance(sparsevec, sparsevec) → double precision | cosine distance | unreleased
inner_product(sparsevec, sparsevec) → double precision | inner product | unreleased
l2_distance(sparsevec, sparsevec) → double precision | Euclidean distance | unreleased
l1_distance(sparsevec, sparsevec) → double precision | taxicab distance | unreleased
## Installation Notes - Linux and Mac ## Installation Notes - Linux and Mac

View File

@@ -1,38 +1,6 @@
-- complain if script is sourced in psql, rather than via CREATE EXTENSION -- complain if script is sourced in psql, rather than via CREATE EXTENSION
\echo Use "ALTER EXTENSION vector UPDATE TO '0.7.0'" to load this file. \quit \echo Use "ALTER EXTENSION vector UPDATE TO '0.7.0'" to load this file. \quit
CREATE FUNCTION quantize_binary(vector) RETURNS bit
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
CREATE FUNCTION subvector(vector, int, int) RETURNS vector
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
CREATE FUNCTION hamming_distance(bit, bit) RETURNS float8
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
CREATE FUNCTION jaccard_distance(bit, bit) RETURNS float8
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
CREATE OPERATOR <~> (
LEFTARG = bit, RIGHTARG = bit, PROCEDURE = hamming_distance,
COMMUTATOR = '<~>'
);
CREATE OPERATOR <%> (
LEFTARG = bit, RIGHTARG = bit, PROCEDURE = jaccard_distance,
COMMUTATOR = '<%>'
);
CREATE OPERATOR CLASS bit_hamming_ops
FOR TYPE bit USING hnsw AS
OPERATOR 1 <~> (bit, bit) FOR ORDER BY float_ops,
FUNCTION 1 hamming_distance(bit, bit);
CREATE OPERATOR CLASS bit_jaccard_ops
FOR TYPE bit USING hnsw AS
OPERATOR 1 <%> (bit, bit) FOR ORDER BY float_ops,
FUNCTION 1 jaccard_distance(bit, bit);
CREATE TYPE halfvec; CREATE TYPE halfvec;
CREATE FUNCTION halfvec_in(cstring, oid, integer) RETURNS halfvec CREATE FUNCTION halfvec_in(cstring, oid, integer) RETURNS halfvec
@@ -74,12 +42,6 @@ CREATE FUNCTION l1_distance(halfvec, halfvec) RETURNS float8
CREATE FUNCTION halfvec_norm(halfvec) RETURNS float8 CREATE FUNCTION halfvec_norm(halfvec) RETURNS float8
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
CREATE FUNCTION quantize_binary(halfvec) RETURNS bit
AS 'MODULE_PATHNAME', 'halfvec_quantize_binary' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
CREATE FUNCTION subvector(halfvec, int, int) RETURNS halfvec
AS 'MODULE_PATHNAME', 'halfvec_subvector' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
CREATE FUNCTION halfvec_l2_squared_distance(halfvec, halfvec) RETURNS float8 CREATE FUNCTION halfvec_l2_squared_distance(halfvec, halfvec) RETURNS float8
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
@@ -160,96 +122,7 @@ CREATE FUNCTION vector_to_halfvec(vector, integer, boolean) RETURNS halfvec
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
CREATE CAST (halfvec AS vector) CREATE CAST (halfvec AS vector)
WITH FUNCTION halfvec_to_vector(halfvec, integer, boolean) AS IMPLICIT; WITH FUNCTION halfvec_to_vector(halfvec, integer, boolean) AS ASSIGNMENT;
CREATE CAST (vector AS halfvec) CREATE CAST (vector AS halfvec)
WITH FUNCTION vector_to_halfvec(vector, integer, boolean) AS IMPLICIT; WITH FUNCTION vector_to_halfvec(vector, integer, boolean) AS ASSIGNMENT;
CREATE TYPE sparsevec;
CREATE FUNCTION sparsevec_in(cstring, oid, integer) RETURNS sparsevec
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
CREATE FUNCTION sparsevec_out(sparsevec) RETURNS cstring
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
CREATE FUNCTION sparsevec_recv(internal, oid, integer) RETURNS sparsevec
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
CREATE FUNCTION sparsevec_send(sparsevec) RETURNS bytea
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
CREATE TYPE sparsevec (
INPUT = sparsevec_in,
OUTPUT = sparsevec_out,
RECEIVE = sparsevec_recv,
SEND = sparsevec_send,
STORAGE = external
);
CREATE FUNCTION l2_distance(sparsevec, sparsevec) RETURNS float8
AS 'MODULE_PATHNAME', 'sparsevec_l2_distance' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
CREATE FUNCTION inner_product(sparsevec, sparsevec) RETURNS float8
AS 'MODULE_PATHNAME', 'sparsevec_inner_product' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
CREATE FUNCTION cosine_distance(sparsevec, sparsevec) RETURNS float8
AS 'MODULE_PATHNAME', 'sparsevec_cosine_distance' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
CREATE FUNCTION sparsevec_norm(sparsevec) RETURNS float8
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
CREATE FUNCTION sparsevec_l2_squared_distance(sparsevec, sparsevec) RETURNS float8
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
CREATE FUNCTION sparsevec_negative_inner_product(sparsevec, sparsevec) RETURNS float8
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
CREATE FUNCTION sparsevec(sparsevec, integer, boolean) RETURNS sparsevec
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
CREATE FUNCTION vector_to_sparsevec(vector, integer, boolean) RETURNS sparsevec
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
CREATE FUNCTION sparsevec_to_vector(sparsevec, integer, boolean) RETURNS vector
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
CREATE CAST (sparsevec AS sparsevec)
WITH FUNCTION sparsevec(sparsevec, integer, boolean) AS IMPLICIT;
CREATE CAST (sparsevec AS vector)
WITH FUNCTION sparsevec_to_vector(sparsevec, integer, boolean) AS IMPLICIT;
CREATE CAST (vector AS sparsevec)
WITH FUNCTION vector_to_sparsevec(vector, integer, boolean) AS IMPLICIT;
CREATE OPERATOR <-> (
LEFTARG = sparsevec, RIGHTARG = sparsevec, PROCEDURE = l2_distance,
COMMUTATOR = '<->'
);
CREATE OPERATOR <#> (
LEFTARG = sparsevec, RIGHTARG = sparsevec, PROCEDURE = sparsevec_negative_inner_product,
COMMUTATOR = '<#>'
);
CREATE OPERATOR <=> (
LEFTARG = sparsevec, RIGHTARG = sparsevec, PROCEDURE = cosine_distance,
COMMUTATOR = '<=>'
);
CREATE OPERATOR CLASS sparsevec_l2_ops
FOR TYPE sparsevec USING hnsw AS
OPERATOR 1 <-> (sparsevec, sparsevec) FOR ORDER BY float_ops,
FUNCTION 1 sparsevec_l2_squared_distance(sparsevec, sparsevec);
CREATE OPERATOR CLASS sparsevec_ip_ops
FOR TYPE sparsevec USING hnsw AS
OPERATOR 1 <#> (sparsevec, sparsevec) FOR ORDER BY float_ops,
FUNCTION 1 sparsevec_negative_inner_product(sparsevec, sparsevec);
CREATE OPERATOR CLASS sparsevec_cosine_ops
FOR TYPE sparsevec USING hnsw AS
OPERATOR 1 <=> (sparsevec, sparsevec) FOR ORDER BY float_ops,
FUNCTION 1 sparsevec_negative_inner_product(sparsevec, sparsevec),
FUNCTION 2 sparsevec_norm(sparsevec);

View File

@@ -1,7 +1,7 @@
-- complain if script is sourced in psql, rather than via CREATE EXTENSION -- complain if script is sourced in psql, rather than via CREATE EXTENSION
\echo Use "CREATE EXTENSION vector" to load this file. \quit \echo Use "CREATE EXTENSION vector" to load this file. \quit
-- vector type -- type
CREATE TYPE vector; CREATE TYPE vector;
@@ -29,7 +29,7 @@ CREATE TYPE vector (
STORAGE = external STORAGE = external
); );
-- vector functions -- functions
CREATE FUNCTION l2_distance(vector, vector) RETURNS float8 CREATE FUNCTION l2_distance(vector, vector) RETURNS float8
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
@@ -58,13 +58,7 @@ CREATE FUNCTION vector_sub(vector, vector) RETURNS vector
CREATE FUNCTION vector_mul(vector, vector) RETURNS vector CREATE FUNCTION vector_mul(vector, vector) RETURNS vector
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
CREATE FUNCTION quantize_binary(vector) RETURNS bit -- private functions
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
CREATE FUNCTION subvector(vector, int, int) RETURNS vector
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
-- vector private functions
CREATE FUNCTION vector_lt(vector, vector) RETURNS bool CREATE FUNCTION vector_lt(vector, vector) RETURNS bool
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
@@ -105,7 +99,7 @@ CREATE FUNCTION vector_avg(double precision[]) RETURNS vector
CREATE FUNCTION vector_combine(double precision[], double precision[]) RETURNS double precision[] CREATE FUNCTION vector_combine(double precision[], double precision[]) RETURNS double precision[]
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
-- vector aggregates -- aggregates
CREATE AGGREGATE avg(vector) ( CREATE AGGREGATE avg(vector) (
SFUNC = vector_accum, SFUNC = vector_accum,
@@ -123,7 +117,7 @@ CREATE AGGREGATE sum(vector) (
PARALLEL = SAFE PARALLEL = SAFE
); );
-- vector cast functions -- cast functions
CREATE FUNCTION vector(vector, integer, boolean) RETURNS vector CREATE FUNCTION vector(vector, integer, boolean) RETURNS vector
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
@@ -143,7 +137,7 @@ CREATE FUNCTION array_to_vector(numeric[], integer, boolean) RETURNS vector
CREATE FUNCTION vector_to_float4(vector, integer, boolean) RETURNS real[] CREATE FUNCTION vector_to_float4(vector, integer, boolean) RETURNS real[]
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
-- vector casts -- casts
CREATE CAST (vector AS vector) CREATE CAST (vector AS vector)
WITH FUNCTION vector(vector, integer, boolean) AS IMPLICIT; WITH FUNCTION vector(vector, integer, boolean) AS IMPLICIT;
@@ -163,7 +157,7 @@ CREATE CAST (double precision[] AS vector)
CREATE CAST (numeric[] AS vector) CREATE CAST (numeric[] AS vector)
WITH FUNCTION array_to_vector(numeric[], integer, boolean) AS ASSIGNMENT; WITH FUNCTION array_to_vector(numeric[], integer, boolean) AS ASSIGNMENT;
-- vector operators -- operators
CREATE OPERATOR <-> ( CREATE OPERATOR <-> (
LEFTARG = vector, RIGHTARG = vector, PROCEDURE = l2_distance, LEFTARG = vector, RIGHTARG = vector, PROCEDURE = l2_distance,
@@ -246,7 +240,7 @@ CREATE ACCESS METHOD hnsw TYPE INDEX HANDLER hnswhandler;
COMMENT ON ACCESS METHOD hnsw IS 'hnsw index access method'; COMMENT ON ACCESS METHOD hnsw IS 'hnsw index access method';
-- vector opclasses -- opclasses
CREATE OPERATOR CLASS vector_ops CREATE OPERATOR CLASS vector_ops
DEFAULT FOR TYPE vector USING btree AS DEFAULT FOR TYPE vector USING btree AS
@@ -294,34 +288,6 @@ CREATE OPERATOR CLASS vector_cosine_ops
FUNCTION 1 vector_negative_inner_product(vector, vector), FUNCTION 1 vector_negative_inner_product(vector, vector),
FUNCTION 2 vector_norm(vector); FUNCTION 2 vector_norm(vector);
-- bit functions
CREATE FUNCTION hamming_distance(bit, bit) RETURNS float8
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
CREATE FUNCTION jaccard_distance(bit, bit) RETURNS float8
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
CREATE OPERATOR <~> (
LEFTARG = bit, RIGHTARG = bit, PROCEDURE = hamming_distance,
COMMUTATOR = '<~>'
);
CREATE OPERATOR <%> (
LEFTARG = bit, RIGHTARG = bit, PROCEDURE = jaccard_distance,
COMMUTATOR = '<%>'
);
CREATE OPERATOR CLASS bit_hamming_ops
FOR TYPE bit USING hnsw AS
OPERATOR 1 <~> (bit, bit) FOR ORDER BY float_ops,
FUNCTION 1 hamming_distance(bit, bit);
CREATE OPERATOR CLASS bit_jaccard_ops
FOR TYPE bit USING hnsw AS
OPERATOR 1 <%> (bit, bit) FOR ORDER BY float_ops,
FUNCTION 1 jaccard_distance(bit, bit);
-- halfvec type -- halfvec type
CREATE TYPE halfvec; CREATE TYPE halfvec;
@@ -367,12 +333,6 @@ CREATE FUNCTION l1_distance(halfvec, halfvec) RETURNS float8
CREATE FUNCTION halfvec_norm(halfvec) RETURNS float8 CREATE FUNCTION halfvec_norm(halfvec) RETURNS float8
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
CREATE FUNCTION quantize_binary(halfvec) RETURNS bit
AS 'MODULE_PATHNAME', 'halfvec_quantize_binary' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
CREATE FUNCTION subvector(halfvec, int, int) RETURNS halfvec
AS 'MODULE_PATHNAME', 'halfvec_subvector' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
-- halfvec private functions -- halfvec private functions
CREATE FUNCTION halfvec_l2_squared_distance(halfvec, halfvec) RETURNS float8 CREATE FUNCTION halfvec_l2_squared_distance(halfvec, halfvec) RETURNS float8
@@ -469,106 +429,3 @@ CREATE CAST (halfvec AS vector)
CREATE CAST (vector AS halfvec) CREATE CAST (vector AS halfvec)
WITH FUNCTION vector_to_halfvec(vector, integer, boolean) AS IMPLICIT; WITH FUNCTION vector_to_halfvec(vector, integer, boolean) AS IMPLICIT;
--- sparsevec type
CREATE TYPE sparsevec;
CREATE FUNCTION sparsevec_in(cstring, oid, integer) RETURNS sparsevec
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
CREATE FUNCTION sparsevec_out(sparsevec) RETURNS cstring
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
CREATE FUNCTION sparsevec_recv(internal, oid, integer) RETURNS sparsevec
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
CREATE FUNCTION sparsevec_send(sparsevec) RETURNS bytea
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
CREATE TYPE sparsevec (
INPUT = sparsevec_in,
OUTPUT = sparsevec_out,
RECEIVE = sparsevec_recv,
SEND = sparsevec_send,
STORAGE = external
);
-- sparsevec functions
CREATE FUNCTION l2_distance(sparsevec, sparsevec) RETURNS float8
AS 'MODULE_PATHNAME', 'sparsevec_l2_distance' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
CREATE FUNCTION inner_product(sparsevec, sparsevec) RETURNS float8
AS 'MODULE_PATHNAME', 'sparsevec_inner_product' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
CREATE FUNCTION cosine_distance(sparsevec, sparsevec) RETURNS float8
AS 'MODULE_PATHNAME', 'sparsevec_cosine_distance' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
CREATE FUNCTION sparsevec_norm(sparsevec) RETURNS float8
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
-- sparsevec private functions
CREATE FUNCTION sparsevec_l2_squared_distance(sparsevec, sparsevec) RETURNS float8
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
CREATE FUNCTION sparsevec_negative_inner_product(sparsevec, sparsevec) RETURNS float8
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
-- sparsevec cast functions
CREATE FUNCTION sparsevec(sparsevec, integer, boolean) RETURNS sparsevec
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
CREATE FUNCTION vector_to_sparsevec(vector, integer, boolean) RETURNS sparsevec
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
CREATE FUNCTION sparsevec_to_vector(sparsevec, integer, boolean) RETURNS vector
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
-- sparsevec casts
CREATE CAST (sparsevec AS sparsevec)
WITH FUNCTION sparsevec(sparsevec, integer, boolean) AS IMPLICIT;
CREATE CAST (sparsevec AS vector)
WITH FUNCTION sparsevec_to_vector(sparsevec, integer, boolean) AS IMPLICIT;
CREATE CAST (vector AS sparsevec)
WITH FUNCTION vector_to_sparsevec(vector, integer, boolean) AS IMPLICIT;
-- sparsevec operators
CREATE OPERATOR <-> (
LEFTARG = sparsevec, RIGHTARG = sparsevec, PROCEDURE = l2_distance,
COMMUTATOR = '<->'
);
CREATE OPERATOR <#> (
LEFTARG = sparsevec, RIGHTARG = sparsevec, PROCEDURE = sparsevec_negative_inner_product,
COMMUTATOR = '<#>'
);
CREATE OPERATOR <=> (
LEFTARG = sparsevec, RIGHTARG = sparsevec, PROCEDURE = cosine_distance,
COMMUTATOR = '<=>'
);
-- sparsevec opclasses
CREATE OPERATOR CLASS sparsevec_l2_ops
FOR TYPE sparsevec USING hnsw AS
OPERATOR 1 <-> (sparsevec, sparsevec) FOR ORDER BY float_ops,
FUNCTION 1 sparsevec_l2_squared_distance(sparsevec, sparsevec);
CREATE OPERATOR CLASS sparsevec_ip_ops
FOR TYPE sparsevec USING hnsw AS
OPERATOR 1 <#> (sparsevec, sparsevec) FOR ORDER BY float_ops,
FUNCTION 1 sparsevec_negative_inner_product(sparsevec, sparsevec);
CREATE OPERATOR CLASS sparsevec_cosine_ops
FOR TYPE sparsevec USING hnsw AS
OPERATOR 1 <=> (sparsevec, sparsevec) FOR ORDER BY float_ops,
FUNCTION 1 sparsevec_negative_inner_product(sparsevec, sparsevec),
FUNCTION 2 sparsevec_norm(sparsevec);

View File

@@ -1,90 +0,0 @@
#include "postgres.h"
#include "bitvector.h"
#include "port/pg_bitutils.h"
#include "utils/varbit.h"
#if PG_VERSION_NUM >= 160000
#include "varatt.h"
#endif
/*
* Allocate and initialize a new bit vector
*/
VarBit *
InitBitVector(int dim)
{
VarBit *result;
int size;
size = VARBITTOTALLEN(dim);
result = (VarBit *) palloc0(size);
SET_VARSIZE(result, size);
VARBITLEN(result) = dim;
return result;
}
/*
* Ensure same dimensions
*/
static inline void
CheckDims(VarBit *a, VarBit *b)
{
if (VARBITLEN(a) != VARBITLEN(b))
ereport(ERROR,
(errcode(ERRCODE_DATA_EXCEPTION),
errmsg("different bit lengths %u and %u", VARBITLEN(a), VARBITLEN(b))));
}
/*
* Get the Hamming distance between two bit vectors
*/
PGDLLEXPORT PG_FUNCTION_INFO_V1(hamming_distance);
Datum
hamming_distance(PG_FUNCTION_ARGS)
{
VarBit *a = PG_GETARG_VARBIT_P(0);
VarBit *b = PG_GETARG_VARBIT_P(1);
unsigned char *ax = VARBITS(a);
unsigned char *bx = VARBITS(b);
uint64 distance = 0;
CheckDims(a, b);
/* TODO Improve performance */
for (uint32 i = 0; i < VARBITBYTES(a); i++)
distance += pg_number_of_ones[ax[i] ^ bx[i]];
PG_RETURN_FLOAT8((double) distance);
}
/*
* Get the Jaccard distance between two bit vectors
*/
PGDLLEXPORT PG_FUNCTION_INFO_V1(jaccard_distance);
Datum
jaccard_distance(PG_FUNCTION_ARGS)
{
VarBit *a = PG_GETARG_VARBIT_P(0);
VarBit *b = PG_GETARG_VARBIT_P(1);
unsigned char *ax = VARBITS(a);
unsigned char *bx = VARBITS(b);
uint64 ab = 0;
uint64 aa;
uint64 bb;
CheckDims(a, b);
/* TODO Improve performance */
for (uint32 i = 0; i < VARBITBYTES(a); i++)
ab += pg_number_of_ones[ax[i] & bx[i]];
if (ab == 0)
PG_RETURN_FLOAT8(1);
aa = pg_popcount((char *) ax, VARBITBYTES(a));
bb = pg_popcount((char *) bx, VARBITBYTES(b));
PG_RETURN_FLOAT8(1 - (ab / ((double) (aa + bb - ab))));
}

View File

@@ -1,8 +0,0 @@
#ifndef BITVECTOR_H
#define BITVECTOR_H
#include "utils/varbit.h"
VarBit *InitBitVector(int dim);
#endif

View File

@@ -2,7 +2,6 @@
#include <math.h> #include <math.h>
#include "bitvector.h"
#include "catalog/pg_type.h" #include "catalog/pg_type.h"
#include "common/shortest_dec.h" #include "common/shortest_dec.h"
#include "fmgr.h" #include "fmgr.h"
@@ -105,30 +104,13 @@ HalfToFloat4(half num)
/* TODO Improve performance */ /* TODO Improve performance */
/* Assumes same endianness for floats and integers */ /* Assumes same endianness for floats and integers */
union /* TODO Use union to swap */
{ uint16 bin = *((uint16 *) &num);
float f; uint32 exponent = (bin & 0x7C00) >> 10;
uint32 i; uint32 mantissa = bin & 0x03FF;
} swapfloat;
union
{
half h;
uint16 i;
} swaphalf;
uint16 bin;
uint32 exponent;
uint32 mantissa;
uint32 result;
swaphalf.h = num;
bin = swaphalf.i;
exponent = (bin & 0x7C00) >> 10;
mantissa = bin & 0x03FF;
/* Sign */ /* Sign */
result = (bin & 0x8000) << 16; uint32 result = (bin & 0x8000) << 16;
if (exponent == 31) if (exponent == 31)
{ {
@@ -174,8 +156,8 @@ HalfToFloat4(half num)
result |= mantissa << 13; result |= mantissa << 13;
} }
swapfloat.i = result; /* TODO Use union to swap */
return swapfloat.f; return *((float *) &result);
#endif #endif
} }
@@ -191,30 +173,13 @@ Float4ToHalfUnchecked(float num)
/* TODO Improve performance */ /* TODO Improve performance */
/* Assumes same endianness for floats and integers */ /* Assumes same endianness for floats and integers */
union /* TODO Use union to swap */
{ uint32 bin = *((uint32 *) &num);
float f; int exponent = (bin & 0x7F800000) >> 23;
uint32 i; int mantissa = bin & 0x007FFFFF;
} swapfloat;
union
{
half h;
uint16 i;
} swaphalf;
uint32 bin;
int exponent;
int mantissa;
uint16 result;
swapfloat.f = num;
bin = swapfloat.i;
exponent = (bin & 0x7F800000) >> 23;
mantissa = bin & 0x007FFFFF;
/* Sign */ /* Sign */
result = (bin & 0x80000000) >> 16; uint16 result = (bin & 0x80000000) >> 16;
if (isinf(num)) if (isinf(num))
{ {
@@ -273,8 +238,8 @@ Float4ToHalfUnchecked(float num)
} }
} }
swaphalf.i = result; /* TODO Use union to swap */
return swaphalf.h; return *((half *) & result);
#endif #endif
} }
@@ -513,9 +478,6 @@ halfvec_in(PG_FUNCTION_ARGS)
PG_RETURN_POINTER(result); PG_RETURN_POINTER(result);
} }
#define AppendChar(ptr, c) (*(ptr)++ = (c))
#define AppendFloat(ptr, f) ((ptr) += float_to_shortest_decimal_bufn((f), (ptr)))
/* /*
* Convert internal representation to textual representation * Convert internal representation to textual representation
*/ */
@@ -527,6 +489,7 @@ halfvec_out(PG_FUNCTION_ARGS)
int dim = vector->dim; int dim = vector->dim;
char *buf; char *buf;
char *ptr; char *ptr;
int n;
/* /*
* Need: * Need:
@@ -541,17 +504,21 @@ halfvec_out(PG_FUNCTION_ARGS)
buf = (char *) palloc(FLOAT_SHORTEST_DECIMAL_LEN * dim + 2); buf = (char *) palloc(FLOAT_SHORTEST_DECIMAL_LEN * dim + 2);
ptr = buf; ptr = buf;
AppendChar(ptr, '['); *ptr = '[';
ptr++;
for (int i = 0; i < dim; i++) for (int i = 0; i < dim; i++)
{ {
if (i > 0) if (i > 0)
AppendChar(ptr, ','); {
*ptr = ',';
ptr++;
}
AppendFloat(ptr, HalfToFloat4(vector->x[i])); n = float_to_shortest_decimal_bufn(HalfToFloat4(vector->x[i]), ptr);
ptr += n;
} }
*ptr = ']';
AppendChar(ptr, ']'); ptr++;
*ptr = '\0'; *ptr = '\0';
PG_FREE_IF_COPY(vector, 0); PG_FREE_IF_COPY(vector, 0);
@@ -764,18 +731,15 @@ Datum
vector_to_halfvec(PG_FUNCTION_ARGS) vector_to_halfvec(PG_FUNCTION_ARGS)
{ {
Vector *vec = PG_GETARG_VECTOR_P(0); Vector *vec = PG_GETARG_VECTOR_P(0);
int32 typmod = PG_GETARG_INT32(1);
HalfVector *result; HalfVector *result;
CheckDim(vec->dim); CheckDim(vec->dim);
CheckExpectedDim(typmod, vec->dim);
result = InitHalfVector(vec->dim); result = InitHalfVector(vec->dim);
for (int i = 0; i < vec->dim; i++) for (int i = 0; i < vec->dim; i++)
{ {
result->x[i] = Float4ToHalfUnchecked(vec->x[i]); result->x[i] = Float4ToHalfUnchecked(vec->x[i]);
/* TODO Better error for overflow */
CheckElement(result->x[i]); CheckElement(result->x[i]);
} }
@@ -968,53 +932,3 @@ halfvec_norm(PG_FUNCTION_ARGS)
PG_RETURN_FLOAT8(sqrt(norm)); PG_RETURN_FLOAT8(sqrt(norm));
} }
/*
* Quantize a half vector
*/
PGDLLEXPORT PG_FUNCTION_INFO_V1(halfvec_quantize_binary);
Datum
halfvec_quantize_binary(PG_FUNCTION_ARGS)
{
HalfVector *a = PG_GETARG_HALFVEC_P(0);
half *ax = a->x;
VarBit *result = InitBitVector(a->dim);
unsigned char *rx = VARBITS(result);
for (int i = 0; i < a->dim; i++)
rx[i / 8] |= (HalfToFloat4(ax[i]) > 0) << (7 - (i % 8));
PG_RETURN_VARBIT_P(result);
}
/*
* Get a subvector
*/
PGDLLEXPORT PG_FUNCTION_INFO_V1(halfvec_subvector);
Datum
halfvec_subvector(PG_FUNCTION_ARGS)
{
HalfVector *a = PG_GETARG_HALFVEC_P(0);
int32 start = PG_GETARG_INT32(1);
int32 count = PG_GETARG_INT32(2);
int32 end = start + count;
half *ax = a->x;
HalfVector *result;
int dim;
/* Indexing starts at 1, like substring */
if (start < 1)
start = 1;
if (end > a->dim)
end = a->dim + 1;
dim = end - start;
CheckDim(dim);
result = InitHalfVector(dim);
for (int i = 0; i < dim; i++)
result->x[i] = ax[start - 1 + i];
PG_RETURN_POINTER(result);
}

View File

@@ -5,8 +5,6 @@
#include <float.h> #include <float.h>
#include "vector.h"
#ifdef __FLT16_MAX__ #ifdef __FLT16_MAX__
#define FLT16_SUPPORT #define FLT16_SUPPORT
#endif #endif
@@ -16,11 +14,11 @@
#define HALF_MAX FLT16_MAX #define HALF_MAX FLT16_MAX
#else #else
/* TODO #pragma message("")? */ /* TODO #pragma message("")? */
#define half uint16 #define half __fp16
#define HALF_MAX 65504 #define HALF_MAX 65504
#endif #endif
#define HALFVEC_MAX_DIM VECTOR_MAX_DIM #define HALFVEC_MAX_DIM 16000
#define HALFVEC_SIZE(_dim) (offsetof(HalfVector, x) + sizeof(half)*(_dim)) #define HALFVEC_SIZE(_dim) (offsetof(HalfVector, x) + sizeof(half)*(_dim))
#define DatumGetHalfVector(x) ((HalfVector *) PG_DETOAST_DATUM(x)) #define DatumGetHalfVector(x) ((HalfVector *) PG_DETOAST_DATUM(x))

View File

@@ -17,7 +17,6 @@
#endif #endif
#define HNSW_MAX_DIM 2000 #define HNSW_MAX_DIM 2000
#define HNSW_MAX_NNZ 1000
/* Support functions */ /* Support functions */
#define HNSW_DISTANCE_PROC 1 #define HNSW_DISTANCE_PROC 1
@@ -56,13 +55,9 @@
#define HNSW_UPDATE_ENTRY_GREATER 1 #define HNSW_UPDATE_ENTRY_GREATER 1
#define HNSW_UPDATE_ENTRY_ALWAYS 2 #define HNSW_UPDATE_ENTRY_ALWAYS 2
typedef enum HnswType /* Data types */
{ #define HNSW_TYPE_VECTOR 1
HNSW_TYPE_VECTOR, #define HNSW_TYPE_HALFVEC 2
HNSW_TYPE_HALFVEC,
HNSW_TYPE_BIT,
HNSW_TYPE_SPARSEVEC
} HnswType;
/* Build phases */ /* Build phases */
/* PROGRESS_CREATEIDX_SUBPHASE_INITIALIZE is 1 */ /* PROGRESS_CREATEIDX_SUBPHASE_INITIALIZE is 1 */
@@ -251,7 +246,7 @@ typedef struct HnswBuildState
Relation index; Relation index;
IndexInfo *indexInfo; IndexInfo *indexInfo;
ForkNumber forkNum; ForkNumber forkNum;
HnswType type; int type;
/* Settings */ /* Settings */
int dimensions; int dimensions;
@@ -376,9 +371,8 @@ typedef struct HnswVacuumState
int HnswGetM(Relation index); int HnswGetM(Relation index);
int HnswGetEfConstruction(Relation index); int HnswGetEfConstruction(Relation index);
FmgrInfo *HnswOptionalProcInfo(Relation index, uint16 procnum); FmgrInfo *HnswOptionalProcInfo(Relation index, uint16 procnum);
HnswType HnswGetType(Relation index); int HnswGetType(Relation index);
bool HnswNormValue(FmgrInfo *procinfo, Oid collation, Datum *value, HnswType type); bool HnswNormValue(FmgrInfo *procinfo, Oid collation, Datum *value, int type);
void HnswCheckValue(Datum value, HnswType type);
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 HnswInit(void); void HnswInit(void);

View File

@@ -44,7 +44,6 @@
#include "access/xact.h" #include "access/xact.h"
#include "access/xloginsert.h" #include "access/xloginsert.h"
#include "catalog/index.h" #include "catalog/index.h"
#include "catalog/pg_type_d.h"
#include "commands/progress.h" #include "commands/progress.h"
#include "hnsw.h" #include "hnsw.h"
#include "miscadmin.h" #include "miscadmin.h"
@@ -487,9 +486,6 @@ InsertTuple(Relation index, Datum *values, bool *isnull, ItemPointer heaptid, Hn
/* Detoast once for all calls */ /* Detoast once for all calls */
Datum value = PointerGetDatum(PG_DETOAST_DATUM(values[0])); Datum value = PointerGetDatum(PG_DETOAST_DATUM(values[0]));
/* Check value */
HnswCheckValue(value, buildstate->type);
/* Normalize if needed */ /* Normalize if needed */
if (buildstate->normprocinfo != NULL) if (buildstate->normprocinfo != NULL)
{ {
@@ -669,28 +665,14 @@ HnswSharedMemoryAlloc(Size size, void *state)
return chunk; return chunk;
} }
/*
* Get max dimensions
*/
static int
GetMaxDimensions(HnswType type)
{
int maxDimensions = HNSW_MAX_DIM;
if (type == HNSW_TYPE_HALFVEC)
maxDimensions *= 2;
else if (type == HNSW_TYPE_BIT)
maxDimensions *= 32;
return maxDimensions;
}
/* /*
* Initialize the build state * Initialize the build state
*/ */
static void static void
InitBuildState(HnswBuildState * buildstate, Relation heap, Relation index, IndexInfo *indexInfo, ForkNumber forkNum) InitBuildState(HnswBuildState * buildstate, Relation heap, Relation index, IndexInfo *indexInfo, ForkNumber forkNum)
{ {
int maxDimensions = HNSW_MAX_DIM;
buildstate->heap = heap; buildstate->heap = heap;
buildstate->index = index; buildstate->index = index;
buildstate->indexInfo = indexInfo; buildstate->indexInfo = indexInfo;
@@ -701,17 +683,15 @@ InitBuildState(HnswBuildState * buildstate, Relation heap, Relation index, Index
buildstate->efConstruction = HnswGetEfConstruction(index); buildstate->efConstruction = HnswGetEfConstruction(index);
buildstate->dimensions = TupleDescAttr(index->rd_att, 0)->atttypmod; buildstate->dimensions = TupleDescAttr(index->rd_att, 0)->atttypmod;
if (buildstate->type == HNSW_TYPE_HALFVEC)
maxDimensions *= 2;
if (buildstate->type != HNSW_TYPE_SPARSEVEC) /* Require column to have dimensions to be indexed */
{ if (buildstate->dimensions < 0)
int maxDimensions = GetMaxDimensions(buildstate->type); elog(ERROR, "column does not have dimensions");
if (buildstate->dimensions < 0) if (buildstate->dimensions > maxDimensions)
elog(ERROR, "column does not have dimensions"); elog(ERROR, "column cannot have more than %d dimensions for hnsw index", maxDimensions);
if (buildstate->dimensions > maxDimensions)
elog(ERROR, "column cannot have more than %d dimensions for hnsw index", maxDimensions);
}
if (buildstate->efConstruction < 2 * buildstate->m) if (buildstate->efConstruction < 2 * buildstate->m)
elog(ERROR, "ef_construction must be greater than or equal to 2 * m"); elog(ERROR, "ef_construction must be greater than or equal to 2 * m");

View File

@@ -614,19 +614,15 @@ HnswInsertTuple(Relation index, Datum *values, bool *isnull, ItemPointer heap_ti
Datum value; Datum value;
FmgrInfo *normprocinfo; FmgrInfo *normprocinfo;
Oid collation = index->rd_indcollation[0]; Oid collation = index->rd_indcollation[0];
HnswType type = HnswGetType(index);
/* Detoast once for all calls */ /* Detoast once for all calls */
value = PointerGetDatum(PG_DETOAST_DATUM(values[0])); value = PointerGetDatum(PG_DETOAST_DATUM(values[0]));
/* Check value */
HnswCheckValue(value, type);
/* Normalize if needed */ /* Normalize if needed */
normprocinfo = HnswOptionalProcInfo(index, HNSW_NORM_PROC); normprocinfo = HnswOptionalProcInfo(index, HNSW_NORM_PROC);
if (normprocinfo != NULL) if (normprocinfo != NULL)
{ {
if (!HnswNormValue(normprocinfo, collation, &value, type)) if (!HnswNormValue(normprocinfo, collation, &value, HnswGetType(index)))
return; return;
} }

View File

@@ -1,6 +1,7 @@
#include "postgres.h" #include "postgres.h"
#include "access/relscan.h" #include "access/relscan.h"
#include "halfvec.h"
#include "hnsw.h" #include "hnsw.h"
#include "pgstat.h" #include "pgstat.h"
#include "storage/bufmgr.h" #include "storage/bufmgr.h"
@@ -40,6 +41,29 @@ GetScanItems(IndexScanDesc scan, Datum q)
return HnswSearchLayer(base, q, ep, hnsw_ef_search, 0, index, procinfo, collation, m, false, NULL); return HnswSearchLayer(base, q, ep, hnsw_ef_search, 0, index, procinfo, collation, m, false, NULL);
} }
/*
* Get dimensions from metapage
*/
static int
GetDimensions(Relation index)
{
Buffer buf;
Page page;
HnswMetaPage metap;
int dimensions;
buf = ReadBuffer(index, HNSW_METAPAGE_BLKNO);
LockBuffer(buf, BUFFER_LOCK_SHARE);
page = BufferGetPage(buf);
metap = HnswPageGetMeta(page);
dimensions = metap->dimensions;
UnlockReleaseBuffer(buf);
return dimensions;
}
/* /*
* Get scan value * Get scan value
*/ */
@@ -50,7 +74,14 @@ GetScanValue(IndexScanDesc scan)
Datum value; Datum value;
if (scan->orderByData->sk_flags & SK_ISNULL) if (scan->orderByData->sk_flags & SK_ISNULL)
value = PointerGetDatum(NULL); {
int dimensions = GetDimensions(scan->indexRelation);
if (HnswGetType(scan->indexRelation) == HNSW_TYPE_HALFVEC)
value = PointerGetDatum(InitHalfVector(dimensions));
else
value = PointerGetDatum(InitVector(dimensions));
}
else else
{ {
value = scan->orderByData->sk_argument; value = scan->orderByData->sk_argument;

View File

@@ -4,11 +4,9 @@
#include "access/generic_xlog.h" #include "access/generic_xlog.h"
#include "catalog/pg_type.h" #include "catalog/pg_type.h"
#include "catalog/pg_type_d.h"
#include "halfvec.h" #include "halfvec.h"
#include "hnsw.h" #include "hnsw.h"
#include "lib/pairingheap.h" #include "lib/pairingheap.h"
#include "sparsevec.h"
#include "storage/bufmgr.h" #include "storage/bufmgr.h"
#include "utils/datum.h" #include "utils/datum.h"
#include "utils/memdebug.h" #include "utils/memdebug.h"
@@ -157,30 +155,23 @@ HnswOptionalProcInfo(Relation index, uint16 procnum)
/* /*
* Get type * Get type
*/ */
HnswType int
HnswGetType(Relation index) HnswGetType(Relation index)
{ {
Oid typid = TupleDescAttr(index->rd_att, 0)->atttypid; Oid typeOid = TupleDescAttr(index->rd_att, 0)->atttypid;
HeapTuple tuple; HeapTuple tuple;
Form_pg_type type; Form_pg_type type;
int result; int result;
if (typid == BITOID || typid == VARBITOID) tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(typeOid));
return HNSW_TYPE_BIT;
tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(typid));
if (!HeapTupleIsValid(tuple)) if (!HeapTupleIsValid(tuple))
elog(ERROR, "cache lookup failed for type %u", typid); elog(ERROR, "cache lookup failed for type %u", typeOid);
type = (Form_pg_type) GETSTRUCT(tuple); type = (Form_pg_type) GETSTRUCT(tuple);
if (strcmp(NameStr(type->typname), "vector") == 0) if (strcmp(NameStr(type->typname), "halfvec") == 0)
result = HNSW_TYPE_VECTOR;
else if (strcmp(NameStr(type->typname), "halfvec") == 0)
result = HNSW_TYPE_HALFVEC; result = HNSW_TYPE_HALFVEC;
else if (strcmp(NameStr(type->typname), "sparsevec") == 0)
result = HNSW_TYPE_SPARSEVEC;
else else
elog(ERROR, "Unsupported type"); result = HNSW_TYPE_VECTOR;
ReleaseSysCache(tuple); ReleaseSysCache(tuple);
@@ -196,24 +187,13 @@ HnswGetType(Relation index)
* if it's different than the original value * if it's different than the original value
*/ */
bool bool
HnswNormValue(FmgrInfo *procinfo, Oid collation, Datum *value, HnswType type) HnswNormValue(FmgrInfo *procinfo, Oid collation, Datum *value, int type)
{ {
double norm = DatumGetFloat8(FunctionCall1Coll(procinfo, collation, *value)); double norm = DatumGetFloat8(FunctionCall1Coll(procinfo, collation, *value));
if (norm > 0) if (norm > 0)
{ {
/* TODO Remove vector-specific code */ if (type == HNSW_TYPE_HALFVEC)
if (type == HNSW_TYPE_VECTOR)
{
Vector *v = DatumGetVector(*value);
Vector *result = InitVector(v->dim);
for (int i = 0; i < v->dim; i++)
result->x[i] = v->x[i] / norm;
*value = PointerGetDatum(result);
}
else if (type == HNSW_TYPE_HALFVEC)
{ {
HalfVector *v = DatumGetHalfVector(*value); HalfVector *v = DatumGetHalfVector(*value);
HalfVector *result = InitHalfVector(v->dim); HalfVector *result = InitHalfVector(v->dim);
@@ -226,18 +206,13 @@ HnswNormValue(FmgrInfo *procinfo, Oid collation, Datum *value, HnswType type)
*value = PointerGetDatum(result); *value = PointerGetDatum(result);
} }
else if (type == HNSW_TYPE_SPARSEVEC) else if (type == HNSW_TYPE_VECTOR)
{ {
SparseVector *v = DatumGetSparseVector(*value); Vector *v = DatumGetVector(*value);
SparseVector *result = InitSparseVector(v->nnz); Vector *result = InitVector(v->dim);
float *vx = SPARSEVEC_VALUES(v);
float *rx = SPARSEVEC_VALUES(result);
for (int i = 0; i < v->nnz; i++) for (int i = 0; i < v->dim; i++)
{ result->x[i] = v->x[i] / norm;
result->indices[i] = v->indices[i];
rx[i] = vx[i] / norm;
}
*value = PointerGetDatum(result); *value = PointerGetDatum(result);
} }
@@ -250,21 +225,6 @@ HnswNormValue(FmgrInfo *procinfo, Oid collation, Datum *value, HnswType type)
return false; return false;
} }
/*
* Check if a value can be indexed
*/
void
HnswCheckValue(Datum value, HnswType type)
{
if (type == HNSW_TYPE_SPARSEVEC)
{
SparseVector *vec = DatumGetSparseVector(value);
if (vec->nnz > HNSW_MAX_NNZ)
elog(ERROR, "sparsevec cannot have more than %d non-zero elements for hnsw index", HNSW_MAX_NNZ);
}
}
/* /*
* New buffer * New buffer
*/ */
@@ -660,12 +620,7 @@ HnswLoadElement(HnswElement element, float *distance, Datum *q, Relation index,
/* Calculate distance */ /* Calculate distance */
if (distance != NULL) if (distance != NULL)
{ *distance = (float) DatumGetFloat8(FunctionCall2Coll(procinfo, collation, *q, PointerGetDatum(&etup->data)));
if (DatumGetPointer(*q) == NULL)
*distance = 0;
else
*distance = (float) DatumGetFloat8(FunctionCall2Coll(procinfo, collation, *q, PointerGetDatum(&etup->data)));
}
UnlockReleaseBuffer(buf); UnlockReleaseBuffer(buf);
} }

View File

@@ -57,7 +57,7 @@ AddSample(Datum *values, IvfflatBuildState * buildstate)
*/ */
if (buildstate->kmeansnormprocinfo != NULL) if (buildstate->kmeansnormprocinfo != NULL)
{ {
if (!IvfflatNormValue(buildstate->kmeansnormprocinfo, buildstate->collation, &value, buildstate->type)) if (!IvfflatNormValue(buildstate->kmeansnormprocinfo, buildstate->collation, &value, buildstate->normvec))
return; return;
} }
@@ -105,7 +105,7 @@ SampleCallback(Relation index, CALLBACK_ITEM_POINTER, Datum *values,
oldCtx = MemoryContextSwitchTo(buildstate->tmpCtx); oldCtx = MemoryContextSwitchTo(buildstate->tmpCtx);
/* Add sample */ /* Add sample */
AddSample(values, buildstate); AddSample(values, state);
/* Reset memory context */ /* Reset memory context */
MemoryContextSwitchTo(oldCtx); MemoryContextSwitchTo(oldCtx);
@@ -153,7 +153,7 @@ AddTupleToSort(Relation index, ItemPointer tid, Datum *values, IvfflatBuildState
/* Normalize if needed */ /* Normalize if needed */
if (buildstate->normprocinfo != NULL) if (buildstate->normprocinfo != NULL)
{ {
if (!IvfflatNormValue(buildstate->normprocinfo, buildstate->collation, &value, buildstate->type)) if (!IvfflatNormValue(buildstate->normprocinfo, buildstate->collation, &value, buildstate->normvec))
return; return;
} }
@@ -312,39 +312,25 @@ InsertTuples(Relation index, IvfflatBuildState * buildstate, ForkNumber forkNum)
} }
} }
/*
* Get max dimensions
*/
static int
GetMaxDimensions(IvfflatType type)
{
return IVFFLAT_MAX_DIM;
}
/* /*
* Initialize the build state * Initialize the build state
*/ */
static void static void
InitBuildState(IvfflatBuildState * buildstate, Relation heap, Relation index, IndexInfo *indexInfo) InitBuildState(IvfflatBuildState * buildstate, Relation heap, Relation index, IndexInfo *indexInfo)
{ {
int maxDimensions;
buildstate->heap = heap; buildstate->heap = heap;
buildstate->index = index; buildstate->index = index;
buildstate->indexInfo = indexInfo; buildstate->indexInfo = indexInfo;
buildstate->type = IvfflatGetType(index);
buildstate->lists = IvfflatGetLists(index); buildstate->lists = IvfflatGetLists(index);
buildstate->dimensions = TupleDescAttr(index->rd_att, 0)->atttypmod; buildstate->dimensions = TupleDescAttr(index->rd_att, 0)->atttypmod;
maxDimensions = GetMaxDimensions(buildstate->type);
/* Require column to have dimensions to be indexed */ /* Require column to have dimensions to be indexed */
if (buildstate->dimensions < 0) if (buildstate->dimensions < 0)
elog(ERROR, "column does not have dimensions"); elog(ERROR, "column does not have dimensions");
if (buildstate->dimensions > maxDimensions) if (buildstate->dimensions > IVFFLAT_MAX_DIM)
elog(ERROR, "column cannot have more than %d dimensions for ivfflat index", maxDimensions); elog(ERROR, "column cannot have more than %d dimensions for ivfflat index", IVFFLAT_MAX_DIM);
buildstate->reltuples = 0; buildstate->reltuples = 0;
buildstate->indtuples = 0; buildstate->indtuples = 0;
@@ -370,6 +356,9 @@ InitBuildState(IvfflatBuildState * buildstate, Relation heap, Relation index, In
buildstate->centers = VectorArrayInit(buildstate->lists, buildstate->dimensions); buildstate->centers = VectorArrayInit(buildstate->lists, buildstate->dimensions);
buildstate->listInfo = palloc(sizeof(ListInfo) * buildstate->lists); buildstate->listInfo = palloc(sizeof(ListInfo) * buildstate->lists);
/* Reuse for each tuple */
buildstate->normvec = InitVector(buildstate->dimensions);
buildstate->tmpCtx = AllocSetContextCreate(CurrentMemoryContext, buildstate->tmpCtx = AllocSetContextCreate(CurrentMemoryContext,
"Ivfflat build temporary context", "Ivfflat build temporary context",
ALLOCSET_DEFAULT_SIZES); ALLOCSET_DEFAULT_SIZES);
@@ -391,6 +380,7 @@ FreeBuildState(IvfflatBuildState * buildstate)
{ {
VectorArrayFree(buildstate->centers); VectorArrayFree(buildstate->centers);
pfree(buildstate->listInfo); pfree(buildstate->listInfo);
pfree(buildstate->normvec);
#ifdef IVFFLAT_KMEANS_DEBUG #ifdef IVFFLAT_KMEANS_DEBUG
pfree(buildstate->listSums); pfree(buildstate->listSums);

View File

@@ -43,11 +43,6 @@
#define IVFFLAT_MAX_LISTS 32768 #define IVFFLAT_MAX_LISTS 32768
#define IVFFLAT_DEFAULT_PROBES 1 #define IVFFLAT_DEFAULT_PROBES 1
typedef enum IvfflatType
{
IVFFLAT_TYPE_VECTOR
} IvfflatType;
/* Build phases */ /* Build phases */
/* PROGRESS_CREATEIDX_SUBPHASE_INITIALIZE is 1 */ /* PROGRESS_CREATEIDX_SUBPHASE_INITIALIZE is 1 */
#define PROGRESS_IVFFLAT_PHASE_KMEANS 2 #define PROGRESS_IVFFLAT_PHASE_KMEANS 2
@@ -158,7 +153,6 @@ typedef struct IvfflatBuildState
Relation heap; Relation heap;
Relation index; Relation index;
IndexInfo *indexInfo; IndexInfo *indexInfo;
IvfflatType type;
/* Settings */ /* Settings */
int dimensions; int dimensions;
@@ -178,6 +172,7 @@ typedef struct IvfflatBuildState
VectorArray samples; VectorArray samples;
VectorArray centers; VectorArray centers;
ListInfo *listInfo; ListInfo *listInfo;
Vector *normvec;
#ifdef IVFFLAT_KMEANS_DEBUG #ifdef IVFFLAT_KMEANS_DEBUG
double inertia; double inertia;
@@ -272,8 +267,7 @@ 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 index, uint16 procnum); FmgrInfo *IvfflatOptionalProcInfo(Relation index, uint16 procnum);
IvfflatType IvfflatGetType(Relation index); bool IvfflatNormValue(FmgrInfo *procinfo, Oid collation, Datum *value, Vector * result);
bool IvfflatNormValue(FmgrInfo *procinfo, Oid collation, Datum *value, IvfflatType type);
int IvfflatGetLists(Relation index); int IvfflatGetLists(Relation index);
void IvfflatGetMetaPageInfo(Relation index, int *lists, int *dimensions); 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);

View File

@@ -85,7 +85,7 @@ InsertTuple(Relation index, Datum *values, bool *isnull, ItemPointer heap_tid, R
normprocinfo = IvfflatOptionalProcInfo(index, IVFFLAT_NORM_PROC); normprocinfo = IvfflatOptionalProcInfo(index, IVFFLAT_NORM_PROC);
if (normprocinfo != NULL) if (normprocinfo != NULL)
{ {
if (!IvfflatNormValue(normprocinfo, index->rd_indcollation[0], &value, IvfflatGetType(index))) if (!IvfflatNormValue(normprocinfo, index->rd_indcollation[0], &value, NULL))
return; return;
} }

View File

@@ -268,7 +268,6 @@ ivfflatgettuple(IndexScanDesc scan, ScanDirection dir)
if (so->first) if (so->first)
{ {
Datum value; Datum value;
IvfflatType type = IvfflatGetType(scan->indexRelation);
/* Count index scan for stats */ /* Count index scan for stats */
pgstat_count_index_scan(scan->indexRelation); pgstat_count_index_scan(scan->indexRelation);
@@ -283,12 +282,7 @@ ivfflatgettuple(IndexScanDesc scan, ScanDirection dir)
elog(ERROR, "non-MVCC snapshots are not supported with ivfflat"); 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(so->dimensions));
if (type == IVFFLAT_TYPE_VECTOR)
value = PointerGetDatum(InitVector(so->dimensions));
else
elog(ERROR, "Unsupported type");
}
else else
{ {
value = scan->orderByData->sk_argument; value = scan->orderByData->sk_argument;
@@ -299,7 +293,7 @@ ivfflatgettuple(IndexScanDesc scan, ScanDirection dir)
/* Fine if normalization fails */ /* Fine if normalization fails */
if (so->normprocinfo != NULL) if (so->normprocinfo != NULL)
IvfflatNormValue(so->normprocinfo, so->collation, &value, type); IvfflatNormValue(so->normprocinfo, so->collation, &value, NULL);
} }
IvfflatBench("GetScanLists", GetScanLists(scan, value)); IvfflatBench("GetScanLists", GetScanLists(scan, value));

View File

@@ -66,15 +66,6 @@ IvfflatOptionalProcInfo(Relation index, uint16 procnum)
return index_getprocinfo(index, 1, procnum); return index_getprocinfo(index, 1, procnum);
} }
/*
* Get type
*/
IvfflatType
IvfflatGetType(Relation index)
{
return IVFFLAT_TYPE_VECTOR;
}
/* /*
* Divide by the norm * Divide by the norm
* *
@@ -84,24 +75,21 @@ IvfflatGetType(Relation index)
* if it's different than the original value * if it's different than the original value
*/ */
bool bool
IvfflatNormValue(FmgrInfo *procinfo, Oid collation, Datum *value, IvfflatType type) IvfflatNormValue(FmgrInfo *procinfo, Oid collation, Datum *value, Vector * result)
{ {
double norm = DatumGetFloat8(FunctionCall1Coll(procinfo, collation, *value)); double norm = DatumGetFloat8(FunctionCall1Coll(procinfo, collation, *value));
if (norm > 0) if (norm > 0)
{ {
if (type == IVFFLAT_TYPE_VECTOR) Vector *v = DatumGetVector(*value);
{
Vector *v = DatumGetVector(*value);
Vector *result = InitVector(v->dim);
for (int i = 0; i < v->dim; i++) if (result == NULL)
result->x[i] = v->x[i] / norm; result = InitVector(v->dim);
*value = PointerGetDatum(result); for (int i = 0; i < v->dim; i++)
} result->x[i] = v->x[i] / norm;
else
elog(ERROR, "Unsupported type"); *value = PointerGetDatum(result);
return true; return true;
} }

View File

@@ -1,658 +0,0 @@
#include "postgres.h"
#include <limits.h>
#include <math.h>
#include "fmgr.h"
#include "libpq/pqformat.h"
#include "sparsevec.h"
#include "utils/array.h"
#include "utils/builtins.h"
#include "vector.h"
#if PG_VERSION_NUM >= 120000
#include "common/shortest_dec.h"
#include "utils/float.h"
#else
#include <float.h>
#include "utils/builtins.h"
#endif
/*
* Ensure valid nnz
*/
static inline void
CheckNnz(int nnz)
{
if (nnz < 0)
ereport(ERROR,
(errcode(ERRCODE_DATA_EXCEPTION),
errmsg("sparsevec must have at least one element")));
if (nnz > SPARSEVEC_MAX_NNZ)
ereport(ERROR,
(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
errmsg("sparsevec cannot have more elements than non-zero elements")));
}
/*
* Ensure valid index
*/
static inline void
CheckIndex(int32 *indices, int i)
{
int32 index = indices[i];
if (index < 0)
ereport(ERROR,
(errcode(ERRCODE_DATA_EXCEPTION),
errmsg("index must not be negative")));
if (i > 0)
{
if (index < indices[i - 1])
ereport(ERROR,
(errcode(ERRCODE_DATA_EXCEPTION),
errmsg("indexes must be in ascending order")));
if (index == indices[i - 1])
ereport(ERROR,
(errcode(ERRCODE_DATA_EXCEPTION),
errmsg("indexes must not contain duplicates")));
}
}
/*
* Ensure finite element
*/
static inline void
CheckElement(float value)
{
if (isnan(value))
ereport(ERROR,
(errcode(ERRCODE_DATA_EXCEPTION),
errmsg("NaN not allowed in sparsevec")));
if (isinf(value))
ereport(ERROR,
(errcode(ERRCODE_DATA_EXCEPTION),
errmsg("infinite value not allowed in sparsevec")));
}
/*
* Allocate and initialize a new sparse vector
*/
SparseVector *
InitSparseVector(int nnz)
{
SparseVector *result;
int size;
size = SPARSEVEC_SIZE(nnz);
result = (SparseVector *) palloc0(size);
SET_VARSIZE(result, size);
result->nnz = nnz;
return result;
}
/*
* Check for whitespace, since array_isspace() is static
*/
static inline bool
sparsevec_isspace(char ch)
{
if (ch == ' ' ||
ch == '\t' ||
ch == '\n' ||
ch == '\r' ||
ch == '\v' ||
ch == '\f')
return true;
return false;
}
/*
* Convert textual representation to internal representation
*/
PGDLLEXPORT PG_FUNCTION_INFO_V1(sparsevec_in);
Datum
sparsevec_in(PG_FUNCTION_ARGS)
{
char *lit = PG_GETARG_CSTRING(0);
char *pt;
char *stringEnd;
SparseVector *result;
float *rvalues;
char *litcopy = pstrdup(lit);
char *str = litcopy;
int32 *indices;
float *values;
int maxNnz;
int nnz = 0;
maxNnz = 1;
pt = str;
while (*pt != '\0')
{
if (*pt == ',')
maxNnz++;
pt++;
}
if (maxNnz > SPARSEVEC_MAX_NNZ)
ereport(ERROR,
(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
errmsg("sparsevec cannot have more than %d non-zero elements", SPARSEVEC_MAX_NNZ)));
indices = palloc(maxNnz * sizeof(int32));
values = palloc(maxNnz * sizeof(float));
while (sparsevec_isspace(*str))
str++;
if (*str != '{')
ereport(ERROR,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("malformed sparsevec literal: \"%s\"", lit),
errdetail("Vector contents must start with \"{\".")));
str++;
pt = strtok(str, ",");
stringEnd = pt;
while (pt != NULL && *stringEnd != '}')
{
long index;
float value;
/* TODO Better error */
if (nnz == maxNnz)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("ran out of buffer: \"%s\"", lit)));
while (sparsevec_isspace(*pt))
pt++;
/* Check for empty string like float4in */
if (*pt == '\0')
ereport(ERROR,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("invalid input syntax for type sparsevec: \"%s\"", lit)));
/* Use similar logic as int2vectorin */
errno = 0;
index = strtol(pt, &stringEnd, 10);
if (stringEnd == pt)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("invalid input syntax for type sparsevec: \"%s\"", lit)));
if (errno == ERANGE || index < 0 || index > INT_MAX)
ereport(ERROR,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("index \"%ld\" is out of range for type sparsevec", index)));
if (stringEnd == pt)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("invalid input syntax for type sparsevec: \"%s\"", lit)));
while (sparsevec_isspace(*stringEnd))
stringEnd++;
if (*stringEnd != ':')
ereport(ERROR,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("invalid input syntax for type sparsevec: \"%s\"", lit)));
stringEnd++;
while (sparsevec_isspace(*stringEnd))
stringEnd++;
errno = 0;
pt = stringEnd;
value = strtof(pt, &stringEnd);
if (stringEnd == pt)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("invalid input syntax for type sparsevec: \"%s\"", lit)));
/* Check for range error like float4in */
if (errno == ERANGE && (value == 0 || isinf(value)))
ereport(ERROR,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("\"%s\" is out of range for type sparsevec", pt)));
/* TODO Decide whether to store zero values */
if (value != 0)
{
indices[nnz] = index;
values[nnz] = value;
nnz++;
}
if (*stringEnd != '\0' && *stringEnd != '}')
ereport(ERROR,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("invalid input syntax for type sparsevec: \"%s\"", lit)));
pt = strtok(NULL, ",");
}
if (stringEnd == NULL || *stringEnd != '}')
ereport(ERROR,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("malformed sparsevec literal: \"%s\"", lit),
errdetail("Unexpected end of input.")));
stringEnd++;
/* Only whitespace is allowed after the closing brace */
while (sparsevec_isspace(*stringEnd))
stringEnd++;
if (*stringEnd != '\0')
ereport(ERROR,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("malformed sparsevec literal: \"%s\"", lit),
errdetail("Junk after closing right brace.")));
pfree(litcopy);
result = InitSparseVector(nnz);
rvalues = SPARSEVEC_VALUES(result);
for (int i = 0; i < nnz; i++)
{
result->indices[i] = indices[i];
rvalues[i] = values[i];
CheckIndex(result->indices, i);
CheckElement(rvalues[i]);
}
PG_RETURN_POINTER(result);
}
#define AppendChar(ptr, c) (*(ptr)++ = (c))
#define AppendFloat(ptr, f) ((ptr) += float_to_shortest_decimal_bufn((f), (ptr)))
#if PG_VERSION_NUM >= 140000
#define AppendInt(ptr, i) ((ptr) += pg_ltoa((i), (ptr)))
#else
#define AppendInt(ptr, i) \
do { \
pg_ltoa(i, ptr); \
while (*ptr != '\0') \
ptr++; \
} while (0)
#endif
/*
* Convert internal representation to textual representation
*/
PGDLLEXPORT PG_FUNCTION_INFO_V1(sparsevec_out);
Datum
sparsevec_out(PG_FUNCTION_ARGS)
{
SparseVector *sparsevec = PG_GETARG_SPARSEVEC_P(0);
float *values = SPARSEVEC_VALUES(sparsevec);
char *buf;
char *ptr;
/*
* Need:
*
* nnz * 10 bytes for index (positive integer)
*
* nnz bytes for :
*
* nnz * (FLOAT_SHORTEST_DECIMAL_LEN - 1) bytes for
* float_to_shortest_decimal_bufn
*
* nnz - 1 bytes for ,
*
* 3 bytes for {, }, and \0
*/
buf = (char *) palloc((11 + FLOAT_SHORTEST_DECIMAL_LEN) * sparsevec->nnz + 2);
ptr = buf;
AppendChar(ptr, '{');
for (int i = 0; i < sparsevec->nnz; i++)
{
if (i > 0)
AppendChar(ptr, ',');
AppendInt(ptr, sparsevec->indices[i]);
AppendChar(ptr, ':');
AppendFloat(ptr, values[i]);
}
AppendChar(ptr, '}');
*ptr = '\0';
PG_FREE_IF_COPY(sparsevec, 0);
PG_RETURN_CSTRING(buf);
}
/*
* Convert external binary representation to internal representation
*/
PGDLLEXPORT PG_FUNCTION_INFO_V1(sparsevec_recv);
Datum
sparsevec_recv(PG_FUNCTION_ARGS)
{
StringInfo buf = (StringInfo) PG_GETARG_POINTER(0);
SparseVector *result;
int32 nnz;
int32 unused;
int32 unused2;
float *values;
nnz = pq_getmsgint(buf, sizeof(int32));
unused = pq_getmsgint(buf, sizeof(int32));
unused2 = pq_getmsgint(buf, sizeof(int32));
CheckNnz(nnz);
if (unused != 0 || unused2 != 0)
ereport(ERROR,
(errcode(ERRCODE_DATA_EXCEPTION),
errmsg("expected unused to be 0, not %d", unused)));
result = InitSparseVector(nnz);
values = SPARSEVEC_VALUES(result);
for (int i = 0; i < nnz; i++)
{
result->indices[i] = pq_getmsgint(buf, sizeof(int32));
CheckIndex(result->indices, i);
}
for (int i = 0; i < nnz; i++)
{
values[i] = pq_getmsgfloat4(buf);
CheckElement(values[i]);
}
PG_RETURN_POINTER(result);
}
/*
* Convert internal representation to the external binary representation
*/
PGDLLEXPORT PG_FUNCTION_INFO_V1(sparsevec_send);
Datum
sparsevec_send(PG_FUNCTION_ARGS)
{
SparseVector *svec = PG_GETARG_SPARSEVEC_P(0);
float *values = SPARSEVEC_VALUES(svec);
StringInfoData buf;
pq_begintypsend(&buf);
pq_sendint(&buf, svec->nnz, sizeof(int32));
pq_sendint(&buf, svec->unused, sizeof(int32));
pq_sendint(&buf, svec->unused2, 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
*/
PGDLLEXPORT PG_FUNCTION_INFO_V1(sparsevec);
Datum
sparsevec(PG_FUNCTION_ARGS)
{
SparseVector *svec = PG_GETARG_SPARSEVEC_P(0);
PG_RETURN_POINTER(svec);
}
/*
* Convert dense vector to sparse vector
*/
PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_to_sparsevec);
Datum
vector_to_sparsevec(PG_FUNCTION_ARGS)
{
Vector *vec = PG_GETARG_VECTOR_P(0);
SparseVector *result;
int dim = vec->dim;
int nnz = 0;
float *values;
int j = 0;
for (int i = 0; i < dim; i++)
{
if (vec->x[i] != 0)
nnz++;
}
CheckNnz(nnz);
result = InitSparseVector(nnz);
values = SPARSEVEC_VALUES(result);
for (int i = 0; i < dim; i++)
{
if (vec->x[i] != 0)
{
/* Safety check */
if (j == nnz)
elog(ERROR, "safety check failed");
result->indices[j] = i;
values[j] = vec->x[i];
j++;
}
}
PG_RETURN_POINTER(result);
}
/*
* Get the L2 squared distance between sparse vectors
*/
static double
l2_distance_squared_internal(SparseVector * a, SparseVector * b)
{
float *ax = SPARSEVEC_VALUES(a);
float *bx = SPARSEVEC_VALUES(b);
double distance = 0.0;
int bpos = 0;
for (int i = 0; i < a->nnz; i++)
{
int ai = a->indices[i];
int bi = -1;
for (int j = bpos; j < b->nnz; j++)
{
bi = b->indices[j];
if (ai == bi)
{
double diff = ax[i] - bx[j];
distance += diff * diff;
}
else if (ai > bi)
distance += bx[j] * bx[j];
/* Update start for next iteration */
if (ai >= bi)
bpos = j + 1;
/* Found or passed it */
if (bi >= ai)
break;
}
if (ai != bi)
distance += ax[i] * ax[i];
}
for (int j = bpos; j < b->nnz; j++)
distance += bx[j] * bx[j];
return distance;
}
/*
* Get the L2 distance between sparse vectors
*/
PGDLLEXPORT PG_FUNCTION_INFO_V1(sparsevec_l2_distance);
Datum
sparsevec_l2_distance(PG_FUNCTION_ARGS)
{
SparseVector *a = PG_GETARG_SPARSEVEC_P(0);
SparseVector *b = PG_GETARG_SPARSEVEC_P(1);
PG_RETURN_FLOAT8(sqrt(l2_distance_squared_internal(a, b)));
}
/*
* Get the L2 squared distance between sparse vectors
* This saves a sqrt calculation
*/
PGDLLEXPORT PG_FUNCTION_INFO_V1(sparsevec_l2_squared_distance);
Datum
sparsevec_l2_squared_distance(PG_FUNCTION_ARGS)
{
SparseVector *a = PG_GETARG_SPARSEVEC_P(0);
SparseVector *b = PG_GETARG_SPARSEVEC_P(1);
PG_RETURN_FLOAT8(l2_distance_squared_internal(a, b));
}
/*
* Get the inner product of two sparse vectors
*/
static double
inner_product_internal(SparseVector * a, SparseVector * b)
{
float *ax = SPARSEVEC_VALUES(a);
float *bx = SPARSEVEC_VALUES(b);
double distance = 0.0;
int bpos = 0;
for (int i = 0; i < a->nnz; i++)
{
int ai = a->indices[i];
for (int j = bpos; j < b->nnz; j++)
{
int bi = b->indices[j];
/* Only update when the same index */
if (ai == bi)
distance += ax[i] * bx[j];
/* Update start for next iteration */
if (ai >= bi)
bpos = j + 1;
/* Found or passed it */
if (bi >= ai)
break;
}
}
return distance;
}
/*
* Get the inner product of two sparse vectors
*/
PGDLLEXPORT PG_FUNCTION_INFO_V1(sparsevec_inner_product);
Datum
sparsevec_inner_product(PG_FUNCTION_ARGS)
{
SparseVector *a = PG_GETARG_SPARSEVEC_P(0);
SparseVector *b = PG_GETARG_SPARSEVEC_P(1);
PG_RETURN_FLOAT8(inner_product_internal(a, b));
}
/*
* Get the negative inner product of two sparse vectors
*/
PGDLLEXPORT PG_FUNCTION_INFO_V1(sparsevec_negative_inner_product);
Datum
sparsevec_negative_inner_product(PG_FUNCTION_ARGS)
{
SparseVector *a = PG_GETARG_SPARSEVEC_P(0);
SparseVector *b = PG_GETARG_SPARSEVEC_P(1);
PG_RETURN_FLOAT8(-inner_product_internal(a, b));
}
/*
* Get the cosine distance between two sparse vectors
*/
PGDLLEXPORT PG_FUNCTION_INFO_V1(sparsevec_cosine_distance);
Datum
sparsevec_cosine_distance(PG_FUNCTION_ARGS)
{
SparseVector *a = PG_GETARG_SPARSEVEC_P(0);
SparseVector *b = PG_GETARG_SPARSEVEC_P(1);
float *ax = SPARSEVEC_VALUES(a);
float *bx = SPARSEVEC_VALUES(b);
float norma = 0.0;
float normb = 0.0;
double similarity;
similarity = inner_product_internal(a, b);
/* Auto-vectorized */
for (int i = 0; i < a->nnz; i++)
norma += ax[i] * ax[i];
/* Auto-vectorized */
for (int i = 0; i < b->nnz; i++)
normb += bx[i] * bx[i];
/* Use sqrt(a * b) over sqrt(a) * sqrt(b) */
similarity /= sqrt((double) norma * (double) normb);
#ifdef _MSC_VER
/* /fp:fast may not propagate NaN */
if (isnan(similarity))
PG_RETURN_FLOAT8(NAN);
#endif
/* Keep in range */
if (similarity > 1)
similarity = 1.0;
else if (similarity < -1)
similarity = -1.0;
PG_RETURN_FLOAT8(1.0 - similarity);
}
/*
* Get the L2 norm of a sparse vector
*/
PGDLLEXPORT PG_FUNCTION_INFO_V1(sparsevec_norm);
Datum
sparsevec_norm(PG_FUNCTION_ARGS)
{
SparseVector *a = PG_GETARG_SPARSEVEC_P(0);
float *ax = SPARSEVEC_VALUES(a);
double norm = 0.0;
/* Auto-vectorized */
for (int i = 0; i < a->nnz; i++)
norm += (double) ax[i] * (double) ax[i];
PG_RETURN_FLOAT8(sqrt(norm));
}

View File

@@ -1,24 +0,0 @@
#ifndef SPARSEVEC_H
#define SPARSEVEC_H
#define SPARSEVEC_MAX_NNZ 100000
/* Ensure values are aligned */
#define SPARSEVEC_SIZE(_nnz) (offsetof(SparseVector, indices) + MAXALIGN((_nnz) * sizeof(int32)) + (_nnz * sizeof(float)))
#define SPARSEVEC_VALUES(x) ((float *) (((char *) (x)) + offsetof(SparseVector, indices) + MAXALIGN((x)->nnz * sizeof(int32))))
#define DatumGetSparseVector(x) ((SparseVector *) PG_DETOAST_DATUM(x))
#define PG_GETARG_SPARSEVEC_P(x) DatumGetSparseVector(PG_GETARG_DATUM(x))
#define PG_RETURN_SPARSEVEC_P(x) PG_RETURN_POINTER(x)
typedef struct SparseVector
{
int32 vl_len_; /* varlena header (do not touch directly!) */
int32 nnz;
int32 unused;
int32 unused2;
int32 indices[FLEXIBLE_ARRAY_MEMBER];
} SparseVector;
SparseVector *InitSparseVector(int nnz);
#endif

View File

@@ -2,7 +2,6 @@
#include <math.h> #include <math.h>
#include "bitvector.h"
#include "catalog/pg_type.h" #include "catalog/pg_type.h"
#include "common/shortest_dec.h" #include "common/shortest_dec.h"
#include "fmgr.h" #include "fmgr.h"
@@ -12,7 +11,6 @@
#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 "sparsevec.h"
#include "utils/array.h" #include "utils/array.h"
#include "utils/builtins.h" #include "utils/builtins.h"
#include "utils/float.h" #include "utils/float.h"
@@ -200,8 +198,6 @@ vector_in(PG_FUNCTION_ARGS)
while (pt != NULL && *stringEnd != ']') while (pt != NULL && *stringEnd != ']')
{ {
float val;
if (dim == VECTOR_MAX_DIM) if (dim == VECTOR_MAX_DIM)
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
@@ -217,23 +213,15 @@ vector_in(PG_FUNCTION_ARGS)
errmsg("invalid input syntax for type vector: \"%s\"", lit))); errmsg("invalid input syntax for type vector: \"%s\"", lit)));
/* Use strtof like float4in to avoid a double-rounding problem */ /* Use strtof like float4in to avoid a double-rounding problem */
errno = 0; x[dim] = strtof(pt, &stringEnd);
val = strtof(pt, &stringEnd); CheckElement(x[dim]);
dim++;
if (stringEnd == pt) if (stringEnd == pt)
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("invalid input syntax for type vector: \"%s\"", lit))); errmsg("invalid input syntax for type vector: \"%s\"", lit)));
/* Check for range error like float4in */
if (errno == ERANGE && isinf(val))
ereport(ERROR,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("\"%s\" is out of range for type vector", pt)));
CheckElement(val);
x[dim++] = val;
while (vector_isspace(*stringEnd)) while (vector_isspace(*stringEnd))
stringEnd++; stringEnd++;
@@ -288,9 +276,6 @@ vector_in(PG_FUNCTION_ARGS)
PG_RETURN_POINTER(result); PG_RETURN_POINTER(result);
} }
#define AppendChar(ptr, c) (*(ptr)++ = (c))
#define AppendFloat(ptr, f) ((ptr) += float_to_shortest_decimal_bufn((f), (ptr)))
/* /*
* Convert internal representation to textual representation * Convert internal representation to textual representation
*/ */
@@ -302,6 +287,7 @@ vector_out(PG_FUNCTION_ARGS)
int dim = vector->dim; int dim = vector->dim;
char *buf; char *buf;
char *ptr; char *ptr;
int n;
/* /*
* Need: * Need:
@@ -316,17 +302,21 @@ vector_out(PG_FUNCTION_ARGS)
buf = (char *) palloc(FLOAT_SHORTEST_DECIMAL_LEN * dim + 2); buf = (char *) palloc(FLOAT_SHORTEST_DECIMAL_LEN * dim + 2);
ptr = buf; ptr = buf;
AppendChar(ptr, '['); *ptr = '[';
ptr++;
for (int i = 0; i < dim; i++) for (int i = 0; i < dim; i++)
{ {
if (i > 0) if (i > 0)
AppendChar(ptr, ','); {
*ptr = ',';
ptr++;
}
AppendFloat(ptr, vector->x[i]); n = float_to_shortest_decimal_bufn(vector->x[i], ptr);
ptr += n;
} }
*ptr = ']';
AppendChar(ptr, ']'); ptr++;
*ptr = '\0'; *ptr = '\0';
PG_FREE_IF_COPY(vector, 0); PG_FREE_IF_COPY(vector, 0);
@@ -551,11 +541,9 @@ Datum
halfvec_to_vector(PG_FUNCTION_ARGS) halfvec_to_vector(PG_FUNCTION_ARGS)
{ {
HalfVector *vec = PG_GETARG_HALFVEC_P(0); HalfVector *vec = PG_GETARG_HALFVEC_P(0);
int32 typmod = PG_GETARG_INT32(1);
Vector *result; Vector *result;
CheckDim(vec->dim); CheckDim(vec->dim);
CheckExpectedDim(typmod, vec->dim);
result = InitVector(vec->dim); result = InitVector(vec->dim);
@@ -893,56 +881,6 @@ vector_mul(PG_FUNCTION_ARGS)
PG_RETURN_POINTER(result); PG_RETURN_POINTER(result);
} }
/*
* Quantize a vector
*/
PGDLLEXPORT PG_FUNCTION_INFO_V1(quantize_binary);
Datum
quantize_binary(PG_FUNCTION_ARGS)
{
Vector *a = PG_GETARG_VECTOR_P(0);
float *ax = a->x;
VarBit *result = InitBitVector(a->dim);
unsigned char *rx = VARBITS(result);
for (int i = 0; i < a->dim; i++)
rx[i / 8] |= (ax[i] > 0) << (7 - (i % 8));
PG_RETURN_VARBIT_P(result);
}
/*
* Get a subvector
*/
PGDLLEXPORT PG_FUNCTION_INFO_V1(subvector);
Datum
subvector(PG_FUNCTION_ARGS)
{
Vector *a = PG_GETARG_VECTOR_P(0);
int32 start = PG_GETARG_INT32(1);
int32 count = PG_GETARG_INT32(2);
int32 end = start + count;
float *ax = a->x;
Vector *result;
int dim;
/* Indexing starts at 1, like substring */
if (start < 1)
start = 1;
if (end > a->dim)
end = a->dim + 1;
dim = end - start;
CheckDim(dim);
result = InitVector(dim);
for (int i = 0; i < dim; i++)
result->x[i] = ax[start - 1 + i];
PG_RETURN_POINTER(result);
}
/* /*
* Internal helper to compare vectors * Internal helper to compare vectors
*/ */
@@ -980,6 +918,9 @@ vector_lt(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);
/* TODO Remove in 0.7.0 */
CheckDims(a, b);
PG_RETURN_BOOL(vector_cmp_internal(a, b) < 0); PG_RETURN_BOOL(vector_cmp_internal(a, b) < 0);
} }
@@ -993,6 +934,9 @@ vector_le(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);
/* TODO Remove in 0.7.0 */
CheckDims(a, b);
PG_RETURN_BOOL(vector_cmp_internal(a, b) <= 0); PG_RETURN_BOOL(vector_cmp_internal(a, b) <= 0);
} }
@@ -1006,6 +950,9 @@ vector_eq(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);
/* TODO Remove in 0.7.0 */
CheckDims(a, b);
PG_RETURN_BOOL(vector_cmp_internal(a, b) == 0); PG_RETURN_BOOL(vector_cmp_internal(a, b) == 0);
} }
@@ -1019,6 +966,9 @@ vector_ne(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);
/* TODO Remove in 0.7.0 */
CheckDims(a, b);
PG_RETURN_BOOL(vector_cmp_internal(a, b) != 0); PG_RETURN_BOOL(vector_cmp_internal(a, b) != 0);
} }
@@ -1032,6 +982,9 @@ vector_ge(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);
/* TODO Remove in 0.7.0 */
CheckDims(a, b);
PG_RETURN_BOOL(vector_cmp_internal(a, b) >= 0); PG_RETURN_BOOL(vector_cmp_internal(a, b) >= 0);
} }
@@ -1045,6 +998,9 @@ vector_gt(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);
/* TODO Remove in 0.7.0 */
CheckDims(a, b);
PG_RETURN_BOOL(vector_cmp_internal(a, b) > 0); PG_RETURN_BOOL(vector_cmp_internal(a, b) > 0);
} }
@@ -1225,34 +1181,3 @@ vector_avg(PG_FUNCTION_ARGS)
PG_RETURN_POINTER(result); PG_RETURN_POINTER(result);
} }
/*
* Convert sparse vector to dense vector
*/
PGDLLEXPORT PG_FUNCTION_INFO_V1(sparsevec_to_vector);
Datum
sparsevec_to_vector(PG_FUNCTION_ARGS)
{
SparseVector *svec = PG_GETARG_SPARSEVEC_P(0);
int32 typmod = PG_GETARG_INT32(1);
Vector *result;
int dim;
float *values = SPARSEVEC_VALUES(svec);
int maxIndex = svec->nnz == 0 ? -1 : svec->indices[svec->nnz - 1];
if (typmod == -1)
dim = maxIndex + 1;
else
dim = typmod;
CheckDim(dim);
if (dim < maxIndex + 1)
elog(ERROR, "Vector must have at least %d dimensions", maxIndex + 1);
result = InitVector(dim);
for (int i = 0; i < svec->nnz; i++)
result->x[svec->indices[i]] = values[i];
PG_RETURN_POINTER(result);
}

View File

@@ -1,64 +0,0 @@
SELECT hamming_distance(B'111', B'111');
hamming_distance
------------------
0
(1 row)
SELECT hamming_distance(B'111', B'110');
hamming_distance
------------------
1
(1 row)
SELECT hamming_distance(B'111', B'100');
hamming_distance
------------------
2
(1 row)
SELECT hamming_distance(B'111', B'000');
hamming_distance
------------------
3
(1 row)
SELECT hamming_distance(B'111', B'00');
ERROR: different bit lengths 3 and 2
SELECT jaccard_distance(B'1111', B'1111');
jaccard_distance
------------------
0
(1 row)
SELECT jaccard_distance(B'1111', B'1110');
jaccard_distance
------------------
0.25
(1 row)
SELECT jaccard_distance(B'1111', B'1100');
jaccard_distance
------------------
0.5
(1 row)
SELECT jaccard_distance(B'1111', B'1000');
jaccard_distance
------------------
0.75
(1 row)
SELECT jaccard_distance(B'1111', B'0000');
jaccard_distance
------------------
1
(1 row)
SELECT jaccard_distance(B'1100', B'1000');
jaccard_distance
------------------
0.5
(1 row)
SELECT jaccard_distance(B'1111', B'000');
ERROR: different bit lengths 4 and 3

View File

@@ -58,12 +58,6 @@ SELECT '[1,2,3]'::halfvec::vector;
[1,2,3] [1,2,3]
(1 row) (1 row)
SELECT '[1,2,3]'::vector::halfvec(2);
ERROR: expected 2 dimensions, not 3
SELECT '[1,2,3]'::halfvec::vector(2);
ERROR: expected 2 dimensions, not 3
SELECT '[65520]'::vector::halfvec;
ERROR: infinite value not allowed in halfvec
SELECT '[1e-8]'::vector::halfvec; SELECT '[1e-8]'::vector::halfvec;
halfvec halfvec
--------- ---------

View File

@@ -24,30 +24,6 @@ SELECT '[1e37]'::vector * '[1e37]';
ERROR: value out of range: overflow ERROR: value out of range: overflow
SELECT '[1e-37]'::vector * '[1e-37]'; SELECT '[1e-37]'::vector * '[1e-37]';
ERROR: value out of range: underflow ERROR: value out of range: underflow
SELECT '[1,2,3]'::vector < '[1,2,3]';
?column?
----------
f
(1 row)
SELECT '[1,2,3]'::vector < '[1,2]';
?column?
----------
f
(1 row)
SELECT '[1,2,3]'::vector <= '[1,2,3]';
?column?
----------
t
(1 row)
SELECT '[1,2,3]'::vector <= '[1,2]';
?column?
----------
f
(1 row)
SELECT '[1,2,3]'::vector = '[1,2,3]'; SELECT '[1,2,3]'::vector = '[1,2,3]';
?column? ?column?
---------- ----------
@@ -55,47 +31,7 @@ SELECT '[1,2,3]'::vector = '[1,2,3]';
(1 row) (1 row)
SELECT '[1,2,3]'::vector = '[1,2]'; SELECT '[1,2,3]'::vector = '[1,2]';
?column? ERROR: different vector dimensions 3 and 2
----------
f
(1 row)
SELECT '[1,2,3]'::vector != '[1,2,3]';
?column?
----------
f
(1 row)
SELECT '[1,2,3]'::vector != '[1,2]';
?column?
----------
t
(1 row)
SELECT '[1,2,3]'::vector >= '[1,2,3]';
?column?
----------
t
(1 row)
SELECT '[1,2,3]'::vector >= '[1,2]';
?column?
----------
t
(1 row)
SELECT '[1,2,3]'::vector > '[1,2,3]';
?column?
----------
f
(1 row)
SELECT '[1,2,3]'::vector > '[1,2]';
?column?
----------
t
(1 row)
SELECT vector_cmp('[1,2,3]', '[1,2,3]'); SELECT vector_cmp('[1,2,3]', '[1,2,3]');
vector_cmp vector_cmp
------------ ------------
@@ -272,48 +208,6 @@ SELECT l1_distance('[3e38]'::vector, '[-3e38]');
Infinity Infinity
(1 row) (1 row)
SELECT quantize_binary('[1,0,-1]'::vector);
quantize_binary
-----------------
100
(1 row)
SELECT quantize_binary('[0,0.1,-0.2,-0.3,0.4,0.5,0.6,-0.7,0.8,-0.9,1]'::vector);
quantize_binary
-----------------
01001110101
(1 row)
SELECT subvector('[1,2,3,4,5]'::vector, 1, 3);
subvector
-----------
[1,2,3]
(1 row)
SELECT subvector('[1,2,3,4,5]'::vector, 3, 2);
subvector
-----------
[3,4]
(1 row)
SELECT subvector('[1,2,3,4,5]'::vector, -1, 3);
subvector
-----------
[1]
(1 row)
SELECT subvector('[1,2,3,4,5]'::vector, 3, 9);
subvector
-----------
[3,4,5]
(1 row)
SELECT subvector('[1,2,3,4,5]'::vector, 1, 0);
ERROR: vector must have at least 1 dimension
SELECT subvector('[1,2,3,4,5]'::vector, 3, -1);
ERROR: vector must have at least 1 dimension
SELECT subvector('[1,2,3,4,5]'::vector, -1, 2);
ERROR: vector must have at least 1 dimension
SELECT avg(v) FROM unnest(ARRAY['[1,2,3]'::vector, '[3,5,7]']) v; SELECT avg(v) FROM unnest(ARRAY['[1,2,3]'::vector, '[3,5,7]']) v;
avg avg
----------- -----------

View File

@@ -102,45 +102,3 @@ SELECT l1_distance('[0,0]'::halfvec, '[0,1]');
SELECT l1_distance('[1,2]'::halfvec, '[3]'); SELECT l1_distance('[1,2]'::halfvec, '[3]');
ERROR: different halfvec dimensions 2 and 1 ERROR: different halfvec dimensions 2 and 1
SELECT quantize_binary('[1,0,-1]'::halfvec);
quantize_binary
-----------------
100
(1 row)
SELECT quantize_binary('[0,0.1,-0.2,-0.3,0.4,0.5,0.6,-0.7,0.8,-0.9,1]'::halfvec);
quantize_binary
-----------------
01001110101
(1 row)
SELECT subvector('[1,2,3,4,5]'::halfvec, 1, 3);
subvector
-----------
[1,2,3]
(1 row)
SELECT subvector('[1,2,3,4,5]'::halfvec, 3, 2);
subvector
-----------
[3,4]
(1 row)
SELECT subvector('[1,2,3,4,5]'::halfvec, -1, 3);
subvector
-----------
[1]
(1 row)
SELECT subvector('[1,2,3,4,5]'::halfvec, 3, 9);
subvector
-----------
[3,4,5]
(1 row)
SELECT subvector('[1,2,3,4,5]'::halfvec, 1, 0);
ERROR: halfvec must have at least 1 dimension
SELECT subvector('[1,2,3,4,5]'::halfvec, 3, -1);
ERROR: halfvec must have at least 1 dimension
SELECT subvector('[1,2,3,4,5]'::halfvec, -1, 2);
ERROR: halfvec must have at least 1 dimension

View File

@@ -1,21 +0,0 @@
SET enable_seqscan = off;
CREATE TABLE t (val bit(3));
INSERT INTO t (val) VALUES (B'000'), (B'100'), (B'111'), (NULL);
CREATE INDEX ON t USING hnsw (val bit_hamming_ops);
INSERT INTO t (val) VALUES (B'110');
SELECT * FROM t ORDER BY val <~> B'111';
val
-----
111
110
100
000
(4 rows)
SELECT COUNT(*) FROM (SELECT * FROM t ORDER BY val <~> (SELECT NULL::bit)) t2;
count
-------
4
(1 row)
DROP TABLE t;

View File

@@ -1,21 +0,0 @@
SET enable_seqscan = off;
CREATE TABLE t (val bit(4));
INSERT INTO t (val) VALUES (B'0000'), (B'1100'), (B'1111'), (NULL);
CREATE INDEX ON t USING hnsw (val bit_jaccard_ops);
INSERT INTO t (val) VALUES (B'1110');
SELECT * FROM t ORDER BY val <%> B'1111';
val
------
1111
1110
1100
0000
(4 rows)
SELECT COUNT(*) FROM (SELECT * FROM t ORDER BY val <%> (SELECT NULL::bit)) t2;
count
-------
4
(1 row)
DROP TABLE t;

View File

@@ -12,11 +12,14 @@ SELECT * FROM t ORDER BY val <-> '[3,3,3]';
[0,0,0] [0,0,0]
(4 rows) (4 rows)
SELECT COUNT(*) FROM (SELECT * FROM t ORDER BY val <-> (SELECT NULL::halfvec)) t2; SELECT * FROM t ORDER BY val <-> (SELECT NULL::halfvec);
count val
------- ---------
4 [0,0,0]
(1 row) [1,1,1]
[1,2,3]
[1,2,4]
(4 rows)
SELECT COUNT(*) FROM t; SELECT COUNT(*) FROM t;
count count

View File

@@ -12,11 +12,14 @@ SELECT * FROM t ORDER BY val <-> '[3,3,3]';
[0,0,0] [0,0,0]
(4 rows) (4 rows)
SELECT COUNT(*) FROM (SELECT * FROM t ORDER BY val <-> (SELECT NULL::vector)) t2; SELECT * FROM t ORDER BY val <-> (SELECT NULL::vector);
count val
------- ---------
4 [0,0,0]
(1 row) [1,1,1]
[1,2,3]
[1,2,4]
(4 rows)
SELECT COUNT(*) FROM t; SELECT COUNT(*) FROM t;
count count

View File

@@ -1,26 +0,0 @@
SET enable_seqscan = off;
CREATE TABLE t (val sparsevec);
INSERT INTO t (val) VALUES ('{}'), ('{0:1,1:2,2:3}'), ('{0:1,1:1,2:1}'), (NULL);
CREATE INDEX ON t USING hnsw (val sparsevec_cosine_ops);
INSERT INTO t (val) VALUES ('{0:1,1:2,2:4}');
SELECT * FROM t ORDER BY val <=> '{0:3,1:3,2:3}';
val
---------------
{0:1,1:1,2:1}
{0:1,1:2,2:3}
{0:1,1:2,2:4}
(3 rows)
SELECT COUNT(*) FROM (SELECT * FROM t ORDER BY val <=> '{}') t2;
count
-------
3
(1 row)
SELECT COUNT(*) FROM (SELECT * FROM t ORDER BY val <=> (SELECT NULL::sparsevec)) t2;
count
-------
3
(1 row)
DROP TABLE t;

View File

@@ -1,21 +0,0 @@
SET enable_seqscan = off;
CREATE TABLE t (val sparsevec);
INSERT INTO t (val) VALUES ('{}'), ('{0:1,1:2,2:3}'), ('{0:1,1:1,2:1}'), (NULL);
CREATE INDEX ON t USING hnsw (val sparsevec_ip_ops);
INSERT INTO t (val) VALUES ('{0:1,1:2,2:4}');
SELECT * FROM t ORDER BY val <#> '{0:3,1:3,2:3}';
val
---------------
{0:1,1:2,2:4}
{0:1,1:2,2:3}
{0:1,1:1,2:1}
{}
(4 rows)
SELECT COUNT(*) FROM (SELECT * FROM t ORDER BY val <#> (SELECT NULL::sparsevec)) t2;
count
-------
4
(1 row)
DROP TABLE t;

View File

@@ -1,43 +0,0 @@
SET enable_seqscan = off;
CREATE TABLE t (val sparsevec);
INSERT INTO t (val) VALUES ('{}'), ('{0:1,1:2,2:3}'), ('{0:1,1:1,2:1}'), (NULL);
CREATE INDEX ON t USING hnsw (val sparsevec_l2_ops);
INSERT INTO t (val) VALUES ('{0:1,1:2,2:4}');
SELECT * FROM t ORDER BY val <-> '{0:3,1:3,2:3}';
val
---------------
{0:1,1:2,2:3}
{0:1,1:2,2:4}
{0:1,1:1,2:1}
{}
(4 rows)
SELECT COUNT(*) FROM (SELECT * FROM t ORDER BY val <-> (SELECT NULL::sparsevec)) t2;
count
-------
4
(1 row)
SELECT COUNT(*) FROM t;
count
-------
5
(1 row)
TRUNCATE t;
SELECT * FROM t ORDER BY val <-> '{0:3,1:3,2:3}';
val
-----
(0 rows)
DROP TABLE t;
-- TODO move
CREATE TABLE t (val sparsevec);
INSERT INTO t (val) VALUES (array_fill(1, ARRAY[1001])::vector::sparsevec);
CREATE INDEX ON t USING hnsw (val sparsevec_l2_ops);
ERROR: sparsevec cannot have more than 1000 non-zero elements for hnsw index
TRUNCATE t;
CREATE INDEX ON t USING hnsw (val sparsevec_l2_ops);
INSERT INTO t (val) VALUES (array_fill(1, ARRAY[1001])::vector::sparsevec);
ERROR: sparsevec cannot have more than 1000 non-zero elements for hnsw index
DROP TABLE t;

View File

@@ -63,25 +63,9 @@ SELECT '[1.5e-38,-1.5e-38]'::vector;
(1 row) (1 row)
SELECT '[4e38,1]'::vector; SELECT '[4e38,1]'::vector;
ERROR: "4e38" is out of range for type vector ERROR: infinite value not allowed in vector
LINE 1: SELECT '[4e38,1]'::vector; LINE 1: SELECT '[4e38,1]'::vector;
^ ^
SELECT '[-4e38,1]'::vector;
ERROR: "-4e38" is out of range for type vector
LINE 1: SELECT '[-4e38,1]'::vector;
^
SELECT '[1e-46,1]'::vector;
vector
--------
[0,1]
(1 row)
SELECT '[-1e-46,1]'::vector;
vector
--------
[-0,1]
(1 row)
SELECT '[1,2,3'::vector; SELECT '[1,2,3'::vector;
ERROR: malformed vector literal: "[1,2,3" ERROR: malformed vector literal: "[1,2,3"
LINE 1: SELECT '[1,2,3'::vector; LINE 1: SELECT '[1,2,3'::vector;

View File

@@ -1,60 +0,0 @@
SELECT l2_distance('{}'::sparsevec, '{0:3,1:4}');
l2_distance
-------------
5
(1 row)
SELECT l2_distance('{}'::sparsevec, '{1:1}');
l2_distance
-------------
1
(1 row)
SELECT '{}'::sparsevec <-> '{0:3,1:4}';
?column?
----------
5
(1 row)
SELECT inner_product('{0:1,1:2}'::sparsevec, '{0:2,1:4}');
inner_product
---------------
10
(1 row)
SELECT sparsevec_negative_inner_product('{0:1,1:2}', '{0:2,1:4}');
sparsevec_negative_inner_product
----------------------------------
-10
(1 row)
SELECT cosine_distance('{0:1,1:2}'::sparsevec, '{0:2,1:4}');
cosine_distance
-----------------
0
(1 row)
SELECT cosine_distance('{0:1,1:2}'::sparsevec, '{}');
cosine_distance
-----------------
NaN
(1 row)
SELECT cosine_distance('{0:1,1:1}'::sparsevec, '{0:-1,1:-1}');
cosine_distance
-----------------
2
(1 row)
SELECT cosine_distance('{0:1}'::sparsevec, '{1:2}');
cosine_distance
-----------------
1
(1 row)
SELECT cosine_distance('{}'::sparsevec, '{}');
cosine_distance
-----------------
NaN
(1 row)

View File

@@ -1,64 +0,0 @@
SELECT '{0:1.5,2:3.5}'::sparsevec;
sparsevec
---------------
{0:1.5,2:3.5}
(1 row)
SELECT '{0:1.5,2:3.5}'::sparsevec::vector;
vector
-------------
[1.5,0,3.5]
(1 row)
SELECT '{0:1.5,2:3.5}'::sparsevec::vector(5);
vector
-----------------
[1.5,0,3.5,0,0]
(1 row)
SELECT '{0:1.5,2:3.5}'::sparsevec::vector(4);
vector
---------------
[1.5,0,3.5,0]
(1 row)
SELECT '{0:1.5,2:3.5}'::sparsevec::vector(2);
ERROR: Vector must have at least 3 dimensions
SELECT '[0,1.5,0,3.5,0]'::vector::sparsevec;
sparsevec
---------------
{1:1.5,3:3.5}
(1 row)
SELECT '{0:0,1:1,2:0}'::sparsevec;
sparsevec
-----------
{1:1}
(1 row)
SELECT '{1:1,0:1}'::sparsevec;
ERROR: indexes must be in ascending order
LINE 1: SELECT '{1:1,0:1}'::sparsevec;
^
SELECT '{}'::sparsevec;
sparsevec
-----------
{}
(1 row)
SELECT '{}'::sparsevec::vector;
ERROR: vector must have at least 1 dimension
SELECT '{-1:1}'::sparsevec;
ERROR: index "-1" is out of range for type sparsevec
LINE 1: SELECT '{-1:1}'::sparsevec;
^
SELECT '{1:1}'::sparsevec;
sparsevec
-----------
{1:1}
(1 row)
SELECT '{}'::sparsevec(2);
ERROR: type modifier is not allowed for type "sparsevec"
LINE 1: SELECT '{}'::sparsevec(2);
^

View File

@@ -1,13 +0,0 @@
SELECT hamming_distance(B'111', B'111');
SELECT hamming_distance(B'111', B'110');
SELECT hamming_distance(B'111', B'100');
SELECT hamming_distance(B'111', B'000');
SELECT hamming_distance(B'111', B'00');
SELECT jaccard_distance(B'1111', B'1111');
SELECT jaccard_distance(B'1111', B'1110');
SELECT jaccard_distance(B'1111', B'1100');
SELECT jaccard_distance(B'1111', B'1000');
SELECT jaccard_distance(B'1111', B'0000');
SELECT jaccard_distance(B'1100', B'1000');
SELECT jaccard_distance(B'1111', B'000');

View File

@@ -12,9 +12,6 @@ SELECT '{{1}}'::real[]::vector;
SELECT '[1,2,3]'::vector::real[]; SELECT '[1,2,3]'::vector::real[];
SELECT '[1,2,3]'::vector::halfvec; SELECT '[1,2,3]'::vector::halfvec;
SELECT '[1,2,3]'::halfvec::vector; SELECT '[1,2,3]'::halfvec::vector;
SELECT '[1,2,3]'::vector::halfvec(2);
SELECT '[1,2,3]'::halfvec::vector(2);
SELECT '[65520]'::vector::halfvec;
SELECT '[1e-8]'::vector::halfvec; SELECT '[1e-8]'::vector::halfvec;
SELECT array_agg(n)::vector FROM generate_series(1, 16001) n; SELECT array_agg(n)::vector FROM generate_series(1, 16001) n;
SELECT array_to_vector(array_agg(n), 16001, false) FROM generate_series(1, 16001) n; SELECT array_to_vector(array_agg(n), 16001, false) FROM generate_series(1, 16001) n;

View File

@@ -6,18 +6,8 @@ SELECT '[1,2,3]'::vector * '[4,5,6]';
SELECT '[1e37]'::vector * '[1e37]'; SELECT '[1e37]'::vector * '[1e37]';
SELECT '[1e-37]'::vector * '[1e-37]'; SELECT '[1e-37]'::vector * '[1e-37]';
SELECT '[1,2,3]'::vector < '[1,2,3]';
SELECT '[1,2,3]'::vector < '[1,2]';
SELECT '[1,2,3]'::vector <= '[1,2,3]';
SELECT '[1,2,3]'::vector <= '[1,2]';
SELECT '[1,2,3]'::vector = '[1,2,3]'; SELECT '[1,2,3]'::vector = '[1,2,3]';
SELECT '[1,2,3]'::vector = '[1,2]'; SELECT '[1,2,3]'::vector = '[1,2]';
SELECT '[1,2,3]'::vector != '[1,2,3]';
SELECT '[1,2,3]'::vector != '[1,2]';
SELECT '[1,2,3]'::vector >= '[1,2,3]';
SELECT '[1,2,3]'::vector >= '[1,2]';
SELECT '[1,2,3]'::vector > '[1,2,3]';
SELECT '[1,2,3]'::vector > '[1,2]';
SELECT vector_cmp('[1,2,3]', '[1,2,3]'); SELECT vector_cmp('[1,2,3]', '[1,2,3]');
SELECT vector_cmp('[1,2,3]', '[0,0,0]'); SELECT vector_cmp('[1,2,3]', '[0,0,0]');
@@ -58,17 +48,6 @@ SELECT l1_distance('[0,0]'::vector, '[0,1]');
SELECT l1_distance('[1,2]'::vector, '[3]'); SELECT l1_distance('[1,2]'::vector, '[3]');
SELECT l1_distance('[3e38]'::vector, '[-3e38]'); SELECT l1_distance('[3e38]'::vector, '[-3e38]');
SELECT quantize_binary('[1,0,-1]'::vector);
SELECT quantize_binary('[0,0.1,-0.2,-0.3,0.4,0.5,0.6,-0.7,0.8,-0.9,1]'::vector);
SELECT subvector('[1,2,3,4,5]'::vector, 1, 3);
SELECT subvector('[1,2,3,4,5]'::vector, 3, 2);
SELECT subvector('[1,2,3,4,5]'::vector, -1, 3);
SELECT subvector('[1,2,3,4,5]'::vector, 3, 9);
SELECT subvector('[1,2,3,4,5]'::vector, 1, 0);
SELECT subvector('[1,2,3,4,5]'::vector, 3, -1);
SELECT subvector('[1,2,3,4,5]'::vector, -1, 2);
SELECT avg(v) FROM unnest(ARRAY['[1,2,3]'::vector, '[3,5,7]']) v; SELECT avg(v) FROM unnest(ARRAY['[1,2,3]'::vector, '[3,5,7]']) v;
SELECT avg(v) FROM unnest(ARRAY['[1,2,3]'::vector, '[3,5,7]', NULL]) v; SELECT avg(v) FROM unnest(ARRAY['[1,2,3]'::vector, '[3,5,7]', NULL]) v;
SELECT avg(v) FROM unnest(ARRAY[]::vector[]) v; SELECT avg(v) FROM unnest(ARRAY[]::vector[]) v;

View File

@@ -21,14 +21,3 @@ SELECT '[1,2]'::halfvec <=> '[2,4]';
SELECT l1_distance('[0,0]'::halfvec, '[3,4]'); SELECT l1_distance('[0,0]'::halfvec, '[3,4]');
SELECT l1_distance('[0,0]'::halfvec, '[0,1]'); SELECT l1_distance('[0,0]'::halfvec, '[0,1]');
SELECT l1_distance('[1,2]'::halfvec, '[3]'); SELECT l1_distance('[1,2]'::halfvec, '[3]');
SELECT quantize_binary('[1,0,-1]'::halfvec);
SELECT quantize_binary('[0,0.1,-0.2,-0.3,0.4,0.5,0.6,-0.7,0.8,-0.9,1]'::halfvec);
SELECT subvector('[1,2,3,4,5]'::halfvec, 1, 3);
SELECT subvector('[1,2,3,4,5]'::halfvec, 3, 2);
SELECT subvector('[1,2,3,4,5]'::halfvec, -1, 3);
SELECT subvector('[1,2,3,4,5]'::halfvec, 3, 9);
SELECT subvector('[1,2,3,4,5]'::halfvec, 1, 0);
SELECT subvector('[1,2,3,4,5]'::halfvec, 3, -1);
SELECT subvector('[1,2,3,4,5]'::halfvec, -1, 2);

View File

@@ -1,12 +0,0 @@
SET enable_seqscan = off;
CREATE TABLE t (val bit(3));
INSERT INTO t (val) VALUES (B'000'), (B'100'), (B'111'), (NULL);
CREATE INDEX ON t USING hnsw (val bit_hamming_ops);
INSERT INTO t (val) VALUES (B'110');
SELECT * FROM t ORDER BY val <~> B'111';
SELECT COUNT(*) FROM (SELECT * FROM t ORDER BY val <~> (SELECT NULL::bit)) t2;
DROP TABLE t;

View File

@@ -1,12 +0,0 @@
SET enable_seqscan = off;
CREATE TABLE t (val bit(4));
INSERT INTO t (val) VALUES (B'0000'), (B'1100'), (B'1111'), (NULL);
CREATE INDEX ON t USING hnsw (val bit_jaccard_ops);
INSERT INTO t (val) VALUES (B'1110');
SELECT * FROM t ORDER BY val <%> B'1111';
SELECT COUNT(*) FROM (SELECT * FROM t ORDER BY val <%> (SELECT NULL::bit)) t2;
DROP TABLE t;

View File

@@ -7,7 +7,7 @@ CREATE INDEX ON t USING hnsw (val halfvec_l2_ops);
INSERT INTO t (val) VALUES ('[1,2,4]'); INSERT INTO t (val) VALUES ('[1,2,4]');
SELECT * FROM t ORDER BY val <-> '[3,3,3]'; SELECT * FROM t ORDER BY val <-> '[3,3,3]';
SELECT COUNT(*) FROM (SELECT * FROM t ORDER BY val <-> (SELECT NULL::halfvec)) t2; SELECT * FROM t ORDER BY val <-> (SELECT NULL::halfvec);
SELECT COUNT(*) FROM t; SELECT COUNT(*) FROM t;
TRUNCATE t; TRUNCATE t;

View File

@@ -7,7 +7,7 @@ CREATE INDEX ON t USING hnsw (val vector_l2_ops);
INSERT INTO t (val) VALUES ('[1,2,4]'); INSERT INTO t (val) VALUES ('[1,2,4]');
SELECT * FROM t ORDER BY val <-> '[3,3,3]'; SELECT * FROM t ORDER BY val <-> '[3,3,3]';
SELECT COUNT(*) FROM (SELECT * FROM t ORDER BY val <-> (SELECT NULL::vector)) t2; SELECT * FROM t ORDER BY val <-> (SELECT NULL::vector);
SELECT COUNT(*) FROM t; SELECT COUNT(*) FROM t;
TRUNCATE t; TRUNCATE t;

View File

@@ -1,13 +0,0 @@
SET enable_seqscan = off;
CREATE TABLE t (val sparsevec);
INSERT INTO t (val) VALUES ('{}'), ('{0:1,1:2,2:3}'), ('{0:1,1:1,2:1}'), (NULL);
CREATE INDEX ON t USING hnsw (val sparsevec_cosine_ops);
INSERT INTO t (val) VALUES ('{0:1,1:2,2:4}');
SELECT * FROM t ORDER BY val <=> '{0:3,1:3,2:3}';
SELECT COUNT(*) FROM (SELECT * FROM t ORDER BY val <=> '{}') t2;
SELECT COUNT(*) FROM (SELECT * FROM t ORDER BY val <=> (SELECT NULL::sparsevec)) t2;
DROP TABLE t;

View File

@@ -1,12 +0,0 @@
SET enable_seqscan = off;
CREATE TABLE t (val sparsevec);
INSERT INTO t (val) VALUES ('{}'), ('{0:1,1:2,2:3}'), ('{0:1,1:1,2:1}'), (NULL);
CREATE INDEX ON t USING hnsw (val sparsevec_ip_ops);
INSERT INTO t (val) VALUES ('{0:1,1:2,2:4}');
SELECT * FROM t ORDER BY val <#> '{0:3,1:3,2:3}';
SELECT COUNT(*) FROM (SELECT * FROM t ORDER BY val <#> (SELECT NULL::sparsevec)) t2;
DROP TABLE t;

View File

@@ -1,25 +0,0 @@
SET enable_seqscan = off;
CREATE TABLE t (val sparsevec);
INSERT INTO t (val) VALUES ('{}'), ('{0:1,1:2,2:3}'), ('{0:1,1:1,2:1}'), (NULL);
CREATE INDEX ON t USING hnsw (val sparsevec_l2_ops);
INSERT INTO t (val) VALUES ('{0:1,1:2,2:4}');
SELECT * FROM t ORDER BY val <-> '{0:3,1:3,2:3}';
SELECT COUNT(*) FROM (SELECT * FROM t ORDER BY val <-> (SELECT NULL::sparsevec)) t2;
SELECT COUNT(*) FROM t;
TRUNCATE t;
SELECT * FROM t ORDER BY val <-> '{0:3,1:3,2:3}';
DROP TABLE t;
-- TODO move
CREATE TABLE t (val sparsevec);
INSERT INTO t (val) VALUES (array_fill(1, ARRAY[1001])::vector::sparsevec);
CREATE INDEX ON t USING hnsw (val sparsevec_l2_ops);
TRUNCATE t;
CREATE INDEX ON t USING hnsw (val sparsevec_l2_ops);
INSERT INTO t (val) VALUES (array_fill(1, ARRAY[1001])::vector::sparsevec);
DROP TABLE t;

View File

@@ -11,9 +11,6 @@ SELECT '[1.5e38,-1.5e38]'::vector;
SELECT '[1.5e+38,-1.5e+38]'::vector; SELECT '[1.5e+38,-1.5e+38]'::vector;
SELECT '[1.5e-38,-1.5e-38]'::vector; SELECT '[1.5e-38,-1.5e-38]'::vector;
SELECT '[4e38,1]'::vector; SELECT '[4e38,1]'::vector;
SELECT '[-4e38,1]'::vector;
SELECT '[1e-46,1]'::vector;
SELECT '[-1e-46,1]'::vector;
SELECT '[1,2,3'::vector; SELECT '[1,2,3'::vector;
SELECT '[1,2,3]9'::vector; SELECT '[1,2,3]9'::vector;
SELECT '1,2,3'::vector; SELECT '1,2,3'::vector;

View File

@@ -1,12 +0,0 @@
SELECT l2_distance('{}'::sparsevec, '{0:3,1:4}');
SELECT l2_distance('{}'::sparsevec, '{1:1}');
SELECT '{}'::sparsevec <-> '{0:3,1:4}';
SELECT inner_product('{0:1,1:2}'::sparsevec, '{0:2,1:4}');
SELECT sparsevec_negative_inner_product('{0:1,1:2}', '{0:2,1:4}');
SELECT cosine_distance('{0:1,1:2}'::sparsevec, '{0:2,1:4}');
SELECT cosine_distance('{0:1,1:2}'::sparsevec, '{}');
SELECT cosine_distance('{0:1,1:1}'::sparsevec, '{0:-1,1:-1}');
SELECT cosine_distance('{0:1}'::sparsevec, '{1:2}');
SELECT cosine_distance('{}'::sparsevec, '{}');

View File

@@ -1,18 +0,0 @@
SELECT '{0:1.5,2:3.5}'::sparsevec;
SELECT '{0:1.5,2:3.5}'::sparsevec::vector;
SELECT '{0:1.5,2:3.5}'::sparsevec::vector(5);
SELECT '{0:1.5,2:3.5}'::sparsevec::vector(4);
SELECT '{0:1.5,2:3.5}'::sparsevec::vector(2);
SELECT '[0,1.5,0,3.5,0]'::vector::sparsevec;
SELECT '{0:0,1:1,2:0}'::sparsevec;
SELECT '{1:1,0:1}'::sparsevec;
SELECT '{}'::sparsevec;
SELECT '{}'::sparsevec::vector;
SELECT '{-1:1}'::sparsevec;
SELECT '{1:1}'::sparsevec;
SELECT '{}'::sparsevec(2);

View File

@@ -86,7 +86,7 @@ foreach (@queries)
push(@expected, $res); push(@expected, $res);
} }
test_recall(0.19, $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

View File

@@ -1,137 +0,0 @@
use strict;
use warnings;
use PostgresNode;
use TestLib;
use Test::More;
my $node;
my @queries = ();
my @expected;
my $limit = 20;
my $dim = 52;
my $max = 2**$dim;
sub test_recall
{
my ($min, $operator) = @_;
my $correct = 0;
my $total = 0;
my $explain = $node->safe_psql("postgres", qq(
SET enable_seqscan = off;
SET hnsw.ef_search = 100;
EXPLAIN ANALYZE SELECT i FROM tst ORDER BY v $operator $queries[0] LIMIT $limit;
));
like($explain, qr/Index Scan/);
for my $i (0 .. $#queries)
{
my $actual = $node->safe_psql("postgres", qq(
SET enable_seqscan = off;
SET hnsw.ef_search = 100;
SELECT i FROM tst ORDER BY v $operator $queries[$i] LIMIT $limit;
));
my @actual_ids = split("\n", $actual);
my @expected_ids = split("\n", $expected[$i]);
my %expected_set = map { $_ => 1 } @expected_ids;
foreach (@actual_ids)
{
if (exists($expected_set{$_}))
{
$correct++;
}
}
$total += $limit;
}
cmp_ok($correct / $total, ">=", $min, $operator);
}
# Initialize node
$node = get_new_node('node');
$node->init;
$node->start;
# Create table
$node->safe_psql("postgres", "CREATE EXTENSION vector;");
$node->safe_psql("postgres", "CREATE TABLE tst (i int4, v bit($dim));");
$node->safe_psql("postgres",
"INSERT INTO tst SELECT i, (random() * $max)::bigint::bit($dim) FROM generate_series(1, 10000) i;"
);
# Generate queries
for (1 .. 20)
{
my $r = int(rand() * $max);
push(@queries, "${r}::bigint::bit($dim)");
}
# Check each index type
my @operators = ("<~>", "<\%>");
my @opclasses = ("bit_hamming_ops", "bit_jaccard_ops");
for my $i (0 .. $#operators)
{
my $operator = $operators[$i];
my $opclass = $opclasses[$i];
# Get exact results
@expected = ();
foreach (@queries)
{
# Handle ties
my $res = $node->safe_psql("postgres", qq(
WITH top AS (
SELECT v $operator $_ AS distance FROM tst ORDER BY v $operator $_ LIMIT $limit
)
SELECT i FROM tst WHERE (v $operator $_) <= (SELECT MAX(distance) FROM top)
));
push(@expected, $res);
}
# Build index serially
$node->safe_psql("postgres", qq(
SET max_parallel_maintenance_workers = 0;
CREATE INDEX idx ON tst USING hnsw (v $opclass);
));
# Test approximate results
my $min = $operator eq "<\%>" ? 0.96 : 0.98;
test_recall($min, $operator);
$node->safe_psql("postgres", "DROP INDEX idx;");
# Build index in parallel in memory
my ($ret, $stdout, $stderr) = $node->psql("postgres", qq(
SET client_min_messages = DEBUG;
SET min_parallel_table_scan_size = 1;
CREATE INDEX idx ON tst USING hnsw (v $opclass);
));
is($ret, 0, $stderr);
like($stderr, qr/using \d+ parallel workers/);
# Test approximate results
test_recall($min, $operator);
$node->safe_psql("postgres", "DROP INDEX idx;");
# Build index in parallel on disk
# Set parallel_workers on table to use workers with low maintenance_work_mem
($ret, $stdout, $stderr) = $node->psql("postgres", qq(
ALTER TABLE tst SET (parallel_workers = 2);
SET client_min_messages = DEBUG;
SET maintenance_work_mem = '4MB';
CREATE INDEX idx ON tst USING hnsw (v $opclass);
ALTER TABLE tst RESET (parallel_workers);
));
is($ret, 0, $stderr);
like($stderr, qr/using \d+ parallel workers/);
like($stderr, qr/hnsw graph no longer fits into maintenance_work_mem/);
$node->safe_psql("postgres", "DROP INDEX idx;");
}
done_testing();

View File

@@ -8,8 +8,6 @@ my $node;
my @queries = (); my @queries = ();
my @expected; my @expected;
my $limit = 20; my $limit = 20;
my $dim = 10;
my $array_sql = join(",", ('random()') x $dim);
sub test_recall sub test_recall
{ {
@@ -54,20 +52,18 @@ $node->start;
# Create table # Create table
$node->safe_psql("postgres", "CREATE EXTENSION vector;"); $node->safe_psql("postgres", "CREATE EXTENSION vector;");
$node->safe_psql("postgres", "CREATE TABLE tst (i int4, v halfvec($dim));"); $node->safe_psql("postgres", "CREATE TABLE tst (i int4, v halfvec(3));");
$node->safe_psql("postgres", $node->safe_psql("postgres",
"INSERT INTO tst SELECT i, ARRAY[$array_sql] FROM generate_series(1, 10000) i;" "INSERT INTO tst SELECT i, ARRAY[random(), random(), random()] FROM generate_series(1, 10000) i;"
); );
# Generate queries # Generate queries
for (1 .. 20) for (1 .. 20)
{ {
my @r = (); my $r1 = rand();
for (1 .. $dim) my $r2 = rand();
{ my $r3 = rand();
push(@r, rand()); push(@queries, "[$r1,$r2,$r3]");
}
push(@queries, "[" . join(",", @r) . "]");
} }
# Check each index type # Check each index type
@@ -94,7 +90,7 @@ for my $i (0 .. $#operators)
)); ));
# Test approximate results # Test approximate results
my $min = $operator eq "<#>" ? 0.95 : 0.98; my $min = $operator eq "<#>" ? 0.80 : ($operator eq "<=>" ? 0.80 : 0.99);
test_recall($min, $operator); test_recall($min, $operator);
$node->safe_psql("postgres", "DROP INDEX idx;"); $node->safe_psql("postgres", "DROP INDEX idx;");