From b544f2fd649509c0aeba47b97de3536d9260bab0 Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Wed, 20 Sep 2023 08:52:28 -0700 Subject: [PATCH 1/6] feat: add pgvector Signed-off-by: Matt Krick --- packages/server/postgres/Dockerfile | 4 + .../server/postgres/extensions/install.sql | 1 + .../postgres/extensions/pgvector/Makefile | 76 ++ .../pgvector/sql/vector--0.1.0--0.1.1.sql | 39 + .../pgvector/sql/vector--0.1.1--0.1.3.sql | 2 + .../pgvector/sql/vector--0.1.3--0.1.4.sql | 2 + .../pgvector/sql/vector--0.1.4--0.1.5.sql | 2 + .../pgvector/sql/vector--0.1.5--0.1.6.sql | 2 + .../pgvector/sql/vector--0.1.6--0.1.7.sql | 8 + .../pgvector/sql/vector--0.1.7--0.1.8.sql | 8 + .../pgvector/sql/vector--0.1.8--0.2.0.sql | 2 + .../pgvector/sql/vector--0.2.0--0.2.1.sql | 19 + .../pgvector/sql/vector--0.2.1--0.2.2.sql | 2 + .../pgvector/sql/vector--0.2.2--0.2.3.sql | 2 + .../pgvector/sql/vector--0.2.3--0.2.4.sql | 2 + .../pgvector/sql/vector--0.2.4--0.2.5.sql | 2 + .../pgvector/sql/vector--0.2.5--0.2.6.sql | 2 + .../pgvector/sql/vector--0.2.6--0.2.7.sql | 2 + .../pgvector/sql/vector--0.2.7--0.3.0.sql | 2 + .../pgvector/sql/vector--0.3.0--0.3.1.sql | 2 + .../pgvector/sql/vector--0.3.1--0.3.2.sql | 2 + .../pgvector/sql/vector--0.3.2--0.4.0.sql | 23 + .../pgvector/sql/vector--0.4.0--0.4.1.sql | 2 + .../pgvector/sql/vector--0.4.1--0.4.2.sql | 2 + .../pgvector/sql/vector--0.4.2--0.4.3.sql | 2 + .../pgvector/sql/vector--0.4.3--0.4.4.sql | 2 + .../pgvector/sql/vector--0.4.4--0.5.0.sql | 43 + .../extensions/pgvector/sql/vector--0.5.0.sql | 292 +++++ .../extensions/pgvector/sql/vector.sql | 292 +++++ .../postgres/extensions/pgvector/src/hnsw.c | 224 ++++ .../postgres/extensions/pgvector/src/hnsw.h | 305 +++++ .../postgres/extensions/pgvector/src/hnsw.o | Bin 0 -> 4224 bytes .../extensions/pgvector/src/hnswbuild.c | 526 ++++++++ .../extensions/pgvector/src/hnswbuild.o | Bin 0 -> 9192 bytes .../extensions/pgvector/src/hnswinsert.c | 590 +++++++++ .../extensions/pgvector/src/hnswinsert.o | Bin 0 -> 8560 bytes .../extensions/pgvector/src/hnswscan.c | 223 ++++ .../extensions/pgvector/src/hnswscan.o | Bin 0 -> 3600 bytes .../extensions/pgvector/src/hnswutils.c | 992 ++++++++++++++ .../extensions/pgvector/src/hnswutils.o | Bin 0 -> 11504 bytes .../extensions/pgvector/src/hnswvacuum.c | 658 ++++++++++ .../extensions/pgvector/src/hnswvacuum.o | Bin 0 -> 8976 bytes .../extensions/pgvector/src/ivfbuild.c | 1111 ++++++++++++++++ .../extensions/pgvector/src/ivfbuild.o | Bin 0 -> 14592 bytes .../extensions/pgvector/src/ivfflat.c | 251 ++++ .../extensions/pgvector/src/ivfflat.h | 306 +++++ .../extensions/pgvector/src/ivfflat.o | Bin 0 -> 4344 bytes .../extensions/pgvector/src/ivfinsert.c | 206 +++ .../extensions/pgvector/src/ivfinsert.o | Bin 0 -> 3624 bytes .../extensions/pgvector/src/ivfkmeans.c | 528 ++++++++ .../extensions/pgvector/src/ivfkmeans.o | Bin 0 -> 9240 bytes .../extensions/pgvector/src/ivfscan.c | 381 ++++++ .../extensions/pgvector/src/ivfscan.o | Bin 0 -> 6344 bytes .../extensions/pgvector/src/ivfutils.c | 220 ++++ .../extensions/pgvector/src/ivfutils.o | Bin 0 -> 3552 bytes .../extensions/pgvector/src/ivfvacuum.c | 157 +++ .../extensions/pgvector/src/ivfvacuum.o | Bin 0 -> 2664 bytes .../postgres/extensions/pgvector/src/vector.c | 1145 +++++++++++++++++ .../postgres/extensions/pgvector/src/vector.h | 23 + .../postgres/extensions/pgvector/src/vector.o | Bin 0 -> 36336 bytes .../extensions/pgvector/vector.control | 4 + .../postgres/extensions/pgvector/vector.so | Bin 0 -> 107270 bytes 62 files changed, 8691 insertions(+) create mode 100644 packages/server/postgres/extensions/pgvector/Makefile create mode 100644 packages/server/postgres/extensions/pgvector/sql/vector--0.1.0--0.1.1.sql create mode 100644 packages/server/postgres/extensions/pgvector/sql/vector--0.1.1--0.1.3.sql create mode 100644 packages/server/postgres/extensions/pgvector/sql/vector--0.1.3--0.1.4.sql create mode 100644 packages/server/postgres/extensions/pgvector/sql/vector--0.1.4--0.1.5.sql create mode 100644 packages/server/postgres/extensions/pgvector/sql/vector--0.1.5--0.1.6.sql create mode 100644 packages/server/postgres/extensions/pgvector/sql/vector--0.1.6--0.1.7.sql create mode 100644 packages/server/postgres/extensions/pgvector/sql/vector--0.1.7--0.1.8.sql create mode 100644 packages/server/postgres/extensions/pgvector/sql/vector--0.1.8--0.2.0.sql create mode 100644 packages/server/postgres/extensions/pgvector/sql/vector--0.2.0--0.2.1.sql create mode 100644 packages/server/postgres/extensions/pgvector/sql/vector--0.2.1--0.2.2.sql create mode 100644 packages/server/postgres/extensions/pgvector/sql/vector--0.2.2--0.2.3.sql create mode 100644 packages/server/postgres/extensions/pgvector/sql/vector--0.2.3--0.2.4.sql create mode 100644 packages/server/postgres/extensions/pgvector/sql/vector--0.2.4--0.2.5.sql create mode 100644 packages/server/postgres/extensions/pgvector/sql/vector--0.2.5--0.2.6.sql create mode 100644 packages/server/postgres/extensions/pgvector/sql/vector--0.2.6--0.2.7.sql create mode 100644 packages/server/postgres/extensions/pgvector/sql/vector--0.2.7--0.3.0.sql create mode 100644 packages/server/postgres/extensions/pgvector/sql/vector--0.3.0--0.3.1.sql create mode 100644 packages/server/postgres/extensions/pgvector/sql/vector--0.3.1--0.3.2.sql create mode 100644 packages/server/postgres/extensions/pgvector/sql/vector--0.3.2--0.4.0.sql create mode 100644 packages/server/postgres/extensions/pgvector/sql/vector--0.4.0--0.4.1.sql create mode 100644 packages/server/postgres/extensions/pgvector/sql/vector--0.4.1--0.4.2.sql create mode 100644 packages/server/postgres/extensions/pgvector/sql/vector--0.4.2--0.4.3.sql create mode 100644 packages/server/postgres/extensions/pgvector/sql/vector--0.4.3--0.4.4.sql create mode 100644 packages/server/postgres/extensions/pgvector/sql/vector--0.4.4--0.5.0.sql create mode 100644 packages/server/postgres/extensions/pgvector/sql/vector--0.5.0.sql create mode 100644 packages/server/postgres/extensions/pgvector/sql/vector.sql create mode 100644 packages/server/postgres/extensions/pgvector/src/hnsw.c create mode 100644 packages/server/postgres/extensions/pgvector/src/hnsw.h create mode 100644 packages/server/postgres/extensions/pgvector/src/hnsw.o create mode 100644 packages/server/postgres/extensions/pgvector/src/hnswbuild.c create mode 100644 packages/server/postgres/extensions/pgvector/src/hnswbuild.o create mode 100644 packages/server/postgres/extensions/pgvector/src/hnswinsert.c create mode 100644 packages/server/postgres/extensions/pgvector/src/hnswinsert.o create mode 100644 packages/server/postgres/extensions/pgvector/src/hnswscan.c create mode 100644 packages/server/postgres/extensions/pgvector/src/hnswscan.o create mode 100644 packages/server/postgres/extensions/pgvector/src/hnswutils.c create mode 100644 packages/server/postgres/extensions/pgvector/src/hnswutils.o create mode 100644 packages/server/postgres/extensions/pgvector/src/hnswvacuum.c create mode 100644 packages/server/postgres/extensions/pgvector/src/hnswvacuum.o create mode 100644 packages/server/postgres/extensions/pgvector/src/ivfbuild.c create mode 100644 packages/server/postgres/extensions/pgvector/src/ivfbuild.o create mode 100644 packages/server/postgres/extensions/pgvector/src/ivfflat.c create mode 100644 packages/server/postgres/extensions/pgvector/src/ivfflat.h create mode 100644 packages/server/postgres/extensions/pgvector/src/ivfflat.o create mode 100644 packages/server/postgres/extensions/pgvector/src/ivfinsert.c create mode 100644 packages/server/postgres/extensions/pgvector/src/ivfinsert.o create mode 100644 packages/server/postgres/extensions/pgvector/src/ivfkmeans.c create mode 100644 packages/server/postgres/extensions/pgvector/src/ivfkmeans.o create mode 100644 packages/server/postgres/extensions/pgvector/src/ivfscan.c create mode 100644 packages/server/postgres/extensions/pgvector/src/ivfscan.o create mode 100644 packages/server/postgres/extensions/pgvector/src/ivfutils.c create mode 100644 packages/server/postgres/extensions/pgvector/src/ivfutils.o create mode 100644 packages/server/postgres/extensions/pgvector/src/ivfvacuum.c create mode 100644 packages/server/postgres/extensions/pgvector/src/ivfvacuum.o create mode 100644 packages/server/postgres/extensions/pgvector/src/vector.c create mode 100644 packages/server/postgres/extensions/pgvector/src/vector.h create mode 100644 packages/server/postgres/extensions/pgvector/src/vector.o create mode 100644 packages/server/postgres/extensions/pgvector/vector.control create mode 100755 packages/server/postgres/extensions/pgvector/vector.so diff --git a/packages/server/postgres/Dockerfile b/packages/server/postgres/Dockerfile index 8f5838419d4..104ed26f993 100644 --- a/packages/server/postgres/Dockerfile +++ b/packages/server/postgres/Dockerfile @@ -6,4 +6,8 @@ RUN apt-get update && apt-get install -y build-essential RUN cd /extensions/postgres-json-schema && make install && make installcheck +RUN apt-get install -y --no-install-recommends postgresql-server-dev-12 + +RUN cd ./extensions/pgvector && ls && make clean && make && make install + COPY extensions/install.sql /docker-entrypoint-initdb.d/ diff --git a/packages/server/postgres/extensions/install.sql b/packages/server/postgres/extensions/install.sql index ddc60250061..b2670548069 100644 --- a/packages/server/postgres/extensions/install.sql +++ b/packages/server/postgres/extensions/install.sql @@ -1 +1,2 @@ CREATE EXTENSION IF NOT EXISTS "postgres-json-schema"; +CREATE EXTENSION IF NOT EXISTS "vector"; diff --git a/packages/server/postgres/extensions/pgvector/Makefile b/packages/server/postgres/extensions/pgvector/Makefile new file mode 100644 index 00000000000..04d39986ed7 --- /dev/null +++ b/packages/server/postgres/extensions/pgvector/Makefile @@ -0,0 +1,76 @@ +EXTENSION = vector +EXTVERSION = 0.5.0 + +MODULE_big = vector +DATA = $(wildcard sql/*--*.sql) +OBJS = src/hnsw.o src/hnswbuild.o src/hnswinsert.o src/hnswscan.o src/hnswutils.o src/hnswvacuum.o src/ivfbuild.o src/ivfflat.o src/ivfinsert.o src/ivfkmeans.o src/ivfscan.o src/ivfutils.o src/ivfvacuum.o src/vector.o +HEADERS = src/vector.h + +TESTS = $(wildcard test/sql/*.sql) +REGRESS = $(patsubst test/sql/%.sql,%,$(TESTS)) +REGRESS_OPTS = --inputdir=test --load-extension=vector + +OPTFLAGS = -march=native + +# Mac ARM doesn't support -march=native +ifeq ($(shell uname -s), Darwin) + ifeq ($(shell uname -p), arm) + # no difference with -march=armv8.5-a + OPTFLAGS = + endif +endif + +# PowerPC doesn't support -march=native +ifneq ($(filter ppc64%, $(shell uname -m)), ) + OPTFLAGS = +endif + +# For auto-vectorization: +# - GCC (needs -ftree-vectorize OR -O3) - https://gcc.gnu.org/projects/tree-ssa/vectorization.html +# - Clang (could use pragma instead) - https://llvm.org/docs/Vectorizers.html +PG_CFLAGS += $(OPTFLAGS) -ftree-vectorize -fassociative-math -fno-signed-zeros -fno-trapping-math + +# Debug GCC auto-vectorization +# PG_CFLAGS += -fopt-info-vec + +# Debug Clang auto-vectorization +# PG_CFLAGS += -Rpass=loop-vectorize -Rpass-analysis=loop-vectorize + +all: sql/$(EXTENSION)--$(EXTVERSION).sql + +sql/$(EXTENSION)--$(EXTVERSION).sql: sql/$(EXTENSION).sql + cp $< $@ + +EXTRA_CLEAN = sql/$(EXTENSION)--$(EXTVERSION).sql + +PG_CONFIG ?= pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +# for Mac +ifeq ($(PROVE),) + PROVE = prove +endif + +# for Postgres 15 +PROVE_FLAGS += -I ./test/perl + +prove_installcheck: + rm -rf $(CURDIR)/tmp_check + cd $(srcdir) && TESTDIR='$(CURDIR)' PATH="$(bindir):$$PATH" PGPORT='6$(DEF_PGPORT)' PG_REGRESS='$(top_builddir)/src/test/regress/pg_regress' $(PROVE) $(PG_PROVE_FLAGS) $(PROVE_FLAGS) $(if $(PROVE_TESTS),$(PROVE_TESTS),test/t/*.pl) + +.PHONY: dist + +dist: + mkdir -p dist + git archive --format zip --prefix=$(EXTENSION)-$(EXTVERSION)/ --output dist/$(EXTENSION)-$(EXTVERSION).zip master + +.PHONY: docker + +docker: + docker build --pull --no-cache --platform linux/amd64 -t ankane/pgvector:latest . + +.PHONY: docker-release + +docker-release: + docker buildx build --push --pull --no-cache --platform linux/amd64,linux/arm64 -t ankane/pgvector:latest . + docker buildx build --push --platform linux/amd64,linux/arm64 -t ankane/pgvector:v$(EXTVERSION) . diff --git a/packages/server/postgres/extensions/pgvector/sql/vector--0.1.0--0.1.1.sql b/packages/server/postgres/extensions/pgvector/sql/vector--0.1.0--0.1.1.sql new file mode 100644 index 00000000000..959a0d72261 --- /dev/null +++ b/packages/server/postgres/extensions/pgvector/sql/vector--0.1.0--0.1.1.sql @@ -0,0 +1,39 @@ +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "ALTER EXTENSION vector UPDATE TO '0.1.1'" to load this file. \quit + +CREATE FUNCTION vector_recv(internal, oid, integer) RETURNS vector + AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION vector_send(vector) RETURNS bytea + AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT; + +ALTER TYPE vector SET ( RECEIVE = vector_recv, SEND = vector_send ); + +-- functions + +ALTER FUNCTION vector_in(cstring, oid, integer) PARALLEL SAFE; +ALTER FUNCTION vector_out(vector) PARALLEL SAFE; +ALTER FUNCTION vector_typmod_in(cstring[]) PARALLEL SAFE; +ALTER FUNCTION vector_recv(internal, oid, integer) PARALLEL SAFE; +ALTER FUNCTION vector_send(vector) PARALLEL SAFE; +ALTER FUNCTION l2_distance(vector, vector) PARALLEL SAFE; +ALTER FUNCTION inner_product(vector, vector) PARALLEL SAFE; +ALTER FUNCTION cosine_distance(vector, vector) PARALLEL SAFE; +ALTER FUNCTION vector_dims(vector) PARALLEL SAFE; +ALTER FUNCTION vector_norm(vector) PARALLEL SAFE; +ALTER FUNCTION vector_add(vector, vector) PARALLEL SAFE; +ALTER FUNCTION vector_sub(vector, vector) PARALLEL SAFE; +ALTER FUNCTION vector_lt(vector, vector) PARALLEL SAFE; +ALTER FUNCTION vector_le(vector, vector) PARALLEL SAFE; +ALTER FUNCTION vector_eq(vector, vector) PARALLEL SAFE; +ALTER FUNCTION vector_ne(vector, vector) PARALLEL SAFE; +ALTER FUNCTION vector_ge(vector, vector) PARALLEL SAFE; +ALTER FUNCTION vector_gt(vector, vector) PARALLEL SAFE; +ALTER FUNCTION vector_cmp(vector, vector) PARALLEL SAFE; +ALTER FUNCTION vector_l2_squared_distance(vector, vector) PARALLEL SAFE; +ALTER FUNCTION vector_negative_inner_product(vector, vector) PARALLEL SAFE; +ALTER FUNCTION vector_spherical_distance(vector, vector) PARALLEL SAFE; +ALTER FUNCTION vector(vector, integer, boolean) PARALLEL SAFE; +ALTER FUNCTION array_to_vector(integer[], integer, boolean) PARALLEL SAFE; +ALTER FUNCTION array_to_vector(real[], integer, boolean) PARALLEL SAFE; +ALTER FUNCTION array_to_vector(double precision[], integer, boolean) PARALLEL SAFE; diff --git a/packages/server/postgres/extensions/pgvector/sql/vector--0.1.1--0.1.3.sql b/packages/server/postgres/extensions/pgvector/sql/vector--0.1.1--0.1.3.sql new file mode 100644 index 00000000000..391835f865c --- /dev/null +++ b/packages/server/postgres/extensions/pgvector/sql/vector--0.1.1--0.1.3.sql @@ -0,0 +1,2 @@ +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "ALTER EXTENSION vector UPDATE TO '0.1.3'" to load this file. \quit diff --git a/packages/server/postgres/extensions/pgvector/sql/vector--0.1.3--0.1.4.sql b/packages/server/postgres/extensions/pgvector/sql/vector--0.1.3--0.1.4.sql new file mode 100644 index 00000000000..56ab0eb501c --- /dev/null +++ b/packages/server/postgres/extensions/pgvector/sql/vector--0.1.3--0.1.4.sql @@ -0,0 +1,2 @@ +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "ALTER EXTENSION vector UPDATE TO '0.1.4'" to load this file. \quit diff --git a/packages/server/postgres/extensions/pgvector/sql/vector--0.1.4--0.1.5.sql b/packages/server/postgres/extensions/pgvector/sql/vector--0.1.4--0.1.5.sql new file mode 100644 index 00000000000..3996b2dcd84 --- /dev/null +++ b/packages/server/postgres/extensions/pgvector/sql/vector--0.1.4--0.1.5.sql @@ -0,0 +1,2 @@ +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "ALTER EXTENSION vector UPDATE TO '0.1.5'" to load this file. \quit diff --git a/packages/server/postgres/extensions/pgvector/sql/vector--0.1.5--0.1.6.sql b/packages/server/postgres/extensions/pgvector/sql/vector--0.1.5--0.1.6.sql new file mode 100644 index 00000000000..fdb605b0b95 --- /dev/null +++ b/packages/server/postgres/extensions/pgvector/sql/vector--0.1.5--0.1.6.sql @@ -0,0 +1,2 @@ +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "ALTER EXTENSION vector UPDATE TO '0.1.6'" to load this file. \quit diff --git a/packages/server/postgres/extensions/pgvector/sql/vector--0.1.6--0.1.7.sql b/packages/server/postgres/extensions/pgvector/sql/vector--0.1.6--0.1.7.sql new file mode 100644 index 00000000000..fcd32f45a90 --- /dev/null +++ b/packages/server/postgres/extensions/pgvector/sql/vector--0.1.6--0.1.7.sql @@ -0,0 +1,8 @@ +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "ALTER EXTENSION vector UPDATE TO '0.1.7'" to load this file. \quit + +CREATE FUNCTION array_to_vector(numeric[], integer, boolean) RETURNS vector + AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE CAST (numeric[] AS vector) + WITH FUNCTION array_to_vector(numeric[], integer, boolean) AS IMPLICIT; diff --git a/packages/server/postgres/extensions/pgvector/sql/vector--0.1.7--0.1.8.sql b/packages/server/postgres/extensions/pgvector/sql/vector--0.1.7--0.1.8.sql new file mode 100644 index 00000000000..5a387a76b6d --- /dev/null +++ b/packages/server/postgres/extensions/pgvector/sql/vector--0.1.7--0.1.8.sql @@ -0,0 +1,8 @@ +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "ALTER EXTENSION vector UPDATE TO '0.1.8'" to load this file. \quit + +CREATE FUNCTION vector_to_float4(vector, integer, boolean) RETURNS real[] + AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE CAST (vector AS real[]) + WITH FUNCTION vector_to_float4(vector, integer, boolean) AS IMPLICIT; diff --git a/packages/server/postgres/extensions/pgvector/sql/vector--0.1.8--0.2.0.sql b/packages/server/postgres/extensions/pgvector/sql/vector--0.1.8--0.2.0.sql new file mode 100644 index 00000000000..1ce0d1efd65 --- /dev/null +++ b/packages/server/postgres/extensions/pgvector/sql/vector--0.1.8--0.2.0.sql @@ -0,0 +1,2 @@ +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "ALTER EXTENSION vector UPDATE TO '0.2.0'" to load this file. \quit diff --git a/packages/server/postgres/extensions/pgvector/sql/vector--0.2.0--0.2.1.sql b/packages/server/postgres/extensions/pgvector/sql/vector--0.2.0--0.2.1.sql new file mode 100644 index 00000000000..47606deb3ad --- /dev/null +++ b/packages/server/postgres/extensions/pgvector/sql/vector--0.2.0--0.2.1.sql @@ -0,0 +1,19 @@ +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "ALTER EXTENSION vector UPDATE TO '0.2.1'" to load this file. \quit + +DROP CAST (integer[] AS vector); +DROP CAST (real[] AS vector); +DROP CAST (double precision[] AS vector); +DROP CAST (numeric[] AS vector); + +CREATE CAST (integer[] AS vector) + WITH FUNCTION array_to_vector(integer[], integer, boolean) AS ASSIGNMENT; + +CREATE CAST (real[] AS vector) + WITH FUNCTION array_to_vector(real[], integer, boolean) AS ASSIGNMENT; + +CREATE CAST (double precision[] AS vector) + WITH FUNCTION array_to_vector(double precision[], integer, boolean) AS ASSIGNMENT; + +CREATE CAST (numeric[] AS vector) + WITH FUNCTION array_to_vector(numeric[], integer, boolean) AS ASSIGNMENT; diff --git a/packages/server/postgres/extensions/pgvector/sql/vector--0.2.1--0.2.2.sql b/packages/server/postgres/extensions/pgvector/sql/vector--0.2.1--0.2.2.sql new file mode 100644 index 00000000000..697c1408d70 --- /dev/null +++ b/packages/server/postgres/extensions/pgvector/sql/vector--0.2.1--0.2.2.sql @@ -0,0 +1,2 @@ +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "ALTER EXTENSION vector UPDATE TO '0.2.2'" to load this file. \quit diff --git a/packages/server/postgres/extensions/pgvector/sql/vector--0.2.2--0.2.3.sql b/packages/server/postgres/extensions/pgvector/sql/vector--0.2.2--0.2.3.sql new file mode 100644 index 00000000000..32b07dc228f --- /dev/null +++ b/packages/server/postgres/extensions/pgvector/sql/vector--0.2.2--0.2.3.sql @@ -0,0 +1,2 @@ +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "ALTER EXTENSION vector UPDATE TO '0.2.3'" to load this file. \quit diff --git a/packages/server/postgres/extensions/pgvector/sql/vector--0.2.3--0.2.4.sql b/packages/server/postgres/extensions/pgvector/sql/vector--0.2.3--0.2.4.sql new file mode 100644 index 00000000000..5d1b34168ba --- /dev/null +++ b/packages/server/postgres/extensions/pgvector/sql/vector--0.2.3--0.2.4.sql @@ -0,0 +1,2 @@ +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "ALTER EXTENSION vector UPDATE TO '0.2.4'" to load this file. \quit diff --git a/packages/server/postgres/extensions/pgvector/sql/vector--0.2.4--0.2.5.sql b/packages/server/postgres/extensions/pgvector/sql/vector--0.2.4--0.2.5.sql new file mode 100644 index 00000000000..b372ed0c8c3 --- /dev/null +++ b/packages/server/postgres/extensions/pgvector/sql/vector--0.2.4--0.2.5.sql @@ -0,0 +1,2 @@ +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "ALTER EXTENSION vector UPDATE TO '0.2.5'" to load this file. \quit diff --git a/packages/server/postgres/extensions/pgvector/sql/vector--0.2.5--0.2.6.sql b/packages/server/postgres/extensions/pgvector/sql/vector--0.2.5--0.2.6.sql new file mode 100644 index 00000000000..e68c1ac0374 --- /dev/null +++ b/packages/server/postgres/extensions/pgvector/sql/vector--0.2.5--0.2.6.sql @@ -0,0 +1,2 @@ +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "ALTER EXTENSION vector UPDATE TO '0.2.6'" to load this file. \quit diff --git a/packages/server/postgres/extensions/pgvector/sql/vector--0.2.6--0.2.7.sql b/packages/server/postgres/extensions/pgvector/sql/vector--0.2.6--0.2.7.sql new file mode 100644 index 00000000000..227c2171c41 --- /dev/null +++ b/packages/server/postgres/extensions/pgvector/sql/vector--0.2.6--0.2.7.sql @@ -0,0 +1,2 @@ +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "ALTER EXTENSION vector UPDATE TO '0.2.7'" to load this file. \quit diff --git a/packages/server/postgres/extensions/pgvector/sql/vector--0.2.7--0.3.0.sql b/packages/server/postgres/extensions/pgvector/sql/vector--0.2.7--0.3.0.sql new file mode 100644 index 00000000000..7e62d39e728 --- /dev/null +++ b/packages/server/postgres/extensions/pgvector/sql/vector--0.2.7--0.3.0.sql @@ -0,0 +1,2 @@ +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "ALTER EXTENSION vector UPDATE TO '0.3.0'" to load this file. \quit diff --git a/packages/server/postgres/extensions/pgvector/sql/vector--0.3.0--0.3.1.sql b/packages/server/postgres/extensions/pgvector/sql/vector--0.3.0--0.3.1.sql new file mode 100644 index 00000000000..f1a8fbce5ae --- /dev/null +++ b/packages/server/postgres/extensions/pgvector/sql/vector--0.3.0--0.3.1.sql @@ -0,0 +1,2 @@ +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "ALTER EXTENSION vector UPDATE TO '0.3.1'" to load this file. \quit diff --git a/packages/server/postgres/extensions/pgvector/sql/vector--0.3.1--0.3.2.sql b/packages/server/postgres/extensions/pgvector/sql/vector--0.3.1--0.3.2.sql new file mode 100644 index 00000000000..c3461a10339 --- /dev/null +++ b/packages/server/postgres/extensions/pgvector/sql/vector--0.3.1--0.3.2.sql @@ -0,0 +1,2 @@ +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "ALTER EXTENSION vector UPDATE TO '0.3.2'" to load this file. \quit diff --git a/packages/server/postgres/extensions/pgvector/sql/vector--0.3.2--0.4.0.sql b/packages/server/postgres/extensions/pgvector/sql/vector--0.3.2--0.4.0.sql new file mode 100644 index 00000000000..3652664777c --- /dev/null +++ b/packages/server/postgres/extensions/pgvector/sql/vector--0.3.2--0.4.0.sql @@ -0,0 +1,23 @@ +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "ALTER EXTENSION vector UPDATE TO '0.4.0'" to load this file. \quit + +-- remove this single line for Postgres < 13 +ALTER TYPE vector SET (STORAGE = extended); + +CREATE FUNCTION vector_accum(double precision[], vector) RETURNS double precision[] + AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION vector_avg(double precision[]) RETURNS vector + AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION vector_combine(double precision[], double precision[]) RETURNS double precision[] + AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE AGGREGATE avg(vector) ( + SFUNC = vector_accum, + STYPE = double precision[], + FINALFUNC = vector_avg, + COMBINEFUNC = vector_combine, + INITCOND = '{0}', + PARALLEL = SAFE +); diff --git a/packages/server/postgres/extensions/pgvector/sql/vector--0.4.0--0.4.1.sql b/packages/server/postgres/extensions/pgvector/sql/vector--0.4.0--0.4.1.sql new file mode 100644 index 00000000000..67ba57ef924 --- /dev/null +++ b/packages/server/postgres/extensions/pgvector/sql/vector--0.4.0--0.4.1.sql @@ -0,0 +1,2 @@ +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "ALTER EXTENSION vector UPDATE TO '0.4.1'" to load this file. \quit diff --git a/packages/server/postgres/extensions/pgvector/sql/vector--0.4.1--0.4.2.sql b/packages/server/postgres/extensions/pgvector/sql/vector--0.4.1--0.4.2.sql new file mode 100644 index 00000000000..24abacce05f --- /dev/null +++ b/packages/server/postgres/extensions/pgvector/sql/vector--0.4.1--0.4.2.sql @@ -0,0 +1,2 @@ +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "ALTER EXTENSION vector UPDATE TO '0.4.2'" to load this file. \quit diff --git a/packages/server/postgres/extensions/pgvector/sql/vector--0.4.2--0.4.3.sql b/packages/server/postgres/extensions/pgvector/sql/vector--0.4.2--0.4.3.sql new file mode 100644 index 00000000000..3db510e557e --- /dev/null +++ b/packages/server/postgres/extensions/pgvector/sql/vector--0.4.2--0.4.3.sql @@ -0,0 +1,2 @@ +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "ALTER EXTENSION vector UPDATE TO '0.4.3'" to load this file. \quit diff --git a/packages/server/postgres/extensions/pgvector/sql/vector--0.4.3--0.4.4.sql b/packages/server/postgres/extensions/pgvector/sql/vector--0.4.3--0.4.4.sql new file mode 100644 index 00000000000..49c4ab4ef77 --- /dev/null +++ b/packages/server/postgres/extensions/pgvector/sql/vector--0.4.3--0.4.4.sql @@ -0,0 +1,2 @@ +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "ALTER EXTENSION vector UPDATE TO '0.4.4'" to load this file. \quit diff --git a/packages/server/postgres/extensions/pgvector/sql/vector--0.4.4--0.5.0.sql b/packages/server/postgres/extensions/pgvector/sql/vector--0.4.4--0.5.0.sql new file mode 100644 index 00000000000..48572bf67a7 --- /dev/null +++ b/packages/server/postgres/extensions/pgvector/sql/vector--0.4.4--0.5.0.sql @@ -0,0 +1,43 @@ +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "ALTER EXTENSION vector UPDATE TO '0.5.0'" to load this file. \quit + +CREATE FUNCTION l1_distance(vector, vector) RETURNS float8 + AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION vector_mul(vector, vector) RETURNS vector + AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE OPERATOR * ( + LEFTARG = vector, RIGHTARG = vector, PROCEDURE = vector_mul, + COMMUTATOR = * +); + +CREATE AGGREGATE sum(vector) ( + SFUNC = vector_add, + STYPE = vector, + COMBINEFUNC = vector_add, + PARALLEL = SAFE +); + +CREATE FUNCTION hnswhandler(internal) RETURNS index_am_handler + AS 'MODULE_PATHNAME' LANGUAGE C; + +CREATE ACCESS METHOD hnsw TYPE INDEX HANDLER hnswhandler; + +COMMENT ON ACCESS METHOD hnsw IS 'hnsw index access method'; + +CREATE OPERATOR CLASS vector_l2_ops + FOR TYPE vector USING hnsw AS + OPERATOR 1 <-> (vector, vector) FOR ORDER BY float_ops, + FUNCTION 1 vector_l2_squared_distance(vector, vector); + +CREATE OPERATOR CLASS vector_ip_ops + FOR TYPE vector USING hnsw AS + OPERATOR 1 <#> (vector, vector) FOR ORDER BY float_ops, + FUNCTION 1 vector_negative_inner_product(vector, vector); + +CREATE OPERATOR CLASS vector_cosine_ops + FOR TYPE vector USING hnsw AS + OPERATOR 1 <=> (vector, vector) FOR ORDER BY float_ops, + FUNCTION 1 vector_negative_inner_product(vector, vector), + FUNCTION 2 vector_norm(vector); diff --git a/packages/server/postgres/extensions/pgvector/sql/vector--0.5.0.sql b/packages/server/postgres/extensions/pgvector/sql/vector--0.5.0.sql new file mode 100644 index 00000000000..137931fed6d --- /dev/null +++ b/packages/server/postgres/extensions/pgvector/sql/vector--0.5.0.sql @@ -0,0 +1,292 @@ +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "CREATE EXTENSION vector" to load this file. \quit + +-- type + +CREATE TYPE vector; + +CREATE FUNCTION vector_in(cstring, oid, integer) RETURNS vector + AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION vector_out(vector) RETURNS cstring + AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION vector_typmod_in(cstring[]) RETURNS integer + AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION vector_recv(internal, oid, integer) RETURNS vector + AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION vector_send(vector) RETURNS bytea + AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE TYPE vector ( + INPUT = vector_in, + OUTPUT = vector_out, + TYPMOD_IN = vector_typmod_in, + RECEIVE = vector_recv, + SEND = vector_send, + STORAGE = extended +); + +-- functions + +CREATE FUNCTION l2_distance(vector, vector) RETURNS float8 + AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION inner_product(vector, vector) RETURNS float8 + AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION cosine_distance(vector, vector) RETURNS float8 + AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION l1_distance(vector, vector) RETURNS float8 + AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION vector_dims(vector) RETURNS integer + AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION vector_norm(vector) RETURNS float8 + AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION vector_add(vector, vector) RETURNS vector + AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION vector_sub(vector, vector) RETURNS vector + AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION vector_mul(vector, vector) RETURNS vector + AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +-- private functions + +CREATE FUNCTION vector_lt(vector, vector) RETURNS bool + AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION vector_le(vector, vector) RETURNS bool + AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION vector_eq(vector, vector) RETURNS bool + AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION vector_ne(vector, vector) RETURNS bool + AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION vector_ge(vector, vector) RETURNS bool + AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION vector_gt(vector, vector) RETURNS bool + AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION vector_cmp(vector, vector) RETURNS int4 + AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION vector_l2_squared_distance(vector, vector) RETURNS float8 + AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION vector_negative_inner_product(vector, vector) RETURNS float8 + AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION vector_spherical_distance(vector, vector) RETURNS float8 + AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION vector_accum(double precision[], vector) RETURNS double precision[] + AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION vector_avg(double precision[]) RETURNS vector + AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION vector_combine(double precision[], double precision[]) RETURNS double precision[] + AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +-- aggregates + +CREATE AGGREGATE avg(vector) ( + SFUNC = vector_accum, + STYPE = double precision[], + FINALFUNC = vector_avg, + COMBINEFUNC = vector_combine, + INITCOND = '{0}', + PARALLEL = SAFE +); + +CREATE AGGREGATE sum(vector) ( + SFUNC = vector_add, + STYPE = vector, + COMBINEFUNC = vector_add, + PARALLEL = SAFE +); + +-- cast functions + +CREATE FUNCTION vector(vector, integer, boolean) RETURNS vector + AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION array_to_vector(integer[], integer, boolean) RETURNS vector + AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION array_to_vector(real[], integer, boolean) RETURNS vector + AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION array_to_vector(double precision[], integer, boolean) RETURNS vector + AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION array_to_vector(numeric[], integer, boolean) RETURNS vector + AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION vector_to_float4(vector, integer, boolean) RETURNS real[] + AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +-- casts + +CREATE CAST (vector AS vector) + WITH FUNCTION vector(vector, integer, boolean) AS IMPLICIT; + +CREATE CAST (vector AS real[]) + WITH FUNCTION vector_to_float4(vector, integer, boolean) AS IMPLICIT; + +CREATE CAST (integer[] AS vector) + WITH FUNCTION array_to_vector(integer[], integer, boolean) AS ASSIGNMENT; + +CREATE CAST (real[] AS vector) + WITH FUNCTION array_to_vector(real[], integer, boolean) AS ASSIGNMENT; + +CREATE CAST (double precision[] AS vector) + WITH FUNCTION array_to_vector(double precision[], integer, boolean) AS ASSIGNMENT; + +CREATE CAST (numeric[] AS vector) + WITH FUNCTION array_to_vector(numeric[], integer, boolean) AS ASSIGNMENT; + +-- operators + +CREATE OPERATOR <-> ( + LEFTARG = vector, RIGHTARG = vector, PROCEDURE = l2_distance, + COMMUTATOR = '<->' +); + +CREATE OPERATOR <#> ( + LEFTARG = vector, RIGHTARG = vector, PROCEDURE = vector_negative_inner_product, + COMMUTATOR = '<#>' +); + +CREATE OPERATOR <=> ( + LEFTARG = vector, RIGHTARG = vector, PROCEDURE = cosine_distance, + COMMUTATOR = '<=>' +); + +CREATE OPERATOR + ( + LEFTARG = vector, RIGHTARG = vector, PROCEDURE = vector_add, + COMMUTATOR = + +); + +CREATE OPERATOR - ( + LEFTARG = vector, RIGHTARG = vector, PROCEDURE = vector_sub, + COMMUTATOR = - +); + +CREATE OPERATOR * ( + LEFTARG = vector, RIGHTARG = vector, PROCEDURE = vector_mul, + COMMUTATOR = * +); + +CREATE OPERATOR < ( + LEFTARG = vector, RIGHTARG = vector, PROCEDURE = vector_lt, + COMMUTATOR = > , NEGATOR = >= , + RESTRICT = scalarltsel, JOIN = scalarltjoinsel +); + +-- should use scalarlesel and scalarlejoinsel, but not supported in Postgres < 11 +CREATE OPERATOR <= ( + LEFTARG = vector, RIGHTARG = vector, PROCEDURE = vector_le, + COMMUTATOR = >= , NEGATOR = > , + RESTRICT = scalarltsel, JOIN = scalarltjoinsel +); + +CREATE OPERATOR = ( + LEFTARG = vector, RIGHTARG = vector, PROCEDURE = vector_eq, + COMMUTATOR = = , NEGATOR = <> , + RESTRICT = eqsel, JOIN = eqjoinsel +); + +CREATE OPERATOR <> ( + LEFTARG = vector, RIGHTARG = vector, PROCEDURE = vector_ne, + COMMUTATOR = <> , NEGATOR = = , + RESTRICT = eqsel, JOIN = eqjoinsel +); + +-- should use scalargesel and scalargejoinsel, but not supported in Postgres < 11 +CREATE OPERATOR >= ( + LEFTARG = vector, RIGHTARG = vector, PROCEDURE = vector_ge, + COMMUTATOR = <= , NEGATOR = < , + RESTRICT = scalargtsel, JOIN = scalargtjoinsel +); + +CREATE OPERATOR > ( + LEFTARG = vector, RIGHTARG = vector, PROCEDURE = vector_gt, + COMMUTATOR = < , NEGATOR = <= , + RESTRICT = scalargtsel, JOIN = scalargtjoinsel +); + +-- access methods + +CREATE FUNCTION ivfflathandler(internal) RETURNS index_am_handler + AS 'MODULE_PATHNAME' LANGUAGE C; + +CREATE ACCESS METHOD ivfflat TYPE INDEX HANDLER ivfflathandler; + +COMMENT ON ACCESS METHOD ivfflat IS 'ivfflat index access method'; + +CREATE FUNCTION hnswhandler(internal) RETURNS index_am_handler + AS 'MODULE_PATHNAME' LANGUAGE C; + +CREATE ACCESS METHOD hnsw TYPE INDEX HANDLER hnswhandler; + +COMMENT ON ACCESS METHOD hnsw IS 'hnsw index access method'; + +-- opclasses + +CREATE OPERATOR CLASS vector_ops + DEFAULT FOR TYPE vector USING btree AS + OPERATOR 1 < , + OPERATOR 2 <= , + OPERATOR 3 = , + OPERATOR 4 >= , + OPERATOR 5 > , + FUNCTION 1 vector_cmp(vector, vector); + +CREATE OPERATOR CLASS vector_l2_ops + DEFAULT FOR TYPE vector USING ivfflat AS + OPERATOR 1 <-> (vector, vector) FOR ORDER BY float_ops, + FUNCTION 1 vector_l2_squared_distance(vector, vector), + FUNCTION 3 l2_distance(vector, vector); + +CREATE OPERATOR CLASS vector_ip_ops + FOR TYPE vector USING ivfflat AS + OPERATOR 1 <#> (vector, vector) FOR ORDER BY float_ops, + FUNCTION 1 vector_negative_inner_product(vector, vector), + FUNCTION 3 vector_spherical_distance(vector, vector), + FUNCTION 4 vector_norm(vector); + +CREATE OPERATOR CLASS vector_cosine_ops + FOR TYPE vector USING ivfflat AS + OPERATOR 1 <=> (vector, vector) FOR ORDER BY float_ops, + FUNCTION 1 vector_negative_inner_product(vector, vector), + FUNCTION 2 vector_norm(vector), + FUNCTION 3 vector_spherical_distance(vector, vector), + FUNCTION 4 vector_norm(vector); + +CREATE OPERATOR CLASS vector_l2_ops + FOR TYPE vector USING hnsw AS + OPERATOR 1 <-> (vector, vector) FOR ORDER BY float_ops, + FUNCTION 1 vector_l2_squared_distance(vector, vector); + +CREATE OPERATOR CLASS vector_ip_ops + FOR TYPE vector USING hnsw AS + OPERATOR 1 <#> (vector, vector) FOR ORDER BY float_ops, + FUNCTION 1 vector_negative_inner_product(vector, vector); + +CREATE OPERATOR CLASS vector_cosine_ops + FOR TYPE vector USING hnsw AS + OPERATOR 1 <=> (vector, vector) FOR ORDER BY float_ops, + FUNCTION 1 vector_negative_inner_product(vector, vector), + FUNCTION 2 vector_norm(vector); diff --git a/packages/server/postgres/extensions/pgvector/sql/vector.sql b/packages/server/postgres/extensions/pgvector/sql/vector.sql new file mode 100644 index 00000000000..137931fed6d --- /dev/null +++ b/packages/server/postgres/extensions/pgvector/sql/vector.sql @@ -0,0 +1,292 @@ +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "CREATE EXTENSION vector" to load this file. \quit + +-- type + +CREATE TYPE vector; + +CREATE FUNCTION vector_in(cstring, oid, integer) RETURNS vector + AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION vector_out(vector) RETURNS cstring + AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION vector_typmod_in(cstring[]) RETURNS integer + AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION vector_recv(internal, oid, integer) RETURNS vector + AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION vector_send(vector) RETURNS bytea + AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE TYPE vector ( + INPUT = vector_in, + OUTPUT = vector_out, + TYPMOD_IN = vector_typmod_in, + RECEIVE = vector_recv, + SEND = vector_send, + STORAGE = extended +); + +-- functions + +CREATE FUNCTION l2_distance(vector, vector) RETURNS float8 + AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION inner_product(vector, vector) RETURNS float8 + AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION cosine_distance(vector, vector) RETURNS float8 + AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION l1_distance(vector, vector) RETURNS float8 + AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION vector_dims(vector) RETURNS integer + AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION vector_norm(vector) RETURNS float8 + AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION vector_add(vector, vector) RETURNS vector + AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION vector_sub(vector, vector) RETURNS vector + AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION vector_mul(vector, vector) RETURNS vector + AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +-- private functions + +CREATE FUNCTION vector_lt(vector, vector) RETURNS bool + AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION vector_le(vector, vector) RETURNS bool + AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION vector_eq(vector, vector) RETURNS bool + AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION vector_ne(vector, vector) RETURNS bool + AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION vector_ge(vector, vector) RETURNS bool + AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION vector_gt(vector, vector) RETURNS bool + AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION vector_cmp(vector, vector) RETURNS int4 + AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION vector_l2_squared_distance(vector, vector) RETURNS float8 + AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION vector_negative_inner_product(vector, vector) RETURNS float8 + AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION vector_spherical_distance(vector, vector) RETURNS float8 + AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION vector_accum(double precision[], vector) RETURNS double precision[] + AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION vector_avg(double precision[]) RETURNS vector + AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION vector_combine(double precision[], double precision[]) RETURNS double precision[] + AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +-- aggregates + +CREATE AGGREGATE avg(vector) ( + SFUNC = vector_accum, + STYPE = double precision[], + FINALFUNC = vector_avg, + COMBINEFUNC = vector_combine, + INITCOND = '{0}', + PARALLEL = SAFE +); + +CREATE AGGREGATE sum(vector) ( + SFUNC = vector_add, + STYPE = vector, + COMBINEFUNC = vector_add, + PARALLEL = SAFE +); + +-- cast functions + +CREATE FUNCTION vector(vector, integer, boolean) RETURNS vector + AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION array_to_vector(integer[], integer, boolean) RETURNS vector + AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION array_to_vector(real[], integer, boolean) RETURNS vector + AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION array_to_vector(double precision[], integer, boolean) RETURNS vector + AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION array_to_vector(numeric[], integer, boolean) RETURNS vector + AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION vector_to_float4(vector, integer, boolean) RETURNS real[] + AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +-- casts + +CREATE CAST (vector AS vector) + WITH FUNCTION vector(vector, integer, boolean) AS IMPLICIT; + +CREATE CAST (vector AS real[]) + WITH FUNCTION vector_to_float4(vector, integer, boolean) AS IMPLICIT; + +CREATE CAST (integer[] AS vector) + WITH FUNCTION array_to_vector(integer[], integer, boolean) AS ASSIGNMENT; + +CREATE CAST (real[] AS vector) + WITH FUNCTION array_to_vector(real[], integer, boolean) AS ASSIGNMENT; + +CREATE CAST (double precision[] AS vector) + WITH FUNCTION array_to_vector(double precision[], integer, boolean) AS ASSIGNMENT; + +CREATE CAST (numeric[] AS vector) + WITH FUNCTION array_to_vector(numeric[], integer, boolean) AS ASSIGNMENT; + +-- operators + +CREATE OPERATOR <-> ( + LEFTARG = vector, RIGHTARG = vector, PROCEDURE = l2_distance, + COMMUTATOR = '<->' +); + +CREATE OPERATOR <#> ( + LEFTARG = vector, RIGHTARG = vector, PROCEDURE = vector_negative_inner_product, + COMMUTATOR = '<#>' +); + +CREATE OPERATOR <=> ( + LEFTARG = vector, RIGHTARG = vector, PROCEDURE = cosine_distance, + COMMUTATOR = '<=>' +); + +CREATE OPERATOR + ( + LEFTARG = vector, RIGHTARG = vector, PROCEDURE = vector_add, + COMMUTATOR = + +); + +CREATE OPERATOR - ( + LEFTARG = vector, RIGHTARG = vector, PROCEDURE = vector_sub, + COMMUTATOR = - +); + +CREATE OPERATOR * ( + LEFTARG = vector, RIGHTARG = vector, PROCEDURE = vector_mul, + COMMUTATOR = * +); + +CREATE OPERATOR < ( + LEFTARG = vector, RIGHTARG = vector, PROCEDURE = vector_lt, + COMMUTATOR = > , NEGATOR = >= , + RESTRICT = scalarltsel, JOIN = scalarltjoinsel +); + +-- should use scalarlesel and scalarlejoinsel, but not supported in Postgres < 11 +CREATE OPERATOR <= ( + LEFTARG = vector, RIGHTARG = vector, PROCEDURE = vector_le, + COMMUTATOR = >= , NEGATOR = > , + RESTRICT = scalarltsel, JOIN = scalarltjoinsel +); + +CREATE OPERATOR = ( + LEFTARG = vector, RIGHTARG = vector, PROCEDURE = vector_eq, + COMMUTATOR = = , NEGATOR = <> , + RESTRICT = eqsel, JOIN = eqjoinsel +); + +CREATE OPERATOR <> ( + LEFTARG = vector, RIGHTARG = vector, PROCEDURE = vector_ne, + COMMUTATOR = <> , NEGATOR = = , + RESTRICT = eqsel, JOIN = eqjoinsel +); + +-- should use scalargesel and scalargejoinsel, but not supported in Postgres < 11 +CREATE OPERATOR >= ( + LEFTARG = vector, RIGHTARG = vector, PROCEDURE = vector_ge, + COMMUTATOR = <= , NEGATOR = < , + RESTRICT = scalargtsel, JOIN = scalargtjoinsel +); + +CREATE OPERATOR > ( + LEFTARG = vector, RIGHTARG = vector, PROCEDURE = vector_gt, + COMMUTATOR = < , NEGATOR = <= , + RESTRICT = scalargtsel, JOIN = scalargtjoinsel +); + +-- access methods + +CREATE FUNCTION ivfflathandler(internal) RETURNS index_am_handler + AS 'MODULE_PATHNAME' LANGUAGE C; + +CREATE ACCESS METHOD ivfflat TYPE INDEX HANDLER ivfflathandler; + +COMMENT ON ACCESS METHOD ivfflat IS 'ivfflat index access method'; + +CREATE FUNCTION hnswhandler(internal) RETURNS index_am_handler + AS 'MODULE_PATHNAME' LANGUAGE C; + +CREATE ACCESS METHOD hnsw TYPE INDEX HANDLER hnswhandler; + +COMMENT ON ACCESS METHOD hnsw IS 'hnsw index access method'; + +-- opclasses + +CREATE OPERATOR CLASS vector_ops + DEFAULT FOR TYPE vector USING btree AS + OPERATOR 1 < , + OPERATOR 2 <= , + OPERATOR 3 = , + OPERATOR 4 >= , + OPERATOR 5 > , + FUNCTION 1 vector_cmp(vector, vector); + +CREATE OPERATOR CLASS vector_l2_ops + DEFAULT FOR TYPE vector USING ivfflat AS + OPERATOR 1 <-> (vector, vector) FOR ORDER BY float_ops, + FUNCTION 1 vector_l2_squared_distance(vector, vector), + FUNCTION 3 l2_distance(vector, vector); + +CREATE OPERATOR CLASS vector_ip_ops + FOR TYPE vector USING ivfflat AS + OPERATOR 1 <#> (vector, vector) FOR ORDER BY float_ops, + FUNCTION 1 vector_negative_inner_product(vector, vector), + FUNCTION 3 vector_spherical_distance(vector, vector), + FUNCTION 4 vector_norm(vector); + +CREATE OPERATOR CLASS vector_cosine_ops + FOR TYPE vector USING ivfflat AS + OPERATOR 1 <=> (vector, vector) FOR ORDER BY float_ops, + FUNCTION 1 vector_negative_inner_product(vector, vector), + FUNCTION 2 vector_norm(vector), + FUNCTION 3 vector_spherical_distance(vector, vector), + FUNCTION 4 vector_norm(vector); + +CREATE OPERATOR CLASS vector_l2_ops + FOR TYPE vector USING hnsw AS + OPERATOR 1 <-> (vector, vector) FOR ORDER BY float_ops, + FUNCTION 1 vector_l2_squared_distance(vector, vector); + +CREATE OPERATOR CLASS vector_ip_ops + FOR TYPE vector USING hnsw AS + OPERATOR 1 <#> (vector, vector) FOR ORDER BY float_ops, + FUNCTION 1 vector_negative_inner_product(vector, vector); + +CREATE OPERATOR CLASS vector_cosine_ops + FOR TYPE vector USING hnsw AS + OPERATOR 1 <=> (vector, vector) FOR ORDER BY float_ops, + FUNCTION 1 vector_negative_inner_product(vector, vector), + FUNCTION 2 vector_norm(vector); diff --git a/packages/server/postgres/extensions/pgvector/src/hnsw.c b/packages/server/postgres/extensions/pgvector/src/hnsw.c new file mode 100644 index 00000000000..1c3c8acfaca --- /dev/null +++ b/packages/server/postgres/extensions/pgvector/src/hnsw.c @@ -0,0 +1,224 @@ +#include "postgres.h" + +#include +#include + +#include "access/amapi.h" +#include "commands/vacuum.h" +#include "hnsw.h" +#include "utils/guc.h" +#include "utils/selfuncs.h" + +#if PG_VERSION_NUM >= 120000 +#include "commands/progress.h" +#endif + +int hnsw_ef_search; +static relopt_kind hnsw_relopt_kind; + +/* + * Initialize index options and variables + */ +void +HnswInit(void) +{ + hnsw_relopt_kind = add_reloption_kind(); + add_int_reloption(hnsw_relopt_kind, "m", "Max number of connections", + HNSW_DEFAULT_M, HNSW_MIN_M, HNSW_MAX_M +#if PG_VERSION_NUM >= 130000 + ,AccessExclusiveLock +#endif + ); + add_int_reloption(hnsw_relopt_kind, "ef_construction", "Size of the dynamic candidate list for construction", + HNSW_DEFAULT_EF_CONSTRUCTION, HNSW_MIN_EF_CONSTRUCTION, HNSW_MAX_EF_CONSTRUCTION +#if PG_VERSION_NUM >= 130000 + ,AccessExclusiveLock +#endif + ); + + DefineCustomIntVariable("hnsw.ef_search", "Sets the size of the dynamic candidate list for search", + "Valid range is 1..1000.", &hnsw_ef_search, + HNSW_DEFAULT_EF_SEARCH, HNSW_MIN_EF_SEARCH, HNSW_MAX_EF_SEARCH, PGC_USERSET, 0, NULL, NULL, NULL); +} + +/* + * Get the name of index build phase + */ +#if PG_VERSION_NUM >= 120000 +static char * +hnswbuildphasename(int64 phasenum) +{ + switch (phasenum) + { + case PROGRESS_CREATEIDX_SUBPHASE_INITIALIZE: + return "initializing"; + case PROGRESS_HNSW_PHASE_LOAD: + return "loading tuples"; + default: + return NULL; + } +} +#endif + +/* + * Estimate the cost of an index scan + */ +static void +hnswcostestimate(PlannerInfo *root, IndexPath *path, double loop_count, + Cost *indexStartupCost, Cost *indexTotalCost, + Selectivity *indexSelectivity, double *indexCorrelation, + double *indexPages) +{ + GenericCosts costs; + int m; + int entryLevel; + Relation index; +#if PG_VERSION_NUM < 120000 + List *qinfos; +#endif + + /* Never use index without order */ + if (path->indexorderbys == NULL) + { + *indexStartupCost = DBL_MAX; + *indexTotalCost = DBL_MAX; + *indexSelectivity = 0; + *indexCorrelation = 0; + *indexPages = 0; + return; + } + + MemSet(&costs, 0, sizeof(costs)); + + index = index_open(path->indexinfo->indexoid, NoLock); + m = HnswGetM(index); + index_close(index, NoLock); + + /* Approximate entry level */ + entryLevel = (int) -log(1.0 / path->indexinfo->tuples) * HnswGetMl(m); + + /* TODO Improve estimate of visited tuples (currently underestimates) */ + /* Account for number of tuples (or entry level), m, and ef_search */ + costs.numIndexTuples = (entryLevel + 2) * m; + +#if PG_VERSION_NUM >= 120000 + genericcostestimate(root, path, loop_count, &costs); +#else + qinfos = deconstruct_indexquals(path); + genericcostestimate(root, path, loop_count, qinfos, &costs); +#endif + + /* Use total cost since most work happens before first tuple is returned */ + *indexStartupCost = costs.indexTotalCost; + *indexTotalCost = costs.indexTotalCost; + *indexSelectivity = costs.indexSelectivity; + *indexCorrelation = costs.indexCorrelation; + *indexPages = costs.numIndexPages; +} + +/* + * Parse and validate the reloptions + */ +static bytea * +hnswoptions(Datum reloptions, bool validate) +{ + static const relopt_parse_elt tab[] = { + {"m", RELOPT_TYPE_INT, offsetof(HnswOptions, m)}, + {"ef_construction", RELOPT_TYPE_INT, offsetof(HnswOptions, efConstruction)}, + }; + +#if PG_VERSION_NUM >= 130000 + return (bytea *) build_reloptions(reloptions, validate, + hnsw_relopt_kind, + sizeof(HnswOptions), + tab, lengthof(tab)); +#else + relopt_value *options; + int numoptions; + HnswOptions *rdopts; + + options = parseRelOptions(reloptions, validate, hnsw_relopt_kind, &numoptions); + rdopts = allocateReloptStruct(sizeof(HnswOptions), options, numoptions); + fillRelOptions((void *) rdopts, sizeof(HnswOptions), options, numoptions, + validate, tab, lengthof(tab)); + + return (bytea *) rdopts; +#endif +} + +/* + * Validate catalog entries for the specified operator class + */ +static bool +hnswvalidate(Oid opclassoid) +{ + return true; +} + +/* + * Define index handler + * + * See https://www.postgresql.org/docs/current/index-api.html + */ +PGDLLEXPORT PG_FUNCTION_INFO_V1(hnswhandler); +Datum +hnswhandler(PG_FUNCTION_ARGS) +{ + IndexAmRoutine *amroutine = makeNode(IndexAmRoutine); + + amroutine->amstrategies = 0; + amroutine->amsupport = 2; +#if PG_VERSION_NUM >= 130000 + amroutine->amoptsprocnum = 0; +#endif + amroutine->amcanorder = false; + amroutine->amcanorderbyop = true; + amroutine->amcanbackward = false; /* can change direction mid-scan */ + amroutine->amcanunique = false; + amroutine->amcanmulticol = false; + amroutine->amoptionalkey = true; + amroutine->amsearcharray = false; + amroutine->amsearchnulls = false; + amroutine->amstorage = false; + amroutine->amclusterable = false; + amroutine->ampredlocks = false; + amroutine->amcanparallel = false; + amroutine->amcaninclude = false; +#if PG_VERSION_NUM >= 130000 + amroutine->amusemaintenanceworkmem = false; /* not used during VACUUM */ + amroutine->amparallelvacuumoptions = VACUUM_OPTION_PARALLEL_BULKDEL; +#endif + amroutine->amkeytype = InvalidOid; + + /* Interface functions */ + amroutine->ambuild = hnswbuild; + amroutine->ambuildempty = hnswbuildempty; + amroutine->aminsert = hnswinsert; + amroutine->ambulkdelete = hnswbulkdelete; + amroutine->amvacuumcleanup = hnswvacuumcleanup; + amroutine->amcanreturn = NULL; /* tuple not included in heapsort */ + amroutine->amcostestimate = hnswcostestimate; + amroutine->amoptions = hnswoptions; + amroutine->amproperty = NULL; /* TODO AMPROP_DISTANCE_ORDERABLE */ +#if PG_VERSION_NUM >= 120000 + amroutine->ambuildphasename = hnswbuildphasename; +#endif + amroutine->amvalidate = hnswvalidate; +#if PG_VERSION_NUM >= 140000 + amroutine->amadjustmembers = NULL; +#endif + amroutine->ambeginscan = hnswbeginscan; + amroutine->amrescan = hnswrescan; + amroutine->amgettuple = hnswgettuple; + amroutine->amgetbitmap = NULL; + amroutine->amendscan = hnswendscan; + amroutine->ammarkpos = NULL; + amroutine->amrestrpos = NULL; + + /* Interface functions to support parallel index scans */ + amroutine->amestimateparallelscan = NULL; + amroutine->aminitparallelscan = NULL; + amroutine->amparallelrescan = NULL; + + PG_RETURN_POINTER(amroutine); +} diff --git a/packages/server/postgres/extensions/pgvector/src/hnsw.h b/packages/server/postgres/extensions/pgvector/src/hnsw.h new file mode 100644 index 00000000000..3e8bdc2c160 --- /dev/null +++ b/packages/server/postgres/extensions/pgvector/src/hnsw.h @@ -0,0 +1,305 @@ +#ifndef HNSW_H +#define HNSW_H + +#include "postgres.h" + +#include "access/generic_xlog.h" +#include "access/reloptions.h" +#include "nodes/execnodes.h" +#include "port.h" /* for random() */ +#include "utils/sampling.h" +#include "vector.h" + +#if PG_VERSION_NUM < 110000 +#error "Requires PostgreSQL 11+" +#endif + +#define HNSW_MAX_DIM 2000 + +/* Support functions */ +#define HNSW_DISTANCE_PROC 1 +#define HNSW_NORM_PROC 2 + +#define HNSW_VERSION 1 +#define HNSW_MAGIC_NUMBER 0xA953A953 +#define HNSW_PAGE_ID 0xFF90 + +/* Preserved page numbers */ +#define HNSW_METAPAGE_BLKNO 0 +#define HNSW_HEAD_BLKNO 1 /* first element page */ + +/* Must correspond to page numbers since page lock is used */ +#define HNSW_UPDATE_LOCK 0 +#define HNSW_SCAN_LOCK 1 + +/* HNSW parameters */ +#define HNSW_DEFAULT_M 16 +#define HNSW_MIN_M 2 +#define HNSW_MAX_M 100 +#define HNSW_DEFAULT_EF_CONSTRUCTION 64 +#define HNSW_MIN_EF_CONSTRUCTION 4 +#define HNSW_MAX_EF_CONSTRUCTION 1000 +#define HNSW_DEFAULT_EF_SEARCH 40 +#define HNSW_MIN_EF_SEARCH 1 +#define HNSW_MAX_EF_SEARCH 1000 + +/* Tuple types */ +#define HNSW_ELEMENT_TUPLE_TYPE 1 +#define HNSW_NEIGHBOR_TUPLE_TYPE 2 + +/* Make graph robust against non-HOT updates */ +#define HNSW_HEAPTIDS 10 + +#define HNSW_UPDATE_ENTRY_GREATER 1 +#define HNSW_UPDATE_ENTRY_ALWAYS 2 + +/* Build phases */ +/* PROGRESS_CREATEIDX_SUBPHASE_INITIALIZE is 1 */ +#define PROGRESS_HNSW_PHASE_LOAD 2 + +#define HNSW_ELEMENT_TUPLE_SIZE(_dim) MAXALIGN(offsetof(HnswElementTupleData, vec) + VECTOR_SIZE(_dim)) +#define HNSW_NEIGHBOR_TUPLE_SIZE(level, m) MAXALIGN(offsetof(HnswNeighborTupleData, indextids) + ((level) + 2) * (m) * sizeof(ItemPointerData)) + +#define HnswPageGetOpaque(page) ((HnswPageOpaque) PageGetSpecialPointer(page)) +#define HnswPageGetMeta(page) ((HnswMetaPageData *) PageGetContents(page)) + +#if PG_VERSION_NUM >= 150000 +#define RandomDouble() pg_prng_double(&pg_global_prng_state) +#else +#define RandomDouble() (((double) random()) / MAX_RANDOM_VALUE) +#endif + +#if PG_VERSION_NUM < 130000 +#define list_delete_last(list) list_truncate(list, list_length(list) - 1) +#define list_sort(list, cmp) list_qsort(list, cmp) +#endif + +#define HnswIsElementTuple(tup) ((tup)->type == HNSW_ELEMENT_TUPLE_TYPE) +#define HnswIsNeighborTuple(tup) ((tup)->type == HNSW_NEIGHBOR_TUPLE_TYPE) + +/* 2 * M connections for ground layer */ +#define HnswGetLayerM(m, layer) (layer == 0 ? (m) * 2 : (m)) + +/* Optimal ML from paper */ +#define HnswGetMl(m) (1 / log(m)) + +/* Ensure fits on page and in uint8 */ +#define HnswGetMaxLevel(m) Min(((BLCKSZ - MAXALIGN(SizeOfPageHeaderData) - MAXALIGN(sizeof(HnswPageOpaqueData)) - offsetof(HnswNeighborTupleData, indextids) - sizeof(ItemIdData)) / (sizeof(ItemPointerData)) / m) - 2, 255) + +/* Variables */ +extern int hnsw_ef_search; + +typedef struct HnswNeighborArray HnswNeighborArray; + +typedef struct HnswElementData +{ + List *heaptids; + uint8 level; + uint8 deleted; + HnswNeighborArray *neighbors; + BlockNumber blkno; + OffsetNumber offno; + OffsetNumber neighborOffno; + BlockNumber neighborPage; + Vector *vec; +} HnswElementData; + +typedef HnswElementData * HnswElement; + +typedef struct HnswCandidate +{ + HnswElement element; + float distance; +} HnswCandidate; + +typedef struct HnswNeighborArray +{ + int length; + HnswCandidate *items; +} HnswNeighborArray; + +typedef struct HnswPairingHeapNode +{ + pairingheap_node ph_node; + HnswCandidate *inner; +} HnswPairingHeapNode; + +/* HNSW index options */ +typedef struct HnswOptions +{ + int32 vl_len_; /* varlena header (do not touch directly!) */ + int m; /* number of connections */ + int efConstruction; /* size of dynamic candidate list */ +} HnswOptions; + +typedef struct HnswBuildState +{ + /* Info */ + Relation heap; + Relation index; + IndexInfo *indexInfo; + ForkNumber forkNum; + + /* Settings */ + int dimensions; + int m; + int efConstruction; + + /* Statistics */ + double indtuples; + double reltuples; + + /* Support functions */ + FmgrInfo *procinfo; + FmgrInfo *normprocinfo; + Oid collation; + + /* Variables */ + List *elements; + HnswElement entryPoint; + double ml; + int maxLevel; + double maxInMemoryElements; + bool flushed; + Vector *normvec; + + /* Memory */ + MemoryContext tmpCtx; +} HnswBuildState; + +typedef struct HnswMetaPageData +{ + uint32 magicNumber; + uint32 version; + uint32 dimensions; + uint16 m; + uint16 efConstruction; + BlockNumber entryBlkno; + OffsetNumber entryOffno; + int16 entryLevel; + BlockNumber insertPage; +} HnswMetaPageData; + +typedef HnswMetaPageData * HnswMetaPage; + +typedef struct HnswPageOpaqueData +{ + BlockNumber nextblkno; + uint16 unused; + uint16 page_id; /* for identification of HNSW indexes */ +} HnswPageOpaqueData; + +typedef HnswPageOpaqueData * HnswPageOpaque; + +typedef struct HnswElementTupleData +{ + uint8 type; + uint8 level; + uint8 deleted; + uint8 unused; + ItemPointerData heaptids[HNSW_HEAPTIDS]; + ItemPointerData neighbortid; + uint16 unused2; + Vector vec; +} HnswElementTupleData; + +typedef HnswElementTupleData * HnswElementTuple; + +typedef struct HnswNeighborTupleData +{ + uint8 type; + uint8 unused; + uint16 count; + ItemPointerData indextids[FLEXIBLE_ARRAY_MEMBER]; +} HnswNeighborTupleData; + +typedef HnswNeighborTupleData * HnswNeighborTuple; + +typedef struct HnswScanOpaqueData +{ + bool first; + Buffer buf; + List *w; + MemoryContext tmpCtx; + + /* Support functions */ + FmgrInfo *procinfo; + FmgrInfo *normprocinfo; + Oid collation; +} HnswScanOpaqueData; + +typedef HnswScanOpaqueData * HnswScanOpaque; + +typedef struct HnswVacuumState +{ + /* Info */ + Relation index; + IndexBulkDeleteResult *stats; + IndexBulkDeleteCallback callback; + void *callback_state; + + /* Settings */ + int m; + int efConstruction; + + /* Support functions */ + FmgrInfo *procinfo; + Oid collation; + + /* Variables */ + HTAB *deleted; + BufferAccessStrategy bas; + HnswNeighborTuple ntup; + HnswElementData highestPoint; + + /* Memory */ + MemoryContext tmpCtx; +} HnswVacuumState; + +/* Methods */ +int HnswGetM(Relation index); +int HnswGetEfConstruction(Relation index); +FmgrInfo *HnswOptionalProcInfo(Relation rel, uint16 procnum); +bool HnswNormValue(FmgrInfo *procinfo, Oid collation, Datum *value, Vector * result); +void HnswCommitBuffer(Buffer buf, GenericXLogState *state); +Buffer HnswNewBuffer(Relation index, ForkNumber forkNum); +void HnswInitPage(Buffer buf, Page page); +void HnswInitRegisterPage(Relation index, Buffer *buf, Page *page, GenericXLogState **state); +void HnswInit(void); +List *HnswSearchLayer(Datum q, List *ep, int ef, int lc, Relation index, FmgrInfo *procinfo, Oid collation, bool inserting, HnswElement skipElement); +HnswElement HnswGetEntryPoint(Relation index); +HnswElement HnswInitElement(ItemPointer tid, int m, double ml, int maxLevel); +void HnswFreeElement(HnswElement element); +HnswElement HnswInitElementFromBlock(BlockNumber blkno, OffsetNumber offno); +void HnswInsertElement(HnswElement element, HnswElement entryPoint, Relation index, FmgrInfo *procinfo, Oid collation, int m, int efConstruction, bool existing); +HnswElement HnswFindDuplicate(HnswElement e); +HnswCandidate *HnswEntryCandidate(HnswElement em, Datum q, Relation rel, FmgrInfo *procinfo, Oid collation, bool loadVec); +void HnswUpdateMetaPage(Relation index, int updateEntry, HnswElement entryPoint, BlockNumber insertPage, ForkNumber forkNum); +void HnswSetNeighborTuple(HnswNeighborTuple ntup, HnswElement e, int m); +void HnswAddHeapTid(HnswElement element, ItemPointer heaptid); +void HnswInitNeighbors(HnswElement element, int m); +bool HnswInsertTuple(Relation index, Datum *values, bool *isnull, ItemPointer heap_tid, Relation heapRel); +void HnswUpdateNeighborPages(Relation index, FmgrInfo *procinfo, Oid collation, HnswElement e, int m, bool checkExisting); +void HnswLoadElementFromTuple(HnswElement element, HnswElementTuple etup, bool loadHeaptids, bool loadVec); +void HnswLoadElement(HnswElement element, float *distance, Datum *q, Relation index, FmgrInfo *procinfo, Oid collation, bool loadVec); +void HnswSetElementTuple(HnswElementTuple etup, HnswElement element); +void HnswUpdateConnection(HnswElement element, HnswCandidate * hc, int m, int lc, int *updateIdx, Relation index, FmgrInfo *procinfo, Oid collation); +void HnswLoadNeighbors(HnswElement element, Relation index); + +/* Index access methods */ +IndexBuildResult *hnswbuild(Relation heap, Relation index, IndexInfo *indexInfo); +void hnswbuildempty(Relation index); +bool hnswinsert(Relation index, Datum *values, bool *isnull, ItemPointer heap_tid, Relation heap, IndexUniqueCheck checkUnique +#if PG_VERSION_NUM >= 140000 + ,bool indexUnchanged +#endif + ,IndexInfo *indexInfo +); +IndexBulkDeleteResult *hnswbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats, IndexBulkDeleteCallback callback, void *callback_state); +IndexBulkDeleteResult *hnswvacuumcleanup(IndexVacuumInfo *info, IndexBulkDeleteResult *stats); +IndexScanDesc hnswbeginscan(Relation index, int nkeys, int norderbys); +void hnswrescan(IndexScanDesc scan, ScanKey keys, int nkeys, ScanKey orderbys, int norderbys); +bool hnswgettuple(IndexScanDesc scan, ScanDirection dir); +void hnswendscan(IndexScanDesc scan); + +#endif diff --git a/packages/server/postgres/extensions/pgvector/src/hnsw.o b/packages/server/postgres/extensions/pgvector/src/hnsw.o new file mode 100644 index 0000000000000000000000000000000000000000..b7273cc2bb1058737ce6ee77be9cc8bebb339bd6 GIT binary patch literal 4224 zcmb7He{5Sv9lv+av0q9_S`k6ohUArI)P*EVw=#wlYtHI)>GaB~G#V4adUouW4-f5+jumNzGU^f& zKKJfk?7VCeoO1WM&-d56``-EP^85RL`_FDdB!a@44%9V@Mny&0hZ<)53(634K>0w} zgar>l#%ho$QFPrg798H^e0t)U=@$2TF`4&RJU665$O$G2kP_GMR@Y|=g*Jh%$Hu3} zTLNz!ftHBJ6WpC}n@-nL1t)LXb3&%;+sEgP>rX=8=S@s-e~J(Fmia@I8gygeSj_&&WxGY{KmrOZ&(SFXf)4Hz3JXeO3B3@kxRS_?W zcx+u!whi|o2zpU@UcZbED#{4z9@GW+T>>&EX4m^ALLLMvUC==YN=~D)@>NmU_}Ttr z{QoVRvUKzAQ?%M#lB>;kNd^9|e1(v5f|9EkWBDK<6@0e*I8dggsTe6u{d9L}Dkznv zm~DbaciJhXsTJ6DoNL)_Zj$O|H)(9jpOZ4gvxKli(Stn`OTQvW-2je$L6QBg}% z$|bU<^pV8@g*3v5uO!I#2KvZV&%YCKjv$6INUJ+#@|H|l4v0Rfa373euwzR$pa!@4 z%5rGSuN7a}k}1obt^R3@HBB1iBa$Vh7sp8Hg|X(+J6G6TH{Yc_L(f#{w32|cqrOf4U#vNXrlj~EY%t^ zsog@(Iwh&H>maE-Chhv<$n!5;yd{TgVM%#WflRt{?xi|xSpJp6$ZH2%Yo6Ep@MrRW z1MBksnkRSd_C}l19=l)dP4qJx5>57Zt{eRcdB)}HzC^!57W$P=vR65G-~f?k`s-gK z&A;GVb$MUyL%Fv`#<_3(XVFZZlIA+_qkBL$M5U=Rh&S!k#bBNY{Z!52U5Q1 z3dkArtif!Yc|%Pv+R2QWQd3DgZKjisp;~6aQD?Jx76Qkt@R_#@&y65Z!ARy)^W==- z6kPwp{}xNQJ)N}7w3<)abB1ab)T1LKM@L6TM~G>gjtSjacBos~WE!YC#hhiZdxM`- z=!n9&IPzQx|0^*N4LBg}jK3iw_Hq9m z@q5L6Irs(oF`*Z8+2KC~J%rVjj>;`9Xfsr2I03pK)5J$BZ;22?-3x=$*@c|>R z0T0412gI2_jJ@ij1{?w(10Db#0}kVF5U2vna6U|kb%A4i1sDb|af~kjzXk4ajOT!d zz%7pPG!Qq9mf#q928gxLra4AfUU9c*lN=*4Ag&@U$}#dN@Y{%YjAMKV_)YLI$M_)d z2)N2I-V1yHyoY0a4-mPxc5{sX8S8+x8h#ImyUV)4G5!`1zcr2R7hvQva6g+r@EfqZ zz%jlG#9C=993v$l&IE0lV`Kq{r-H^_7Qjdv_;tiN4aCz+(>O*ZfdTLs$4C^2A!=hl zW{(`RCDaBvMpPh{MC;=i=>;p<8AF9|FOObCn$R0RTD zdxc(*%2J4~NULa2g4!Sr-ta7^p*ruxhyNRB(I>oOYc#I-lm_@ z5UtaX(7sPUKzp6uK)X(_LHj0MLwkk3f%X!;jP@1!D%uOQOat-_={@Ov_UYhikkXBg zQZPiX1m8gWYVbAW)GZd0(S?b5<>%;kLFT}eY&@vm`4NGPaqqlH2)%bcObG6s2cIyG4HFje zg5rDKf_Do}1&=e1>dpWALhsH0F~(u*jc=6kw)h7bM{>P)<--_nH^$%OuJy@9imUd<0Nib?wif=mx1mE(esAoNi2N?mp;*RbNZ}l&t}~q^LQLs2Bhq} zKTf8K#Z1aFl6Emi^qI3pKKuFhBbh}Wgy_k1S~qQ{^(6wDHWu`3&alONj5!Pz?+{M& zMyqew=@!vjuMpkJ&I#wD^=#U(49DO+l`S|%!7(#<(GY#ku#LQ#+AidC!F$9t;iE}e z*@B4RW+peEEEsqo3E7 + +#include "catalog/index.h" +#include "hnsw.h" +#include "miscadmin.h" +#include "lib/pairingheap.h" +#include "nodes/pg_list.h" +#include "storage/bufmgr.h" +#include "utils/memutils.h" + +#if PG_VERSION_NUM >= 140000 +#include "utils/backend_progress.h" +#elif PG_VERSION_NUM >= 120000 +#include "pgstat.h" +#endif + +#if PG_VERSION_NUM >= 120000 +#include "access/tableam.h" +#include "commands/progress.h" +#else +#define PROGRESS_CREATEIDX_TUPLES_DONE 0 +#endif + +#if PG_VERSION_NUM >= 130000 +#define CALLBACK_ITEM_POINTER ItemPointer tid +#else +#define CALLBACK_ITEM_POINTER HeapTuple hup +#endif + +#if PG_VERSION_NUM >= 120000 +#define UpdateProgress(index, val) pgstat_progress_update_param(index, val) +#else +#define UpdateProgress(index, val) ((void)val) +#endif + +/* + * Create the metapage + */ +static void +CreateMetaPage(HnswBuildState * buildstate) +{ + Relation index = buildstate->index; + ForkNumber forkNum = buildstate->forkNum; + Buffer buf; + Page page; + GenericXLogState *state; + HnswMetaPage metap; + + buf = HnswNewBuffer(index, forkNum); + HnswInitRegisterPage(index, &buf, &page, &state); + + /* Set metapage data */ + metap = HnswPageGetMeta(page); + metap->magicNumber = HNSW_MAGIC_NUMBER; + metap->version = HNSW_VERSION; + metap->dimensions = buildstate->dimensions; + metap->m = buildstate->m; + metap->efConstruction = buildstate->efConstruction; + metap->entryBlkno = InvalidBlockNumber; + metap->entryOffno = InvalidOffsetNumber; + metap->entryLevel = -1; + metap->insertPage = InvalidBlockNumber; + ((PageHeader) page)->pd_lower = + ((char *) metap + sizeof(HnswMetaPageData)) - (char *) page; + + HnswCommitBuffer(buf, state); +} + +/* + * Add a new page + */ +static void +HnswBuildAppendPage(Relation index, Buffer *buf, Page *page, GenericXLogState **state, ForkNumber forkNum) +{ + /* Add a new page */ + Buffer newbuf = HnswNewBuffer(index, forkNum); + + /* Update previous page */ + HnswPageGetOpaque(*page)->nextblkno = BufferGetBlockNumber(newbuf); + + /* Commit */ + MarkBufferDirty(*buf); + GenericXLogFinish(*state); + UnlockReleaseBuffer(*buf); + + /* Can take a while, so ensure we can interrupt */ + /* Needs to be called when no buffer locks are held */ + LockBuffer(newbuf, BUFFER_LOCK_UNLOCK); + CHECK_FOR_INTERRUPTS(); + LockBuffer(newbuf, BUFFER_LOCK_EXCLUSIVE); + + /* Prepare new page */ + *buf = newbuf; + *state = GenericXLogStart(index); + *page = GenericXLogRegisterBuffer(*state, *buf, GENERIC_XLOG_FULL_IMAGE); + HnswInitPage(*buf, *page); +} + +/* + * Create element pages + */ +static void +CreateElementPages(HnswBuildState * buildstate) +{ + Relation index = buildstate->index; + ForkNumber forkNum = buildstate->forkNum; + int dimensions = buildstate->dimensions; + Size etupSize; + Size maxSize; + HnswElementTuple etup; + HnswNeighborTuple ntup; + BlockNumber insertPage; + Buffer buf; + Page page; + GenericXLogState *state; + ListCell *lc; + + /* Calculate sizes */ + maxSize = BLCKSZ - MAXALIGN(SizeOfPageHeaderData) - MAXALIGN(sizeof(HnswPageOpaqueData)); + etupSize = HNSW_ELEMENT_TUPLE_SIZE(dimensions); + + /* Allocate once */ + etup = palloc0(etupSize); + ntup = palloc0(maxSize); + + /* Prepare first page */ + buf = HnswNewBuffer(index, forkNum); + state = GenericXLogStart(index); + page = GenericXLogRegisterBuffer(state, buf, GENERIC_XLOG_FULL_IMAGE); + HnswInitPage(buf, page); + + foreach(lc, buildstate->elements) + { + HnswElement element = lfirst(lc); + Size ntupSize; + Size combinedSize; + + HnswSetElementTuple(etup, element); + + /* Calculate sizes */ + ntupSize = HNSW_NEIGHBOR_TUPLE_SIZE(element->level, buildstate->m); + combinedSize = etupSize + ntupSize + sizeof(ItemIdData); + + /* Keep element and neighbors on the same page if possible */ + if (PageGetFreeSpace(page) < etupSize || (combinedSize <= maxSize && PageGetFreeSpace(page) < combinedSize)) + HnswBuildAppendPage(index, &buf, &page, &state, forkNum); + + /* Calculate offsets */ + element->blkno = BufferGetBlockNumber(buf); + element->offno = OffsetNumberNext(PageGetMaxOffsetNumber(page)); + if (combinedSize <= maxSize) + { + element->neighborPage = element->blkno; + element->neighborOffno = OffsetNumberNext(element->offno); + } + else + { + element->neighborPage = element->blkno + 1; + element->neighborOffno = FirstOffsetNumber; + } + + ItemPointerSet(&etup->neighbortid, element->neighborPage, element->neighborOffno); + + /* Add element */ + if (PageAddItem(page, (Item) etup, etupSize, InvalidOffsetNumber, false, false) != element->offno) + elog(ERROR, "failed to add index item to \"%s\"", RelationGetRelationName(index)); + + /* Add new page if needed */ + if (PageGetFreeSpace(page) < ntupSize) + HnswBuildAppendPage(index, &buf, &page, &state, forkNum); + + /* Add placeholder for neighbors */ + if (PageAddItem(page, (Item) ntup, ntupSize, InvalidOffsetNumber, false, false) != element->neighborOffno) + elog(ERROR, "failed to add index item to \"%s\"", RelationGetRelationName(index)); + } + + insertPage = BufferGetBlockNumber(buf); + + /* Commit */ + MarkBufferDirty(buf); + GenericXLogFinish(state); + UnlockReleaseBuffer(buf); + + HnswUpdateMetaPage(index, HNSW_UPDATE_ENTRY_ALWAYS, buildstate->entryPoint, insertPage, forkNum); + + pfree(etup); + pfree(ntup); +} + +/* + * Create neighbor pages + */ +static void +CreateNeighborPages(HnswBuildState * buildstate) +{ + Relation index = buildstate->index; + ForkNumber forkNum = buildstate->forkNum; + int m = buildstate->m; + ListCell *lc; + HnswNeighborTuple ntup; + + /* Allocate once */ + ntup = palloc0(BLCKSZ); + + foreach(lc, buildstate->elements) + { + HnswElement e = lfirst(lc); + Buffer buf; + Page page; + GenericXLogState *state; + Size ntupSize = HNSW_NEIGHBOR_TUPLE_SIZE(e->level, m); + + /* Can take a while, so ensure we can interrupt */ + /* Needs to be called when no buffer locks are held */ + CHECK_FOR_INTERRUPTS(); + + buf = ReadBufferExtended(index, forkNum, e->neighborPage, RBM_NORMAL, NULL); + LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE); + state = GenericXLogStart(index); + page = GenericXLogRegisterBuffer(state, buf, 0); + + HnswSetNeighborTuple(ntup, e, m); + + if (!PageIndexTupleOverwrite(page, e->neighborOffno, (Item) ntup, ntupSize)) + elog(ERROR, "failed to add index item to \"%s\"", RelationGetRelationName(index)); + + /* Commit */ + MarkBufferDirty(buf); + GenericXLogFinish(state); + UnlockReleaseBuffer(buf); + } + + pfree(ntup); +} + +/* + * Free elements + */ +static void +FreeElements(HnswBuildState * buildstate) +{ + ListCell *lc; + + foreach(lc, buildstate->elements) + HnswFreeElement(lfirst(lc)); + + list_free(buildstate->elements); +} + +/* + * Flush pages + */ +static void +FlushPages(HnswBuildState * buildstate) +{ + CreateMetaPage(buildstate); + CreateElementPages(buildstate); + CreateNeighborPages(buildstate); + + buildstate->flushed = true; + FreeElements(buildstate); +} + +/* + * Insert tuple + */ +static bool +InsertTuple(Relation index, Datum *values, HnswElement element, HnswBuildState * buildstate, HnswElement * dup) +{ + FmgrInfo *procinfo = buildstate->procinfo; + Oid collation = buildstate->collation; + HnswElement entryPoint = buildstate->entryPoint; + int efConstruction = buildstate->efConstruction; + int m = buildstate->m; + + /* Detoast once for all calls */ + Datum value = PointerGetDatum(PG_DETOAST_DATUM(values[0])); + + /* Normalize if needed */ + if (buildstate->normprocinfo != NULL) + { + if (!HnswNormValue(buildstate->normprocinfo, collation, &value, buildstate->normvec)) + return false; + } + + /* Copy value to element so accessible outside of memory context */ + memcpy(element->vec, DatumGetVector(value), VECTOR_SIZE(buildstate->dimensions)); + + /* Insert element in graph */ + HnswInsertElement(element, entryPoint, NULL, procinfo, collation, m, efConstruction, false); + + /* Look for duplicate */ + *dup = HnswFindDuplicate(element); + + /* Update neighbors if needed */ + if (*dup == NULL) + { + for (int lc = element->level; lc >= 0; lc--) + { + int lm = HnswGetLayerM(m, lc); + HnswNeighborArray *neighbors = &element->neighbors[lc]; + + for (int i = 0; i < neighbors->length; i++) + HnswUpdateConnection(element, &neighbors->items[i], lm, lc, NULL, NULL, procinfo, collation); + } + } + + /* Update entry point if needed */ + if (*dup == NULL && (entryPoint == NULL || element->level > entryPoint->level)) + buildstate->entryPoint = element; + + UpdateProgress(PROGRESS_CREATEIDX_TUPLES_DONE, ++buildstate->indtuples); + + return *dup == NULL; +} + +/* + * Callback for table_index_build_scan + */ +static void +BuildCallback(Relation index, CALLBACK_ITEM_POINTER, Datum *values, + bool *isnull, bool tupleIsAlive, void *state) +{ + HnswBuildState *buildstate = (HnswBuildState *) state; + MemoryContext oldCtx; + HnswElement element; + HnswElement dup = NULL; + bool inserted; + +#if PG_VERSION_NUM < 130000 + ItemPointer tid = &hup->t_self; +#endif + + /* Skip nulls */ + if (isnull[0]) + return; + + if (buildstate->indtuples >= buildstate->maxInMemoryElements) + { + if (!buildstate->flushed) + { + ereport(NOTICE, + (errmsg("hnsw graph no longer fits into maintenance_work_mem after " INT64_FORMAT " tuples", (int64) buildstate->indtuples), + errdetail("Building will take significantly more time."), + errhint("Increase maintenance_work_mem to speed up builds."))); + + FlushPages(buildstate); + } + + oldCtx = MemoryContextSwitchTo(buildstate->tmpCtx); + + if (HnswInsertTuple(buildstate->index, values, isnull, tid, buildstate->heap)) + UpdateProgress(PROGRESS_CREATEIDX_TUPLES_DONE, ++buildstate->indtuples); + + /* Reset memory context */ + MemoryContextSwitchTo(oldCtx); + MemoryContextReset(buildstate->tmpCtx); + + return; + } + + /* Allocate necessary memory outside of memory context */ + element = HnswInitElement(tid, buildstate->m, buildstate->ml, buildstate->maxLevel); + element->vec = palloc(VECTOR_SIZE(buildstate->dimensions)); + + /* Use memory context since detoast can allocate */ + oldCtx = MemoryContextSwitchTo(buildstate->tmpCtx); + + /* Insert tuple */ + inserted = InsertTuple(index, values, element, buildstate, &dup); + + /* Reset memory context */ + MemoryContextSwitchTo(oldCtx); + MemoryContextReset(buildstate->tmpCtx); + + /* Add outside memory context */ + if (dup != NULL) + HnswAddHeapTid(dup, tid); + + /* Add to buildstate or free */ + if (inserted) + buildstate->elements = lappend(buildstate->elements, element); + else + HnswFreeElement(element); +} + +/* + * Get the max number of elements that fit into maintenance_work_mem + */ +static double +HnswGetMaxInMemoryElements(int m, double ml, int dimensions) +{ + Size elementSize = sizeof(HnswElementData); + double avgLevel = -log(0.5) * ml; + + elementSize += sizeof(HnswNeighborArray) * (avgLevel + 1); + elementSize += sizeof(HnswCandidate) * (m * (avgLevel + 2)); + elementSize += sizeof(ItemPointerData); + elementSize += VECTOR_SIZE(dimensions); + return (maintenance_work_mem * 1024L) / elementSize; +} + +/* + * Initialize the build state + */ +static void +InitBuildState(HnswBuildState * buildstate, Relation heap, Relation index, IndexInfo *indexInfo, ForkNumber forkNum) +{ + buildstate->heap = heap; + buildstate->index = index; + buildstate->indexInfo = indexInfo; + buildstate->forkNum = forkNum; + + buildstate->m = HnswGetM(index); + buildstate->efConstruction = HnswGetEfConstruction(index); + buildstate->dimensions = TupleDescAttr(index->rd_att, 0)->atttypmod; + + /* Require column to have dimensions to be indexed */ + if (buildstate->dimensions < 0) + elog(ERROR, "column does not have dimensions"); + + if (buildstate->dimensions > HNSW_MAX_DIM) + elog(ERROR, "column cannot have more than %d dimensions for hnsw index", HNSW_MAX_DIM); + + if (buildstate->efConstruction < 2 * buildstate->m) + elog(ERROR, "ef_construction must be greater than or equal to 2 * m"); + + buildstate->reltuples = 0; + buildstate->indtuples = 0; + + /* Get support functions */ + buildstate->procinfo = index_getprocinfo(index, 1, HNSW_DISTANCE_PROC); + buildstate->normprocinfo = HnswOptionalProcInfo(index, HNSW_NORM_PROC); + buildstate->collation = index->rd_indcollation[0]; + + buildstate->elements = NIL; + buildstate->entryPoint = NULL; + buildstate->ml = HnswGetMl(buildstate->m); + buildstate->maxLevel = HnswGetMaxLevel(buildstate->m); + buildstate->maxInMemoryElements = HnswGetMaxInMemoryElements(buildstate->m, buildstate->ml, buildstate->dimensions); + buildstate->flushed = false; + + /* Reuse for each tuple */ + buildstate->normvec = InitVector(buildstate->dimensions); + + buildstate->tmpCtx = AllocSetContextCreate(CurrentMemoryContext, + "Hnsw build temporary context", + ALLOCSET_DEFAULT_SIZES); +} + +/* + * Free resources + */ +static void +FreeBuildState(HnswBuildState * buildstate) +{ + pfree(buildstate->normvec); + MemoryContextDelete(buildstate->tmpCtx); +} + +/* + * Build graph + */ +static void +BuildGraph(HnswBuildState * buildstate, ForkNumber forkNum) +{ + UpdateProgress(PROGRESS_CREATEIDX_SUBPHASE, PROGRESS_HNSW_PHASE_LOAD); + +#if PG_VERSION_NUM >= 120000 + buildstate->reltuples = table_index_build_scan(buildstate->heap, buildstate->index, buildstate->indexInfo, + true, true, BuildCallback, (void *) buildstate, NULL); +#else + buildstate->reltuples = IndexBuildHeapScan(buildstate->heap, buildstate->index, buildstate->indexInfo, + true, BuildCallback, (void *) buildstate, NULL); +#endif +} + +/* + * Build the index + */ +static void +BuildIndex(Relation heap, Relation index, IndexInfo *indexInfo, + HnswBuildState * buildstate, ForkNumber forkNum) +{ + InitBuildState(buildstate, heap, index, indexInfo, forkNum); + + if (buildstate->heap != NULL) + BuildGraph(buildstate, forkNum); + + if (!buildstate->flushed) + FlushPages(buildstate); + + FreeBuildState(buildstate); +} + +/* + * Build the index for a logged table + */ +IndexBuildResult * +hnswbuild(Relation heap, Relation index, IndexInfo *indexInfo) +{ + IndexBuildResult *result; + HnswBuildState buildstate; + + BuildIndex(heap, index, indexInfo, &buildstate, MAIN_FORKNUM); + + result = (IndexBuildResult *) palloc(sizeof(IndexBuildResult)); + result->heap_tuples = buildstate.reltuples; + result->index_tuples = buildstate.indtuples; + + return result; +} + +/* + * Build the index for an unlogged table + */ +void +hnswbuildempty(Relation index) +{ + IndexInfo *indexInfo = BuildIndexInfo(index); + HnswBuildState buildstate; + + BuildIndex(NULL, index, indexInfo, &buildstate, INIT_FORKNUM); +} diff --git a/packages/server/postgres/extensions/pgvector/src/hnswbuild.o b/packages/server/postgres/extensions/pgvector/src/hnswbuild.o new file mode 100644 index 0000000000000000000000000000000000000000..35d3a7ffb30d1c15ef6eaf9af6f10d62ec8e0e04 GIT binary patch literal 9192 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 + +#include "hnsw.h" +#include "storage/bufmgr.h" +#include "storage/lmgr.h" +#include "utils/memutils.h" + +/* + * Get the insert page + */ +static BlockNumber +GetInsertPage(Relation index) +{ + Buffer buf; + Page page; + HnswMetaPage metap; + BlockNumber insertPage; + + buf = ReadBuffer(index, HNSW_METAPAGE_BLKNO); + LockBuffer(buf, BUFFER_LOCK_SHARE); + page = BufferGetPage(buf); + metap = HnswPageGetMeta(page); + + insertPage = metap->insertPage; + + UnlockReleaseBuffer(buf); + + return insertPage; +} + +/* + * Check for a free offset + */ +static bool +HnswFreeOffset(Relation index, Buffer buf, Page page, HnswElement element, Size ntupSize, Buffer *nbuf, Page *npage, OffsetNumber *freeOffno, OffsetNumber *freeNeighborOffno, BlockNumber *newInsertPage) +{ + OffsetNumber offno; + OffsetNumber maxoffno = PageGetMaxOffsetNumber(page); + + for (offno = FirstOffsetNumber; offno <= maxoffno; offno = OffsetNumberNext(offno)) + { + HnswElementTuple etup = (HnswElementTuple) PageGetItem(page, PageGetItemId(page, offno)); + + /* Skip neighbor tuples */ + if (!HnswIsElementTuple(etup)) + continue; + + if (etup->deleted) + { + BlockNumber elementPage = BufferGetBlockNumber(buf); + BlockNumber neighborPage = ItemPointerGetBlockNumber(&etup->neighbortid); + OffsetNumber neighborOffno = ItemPointerGetOffsetNumber(&etup->neighbortid); + ItemId itemid; + + if (!BlockNumberIsValid(*newInsertPage)) + *newInsertPage = elementPage; + + if (neighborPage == elementPage) + { + *nbuf = buf; + *npage = page; + } + else + { + *nbuf = ReadBuffer(index, neighborPage); + LockBuffer(*nbuf, BUFFER_LOCK_EXCLUSIVE); + + /* Skip WAL for now */ + *npage = BufferGetPage(*nbuf); + } + + itemid = PageGetItemId(*npage, neighborOffno); + + /* Check for space on neighbor tuple page */ + if (PageGetFreeSpace(*npage) + ItemIdGetLength(itemid) - sizeof(ItemIdData) >= ntupSize) + { + *freeOffno = offno; + *freeNeighborOffno = neighborOffno; + return true; + } + else if (*nbuf != buf) + UnlockReleaseBuffer(*nbuf); + } + } + + return false; +} + +/* + * Add a new page + */ +static void +HnswInsertAppendPage(Relation index, Buffer *nbuf, Page *npage, GenericXLogState *state, Page page) +{ + /* Add a new page */ + LockRelationForExtension(index, ExclusiveLock); + *nbuf = HnswNewBuffer(index, MAIN_FORKNUM); + UnlockRelationForExtension(index, ExclusiveLock); + + /* Init new page */ + *npage = GenericXLogRegisterBuffer(state, *nbuf, GENERIC_XLOG_FULL_IMAGE); + HnswInitPage(*nbuf, *npage); + + /* Update previous buffer */ + HnswPageGetOpaque(page)->nextblkno = BufferGetBlockNumber(*nbuf); +} + +/* + * Add to element and neighbor pages + */ +static void +WriteNewElementPages(Relation index, HnswElement e, int m, BlockNumber insertPage, BlockNumber *updatedInsertPage) +{ + Buffer buf; + Page page; + GenericXLogState *state; + Size etupSize; + Size ntupSize; + Size combinedSize; + Size maxSize; + Size minCombinedSize; + HnswElementTuple etup; + BlockNumber currentPage = insertPage; + int dimensions = e->vec->dim; + HnswNeighborTuple ntup; + Buffer nbuf; + Page npage; + OffsetNumber freeOffno = InvalidOffsetNumber; + OffsetNumber freeNeighborOffno = InvalidOffsetNumber; + BlockNumber newInsertPage = InvalidBlockNumber; + + /* Calculate sizes */ + etupSize = HNSW_ELEMENT_TUPLE_SIZE(dimensions); + ntupSize = HNSW_NEIGHBOR_TUPLE_SIZE(e->level, m); + combinedSize = etupSize + ntupSize + sizeof(ItemIdData); + maxSize = BLCKSZ - MAXALIGN(SizeOfPageHeaderData) - MAXALIGN(sizeof(HnswPageOpaqueData)); + minCombinedSize = etupSize + HNSW_NEIGHBOR_TUPLE_SIZE(0, m) + sizeof(ItemIdData); + + /* Prepare element tuple */ + etup = palloc0(etupSize); + HnswSetElementTuple(etup, e); + + /* Prepare neighbor tuple */ + ntup = palloc0(ntupSize); + HnswSetNeighborTuple(ntup, e, m); + + /* Find a page (or two if needed) to insert the tuples */ + for (;;) + { + buf = ReadBuffer(index, currentPage); + LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE); + + state = GenericXLogStart(index); + page = GenericXLogRegisterBuffer(state, buf, 0); + + /* Keep track of first page where element at level 0 can fit */ + if (!BlockNumberIsValid(newInsertPage) && PageGetFreeSpace(page) >= minCombinedSize) + newInsertPage = currentPage; + + /* First, try the fastest path */ + /* Space for both tuples on the current page */ + /* This can split existing tuples in rare cases */ + if (PageGetFreeSpace(page) >= combinedSize) + { + nbuf = buf; + npage = page; + break; + } + + /* Next, try space from a deleted element */ + if (HnswFreeOffset(index, buf, page, e, ntupSize, &nbuf, &npage, &freeOffno, &freeNeighborOffno, &newInsertPage)) + { + if (nbuf != buf) + npage = GenericXLogRegisterBuffer(state, nbuf, 0); + + break; + } + + /* Finally, try space for element only if last page */ + /* Skip if both tuples can fit on the same page */ + if (combinedSize > maxSize && PageGetFreeSpace(page) >= etupSize && !BlockNumberIsValid(HnswPageGetOpaque(page)->nextblkno)) + { + HnswInsertAppendPage(index, &nbuf, &npage, state, page); + break; + } + + currentPage = HnswPageGetOpaque(page)->nextblkno; + + if (BlockNumberIsValid(currentPage)) + { + /* Move to next page */ + GenericXLogAbort(state); + UnlockReleaseBuffer(buf); + } + else + { + Buffer newbuf; + Page newpage; + + HnswInsertAppendPage(index, &newbuf, &newpage, state, page); + + /* Commit */ + MarkBufferDirty(newbuf); + MarkBufferDirty(buf); + GenericXLogFinish(state); + + /* Unlock previous buffer */ + UnlockReleaseBuffer(buf); + + /* Prepare new buffer */ + state = GenericXLogStart(index); + buf = newbuf; + page = GenericXLogRegisterBuffer(state, buf, 0); + + /* Create new page for neighbors if needed */ + if (PageGetFreeSpace(page) < combinedSize) + HnswInsertAppendPage(index, &nbuf, &npage, state, page); + else + { + nbuf = buf; + npage = page; + } + + break; + } + } + + e->blkno = BufferGetBlockNumber(buf); + e->neighborPage = BufferGetBlockNumber(nbuf); + + /* Added tuple to new page if newInsertPage is not set */ + /* So can set to neighbor page instead of element page */ + if (!BlockNumberIsValid(newInsertPage)) + newInsertPage = e->neighborPage; + + if (OffsetNumberIsValid(freeOffno)) + { + e->offno = freeOffno; + e->neighborOffno = freeNeighborOffno; + } + else + { + e->offno = OffsetNumberNext(PageGetMaxOffsetNumber(page)); + if (nbuf == buf) + e->neighborOffno = OffsetNumberNext(e->offno); + else + e->neighborOffno = FirstOffsetNumber; + } + + ItemPointerSet(&etup->neighbortid, e->neighborPage, e->neighborOffno); + + /* Add element and neighbors */ + if (OffsetNumberIsValid(freeOffno)) + { + if (!PageIndexTupleOverwrite(page, e->offno, (Item) etup, etupSize)) + elog(ERROR, "failed to add index item to \"%s\"", RelationGetRelationName(index)); + + if (!PageIndexTupleOverwrite(npage, e->neighborOffno, (Item) ntup, ntupSize)) + elog(ERROR, "failed to add index item to \"%s\"", RelationGetRelationName(index)); + } + else + { + if (PageAddItem(page, (Item) etup, etupSize, InvalidOffsetNumber, false, false) != e->offno) + elog(ERROR, "failed to add index item to \"%s\"", RelationGetRelationName(index)); + + if (PageAddItem(npage, (Item) ntup, ntupSize, InvalidOffsetNumber, false, false) != e->neighborOffno) + elog(ERROR, "failed to add index item to \"%s\"", RelationGetRelationName(index)); + } + + /* Commit */ + MarkBufferDirty(buf); + if (nbuf != buf) + MarkBufferDirty(nbuf); + GenericXLogFinish(state); + UnlockReleaseBuffer(buf); + if (nbuf != buf) + UnlockReleaseBuffer(nbuf); + + /* Update the insert page */ + if (BlockNumberIsValid(newInsertPage) && newInsertPage != insertPage) + *updatedInsertPage = newInsertPage; +} + +/* + * Check if connection already exists + */ +static bool +ConnectionExists(HnswElement e, HnswNeighborTuple ntup, int startIdx, int lm) +{ + for (int i = 0; i < lm; i++) + { + ItemPointer indextid = &ntup->indextids[startIdx + i]; + + if (!ItemPointerIsValid(indextid)) + break; + + if (ItemPointerGetBlockNumber(indextid) == e->blkno && ItemPointerGetOffsetNumber(indextid) == e->offno) + return true; + } + + return false; +} + +/* + * Update neighbors + */ +void +HnswUpdateNeighborPages(Relation index, FmgrInfo *procinfo, Oid collation, HnswElement e, int m, bool checkExisting) +{ + for (int lc = e->level; lc >= 0; lc--) + { + int lm = HnswGetLayerM(m, lc); + HnswNeighborArray *neighbors = &e->neighbors[lc]; + + for (int i = 0; i < neighbors->length; i++) + { + HnswCandidate *hc = &neighbors->items[i]; + Buffer buf; + Page page; + GenericXLogState *state; + ItemId itemid; + HnswNeighborTuple ntup; + Size ntupSize; + int idx = -1; + int startIdx; + OffsetNumber offno = hc->element->neighborOffno; + + /* Get latest neighbors since they may have changed */ + /* Do not lock yet since selecting neighbors can take time */ + HnswLoadNeighbors(hc->element, index); + + /* + * Could improve performance for vacuuming by checking neighbors + * against list of elements being deleted to find index. It's + * important to exclude already deleted elements for this since + * they can be replaced at any time. + */ + + /* Select neighbors */ + HnswUpdateConnection(e, hc, lm, lc, &idx, index, procinfo, collation); + + /* New element was not selected as a neighbor */ + if (idx == -1) + continue; + + /* Register page */ + buf = ReadBuffer(index, hc->element->neighborPage); + LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE); + state = GenericXLogStart(index); + page = GenericXLogRegisterBuffer(state, buf, 0); + + /* Get tuple */ + itemid = PageGetItemId(page, offno); + ntup = (HnswNeighborTuple) PageGetItem(page, itemid); + ntupSize = ItemIdGetLength(itemid); + + /* Calculate index for update */ + startIdx = (hc->element->level - lc) * m; + + /* Check for existing connection */ + if (checkExisting && ConnectionExists(e, ntup, startIdx, lm)) + idx = -1; + else if (idx == -2) + { + /* Find free offset if still exists */ + /* TODO Retry updating connections if not */ + for (int j = 0; j < lm; j++) + { + if (!ItemPointerIsValid(&ntup->indextids[startIdx + j])) + { + idx = startIdx + j; + break; + } + } + } + else + idx += startIdx; + + /* Make robust to issues */ + if (idx >= 0 && idx < ntup->count) + { + ItemPointer indextid = &ntup->indextids[idx]; + + /* Update neighbor */ + ItemPointerSet(indextid, e->blkno, e->offno); + + /* Overwrite tuple */ + if (!PageIndexTupleOverwrite(page, offno, (Item) ntup, ntupSize)) + elog(ERROR, "failed to add index item to \"%s\"", RelationGetRelationName(index)); + + /* Commit */ + MarkBufferDirty(buf); + GenericXLogFinish(state); + } + else + GenericXLogAbort(state); + + UnlockReleaseBuffer(buf); + } + } +} + +/* + * Add a heap TID to an existing element + */ +static bool +HnswAddDuplicate(Relation index, HnswElement element, HnswElement dup) +{ + Buffer buf; + Page page; + GenericXLogState *state; + Size etupSize = HNSW_ELEMENT_TUPLE_SIZE(dup->vec->dim); + HnswElementTuple etup; + int i; + + /* Read page */ + buf = ReadBuffer(index, dup->blkno); + LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE); + state = GenericXLogStart(index); + page = GenericXLogRegisterBuffer(state, buf, 0); + + /* Find space */ + etup = (HnswElementTuple) PageGetItem(page, PageGetItemId(page, dup->offno)); + for (i = 0; i < HNSW_HEAPTIDS; i++) + { + if (!ItemPointerIsValid(&etup->heaptids[i])) + break; + } + + /* Either being deleted or we lost our chance to another backend */ + if (i == 0 || i == HNSW_HEAPTIDS) + { + GenericXLogAbort(state); + UnlockReleaseBuffer(buf); + return false; + } + + /* Add heap TID */ + etup->heaptids[i] = *((ItemPointer) linitial(element->heaptids)); + + /* Overwrite tuple */ + if (!PageIndexTupleOverwrite(page, dup->offno, (Item) etup, etupSize)) + elog(ERROR, "failed to add index item to \"%s\"", RelationGetRelationName(index)); + + /* Commit */ + MarkBufferDirty(buf); + GenericXLogFinish(state); + UnlockReleaseBuffer(buf); + + return true; +} + +/* + * Write changes to disk + */ +static void +WriteElement(Relation index, FmgrInfo *procinfo, Oid collation, HnswElement element, int m, int efConstruction, HnswElement dup, HnswElement entryPoint) +{ + BlockNumber newInsertPage = InvalidBlockNumber; + + /* Try to add to existing page */ + if (dup != NULL) + { + if (HnswAddDuplicate(index, element, dup)) + return; + } + + /* Write element and neighbor tuples */ + WriteNewElementPages(index, element, m, GetInsertPage(index), &newInsertPage); + + /* Update insert page if needed */ + if (BlockNumberIsValid(newInsertPage)) + HnswUpdateMetaPage(index, 0, NULL, newInsertPage, MAIN_FORKNUM); + + /* Update neighbors */ + HnswUpdateNeighborPages(index, procinfo, collation, element, m, false); + + /* Update metapage if needed */ + if (entryPoint == NULL || element->level > entryPoint->level) + HnswUpdateMetaPage(index, HNSW_UPDATE_ENTRY_GREATER, element, InvalidBlockNumber, MAIN_FORKNUM); +} + +/* + * Insert a tuple into the index + */ +bool +HnswInsertTuple(Relation index, Datum *values, bool *isnull, ItemPointer heap_tid, Relation heapRel) +{ + Datum value; + FmgrInfo *normprocinfo; + HnswElement entryPoint; + HnswElement element; + int m = HnswGetM(index); + int efConstruction = HnswGetEfConstruction(index); + double ml = HnswGetMl(m); + FmgrInfo *procinfo = index_getprocinfo(index, 1, HNSW_DISTANCE_PROC); + Oid collation = index->rd_indcollation[0]; + HnswElement dup; + LOCKMODE lockmode = ShareLock; + + /* Detoast once for all calls */ + value = PointerGetDatum(PG_DETOAST_DATUM(values[0])); + + /* Normalize if needed */ + normprocinfo = HnswOptionalProcInfo(index, HNSW_NORM_PROC); + if (normprocinfo != NULL) + { + if (!HnswNormValue(normprocinfo, collation, &value, NULL)) + return false; + } + + /* Create an element */ + element = HnswInitElement(heap_tid, m, ml, HnswGetMaxLevel(m)); + element->vec = DatumGetVector(value); + + /* + * Get a shared lock. This allows vacuum to ensure no in-flight inserts + * before repairing graph. Use a page lock so it does not interfere with + * buffer lock (or reads when vacuuming). + */ + LockPage(index, HNSW_UPDATE_LOCK, lockmode); + + /* Get entry point */ + entryPoint = HnswGetEntryPoint(index); + + /* Prevent concurrent inserts when likely updating entry point */ + if (entryPoint == NULL || element->level > entryPoint->level) + { + /* Release shared lock */ + UnlockPage(index, HNSW_UPDATE_LOCK, lockmode); + + /* Get exclusive lock */ + lockmode = ExclusiveLock; + LockPage(index, HNSW_UPDATE_LOCK, lockmode); + + /* Get latest entry point after lock is acquired */ + entryPoint = HnswGetEntryPoint(index); + } + + /* Insert element in graph */ + HnswInsertElement(element, entryPoint, index, procinfo, collation, m, efConstruction, false); + + /* Look for duplicate */ + dup = HnswFindDuplicate(element); + + /* Write to disk */ + WriteElement(index, procinfo, collation, element, m, efConstruction, dup, entryPoint); + + /* Release lock */ + UnlockPage(index, HNSW_UPDATE_LOCK, lockmode); + + return true; +} + +/* + * Insert a tuple into the index + */ +bool +hnswinsert(Relation index, Datum *values, bool *isnull, ItemPointer heap_tid, + Relation heap, IndexUniqueCheck checkUnique +#if PG_VERSION_NUM >= 140000 + ,bool indexUnchanged +#endif + ,IndexInfo *indexInfo +) +{ + MemoryContext oldCtx; + MemoryContext insertCtx; + + /* Skip nulls */ + if (isnull[0]) + return false; + + /* Create memory context */ + insertCtx = AllocSetContextCreate(CurrentMemoryContext, + "Hnsw insert temporary context", + ALLOCSET_DEFAULT_SIZES); + oldCtx = MemoryContextSwitchTo(insertCtx); + + /* Insert tuple */ + HnswInsertTuple(index, values, isnull, heap_tid, heap); + + /* Delete memory context */ + MemoryContextSwitchTo(oldCtx); + MemoryContextDelete(insertCtx); + + return false; +} diff --git a/packages/server/postgres/extensions/pgvector/src/hnswinsert.o b/packages/server/postgres/extensions/pgvector/src/hnswinsert.o new file mode 100644 index 0000000000000000000000000000000000000000..a9e60a05847d711fb15a5be0468e0ce1f36799d9 GIT binary patch literal 8560 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 + Relation index = scan->indexRelation; + FmgrInfo *procinfo = so->procinfo; + Oid collation = so->collation; + List *ep; + List *w; + HnswElement entryPoint = HnswGetEntryPoint(index); + + if (entryPoint == NULL) + return NIL; + + ep = list_make1(HnswEntryCandidate(entryPoint, q, index, procinfo, collation, false)); + + for (int lc = entryPoint->level; lc >= 1; lc--) + { + w = HnswSearchLayer(q, ep, 1, lc, index, procinfo, collation, false, NULL); + ep = w; + } + + return HnswSearchLayer(q, ep, hnsw_ef_search, 0, index, procinfo, collation, 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; +} + +/* + * Prepare for an index scan + */ +IndexScanDesc +hnswbeginscan(Relation index, int nkeys, int norderbys) +{ + IndexScanDesc scan; + HnswScanOpaque so; + + scan = RelationGetIndexScan(index, nkeys, norderbys); + + so = (HnswScanOpaque) palloc(sizeof(HnswScanOpaqueData)); + so->buf = InvalidBuffer; + so->first = true; + so->tmpCtx = AllocSetContextCreate(CurrentMemoryContext, + "Hnsw scan temporary context", + ALLOCSET_DEFAULT_SIZES); + + /* Set support functions */ + so->procinfo = index_getprocinfo(index, 1, HNSW_DISTANCE_PROC); + so->normprocinfo = HnswOptionalProcInfo(index, HNSW_NORM_PROC); + so->collation = index->rd_indcollation[0]; + + scan->opaque = so; + + return scan; +} + +/* + * Start or restart an index scan + */ +void +hnswrescan(IndexScanDesc scan, ScanKey keys, int nkeys, ScanKey orderbys, int norderbys) +{ + HnswScanOpaque so = (HnswScanOpaque) scan->opaque; + + so->first = true; + MemoryContextReset(so->tmpCtx); + + if (keys && scan->numberOfKeys > 0) + memmove(scan->keyData, keys, scan->numberOfKeys * sizeof(ScanKeyData)); + + if (orderbys && scan->numberOfOrderBys > 0) + memmove(scan->orderByData, orderbys, scan->numberOfOrderBys * sizeof(ScanKeyData)); +} + +/* + * Fetch the next tuple in the given scan + */ +bool +hnswgettuple(IndexScanDesc scan, ScanDirection dir) +{ + HnswScanOpaque so = (HnswScanOpaque) scan->opaque; + MemoryContext oldCtx = MemoryContextSwitchTo(so->tmpCtx); + + /* + * Index can be used to scan backward, but Postgres doesn't support + * backward scan on operators + */ + Assert(ScanDirectionIsForward(dir)); + + if (so->first) + { + Datum value; + + /* Count index scan for stats */ + pgstat_count_index_scan(scan->indexRelation); + + /* Safety check */ + if (scan->orderByData == NULL) + elog(ERROR, "cannot scan hnsw index without order"); + + if (scan->orderByData->sk_flags & SK_ISNULL) + value = PointerGetDatum(InitVector(GetDimensions(scan->indexRelation))); + else + { + value = scan->orderByData->sk_argument; + + /* Value should not be compressed or toasted */ + Assert(!VARATT_IS_COMPRESSED(DatumGetPointer(value))); + Assert(!VARATT_IS_EXTENDED(DatumGetPointer(value))); + + /* Fine if normalization fails */ + if (so->normprocinfo != NULL) + HnswNormValue(so->normprocinfo, so->collation, &value, NULL); + } + + /* + * Get a shared lock. This allows vacuum to ensure no in-flight scans + * before marking tuples as deleted. + */ + LockPage(scan->indexRelation, HNSW_SCAN_LOCK, ShareLock); + + so->w = GetScanItems(scan, value); + + /* Release shared lock */ + UnlockPage(scan->indexRelation, HNSW_SCAN_LOCK, ShareLock); + + so->first = false; + } + + while (list_length(so->w) > 0) + { + HnswCandidate *hc = llast(so->w); + ItemPointer tid; + BlockNumber indexblkno; + + /* Move to next element if no valid heap tids */ + if (list_length(hc->element->heaptids) == 0) + { + so->w = list_delete_last(so->w); + continue; + } + + tid = llast(hc->element->heaptids); + indexblkno = hc->element->blkno; + + hc->element->heaptids = list_delete_last(hc->element->heaptids); + + MemoryContextSwitchTo(oldCtx); + +#if PG_VERSION_NUM >= 120000 + scan->xs_heaptid = *tid; +#else + scan->xs_ctup.t_self = *tid; +#endif + + if (BufferIsValid(so->buf)) + ReleaseBuffer(so->buf); + + /* + * An index scan must maintain a pin on the index page holding the + * item last returned by amgettuple + * + * https://www.postgresql.org/docs/current/index-locking.html + */ + so->buf = ReadBuffer(scan->indexRelation, indexblkno); + + scan->xs_recheckorderby = false; + return true; + } + + MemoryContextSwitchTo(oldCtx); + return false; +} + +/* + * End a scan and release resources + */ +void +hnswendscan(IndexScanDesc scan) +{ + HnswScanOpaque so = (HnswScanOpaque) scan->opaque; + + /* Release pin */ + if (BufferIsValid(so->buf)) + ReleaseBuffer(so->buf); + + MemoryContextDelete(so->tmpCtx); + + pfree(so); + scan->opaque = NULL; +} diff --git a/packages/server/postgres/extensions/pgvector/src/hnswscan.o b/packages/server/postgres/extensions/pgvector/src/hnswscan.o new file mode 100644 index 0000000000000000000000000000000000000000..518eeb55b04be87112f1cd6a008de9f177ec2f76 GIT binary patch literal 3600 zcmb7HTWlQF8UA->*B;ZlWGm0Hz+7sYYEf6k1v z9_NK4pZ(5%|L6bDoHPF5+kao*Mie6q{z+h7ktILcv#X(+wh4ub8m0PGdh?t$2Q(DZZ5GI~us+nk7q2fm>{bDd)KjYiX zY@y1Mx@*1}HU}`-&&&?V>k8?eR7rniRN$*TcMp|c+sE>T`PIp8A6|1}rEkZS(uz=#UmSTRP`et`F6P&6FY;;GYE*7V zU4E__m8UU}t5j)Gjn4EoYERc7&KE}I9MM7@@@-AfbX^_lfcWWK!HXH4eKC4iYc?uZ zYpJ5|GEfs*Xuj7de|nInRn(cE5&j#Vg|AppORfdinq>{Km50L~ai4x!Lyxt-WW1ss zt~-zI*-$O}QfMQa}4(T>%jLnwa9ayQSrdvz1^tnX}w`L z6Q_k0=)4f4_BGUJ2{yb>K2sZE(=h)9 zZ2tk<+A7<|T6*C(*Vl%^{1xn*&=z$WyqO)&iA~S&h)SJ0p5xsZ{f=PwG~%yL{t&%9 zhMxAKw>{`_cj=nbRYK3Fc@J`3*jMrT+JY80${P08y~w3bGb)FlO0Sd@^c}Un2>F(T zQU2BSwf3#(?~fDo*o&V$ztX;!>!kVhor5k80PGE_TvUwmAGa72nLp7)`Z+2iud+eZ z&hfbRSAw1(cKPf9rBg?~e_LC-jpfTHeviF}ceh8rw++~P#hM%T#VzuE2>W^#@7L;N z7oPJy|GiqBe5bUk()5f{9K=5I_Z9q3VNSFEaE)VUsvA7BUiMBW{jbOE!fCybGHu~M*uU+D>4!j$B$d&E@7J<;Oc{V^JKs`W! zFRxXM-niP{aYR!VmCH(8T~sftOKMzQR#(((z=gzx#9N74WmcQhYLz#&8T?LZuWIZ7 zBZIo^K%j7N{yV@6zbA&IJRa_wpD>L%gg)~)63`O?{ZIH}`MH2T7RW64%hu;@5 z=-*NmzbT-@ea!t~dARTE1A115ClfUO*2nW$Rnxq#rukd1n&xkiYI-&z|5rqQK0=?3 z(EJTk?eEP9&9$kP|27hj-^*(GUm|pCME~uGT#5Lfi^z)+`+*3(KVpAAB43R7;{zKi z>*JA#{u>dxE#jZw&uV)TJ>#xx+1^jBoa0XP1V^vPW(uAdv9gwDiL6=hNbpJhBg;Ex zd+x-5ld-|tU^8eHEb#iBl$q@wA04&a?yQqK<*Qn5fkYLvKv~g-?`sl$cE)?$N_h^} z9O0y8x~X)(If0{{eVvk4Bz|VIw?SlN7OarDi8Mz-k$+bRTm^a8NoDL&NBTdJ_cD%c zW(UCS^SOLuJZFt%Y`#HAFzH%1)|NdY3B)ql7B%SOd+4OE?$;@2p0W;!OfH`#VYzOu zFeXqR%S9X#U0Ec4((;1R_PCbmS$*qb=#OUXOd%~p3u{yq{2GwRS-G6^q9qf5)Nyl< zo7r)Y8_M46_ekpaz3DOSk<18UNQbC^ybQBKh|zI7B}5xP<2pa6?0`823$D0a)hEI=L#$wM3Cwy|Lwq|kYQ=>IJ&@=gE% literal 0 HcmV?d00001 diff --git a/packages/server/postgres/extensions/pgvector/src/hnswutils.c b/packages/server/postgres/extensions/pgvector/src/hnswutils.c new file mode 100644 index 00000000000..8e6f2a9cf6d --- /dev/null +++ b/packages/server/postgres/extensions/pgvector/src/hnswutils.c @@ -0,0 +1,992 @@ +#include "postgres.h" + +#include + +#include "hnsw.h" +#include "storage/bufmgr.h" +#include "vector.h" + +/* + * Get the max number of connections in an upper layer for each element in the index + */ +int +HnswGetM(Relation index) +{ + HnswOptions *opts = (HnswOptions *) index->rd_options; + + if (opts) + return opts->m; + + return HNSW_DEFAULT_M; +} + +/* + * Get the size of the dynamic candidate list in the index + */ +int +HnswGetEfConstruction(Relation index) +{ + HnswOptions *opts = (HnswOptions *) index->rd_options; + + if (opts) + return opts->efConstruction; + + return HNSW_DEFAULT_EF_CONSTRUCTION; +} + +/* + * Get proc + */ +FmgrInfo * +HnswOptionalProcInfo(Relation rel, uint16 procnum) +{ + if (!OidIsValid(index_getprocid(rel, 1, procnum))) + return NULL; + + return index_getprocinfo(rel, 1, procnum); +} + +/* + * Divide by the norm + * + * Returns false if value should not be indexed + * + * The caller needs to free the pointer stored in value + * if it's different than the original value + */ +bool +HnswNormValue(FmgrInfo *procinfo, Oid collation, Datum *value, Vector * result) +{ + double norm = DatumGetFloat8(FunctionCall1Coll(procinfo, collation, *value)); + + if (norm > 0) + { + Vector *v = DatumGetVector(*value); + + if (result == NULL) + result = InitVector(v->dim); + + for (int i = 0; i < v->dim; i++) + result->x[i] = v->x[i] / norm; + + *value = PointerGetDatum(result); + + return true; + } + + return false; +} + +/* + * New buffer + */ +Buffer +HnswNewBuffer(Relation index, ForkNumber forkNum) +{ + Buffer buf = ReadBufferExtended(index, forkNum, P_NEW, RBM_NORMAL, NULL); + + LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE); + return buf; +} + +/* + * Init page + */ +void +HnswInitPage(Buffer buf, Page page) +{ + PageInit(page, BufferGetPageSize(buf), sizeof(HnswPageOpaqueData)); + HnswPageGetOpaque(page)->nextblkno = InvalidBlockNumber; + HnswPageGetOpaque(page)->page_id = HNSW_PAGE_ID; +} + +/* + * Init and register page + */ +void +HnswInitRegisterPage(Relation index, Buffer *buf, Page *page, GenericXLogState **state) +{ + *state = GenericXLogStart(index); + *page = GenericXLogRegisterBuffer(*state, *buf, GENERIC_XLOG_FULL_IMAGE); + HnswInitPage(*buf, *page); +} + +/* + * Commit buffer + */ +void +HnswCommitBuffer(Buffer buf, GenericXLogState *state) +{ + MarkBufferDirty(buf); + GenericXLogFinish(state); + UnlockReleaseBuffer(buf); +} + +/* + * Allocate neighbors + */ +void +HnswInitNeighbors(HnswElement element, int m) +{ + int level = element->level; + + element->neighbors = palloc(sizeof(HnswNeighborArray) * (level + 1)); + + for (int lc = 0; lc <= level; lc++) + { + HnswNeighborArray *a; + int lm = HnswGetLayerM(m, lc); + + a = &element->neighbors[lc]; + a->length = 0; + a->items = palloc(sizeof(HnswCandidate) * lm); + } +} + +/* + * Allocate an element + */ +HnswElement +HnswInitElement(ItemPointer heaptid, int m, double ml, int maxLevel) +{ + HnswElement element = palloc(sizeof(HnswElementData)); + + int level = (int) (-log(RandomDouble()) * ml); + + /* Cap level */ + if (level > maxLevel) + level = maxLevel; + + element->heaptids = NIL; + HnswAddHeapTid(element, heaptid); + + element->level = level; + element->deleted = 0; + + HnswInitNeighbors(element, m); + + return element; +} + +/* + * Free an element + */ +void +HnswFreeElement(HnswElement element) +{ + list_free_deep(element->heaptids); + for (int lc = 0; lc <= element->level; lc++) + pfree(element->neighbors[lc].items); + pfree(element->neighbors); + pfree(element->vec); + pfree(element); +} + +/* + * Add a heap TID to an element + */ +void +HnswAddHeapTid(HnswElement element, ItemPointer heaptid) +{ + ItemPointer copy = palloc(sizeof(ItemPointerData)); + + ItemPointerCopy(heaptid, copy); + element->heaptids = lappend(element->heaptids, copy); +} + +/* + * Allocate an element from block and offset numbers + */ +HnswElement +HnswInitElementFromBlock(BlockNumber blkno, OffsetNumber offno) +{ + HnswElement element = palloc(sizeof(HnswElementData)); + + element->blkno = blkno; + element->offno = offno; + element->neighbors = NULL; + element->vec = NULL; + return element; +} + +/* + * Get the entry point + */ +HnswElement +HnswGetEntryPoint(Relation index) +{ + Buffer buf; + Page page; + HnswMetaPage metap; + HnswElement entryPoint = NULL; + + buf = ReadBuffer(index, HNSW_METAPAGE_BLKNO); + LockBuffer(buf, BUFFER_LOCK_SHARE); + page = BufferGetPage(buf); + metap = HnswPageGetMeta(page); + + if (BlockNumberIsValid(metap->entryBlkno)) + entryPoint = HnswInitElementFromBlock(metap->entryBlkno, metap->entryOffno); + + UnlockReleaseBuffer(buf); + + return entryPoint; +} + +/* + * Update the metapage info + */ +static void +HnswUpdateMetaPageInfo(Page page, int updateEntry, HnswElement entryPoint, BlockNumber insertPage) +{ + HnswMetaPage metap = HnswPageGetMeta(page); + + if (updateEntry) + { + if (entryPoint == NULL) + { + metap->entryBlkno = InvalidBlockNumber; + metap->entryOffno = InvalidOffsetNumber; + metap->entryLevel = -1; + } + else if (entryPoint->level > metap->entryLevel || updateEntry == HNSW_UPDATE_ENTRY_ALWAYS) + { + metap->entryBlkno = entryPoint->blkno; + metap->entryOffno = entryPoint->offno; + metap->entryLevel = entryPoint->level; + } + } + + if (BlockNumberIsValid(insertPage)) + metap->insertPage = insertPage; +} + +/* + * Update the metapage + */ +void +HnswUpdateMetaPage(Relation index, int updateEntry, HnswElement entryPoint, BlockNumber insertPage, ForkNumber forkNum) +{ + Buffer buf; + Page page; + GenericXLogState *state; + + buf = ReadBufferExtended(index, forkNum, HNSW_METAPAGE_BLKNO, RBM_NORMAL, NULL); + LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE); + state = GenericXLogStart(index); + page = GenericXLogRegisterBuffer(state, buf, 0); + + HnswUpdateMetaPageInfo(page, updateEntry, entryPoint, insertPage); + + HnswCommitBuffer(buf, state); +} + +/* + * Set element tuple, except for neighbor info + */ +void +HnswSetElementTuple(HnswElementTuple etup, HnswElement element) +{ + etup->type = HNSW_ELEMENT_TUPLE_TYPE; + etup->level = element->level; + etup->deleted = 0; + for (int i = 0; i < HNSW_HEAPTIDS; i++) + { + if (i < list_length(element->heaptids)) + etup->heaptids[i] = *((ItemPointer) list_nth(element->heaptids, i)); + else + ItemPointerSetInvalid(&etup->heaptids[i]); + } + memcpy(&etup->vec, element->vec, VECTOR_SIZE(element->vec->dim)); +} + +/* + * Set neighbor tuple + */ +void +HnswSetNeighborTuple(HnswNeighborTuple ntup, HnswElement e, int m) +{ + int idx = 0; + + ntup->type = HNSW_NEIGHBOR_TUPLE_TYPE; + + for (int lc = e->level; lc >= 0; lc--) + { + HnswNeighborArray *neighbors = &e->neighbors[lc]; + int lm = HnswGetLayerM(m, lc); + + for (int i = 0; i < lm; i++) + { + ItemPointer indextid = &ntup->indextids[idx++]; + + if (i < neighbors->length) + { + HnswCandidate *hc = &neighbors->items[i]; + + ItemPointerSet(indextid, hc->element->blkno, hc->element->offno); + } + else + ItemPointerSetInvalid(indextid); + } + } + + ntup->count = idx; +} + +/* + * Load neighbors from page + */ +static void +LoadNeighborsFromPage(HnswElement element, Relation index, Page page) +{ + HnswNeighborTuple ntup = (HnswNeighborTuple) PageGetItem(page, PageGetItemId(page, element->neighborOffno)); + int m = HnswGetM(index); + int neighborCount = (element->level + 2) * m; + + Assert(HnswIsNeighborTuple(ntup)); + + HnswInitNeighbors(element, m); + + /* Ensure expected neighbors */ + if (ntup->count != neighborCount) + return; + + for (int i = 0; i < neighborCount; i++) + { + HnswElement e; + int level; + HnswCandidate *hc; + ItemPointer indextid; + HnswNeighborArray *neighbors; + + indextid = &ntup->indextids[i]; + + if (!ItemPointerIsValid(indextid)) + continue; + + e = HnswInitElementFromBlock(ItemPointerGetBlockNumber(indextid), ItemPointerGetOffsetNumber(indextid)); + + /* Calculate level based on offset */ + level = element->level - i / m; + if (level < 0) + level = 0; + + neighbors = &element->neighbors[level]; + hc = &neighbors->items[neighbors->length++]; + hc->element = e; + } +} + +/* + * Load neighbors + */ +void +HnswLoadNeighbors(HnswElement element, Relation index) +{ + Buffer buf; + Page page; + + buf = ReadBuffer(index, element->neighborPage); + LockBuffer(buf, BUFFER_LOCK_SHARE); + page = BufferGetPage(buf); + + LoadNeighborsFromPage(element, index, page); + + UnlockReleaseBuffer(buf); +} + +/* + * Load an element from a tuple + */ +void +HnswLoadElementFromTuple(HnswElement element, HnswElementTuple etup, bool loadHeaptids, bool loadVec) +{ + element->level = etup->level; + element->deleted = etup->deleted; + element->neighborPage = ItemPointerGetBlockNumber(&etup->neighbortid); + element->neighborOffno = ItemPointerGetOffsetNumber(&etup->neighbortid); + element->heaptids = NIL; + + if (loadHeaptids) + { + for (int i = 0; i < HNSW_HEAPTIDS; i++) + { + /* Can stop at first invalid */ + if (!ItemPointerIsValid(&etup->heaptids[i])) + break; + + HnswAddHeapTid(element, &etup->heaptids[i]); + } + } + + if (loadVec) + { + element->vec = palloc(VECTOR_SIZE(etup->vec.dim)); + memcpy(element->vec, &etup->vec, VECTOR_SIZE(etup->vec.dim)); + } +} + +/* + * Load an element and optionally get its distance from q + */ +void +HnswLoadElement(HnswElement element, float *distance, Datum *q, Relation index, FmgrInfo *procinfo, Oid collation, bool loadVec) +{ + Buffer buf; + Page page; + HnswElementTuple etup; + + /* Read vector */ + buf = ReadBuffer(index, element->blkno); + LockBuffer(buf, BUFFER_LOCK_SHARE); + page = BufferGetPage(buf); + + etup = (HnswElementTuple) PageGetItem(page, PageGetItemId(page, element->offno)); + + Assert(HnswIsElementTuple(etup)); + + /* Load element */ + HnswLoadElementFromTuple(element, etup, true, loadVec); + + /* Calculate distance */ + if (distance != NULL) + *distance = (float) DatumGetFloat8(FunctionCall2Coll(procinfo, collation, *q, PointerGetDatum(&etup->vec))); + + UnlockReleaseBuffer(buf); +} + +/* + * Get the distance for a candidate + */ +static float +GetCandidateDistance(HnswCandidate * hc, Datum q, FmgrInfo *procinfo, Oid collation) +{ + return DatumGetFloat8(FunctionCall2Coll(procinfo, collation, q, PointerGetDatum(hc->element->vec))); +} + +/* + * Create a candidate for the entry point + */ +HnswCandidate * +HnswEntryCandidate(HnswElement entryPoint, Datum q, Relation index, FmgrInfo *procinfo, Oid collation, bool loadVec) +{ + HnswCandidate *hc = palloc(sizeof(HnswCandidate)); + + hc->element = entryPoint; + if (index == NULL) + hc->distance = GetCandidateDistance(hc, q, procinfo, collation); + else + HnswLoadElement(hc->element, &hc->distance, &q, index, procinfo, collation, loadVec); + return hc; +} + +/* + * Compare candidate distances + */ +static int +CompareNearestCandidates(const pairingheap_node *a, const pairingheap_node *b, void *arg) +{ + if (((const HnswPairingHeapNode *) a)->inner->distance < ((const HnswPairingHeapNode *) b)->inner->distance) + return 1; + + if (((const HnswPairingHeapNode *) a)->inner->distance > ((const HnswPairingHeapNode *) b)->inner->distance) + return -1; + + return 0; +} + +/* + * Compare candidate distances + */ +static int +CompareFurthestCandidates(const pairingheap_node *a, const pairingheap_node *b, void *arg) +{ + if (((const HnswPairingHeapNode *) a)->inner->distance < ((const HnswPairingHeapNode *) b)->inner->distance) + return -1; + + if (((const HnswPairingHeapNode *) a)->inner->distance > ((const HnswPairingHeapNode *) b)->inner->distance) + return 1; + + return 0; +} + +/* + * Create a pairing heap node for a candidate + */ +static HnswPairingHeapNode * +CreatePairingHeapNode(HnswCandidate * c) +{ + HnswPairingHeapNode *node = palloc(sizeof(HnswPairingHeapNode)); + + node->inner = c; + return node; +} + +/* + * Add to visited + */ +static inline void +AddToVisited(HTAB *v, HnswCandidate * hc, Relation index, bool *found) +{ + if (index == NULL) + hash_search(v, &hc->element, HASH_ENTER, found); + else + { + ItemPointerData indextid; + + ItemPointerSet(&indextid, hc->element->blkno, hc->element->offno); + hash_search(v, &indextid, HASH_ENTER, found); + } +} + +/* + * Algorithm 2 from paper + */ +List * +HnswSearchLayer(Datum q, List *ep, int ef, int lc, Relation index, FmgrInfo *procinfo, Oid collation, bool inserting, HnswElement skipElement) +{ + ListCell *lc2; + + List *w = NIL; + pairingheap *C = pairingheap_allocate(CompareNearestCandidates, NULL); + pairingheap *W = pairingheap_allocate(CompareFurthestCandidates, NULL); + int wlen = 0; + HASHCTL hash_ctl; + HTAB *v; + + /* Create hash table */ + if (index == NULL) + { + hash_ctl.keysize = sizeof(HnswElement *); + hash_ctl.entrysize = sizeof(HnswElement *); + } + else + { + hash_ctl.keysize = sizeof(ItemPointerData); + hash_ctl.entrysize = sizeof(ItemPointerData); + } + + hash_ctl.hcxt = CurrentMemoryContext; + v = hash_create("hnsw visited", 256, &hash_ctl, HASH_ELEM | HASH_BLOBS | HASH_CONTEXT); + + /* Add entry points to v, C, and W */ + foreach(lc2, ep) + { + HnswCandidate *hc = (HnswCandidate *) lfirst(lc2); + + AddToVisited(v, hc, index, NULL); + + pairingheap_add(C, &(CreatePairingHeapNode(hc)->ph_node)); + pairingheap_add(W, &(CreatePairingHeapNode(hc)->ph_node)); + + /* + * Do not count elements being deleted towards ef when vacuuming. It + * would be ideal to do this for inserts as well, but this could + * affect insert performance. + */ + if (skipElement == NULL || list_length(hc->element->heaptids) != 0) + wlen++; + } + + while (!pairingheap_is_empty(C)) + { + HnswNeighborArray *neighborhood; + HnswCandidate *c = ((HnswPairingHeapNode *) pairingheap_remove_first(C))->inner; + HnswCandidate *f = ((HnswPairingHeapNode *) pairingheap_first(W))->inner; + + if (c->distance > f->distance) + break; + + if (c->element->neighbors == NULL) + HnswLoadNeighbors(c->element, index); + + /* Get the neighborhood at layer lc */ + neighborhood = &c->element->neighbors[lc]; + + for (int i = 0; i < neighborhood->length; i++) + { + HnswCandidate *e = &neighborhood->items[i]; + bool visited; + + AddToVisited(v, e, index, &visited); + + if (!visited) + { + float eDistance; + + f = ((HnswPairingHeapNode *) pairingheap_first(W))->inner; + + if (index == NULL) + eDistance = GetCandidateDistance(e, q, procinfo, collation); + else + HnswLoadElement(e->element, &eDistance, &q, index, procinfo, collation, inserting); + + Assert(!e->element->deleted); + + /* Make robust to issues */ + if (e->element->level < lc) + continue; + + if (eDistance < f->distance || wlen < ef) + { + /* Copy e */ + HnswCandidate *ec = palloc(sizeof(HnswCandidate)); + + ec->element = e->element; + ec->distance = eDistance; + + pairingheap_add(C, &(CreatePairingHeapNode(ec)->ph_node)); + pairingheap_add(W, &(CreatePairingHeapNode(ec)->ph_node)); + + /* + * Do not count elements being deleted towards ef when + * vacuuming. It would be ideal to do this for inserts as + * well, but this could affect insert performance. + */ + if (skipElement == NULL || list_length(e->element->heaptids) != 0) + { + wlen++; + + /* No need to decrement wlen */ + if (wlen > ef) + pairingheap_remove_first(W); + } + } + } + } + } + + /* Add each element of W to w */ + while (!pairingheap_is_empty(W)) + { + HnswCandidate *hc = ((HnswPairingHeapNode *) pairingheap_remove_first(W))->inner; + + w = lappend(w, hc); + } + + return w; +} + +/* + * Calculate the distance between elements + */ +static float +HnswGetDistance(HnswElement a, HnswElement b, int lc, FmgrInfo *procinfo, Oid collation) +{ + /* Look for cached distance */ + if (a->neighbors != NULL) + { + Assert(a->level >= lc); + + for (int i = 0; i < a->neighbors[lc].length; i++) + { + if (a->neighbors[lc].items[i].element == b) + return a->neighbors[lc].items[i].distance; + } + } + + if (b->neighbors != NULL) + { + Assert(b->level >= lc); + + for (int i = 0; i < b->neighbors[lc].length; i++) + { + if (b->neighbors[lc].items[i].element == a) + return b->neighbors[lc].items[i].distance; + } + } + + return DatumGetFloat8(FunctionCall2Coll(procinfo, collation, PointerGetDatum(a->vec), PointerGetDatum(b->vec))); +} + +/* + * Check if an element is closer to q than any element from R + */ +static bool +CheckElementCloser(HnswCandidate * e, List *r, int lc, FmgrInfo *procinfo, Oid collation) +{ + ListCell *lc2; + + foreach(lc2, r) + { + HnswCandidate *ri = lfirst(lc2); + float distance = HnswGetDistance(e->element, ri->element, lc, procinfo, collation); + + if (distance <= e->distance) + return false; + } + + return true; +} + +/* + * Algorithm 4 from paper + */ +static List * +SelectNeighbors(List *c, int m, int lc, FmgrInfo *procinfo, Oid collation, HnswCandidate * *pruned) +{ + List *r = NIL; + List *w = list_copy(c); + pairingheap *wd; + + if (list_length(w) <= m) + return w; + + wd = pairingheap_allocate(CompareNearestCandidates, NULL); + + while (list_length(w) > 0 && list_length(r) < m) + { + /* Assumes w is already ordered desc */ + HnswCandidate *e = llast(w); + bool closer; + + w = list_delete_last(w); + + closer = CheckElementCloser(e, r, lc, procinfo, collation); + + if (closer) + r = lappend(r, e); + else + pairingheap_add(wd, &(CreatePairingHeapNode(e)->ph_node)); + } + + /* Keep pruned connections */ + while (!pairingheap_is_empty(wd) && list_length(r) < m) + r = lappend(r, ((HnswPairingHeapNode *) pairingheap_remove_first(wd))->inner); + + /* Return pruned for update connections */ + if (pruned != NULL) + { + if (!pairingheap_is_empty(wd)) + *pruned = ((HnswPairingHeapNode *) pairingheap_first(wd))->inner; + else + *pruned = linitial(w); + } + + return r; +} + +/* + * Find duplicate element + */ +HnswElement +HnswFindDuplicate(HnswElement e) +{ + HnswNeighborArray *neighbors = &e->neighbors[0]; + + for (int i = 0; i < neighbors->length; i++) + { + HnswCandidate *neighbor = &neighbors->items[i]; + + /* Exit early since ordered by distance */ + if (vector_cmp_internal(e->vec, neighbor->element->vec) != 0) + break; + + /* Check for space */ + if (list_length(neighbor->element->heaptids) < HNSW_HEAPTIDS) + return neighbor->element; + } + + return NULL; +} + +/* + * Add connections + */ +static void +AddConnections(HnswElement element, List *neighbors, int m, int lc) +{ + ListCell *lc2; + HnswNeighborArray *a = &element->neighbors[lc]; + + foreach(lc2, neighbors) + a->items[a->length++] = *((HnswCandidate *) lfirst(lc2)); +} + +/* + * Compare candidate distances + */ +static int +#if PG_VERSION_NUM >= 130000 +CompareCandidateDistances(const ListCell *a, const ListCell *b) +#else +CompareCandidateDistances(const void *a, const void *b) +#endif +{ + HnswCandidate *hca = lfirst((ListCell *) a); + HnswCandidate *hcb = lfirst((ListCell *) b); + + if (hca->distance < hcb->distance) + return 1; + + if (hca->distance > hcb->distance) + return -1; + + return 0; +} + +/* + * Update connections + */ +void +HnswUpdateConnection(HnswElement element, HnswCandidate * hc, int m, int lc, int *updateIdx, Relation index, FmgrInfo *procinfo, Oid collation) +{ + HnswNeighborArray *currentNeighbors = &hc->element->neighbors[lc]; + + HnswCandidate hc2; + + hc2.element = element; + hc2.distance = hc->distance; + + if (currentNeighbors->length < m) + { + currentNeighbors->items[currentNeighbors->length++] = hc2; + + /* Track update */ + if (updateIdx != NULL) + *updateIdx = -2; + } + else + { + /* Shrink connections */ + HnswCandidate *pruned = NULL; + + /* Load elements on insert */ + if (index != NULL) + { + Datum q = PointerGetDatum(hc->element->vec); + + for (int i = 0; i < currentNeighbors->length; i++) + { + HnswCandidate *hc3 = ¤tNeighbors->items[i]; + + if (hc3->element->vec == NULL) + HnswLoadElement(hc3->element, &hc3->distance, &q, index, procinfo, collation, true); + else + hc3->distance = GetCandidateDistance(hc3, q, procinfo, collation); + + /* Prune element if being deleted */ + if (list_length(hc3->element->heaptids) == 0) + { + pruned = ¤tNeighbors->items[i]; + break; + } + } + } + + if (pruned == NULL) + { + List *c = NIL; + + /* Add and sort candidates */ + for (int i = 0; i < currentNeighbors->length; i++) + c = lappend(c, ¤tNeighbors->items[i]); + c = lappend(c, &hc2); + list_sort(c, CompareCandidateDistances); + + SelectNeighbors(c, m, lc, procinfo, collation, &pruned); + + /* Should not happen */ + if (pruned == NULL) + return; + } + + /* Find and replace the pruned element */ + for (int i = 0; i < currentNeighbors->length; i++) + { + if (currentNeighbors->items[i].element == pruned->element) + { + currentNeighbors->items[i] = hc2; + + /* Track update */ + if (updateIdx != NULL) + *updateIdx = i; + + break; + } + } + } +} + +/* + * Remove elements being deleted or skipped + */ +static List * +RemoveElements(List *w, HnswElement skipElement) +{ + ListCell *lc2; + List *w2 = NIL; + + foreach(lc2, w) + { + HnswCandidate *hc = (HnswCandidate *) lfirst(lc2); + + /* Skip self for vacuuming update */ + if (skipElement != NULL && hc->element->blkno == skipElement->blkno && hc->element->offno == skipElement->offno) + continue; + + if (list_length(hc->element->heaptids) != 0) + w2 = lappend(w2, hc); + } + + return w2; +} + +/* + * Algorithm 1 from paper + */ +void +HnswInsertElement(HnswElement element, HnswElement entryPoint, Relation index, FmgrInfo *procinfo, Oid collation, int m, int efConstruction, bool existing) +{ + List *ep; + List *w; + int level = element->level; + int entryLevel; + Datum q = PointerGetDatum(element->vec); + HnswElement skipElement = existing ? element : NULL; + + /* No neighbors if no entry point */ + if (entryPoint == NULL) + return; + + /* Get entry point and level */ + ep = list_make1(HnswEntryCandidate(entryPoint, q, index, procinfo, collation, true)); + entryLevel = entryPoint->level; + + /* 1st phase: greedy search to insert level */ + for (int lc = entryLevel; lc >= level + 1; lc--) + { + w = HnswSearchLayer(q, ep, 1, lc, index, procinfo, collation, true, skipElement); + ep = w; + } + + if (level > entryLevel) + level = entryLevel; + + /* Add one for existing element */ + if (existing) + efConstruction++; + + /* 2nd phase */ + for (int lc = level; lc >= 0; lc--) + { + int lm = HnswGetLayerM(m, lc); + List *neighbors; + List *lw; + + w = HnswSearchLayer(q, ep, efConstruction, lc, index, procinfo, collation, true, skipElement); + + /* Elements being deleted or skipped can help with search */ + /* but should be removed before selecting neighbors */ + if (index != NULL) + lw = RemoveElements(w, skipElement); + else + lw = w; + + neighbors = SelectNeighbors(lw, lm, lc, procinfo, collation, NULL); + + AddConnections(element, neighbors, lm, lc); + + ep = w; + } +} diff --git a/packages/server/postgres/extensions/pgvector/src/hnswutils.o b/packages/server/postgres/extensions/pgvector/src/hnswutils.o new file mode 100644 index 0000000000000000000000000000000000000000..52d8670f33aefa86f0df8af8f0d317f8bf5ce54d GIT binary patch literal 11504 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 + +#include "commands/vacuum.h" +#include "hnsw.h" +#include "storage/bufmgr.h" +#include "storage/lmgr.h" +#include "utils/memutils.h" + +/* + * Check if deleted list contains an index TID + */ +static bool +DeletedContains(HTAB *deleted, ItemPointer indextid) +{ + bool found; + + hash_search(deleted, indextid, HASH_FIND, &found); + return found; +} + +/* + * Remove deleted heap TIDs + * + * OK to remove for entry point, since always considered for searches and inserts + */ +static void +RemoveHeapTids(HnswVacuumState * vacuumstate) +{ + BlockNumber blkno = HNSW_HEAD_BLKNO; + HnswElement highestPoint = &vacuumstate->highestPoint; + Relation index = vacuumstate->index; + BufferAccessStrategy bas = vacuumstate->bas; + HnswElement entryPoint = HnswGetEntryPoint(vacuumstate->index); + IndexBulkDeleteResult *stats = vacuumstate->stats; + + /* Store separately since highestPoint.level is uint8 */ + int highestLevel = -1; + + /* Initialize highest point */ + highestPoint->blkno = InvalidBlockNumber; + highestPoint->offno = InvalidOffsetNumber; + + while (BlockNumberIsValid(blkno)) + { + Buffer buf; + Page page; + GenericXLogState *state; + OffsetNumber offno; + OffsetNumber maxoffno; + bool updated = false; + + vacuum_delay_point(); + + buf = ReadBufferExtended(index, MAIN_FORKNUM, blkno, RBM_NORMAL, bas); + LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE); + state = GenericXLogStart(index); + page = GenericXLogRegisterBuffer(state, buf, 0); + maxoffno = PageGetMaxOffsetNumber(page); + + /* Iterate over nodes */ + for (offno = FirstOffsetNumber; offno <= maxoffno; offno = OffsetNumberNext(offno)) + { + HnswElementTuple etup = (HnswElementTuple) PageGetItem(page, PageGetItemId(page, offno)); + int idx = 0; + bool itemUpdated = false; + + /* Skip neighbor tuples */ + if (!HnswIsElementTuple(etup)) + continue; + + if (ItemPointerIsValid(&etup->heaptids[0])) + { + for (int i = 0; i < HNSW_HEAPTIDS; i++) + { + /* Stop at first unused */ + if (!ItemPointerIsValid(&etup->heaptids[i])) + break; + + if (vacuumstate->callback(&etup->heaptids[i], vacuumstate->callback_state)) + { + itemUpdated = true; + stats->tuples_removed++; + } + else + { + /* Move to front of list */ + etup->heaptids[idx++] = etup->heaptids[i]; + stats->num_index_tuples++; + } + } + + if (itemUpdated) + { + Size etupSize = HNSW_ELEMENT_TUPLE_SIZE(etup->vec.dim); + + /* Mark rest as invalid */ + for (int i = idx; i < HNSW_HEAPTIDS; i++) + ItemPointerSetInvalid(&etup->heaptids[i]); + + if (!PageIndexTupleOverwrite(page, offno, (Item) etup, etupSize)) + elog(ERROR, "failed to add index item to \"%s\"", RelationGetRelationName(index)); + + updated = true; + } + } + + if (!ItemPointerIsValid(&etup->heaptids[0])) + { + ItemPointerData ip; + + /* Add to deleted list */ + ItemPointerSet(&ip, blkno, offno); + + (void) hash_search(vacuumstate->deleted, &ip, HASH_ENTER, NULL); + } + else if (etup->level > highestLevel && !(entryPoint != NULL && blkno == entryPoint->blkno && offno == entryPoint->offno)) + { + /* Keep track of highest non-entry point */ + highestPoint->blkno = blkno; + highestPoint->offno = offno; + highestPoint->level = etup->level; + highestLevel = etup->level; + } + } + + blkno = HnswPageGetOpaque(page)->nextblkno; + + if (updated) + { + MarkBufferDirty(buf); + GenericXLogFinish(state); + } + else + GenericXLogAbort(state); + + UnlockReleaseBuffer(buf); + } +} + +/* + * Check for deleted neighbors + */ +static bool +NeedsUpdated(HnswVacuumState * vacuumstate, HnswElement element) +{ + Relation index = vacuumstate->index; + BufferAccessStrategy bas = vacuumstate->bas; + Buffer buf; + Page page; + HnswNeighborTuple ntup; + bool needsUpdated = false; + + buf = ReadBufferExtended(index, MAIN_FORKNUM, element->neighborPage, RBM_NORMAL, bas); + LockBuffer(buf, BUFFER_LOCK_SHARE); + page = BufferGetPage(buf); + ntup = (HnswNeighborTuple) PageGetItem(page, PageGetItemId(page, element->neighborOffno)); + + Assert(HnswIsNeighborTuple(ntup)); + + /* Check neighbors */ + for (int i = 0; i < ntup->count; i++) + { + ItemPointer indextid = &ntup->indextids[i]; + + if (!ItemPointerIsValid(indextid)) + continue; + + /* Check if in deleted list */ + if (DeletedContains(vacuumstate->deleted, indextid)) + { + needsUpdated = true; + break; + } + } + + /* Also update if layer 0 is not full */ + /* This could indicate too many candidates being deleted during insert */ + if (!needsUpdated) + needsUpdated = !ItemPointerIsValid(&ntup->indextids[ntup->count - 1]); + + UnlockReleaseBuffer(buf); + + return needsUpdated; +} + +/* + * Repair graph for a single element + */ +static void +RepairGraphElement(HnswVacuumState * vacuumstate, HnswElement element, HnswElement entryPoint) +{ + Relation index = vacuumstate->index; + Buffer buf; + Page page; + GenericXLogState *state; + int m = vacuumstate->m; + int efConstruction = vacuumstate->efConstruction; + FmgrInfo *procinfo = vacuumstate->procinfo; + Oid collation = vacuumstate->collation; + BufferAccessStrategy bas = vacuumstate->bas; + HnswNeighborTuple ntup = vacuumstate->ntup; + Size ntupSize = HNSW_NEIGHBOR_TUPLE_SIZE(element->level, m); + + /* Skip if element is entry point */ + if (entryPoint != NULL && element->blkno == entryPoint->blkno && element->offno == entryPoint->offno) + return; + + /* Init fields */ + HnswInitNeighbors(element, m); + element->heaptids = NIL; + + /* Add element to graph, skipping itself */ + HnswInsertElement(element, entryPoint, index, procinfo, collation, m, efConstruction, true); + + /* Update neighbor tuple */ + /* Do this before getting page to minimize locking */ + HnswSetNeighborTuple(ntup, element, m); + + /* Get neighbor page */ + buf = ReadBufferExtended(index, MAIN_FORKNUM, element->neighborPage, RBM_NORMAL, bas); + LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE); + state = GenericXLogStart(index); + page = GenericXLogRegisterBuffer(state, buf, 0); + + /* Overwrite tuple */ + if (!PageIndexTupleOverwrite(page, element->neighborOffno, (Item) ntup, ntupSize)) + elog(ERROR, "failed to add index item to \"%s\"", RelationGetRelationName(index)); + + /* Commit */ + MarkBufferDirty(buf); + GenericXLogFinish(state); + UnlockReleaseBuffer(buf); + + /* Update neighbors */ + HnswUpdateNeighborPages(index, procinfo, collation, element, m, true); +} + +/* + * Repair graph entry point + */ +static void +RepairGraphEntryPoint(HnswVacuumState * vacuumstate) +{ + Relation index = vacuumstate->index; + HnswElement highestPoint = &vacuumstate->highestPoint; + HnswElement entryPoint; + MemoryContext oldCtx = MemoryContextSwitchTo(vacuumstate->tmpCtx); + + if (!BlockNumberIsValid(highestPoint->blkno)) + highestPoint = NULL; + + /* + * Repair graph for highest non-entry point. Highest point may be outdated + * due to inserts that happen during and after RemoveHeapTids. + */ + if (highestPoint != NULL) + { + /* Get a shared lock */ + LockPage(index, HNSW_UPDATE_LOCK, ShareLock); + + /* Load element */ + HnswLoadElement(highestPoint, NULL, NULL, index, vacuumstate->procinfo, vacuumstate->collation, true); + + /* Repair if needed */ + if (NeedsUpdated(vacuumstate, highestPoint)) + RepairGraphElement(vacuumstate, highestPoint, HnswGetEntryPoint(index)); + + /* Release lock */ + UnlockPage(index, HNSW_UPDATE_LOCK, ShareLock); + } + + /* Prevent concurrent inserts when possibly updating entry point */ + LockPage(index, HNSW_UPDATE_LOCK, ExclusiveLock); + + /* Get latest entry point */ + entryPoint = HnswGetEntryPoint(index); + + if (entryPoint != NULL) + { + ItemPointerData epData; + + ItemPointerSet(&epData, entryPoint->blkno, entryPoint->offno); + + if (DeletedContains(vacuumstate->deleted, &epData)) + { + /* + * Replace the entry point with the highest point. If highest + * point is outdated and empty, the entry point will be empty + * until an element is repaired. + */ + HnswUpdateMetaPage(index, HNSW_UPDATE_ENTRY_ALWAYS, highestPoint, InvalidBlockNumber, MAIN_FORKNUM); + } + else + { + /* + * Repair the entry point with the highest point. If highest point + * is outdated, this can remove connections at higher levels in + * the graph until they are repaired, but this should be fine. + */ + HnswLoadElement(entryPoint, NULL, NULL, index, vacuumstate->procinfo, vacuumstate->collation, true); + + if (NeedsUpdated(vacuumstate, entryPoint)) + { + /* Reset neighbors from previous update */ + if (highestPoint != NULL) + highestPoint->neighbors = NULL; + + RepairGraphElement(vacuumstate, entryPoint, highestPoint); + } + } + } + + /* Release lock */ + UnlockPage(index, HNSW_UPDATE_LOCK, ExclusiveLock); + + /* Reset memory context */ + MemoryContextSwitchTo(oldCtx); + MemoryContextReset(vacuumstate->tmpCtx); +} + +/* + * Repair graph for all elements + */ +static void +RepairGraph(HnswVacuumState * vacuumstate) +{ + Relation index = vacuumstate->index; + BufferAccessStrategy bas = vacuumstate->bas; + BlockNumber blkno = HNSW_HEAD_BLKNO; + + /* Wait for inserts to complete */ + LockPage(index, HNSW_UPDATE_LOCK, ExclusiveLock); + UnlockPage(index, HNSW_UPDATE_LOCK, ExclusiveLock); + + /* Repair entry point first */ + RepairGraphEntryPoint(vacuumstate); + + while (BlockNumberIsValid(blkno)) + { + Buffer buf; + Page page; + OffsetNumber offno; + OffsetNumber maxoffno; + List *elements = NIL; + ListCell *lc2; + MemoryContext oldCtx; + + vacuum_delay_point(); + + oldCtx = MemoryContextSwitchTo(vacuumstate->tmpCtx); + + buf = ReadBufferExtended(index, MAIN_FORKNUM, blkno, RBM_NORMAL, bas); + LockBuffer(buf, BUFFER_LOCK_SHARE); + page = BufferGetPage(buf); + maxoffno = PageGetMaxOffsetNumber(page); + + /* Load items into memory to minimize locking */ + for (offno = FirstOffsetNumber; offno <= maxoffno; offno = OffsetNumberNext(offno)) + { + HnswElementTuple etup = (HnswElementTuple) PageGetItem(page, PageGetItemId(page, offno)); + HnswElement element; + + /* Skip neighbor tuples */ + if (!HnswIsElementTuple(etup)) + continue; + + /* Skip updating neighbors if being deleted */ + if (!ItemPointerIsValid(&etup->heaptids[0])) + continue; + + /* Create an element */ + element = HnswInitElementFromBlock(blkno, offno); + HnswLoadElementFromTuple(element, etup, false, true); + + elements = lappend(elements, element); + } + + blkno = HnswPageGetOpaque(page)->nextblkno; + + UnlockReleaseBuffer(buf); + + /* Update neighbor pages */ + foreach(lc2, elements) + { + HnswElement element = (HnswElement) lfirst(lc2); + HnswElement entryPoint; + LOCKMODE lockmode = ShareLock; + + /* Check if any neighbors point to deleted values */ + if (!NeedsUpdated(vacuumstate, element)) + continue; + + /* Get a shared lock */ + LockPage(index, HNSW_UPDATE_LOCK, lockmode); + + /* Refresh entry point for each element */ + entryPoint = HnswGetEntryPoint(index); + + /* Prevent concurrent inserts when likely updating entry point */ + if (entryPoint == NULL || element->level > entryPoint->level) + { + /* Release shared lock */ + UnlockPage(index, HNSW_UPDATE_LOCK, lockmode); + + /* Get exclusive lock */ + lockmode = ExclusiveLock; + LockPage(index, HNSW_UPDATE_LOCK, lockmode); + + /* Get latest entry point after lock is acquired */ + entryPoint = HnswGetEntryPoint(index); + } + + /* Repair connections */ + RepairGraphElement(vacuumstate, element, entryPoint); + + /* + * Update metapage if needed. Should only happen if entry point + * was replaced and highest point was outdated. + */ + if (entryPoint == NULL || element->level > entryPoint->level) + HnswUpdateMetaPage(index, HNSW_UPDATE_ENTRY_GREATER, element, InvalidBlockNumber, MAIN_FORKNUM); + + /* Release lock */ + UnlockPage(index, HNSW_UPDATE_LOCK, lockmode); + } + + /* Reset memory context */ + MemoryContextSwitchTo(oldCtx); + MemoryContextReset(vacuumstate->tmpCtx); + } +} + +/* + * Mark items as deleted + */ +static void +MarkDeleted(HnswVacuumState * vacuumstate) +{ + BlockNumber blkno = HNSW_HEAD_BLKNO; + BlockNumber insertPage = InvalidBlockNumber; + Relation index = vacuumstate->index; + BufferAccessStrategy bas = vacuumstate->bas; + + /* Wait for selects to complete */ + LockPage(index, HNSW_SCAN_LOCK, ExclusiveLock); + UnlockPage(index, HNSW_SCAN_LOCK, ExclusiveLock); + + while (BlockNumberIsValid(blkno)) + { + Buffer buf; + Page page; + GenericXLogState *state; + OffsetNumber offno; + OffsetNumber maxoffno; + + vacuum_delay_point(); + + buf = ReadBufferExtended(index, MAIN_FORKNUM, blkno, RBM_NORMAL, bas); + + /* + * ambulkdelete cannot delete entries from pages that are pinned by + * other backends + * + * https://www.postgresql.org/docs/current/index-locking.html + */ + LockBufferForCleanup(buf); + + state = GenericXLogStart(index); + page = GenericXLogRegisterBuffer(state, buf, 0); + maxoffno = PageGetMaxOffsetNumber(page); + + /* Update element and neighbors together */ + for (offno = FirstOffsetNumber; offno <= maxoffno; offno = OffsetNumberNext(offno)) + { + HnswElementTuple etup = (HnswElementTuple) PageGetItem(page, PageGetItemId(page, offno)); + HnswNeighborTuple ntup; + Size etupSize; + Size ntupSize; + Buffer nbuf; + Page npage; + BlockNumber neighborPage; + OffsetNumber neighborOffno; + + /* Skip neighbor tuples */ + if (!HnswIsElementTuple(etup)) + continue; + + /* Skip deleted tuples */ + if (etup->deleted) + { + /* Set to first free page */ + if (!BlockNumberIsValid(insertPage)) + insertPage = blkno; + + continue; + } + + /* Skip live tuples */ + if (ItemPointerIsValid(&etup->heaptids[0])) + continue; + + /* Calculate sizes */ + etupSize = HNSW_ELEMENT_TUPLE_SIZE(etup->vec.dim); + ntupSize = HNSW_NEIGHBOR_TUPLE_SIZE(etup->level, vacuumstate->m); + + /* Get neighbor page */ + neighborPage = ItemPointerGetBlockNumber(&etup->neighbortid); + neighborOffno = ItemPointerGetOffsetNumber(&etup->neighbortid); + + if (neighborPage == blkno) + { + nbuf = buf; + npage = page; + } + else + { + nbuf = ReadBufferExtended(index, MAIN_FORKNUM, neighborPage, RBM_NORMAL, bas); + LockBuffer(nbuf, BUFFER_LOCK_EXCLUSIVE); + npage = GenericXLogRegisterBuffer(state, nbuf, 0); + } + + ntup = (HnswNeighborTuple) PageGetItem(npage, PageGetItemId(npage, neighborOffno)); + + /* Overwrite element */ + etup->deleted = 1; + MemSet(&etup->vec.x, 0, etup->vec.dim * sizeof(float)); + + /* Overwrite neighbors */ + for (int i = 0; i < ntup->count; i++) + ItemPointerSetInvalid(&ntup->indextids[i]); + + /* Overwrite element tuple */ + if (!PageIndexTupleOverwrite(page, offno, (Item) etup, etupSize)) + elog(ERROR, "failed to add index item to \"%s\"", RelationGetRelationName(index)); + + /* Overwrite neighbor tuple */ + if (!PageIndexTupleOverwrite(npage, neighborOffno, (Item) ntup, ntupSize)) + elog(ERROR, "failed to add index item to \"%s\"", RelationGetRelationName(index)); + + /* Commit */ + MarkBufferDirty(buf); + if (nbuf != buf) + MarkBufferDirty(nbuf); + GenericXLogFinish(state); + if (nbuf != buf) + UnlockReleaseBuffer(nbuf); + + /* Set to first free page */ + if (!BlockNumberIsValid(insertPage)) + insertPage = blkno; + + /* Prepare new xlog */ + state = GenericXLogStart(index); + page = GenericXLogRegisterBuffer(state, buf, 0); + } + + blkno = HnswPageGetOpaque(page)->nextblkno; + + GenericXLogAbort(state); + UnlockReleaseBuffer(buf); + } + + /* Update insert page last, after everything has been marked as deleted */ + HnswUpdateMetaPage(index, 0, NULL, insertPage, MAIN_FORKNUM); +} + +/* + * Initialize the vacuum state + */ +static void +InitVacuumState(HnswVacuumState * vacuumstate, IndexVacuumInfo *info, IndexBulkDeleteResult *stats, IndexBulkDeleteCallback callback, void *callback_state) +{ + Relation index = info->index; + HASHCTL hash_ctl; + + if (stats == NULL) + stats = (IndexBulkDeleteResult *) palloc0(sizeof(IndexBulkDeleteResult)); + + vacuumstate->index = index; + vacuumstate->stats = stats; + vacuumstate->callback = callback; + vacuumstate->callback_state = callback_state; + vacuumstate->m = HnswGetM(index); + vacuumstate->efConstruction = HnswGetEfConstruction(index); + vacuumstate->bas = GetAccessStrategy(BAS_BULKREAD); + vacuumstate->procinfo = index_getprocinfo(index, 1, HNSW_DISTANCE_PROC); + vacuumstate->collation = index->rd_indcollation[0]; + vacuumstate->ntup = palloc0(BLCKSZ); + vacuumstate->tmpCtx = AllocSetContextCreate(CurrentMemoryContext, + "Hnsw vacuum temporary context", + ALLOCSET_DEFAULT_SIZES); + + /* Create hash table */ + hash_ctl.keysize = sizeof(ItemPointerData); + hash_ctl.entrysize = sizeof(ItemPointerData); + hash_ctl.hcxt = CurrentMemoryContext; + vacuumstate->deleted = hash_create("hnswbulkdelete indextids", 256, &hash_ctl, HASH_ELEM | HASH_BLOBS | HASH_CONTEXT); +} + +/* + * Free resources + */ +static void +FreeVacuumState(HnswVacuumState * vacuumstate) +{ + hash_destroy(vacuumstate->deleted); + FreeAccessStrategy(vacuumstate->bas); + pfree(vacuumstate->ntup); + MemoryContextDelete(vacuumstate->tmpCtx); +} + +/* + * Bulk delete tuples from the index + */ +IndexBulkDeleteResult * +hnswbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats, + IndexBulkDeleteCallback callback, void *callback_state) +{ + HnswVacuumState vacuumstate; + + InitVacuumState(&vacuumstate, info, stats, callback, callback_state); + + /* Pass 1: Remove heap TIDs */ + RemoveHeapTids(&vacuumstate); + + /* Pass 2: Repair graph */ + RepairGraph(&vacuumstate); + + /* Pass 3: Mark as deleted */ + MarkDeleted(&vacuumstate); + + FreeVacuumState(&vacuumstate); + + return vacuumstate.stats; +} + +/* + * Clean up after a VACUUM operation + */ +IndexBulkDeleteResult * +hnswvacuumcleanup(IndexVacuumInfo *info, IndexBulkDeleteResult *stats) +{ + Relation rel = info->index; + + if (info->analyze_only) + return stats; + + /* stats is NULL if ambulkdelete not called */ + /* OK to return NULL if index not changed */ + if (stats == NULL) + return NULL; + + stats->num_pages = RelationGetNumberOfBlocks(rel); + + return stats; +} diff --git a/packages/server/postgres/extensions/pgvector/src/hnswvacuum.o b/packages/server/postgres/extensions/pgvector/src/hnswvacuum.o new file mode 100644 index 0000000000000000000000000000000000000000..c2391ee0765ca88ee417530e954670977ed3c1e5 GIT binary patch literal 8976 zcmb_ieQ;A(cE9gEV?WtI01IpbYYPlni(oLpdS)3sgGp|fO)HcXc^J1r^cB+xdKKQdE8lC3?E7J>ka*r>nr zKK$`QcBj*QqxXLA+;i`_=bn4+Irl#Mr-aM5ED?tC5(8*W?$xI0v?N`B`t zG+irNO>J-)bQa#}&Y(hEvK&$NMYMN~c5T?{8l#Q3l4;r-$s>_vR>posMHwtcU|h27 z_eGSD+rMn3S|rO8<(27j%Jc@_8DqLo(Nw+M09p2gBOza4w_aeD*PzS$h0Z??dE+mA z_@hOT7i?*Ddm?gMV6QLGD9g_3Q6^r7ba|TPG0t4Z1eAMWa8au`LFCgY%g3!v#T2~Y z#)r?tP0psh#QlTgZFK?>6)qER5uUzV%-1NYp^)cG92W39`5FQ zhGe1V{*N8i}DcLkyMp1}87nUp%f*t={}tPcGgyDqTam8Q9n$KC zSqi4?3iDE`0XOW`HIK14&tlgE)XA83N~`yCmby%`rifck32(0RqHed4KZO3eaK}$q z30FB=tWNl;u0u0R-Q6msl0gZ&*-&`Adgu~ilykPX77c5VYX%;Az#IG zCR^5!$7tmoBRx2b;f9dcD*(ULZ8-$JDrszb9m0nwyABz!gIb;fE#^y6%g9@GK6WZ( z;Ve1!4gB^Zh3y&aWQhfK>E-@!)2@d(EHzZb66r8YlzUj*ew2%s6l094eRInMZ@nZ8kQ<40t`zl-q|2>n zn-3XzxVPuBIM#rb=5t_6$qAnzCH^8uir&m*)3YrsYT;O7;CU(aK*;H3oHW8pyoFdu zBgba5_*>twJEu72su8T6bEdPulkhH1m!cxa$_HM54*od@Z7M#sjbyD{P4b0Vz5kO= z=S{_&?aX8=Yfi+X=_lEqz6|EN^{RB#&c*5$ae2=V3X^-~{cO(%JaauaiN&99X+PrQ zCU^cTWE|cx<;2%ZrBom2uc6cRgOz7*agm+<=n7=ncjTP7xm-&5I7{b}_lqu6C~P14TlNm>iuYdqH?f|J z3wQP(IRN|i3kAJ5bbpFfvXwM{1DIEDHf;J$y1XV!S~Iv;N>p%S^Y}T;zMFOpu98xN zjGZqjkP>Ok6V|h&cz zgte~ubAOJMA|FU@m3lUzE+O$BqP!9`t+@fLxq(d-19X3f-iB|h{;$*bsIG^PiUCXglN~la!WU*!Ug^ph~2+Y+l%v9IUX}$fI9x3(J71C`Mm0zQn z@dfHUY4iu#*TGZ!W~`jr)B6pqrT+0elU$>p8-^Uc9g^+&2iiJJh?O0=SSJ_6)l#&3 z+&X#ZSKL+Tp^mpZ{K9JTOOi+H`v!Eu8k&LBsPd62tz%=z`vYUhQ}xCgC?|VJn9KBQ zu&+9XJotz{MtvNH-jLsQ3~}-}kGilgjeQ0BMr^Yhyc6SizeTLFP1j=80P6o0+IA4% zm`BK084p?TcUDvFzk@f7D%gPH6{_Wjb)*()randox@Pgu2R_6xqD znmH3=+B14gn}xY-d`fzmvtX?ww(d}4t7eC95gTSbOrO7;DJ3Y*{7owDnvMETpbyfO zbkbw&sIAAwp!xBiWwH3>%veGe#GAbj(yq(dQYtU{@{nOu)n^{1F?X5|pVr4oH1RHB zZBa}fm@lod*bwhHOS2EL!(4v|?Tj*GUJM!3-stZJ);h_>B)$7F){(jV0_0F%_F}EC z%R_0`71VPf##2nkKG52MxIEW^xGcoAGM69Gbt)3E=4ky6VmXqFORS4k;*CSu9~w&yf_Hydkc0r}CRtW_0k+8vD1B{HtFh>16;JrCN; z7j#-UuG5Z~o$co06=mgdB0d{e)P`)ErB8zYg2o(AV@`*g4fMB~D?V zVEAT*kk_1Cs_uu?wM}Cq|DoKh&W+yIpbftw*~EXGpL%G0>vN9zRCAV*6KKDs$_?rm z)=`h&{n)=^uO1(1--!MD2JGXjv7fKPzJ7hraImsxu=t<-mjwCK{w%#6bXVo+nB4jg z*zPJY8=TQMOKzTq@4uN&pYOqbsWzQ1 zj}=O(FDe-J1T#n1(Le>p&#@IG>kj5cbVx_UN$e{5isc2%3ED%ceuH<>g8dW6_9XM( znvJuVwH$SfSWfdf(q0X_RKY&$F(;KhuveVyHXQs7=7&QW51xQJuvVoyhZac z6SlMZtL?L_$U|jPqM)I=rv!7u^X$BDwv>2d4(;=@n-5@2zO_>Hjc24N(H!WJ_6?9( z4cS%bV?Fw+gfIM)I%k^iTPOJbY^+hzo8k%0&ue;)zb?$hUSekN7qI1*h|OPt|9kAG zf2hwL?E$Z&-3`6nuKBe0()MI(>@srL_}B$L^`WhUf>FD>F{d{m?+asp%hQ@O)|8PS z40F^(c(b`bY zk5q2FV{W_F<4k#=N?QvyX8ZU5j~wn#BrG1j2i5FS$K!Z7sHl($X3XxkFK#Cm2vaBYDBy)#h(*RQyUrvElm) zJ}&th!>q~e^DB+ENYLhPY}Cqa_)Y{FLEHR=;rT2a@+_s+n&>4SR;RQCUs7rmcdLs8 z)G4iQUuaXv-Rj-oS6Y-ngl%?*nyb~08jaE58x201VeR6}%EXJx=~{;y-yXihuh#ji z!9?8u4*wPHvkrR&{G!M3hx89uHQys&!lsq3@8fs;5&kECgueg>?X`@RP6Mt6ioh9+ zIWshL(8nl;aLUreD4ITYJ47Kz!kmOtfV(;R-sG;Km5I^U64ISrz zB&QEZa=JBi90!t|7?9-b140F7L_~nIfro)V z0KNjmC%e)Ja52yeTn^j@ECEWug}{8^0^p12jO1E@4qzWTeTp%Ew}$myz5TD|28@$^aED`htWCVM?jK$0Z4lH0ZH%AMXmfJAoce< zU@3kVfy6%pM9eKc4kSIgfOCKm;0jdB+K$7Fs%d7SBG9Z4Xlkp(_Fb=AA;AJ4`(E)r4^KnSSdOwin$E#uei$EIZHlR8` zKpKY)xD03o7NGt#4eK-X@*!+is644*{cRxi-w&j5Tmq6_=YdZGyMUxuE0Fv~0@CERu5@NOC>_QvGfq^?ydg`Ys@~kLi3Tko=$;NPch;AG>M3 zYmgkN|BHxsG|zoNn$I&pvcqv8*&zlbJ4AqFM?a9}QvyPybDf5cav(%I%QSS90MUhW zfrbtW%Jb0f7GmTxz|Vm&q4P5h9T$Q4CSKYH#E_lc8ahq^X?!O&tf%BIq24RNXMtWI z;&tgxpbh8*&IhgrO2B>~y3V|$<0&0uIyUGi>1fjtptj@ri~J>?jOcoB|d8_a?p8bVi;(4*?Qc)Iv9tS4$ zR@8^*AzOzniytnE;d#3#2_w+;V@%3a4A{7)5{-#4{w~vQqOCeDA?VWedrTtL-=@2A!Tu^+CU?(|<|zQOC%y??DF9Mn3&e<7adrKGTKsKk+jpot8)*Xd}Na)ag%j zev?MCe7(Q_ruMjhq0^Ui`hrgH)#;l$-K6XPp-z7c9jN_hI^D0=H^%oHqS2@EccPDA zizgt%xIVx;(Z+sb3HamKtOCS%dJ`Isr?I9c(Df$zs7c=ICV4?qeVd8SG_`-z#6O4e zjn^k&sz1ZjAFadj^}jaJR+GF(CVAB+{$`W>g(ezjy$St&YNFSgs(C!6He!*|Ih#_*YXkfNB& zs@g&!Y}z+tS09R2>Bj{$4k#g?XQwl`yK+}B6d`e=CEFuzD$z~^=+N(u%B?{kcK$M* zLeTMsKqM61Hda&{2rHq8aXyeAb7t_%vfR`b@W}F#$8c4-&Y-(-RKx^*>y)sfcU~Lt zMYbru-Ck%NX0kKrar-qRt@j5#&8kicamE#bBE5ICHo{Iu<8AKUN|;ShYr?t5FE4?i zC)lM<+3gGCZ|s_AwR3%fn7kuE-Phq%8_WRP>{*$ zu}$8sL|Q{Zk1x=KBE#TKm^$dw=85=%0VXS8bFQm5y^0 z(o*MW_9q228*6W4)4o}WxV5s@Cae-B``xXrN}y34Uzi*o|DWJ*9Pbe0kGb*|MQIFc z9)vb^in~#pI0#{{^A1s~7+P literal 0 HcmV?d00001 diff --git a/packages/server/postgres/extensions/pgvector/src/ivfbuild.c b/packages/server/postgres/extensions/pgvector/src/ivfbuild.c new file mode 100644 index 00000000000..cc4f7a3a99e --- /dev/null +++ b/packages/server/postgres/extensions/pgvector/src/ivfbuild.c @@ -0,0 +1,1111 @@ +#include "postgres.h" + +#include + +#include "access/parallel.h" +#include "access/xact.h" +#include "catalog/index.h" +#include "catalog/pg_operator_d.h" +#include "catalog/pg_type_d.h" +#include "ivfflat.h" +#include "miscadmin.h" +#include "storage/bufmgr.h" +#include "utils/memutils.h" +#include "tcop/tcopprot.h" + +#if PG_VERSION_NUM >= 140000 +#include "utils/backend_progress.h" +#elif PG_VERSION_NUM >= 120000 +#include "pgstat.h" +#endif + +#if PG_VERSION_NUM >= 120000 +#include "access/tableam.h" +#include "commands/progress.h" +#else +#define PROGRESS_CREATEIDX_SUBPHASE 0 +#define PROGRESS_CREATEIDX_TUPLES_TOTAL 0 +#define PROGRESS_CREATEIDX_TUPLES_DONE 0 +#endif + +#if PG_VERSION_NUM >= 130000 +#define CALLBACK_ITEM_POINTER ItemPointer tid +#else +#define CALLBACK_ITEM_POINTER HeapTuple hup +#endif + +#if PG_VERSION_NUM >= 120000 +#define UpdateProgress(index, val) pgstat_progress_update_param(index, val) +#else +#define UpdateProgress(index, val) ((void)val) +#endif + +#if PG_VERSION_NUM >= 140000 +#include "utils/backend_status.h" +#include "utils/wait_event.h" +#endif + +#if PG_VERSION_NUM >= 120000 +#include "access/table.h" +#include "optimizer/optimizer.h" +#else +#include "access/heapam.h" +#include "optimizer/planner.h" +#include "pgstat.h" +#endif + +#define PARALLEL_KEY_IVFFLAT_SHARED UINT64CONST(0xA000000000000001) +#define PARALLEL_KEY_TUPLESORT UINT64CONST(0xA000000000000002) +#define PARALLEL_KEY_IVFFLAT_CENTERS UINT64CONST(0xA000000000000003) +#define PARALLEL_KEY_QUERY_TEXT UINT64CONST(0xA000000000000004) + +/* + * Add sample + */ +static void +AddSample(Datum *values, IvfflatBuildState * buildstate) +{ + VectorArray samples = buildstate->samples; + int targsamples = samples->maxlen; + + /* Detoast once for all calls */ + Datum value = PointerGetDatum(PG_DETOAST_DATUM(values[0])); + + /* + * Normalize with KMEANS_NORM_PROC since spherical distance function + * expects unit vectors + */ + if (buildstate->kmeansnormprocinfo != NULL) + { + if (!IvfflatNormValue(buildstate->kmeansnormprocinfo, buildstate->collation, &value, buildstate->normvec)) + return; + } + + if (samples->length < targsamples) + { + VectorArraySet(samples, samples->length, DatumGetVector(value)); + samples->length++; + } + else + { + if (buildstate->rowstoskip < 0) + buildstate->rowstoskip = reservoir_get_next_S(&buildstate->rstate, samples->length, targsamples); + + if (buildstate->rowstoskip <= 0) + { +#if PG_VERSION_NUM >= 150000 + int k = (int) (targsamples * sampler_random_fract(&buildstate->rstate.randstate)); +#else + int k = (int) (targsamples * sampler_random_fract(buildstate->rstate.randstate)); +#endif + + Assert(k >= 0 && k < targsamples); + VectorArraySet(samples, k, DatumGetVector(value)); + } + + buildstate->rowstoskip -= 1; + } +} + +/* + * Callback for sampling + */ +static void +SampleCallback(Relation index, CALLBACK_ITEM_POINTER, Datum *values, + bool *isnull, bool tupleIsAlive, void *state) +{ + IvfflatBuildState *buildstate = (IvfflatBuildState *) state; + MemoryContext oldCtx; + + /* Skip nulls */ + if (isnull[0]) + return; + + /* Use memory context since detoast can allocate */ + oldCtx = MemoryContextSwitchTo(buildstate->tmpCtx); + + /* Add sample */ + AddSample(values, state); + + /* Reset memory context */ + MemoryContextSwitchTo(oldCtx); + MemoryContextReset(buildstate->tmpCtx); +} + +/* + * Sample rows with same logic as ANALYZE + */ +static void +SampleRows(IvfflatBuildState * buildstate) +{ + int targsamples = buildstate->samples->maxlen; + BlockNumber totalblocks = RelationGetNumberOfBlocks(buildstate->heap); + + buildstate->rowstoskip = -1; + + BlockSampler_Init(&buildstate->bs, totalblocks, targsamples, RandomInt()); + + reservoir_init_selection_state(&buildstate->rstate, targsamples); + while (BlockSampler_HasMore(&buildstate->bs)) + { + BlockNumber targblock = BlockSampler_Next(&buildstate->bs); + +#if PG_VERSION_NUM >= 120000 + table_index_build_range_scan(buildstate->heap, buildstate->index, buildstate->indexInfo, + false, true, false, targblock, 1, SampleCallback, (void *) buildstate, NULL); +#else + IndexBuildHeapRangeScan(buildstate->heap, buildstate->index, buildstate->indexInfo, + false, true, targblock, 1, SampleCallback, (void *) buildstate, NULL); +#endif + } +} + +/* + * Add tuple to sort + */ +static void +AddTupleToSort(Relation index, ItemPointer tid, Datum *values, IvfflatBuildState * buildstate) +{ + double distance; + double minDistance = DBL_MAX; + int closestCenter = 0; + VectorArray centers = buildstate->centers; + TupleTableSlot *slot = buildstate->slot; + + /* Detoast once for all calls */ + Datum value = PointerGetDatum(PG_DETOAST_DATUM(values[0])); + + /* Normalize if needed */ + if (buildstate->normprocinfo != NULL) + { + if (!IvfflatNormValue(buildstate->normprocinfo, buildstate->collation, &value, buildstate->normvec)) + return; + } + + /* Find the list that minimizes the distance */ + for (int i = 0; i < centers->length; i++) + { + distance = DatumGetFloat8(FunctionCall2Coll(buildstate->procinfo, buildstate->collation, value, PointerGetDatum(VectorArrayGet(centers, i)))); + + if (distance < minDistance) + { + minDistance = distance; + closestCenter = i; + } + } + +#ifdef IVFFLAT_KMEANS_DEBUG + buildstate->inertia += minDistance; + buildstate->listSums[closestCenter] += minDistance; + buildstate->listCounts[closestCenter]++; +#endif + + /* Create a virtual tuple */ + ExecClearTuple(slot); + slot->tts_values[0] = Int32GetDatum(closestCenter); + slot->tts_isnull[0] = false; + slot->tts_values[1] = PointerGetDatum(tid); + slot->tts_isnull[1] = false; + slot->tts_values[2] = value; + slot->tts_isnull[2] = false; + ExecStoreVirtualTuple(slot); + + /* + * Add tuple to sort + * + * tuplesort_puttupleslot comment: Input data is always copied; the caller + * need not save it. + */ + tuplesort_puttupleslot(buildstate->sortstate, slot); + + buildstate->indtuples++; +} + +/* + * Callback for table_index_build_scan + */ +static void +BuildCallback(Relation index, CALLBACK_ITEM_POINTER, Datum *values, + bool *isnull, bool tupleIsAlive, void *state) +{ + IvfflatBuildState *buildstate = (IvfflatBuildState *) state; + MemoryContext oldCtx; + +#if PG_VERSION_NUM < 130000 + ItemPointer tid = &hup->t_self; +#endif + + /* Skip nulls */ + if (isnull[0]) + return; + + /* Use memory context since detoast can allocate */ + oldCtx = MemoryContextSwitchTo(buildstate->tmpCtx); + + /* Add tuple to sort */ + AddTupleToSort(index, tid, values, buildstate); + + /* Reset memory context */ + MemoryContextSwitchTo(oldCtx); + MemoryContextReset(buildstate->tmpCtx); +} + +/* + * Get index tuple from sort state + */ +static inline void +GetNextTuple(Tuplesortstate *sortstate, TupleDesc tupdesc, TupleTableSlot *slot, IndexTuple *itup, int *list) +{ + Datum value; + bool isnull; + + if (tuplesort_gettupleslot(sortstate, true, false, slot, NULL)) + { + *list = DatumGetInt32(slot_getattr(slot, 1, &isnull)); + value = slot_getattr(slot, 3, &isnull); + + /* Form the index tuple */ + *itup = index_form_tuple(tupdesc, &value, &isnull); + (*itup)->t_tid = *((ItemPointer) DatumGetPointer(slot_getattr(slot, 2, &isnull))); + } + else + *list = -1; +} + +/* + * Create initial entry pages + */ +static void +InsertTuples(Relation index, IvfflatBuildState * buildstate, ForkNumber forkNum) +{ + int list; + IndexTuple itup = NULL; /* silence compiler warning */ + int64 inserted = 0; + +#if PG_VERSION_NUM >= 120000 + TupleTableSlot *slot = MakeSingleTupleTableSlot(buildstate->tupdesc, &TTSOpsMinimalTuple); +#else + TupleTableSlot *slot = MakeSingleTupleTableSlot(buildstate->tupdesc); +#endif + TupleDesc tupdesc = RelationGetDescr(index); + + UpdateProgress(PROGRESS_CREATEIDX_SUBPHASE, PROGRESS_IVFFLAT_PHASE_LOAD); + + UpdateProgress(PROGRESS_CREATEIDX_TUPLES_TOTAL, buildstate->indtuples); + + GetNextTuple(buildstate->sortstate, tupdesc, slot, &itup, &list); + + for (int i = 0; i < buildstate->centers->length; i++) + { + Buffer buf; + Page page; + GenericXLogState *state; + BlockNumber startPage; + BlockNumber insertPage; + + /* Can take a while, so ensure we can interrupt */ + /* Needs to be called when no buffer locks are held */ + CHECK_FOR_INTERRUPTS(); + + buf = IvfflatNewBuffer(index, forkNum); + IvfflatInitRegisterPage(index, &buf, &page, &state); + + startPage = BufferGetBlockNumber(buf); + + /* Get all tuples for list */ + while (list == i) + { + /* Check for free space */ + Size itemsz = MAXALIGN(IndexTupleSize(itup)); + + if (PageGetFreeSpace(page) < itemsz) + IvfflatAppendPage(index, &buf, &page, &state, forkNum); + + /* Add the item */ + if (PageAddItem(page, (Item) itup, itemsz, InvalidOffsetNumber, false, false) == InvalidOffsetNumber) + elog(ERROR, "failed to add index item to \"%s\"", RelationGetRelationName(index)); + + pfree(itup); + + UpdateProgress(PROGRESS_CREATEIDX_TUPLES_DONE, ++inserted); + + GetNextTuple(buildstate->sortstate, tupdesc, slot, &itup, &list); + } + + insertPage = BufferGetBlockNumber(buf); + + IvfflatCommitBuffer(buf, state); + + /* Set the start and insert pages */ + IvfflatUpdateList(index, buildstate->listInfo[i], insertPage, InvalidBlockNumber, startPage, forkNum); + } +} + +/* + * Initialize the build state + */ +static void +InitBuildState(IvfflatBuildState * buildstate, Relation heap, Relation index, IndexInfo *indexInfo) +{ + buildstate->heap = heap; + buildstate->index = index; + buildstate->indexInfo = indexInfo; + + buildstate->lists = IvfflatGetLists(index); + buildstate->dimensions = TupleDescAttr(index->rd_att, 0)->atttypmod; + + /* Require column to have dimensions to be indexed */ + if (buildstate->dimensions < 0) + elog(ERROR, "column does not have dimensions"); + + if (buildstate->dimensions > IVFFLAT_MAX_DIM) + elog(ERROR, "column cannot have more than %d dimensions for ivfflat index", IVFFLAT_MAX_DIM); + + buildstate->reltuples = 0; + buildstate->indtuples = 0; + + /* Get support functions */ + buildstate->procinfo = index_getprocinfo(index, 1, IVFFLAT_DISTANCE_PROC); + buildstate->normprocinfo = IvfflatOptionalProcInfo(index, IVFFLAT_NORM_PROC); + buildstate->kmeansnormprocinfo = IvfflatOptionalProcInfo(index, IVFFLAT_KMEANS_NORM_PROC); + buildstate->collation = index->rd_indcollation[0]; + + /* Require more than one dimension for spherical k-means */ + if (buildstate->kmeansnormprocinfo != NULL && buildstate->dimensions == 1) + elog(ERROR, "dimensions must be greater than one for this opclass"); + + /* Create tuple description for sorting */ +#if PG_VERSION_NUM >= 120000 + buildstate->tupdesc = CreateTemplateTupleDesc(3); +#else + buildstate->tupdesc = CreateTemplateTupleDesc(3, false); +#endif + TupleDescInitEntry(buildstate->tupdesc, (AttrNumber) 1, "list", INT4OID, -1, 0); + TupleDescInitEntry(buildstate->tupdesc, (AttrNumber) 2, "tid", TIDOID, -1, 0); + TupleDescInitEntry(buildstate->tupdesc, (AttrNumber) 3, "vector", RelationGetDescr(index)->attrs[0].atttypid, -1, 0); + +#if PG_VERSION_NUM >= 120000 + buildstate->slot = MakeSingleTupleTableSlot(buildstate->tupdesc, &TTSOpsVirtual); +#else + buildstate->slot = MakeSingleTupleTableSlot(buildstate->tupdesc); +#endif + + buildstate->centers = VectorArrayInit(buildstate->lists, buildstate->dimensions); + buildstate->listInfo = palloc(sizeof(ListInfo) * buildstate->lists); + + /* Reuse for each tuple */ + buildstate->normvec = InitVector(buildstate->dimensions); + + buildstate->tmpCtx = AllocSetContextCreate(CurrentMemoryContext, + "Ivfflat build temporary context", + ALLOCSET_DEFAULT_SIZES); + +#ifdef IVFFLAT_KMEANS_DEBUG + buildstate->inertia = 0; + buildstate->listSums = palloc0(sizeof(double) * buildstate->lists); + buildstate->listCounts = palloc0(sizeof(int) * buildstate->lists); +#endif + + buildstate->ivfleader = NULL; +} + +/* + * Free resources + */ +static void +FreeBuildState(IvfflatBuildState * buildstate) +{ + VectorArrayFree(buildstate->centers); + pfree(buildstate->listInfo); + pfree(buildstate->normvec); + +#ifdef IVFFLAT_KMEANS_DEBUG + pfree(buildstate->listSums); + pfree(buildstate->listCounts); +#endif + + MemoryContextDelete(buildstate->tmpCtx); +} + +/* + * Compute centers + */ +static void +ComputeCenters(IvfflatBuildState * buildstate) +{ + int numSamples; + + UpdateProgress(PROGRESS_CREATEIDX_SUBPHASE, PROGRESS_IVFFLAT_PHASE_KMEANS); + + /* Target 50 samples per list, with at least 10000 samples */ + /* The number of samples has a large effect on index build time */ + numSamples = buildstate->lists * 50; + if (numSamples < 10000) + numSamples = 10000; + + /* Skip samples for unlogged table */ + if (buildstate->heap == NULL) + numSamples = 1; + + /* Sample rows */ + /* TODO Ensure within maintenance_work_mem */ + buildstate->samples = VectorArrayInit(numSamples, buildstate->dimensions); + if (buildstate->heap != NULL) + { + SampleRows(buildstate); + + if (buildstate->samples->length < buildstate->lists) + { + ereport(NOTICE, + (errmsg("ivfflat index created with little data"), + errdetail("This will cause low recall."), + errhint("Drop the index until the table has more data."))); + } + } + + /* Calculate centers */ + IvfflatBench("k-means", IvfflatKmeans(buildstate->index, buildstate->samples, buildstate->centers)); + + /* Free samples before we allocate more memory */ + VectorArrayFree(buildstate->samples); +} + +/* + * Create the metapage + */ +static void +CreateMetaPage(Relation index, int dimensions, int lists, ForkNumber forkNum) +{ + Buffer buf; + Page page; + GenericXLogState *state; + IvfflatMetaPage metap; + + buf = IvfflatNewBuffer(index, forkNum); + IvfflatInitRegisterPage(index, &buf, &page, &state); + + /* Set metapage data */ + metap = IvfflatPageGetMeta(page); + metap->magicNumber = IVFFLAT_MAGIC_NUMBER; + metap->version = IVFFLAT_VERSION; + metap->dimensions = dimensions; + metap->lists = lists; + ((PageHeader) page)->pd_lower = + ((char *) metap + sizeof(IvfflatMetaPageData)) - (char *) page; + + IvfflatCommitBuffer(buf, state); +} + +/* + * Create list pages + */ +static void +CreateListPages(Relation index, VectorArray centers, int dimensions, + int lists, ForkNumber forkNum, ListInfo * *listInfo) +{ + Buffer buf; + Page page; + GenericXLogState *state; + OffsetNumber offno; + Size itemsz; + IvfflatList list; + + itemsz = MAXALIGN(IVFFLAT_LIST_SIZE(dimensions)); + list = palloc(itemsz); + + buf = IvfflatNewBuffer(index, forkNum); + IvfflatInitRegisterPage(index, &buf, &page, &state); + + for (int i = 0; i < lists; i++) + { + /* Load list */ + list->startPage = InvalidBlockNumber; + list->insertPage = InvalidBlockNumber; + memcpy(&list->center, VectorArrayGet(centers, i), VECTOR_SIZE(dimensions)); + + /* Ensure free space */ + if (PageGetFreeSpace(page) < itemsz) + IvfflatAppendPage(index, &buf, &page, &state, forkNum); + + /* Add the item */ + offno = PageAddItem(page, (Item) list, itemsz, InvalidOffsetNumber, false, false); + if (offno == InvalidOffsetNumber) + elog(ERROR, "failed to add index item to \"%s\"", RelationGetRelationName(index)); + + /* Save location info */ + (*listInfo)[i].blkno = BufferGetBlockNumber(buf); + (*listInfo)[i].offno = offno; + } + + IvfflatCommitBuffer(buf, state); + + pfree(list); +} + +/* + * Print k-means metrics + */ +#ifdef IVFFLAT_KMEANS_DEBUG +static void +PrintKmeansMetrics(IvfflatBuildState * buildstate) +{ + elog(INFO, "inertia: %.3e", buildstate->inertia); + + /* Calculate Davies-Bouldin index */ + if (buildstate->lists > 1 && !buildstate->ivfleader) + { + double db = 0.0; + + /* Calculate average distance */ + for (int i = 0; i < buildstate->lists; i++) + { + if (buildstate->listCounts[i] > 0) + buildstate->listSums[i] /= buildstate->listCounts[i]; + } + + for (int i = 0; i < buildstate->lists; i++) + { + double max = 0.0; + double distance; + + for (int j = 0; j < buildstate->lists; j++) + { + if (j == i) + continue; + + distance = DatumGetFloat8(FunctionCall2Coll(buildstate->procinfo, buildstate->collation, PointerGetDatum(VectorArrayGet(buildstate->centers, i)), PointerGetDatum(VectorArrayGet(buildstate->centers, j)))); + distance = (buildstate->listSums[i] + buildstate->listSums[j]) / distance; + + if (distance > max) + max = distance; + } + db += max; + } + db /= buildstate->lists; + elog(INFO, "davies-bouldin: %.3f", db); + } +} +#endif + +/* + * Within leader, wait for end of heap scan + */ +static double +ParallelHeapScan(IvfflatBuildState * buildstate) +{ + IvfflatShared *ivfshared = buildstate->ivfleader->ivfshared; + int nparticipanttuplesorts; + double reltuples; + + nparticipanttuplesorts = buildstate->ivfleader->nparticipanttuplesorts; + for (;;) + { + SpinLockAcquire(&ivfshared->mutex); + if (ivfshared->nparticipantsdone == nparticipanttuplesorts) + { + buildstate->indtuples = ivfshared->indtuples; + reltuples = ivfshared->reltuples; +#ifdef IVFFLAT_KMEANS_DEBUG + buildstate->inertia = ivfshared->inertia; +#endif + SpinLockRelease(&ivfshared->mutex); + break; + } + SpinLockRelease(&ivfshared->mutex); + + ConditionVariableSleep(&ivfshared->workersdonecv, + WAIT_EVENT_PARALLEL_CREATE_INDEX_SCAN); + } + + ConditionVariableCancelSleep(); + + return reltuples; +} + +/* + * Perform a worker's portion of a parallel sort + */ +static void +IvfflatParallelScanAndSort(IvfflatSpool * ivfspool, IvfflatShared * ivfshared, Sharedsort *sharedsort, Vector * ivfcenters, int sortmem, bool progress) +{ + SortCoordinate coordinate; + IvfflatBuildState buildstate; +#if PG_VERSION_NUM >= 120000 + TableScanDesc scan; +#else + HeapScanDesc scan; +#endif + double reltuples; + IndexInfo *indexInfo; + + /* Sort options, which must match AssignTuples */ + AttrNumber attNums[] = {1}; + Oid sortOperators[] = {Int4LessOperator}; + Oid sortCollations[] = {InvalidOid}; + bool nullsFirstFlags[] = {false}; + + /* Initialize local tuplesort coordination state */ + coordinate = palloc0(sizeof(SortCoordinateData)); + coordinate->isWorker = true; + coordinate->nParticipants = -1; + coordinate->sharedsort = sharedsort; + + /* Join parallel scan */ + indexInfo = BuildIndexInfo(ivfspool->index); + indexInfo->ii_Concurrent = ivfshared->isconcurrent; + InitBuildState(&buildstate, ivfspool->heap, ivfspool->index, indexInfo); + memcpy(buildstate.centers->items, ivfcenters, VECTOR_SIZE(buildstate.centers->dim) * buildstate.centers->maxlen); + buildstate.centers->length = buildstate.centers->maxlen; + ivfspool->sortstate = tuplesort_begin_heap(buildstate.tupdesc, 1, attNums, sortOperators, sortCollations, nullsFirstFlags, sortmem, coordinate, false); + buildstate.sortstate = ivfspool->sortstate; +#if PG_VERSION_NUM >= 120000 + scan = table_beginscan_parallel(ivfspool->heap, + ParallelTableScanFromIvfflatShared(ivfshared)); + reltuples = table_index_build_scan(ivfspool->heap, ivfspool->index, indexInfo, + true, progress, BuildCallback, + (void *) &buildstate, scan); +#else + scan = heap_beginscan_parallel(ivfspool->heap, &ivfshared->heapdesc); + reltuples = IndexBuildHeapScan(ivfspool->heap, ivfspool->index, indexInfo, + true, BuildCallback, + (void *) &buildstate, scan); +#endif + + /* Execute this worker's part of the sort */ + tuplesort_performsort(ivfspool->sortstate); + + /* Record statistics */ + SpinLockAcquire(&ivfshared->mutex); + ivfshared->nparticipantsdone++; + ivfshared->reltuples += reltuples; + ivfshared->indtuples += buildstate.indtuples; +#ifdef IVFFLAT_KMEANS_DEBUG + ivfshared->inertia += buildstate.inertia; +#endif + SpinLockRelease(&ivfshared->mutex); + + /* Log statistics */ + if (progress) + ereport(DEBUG1, (errmsg("leader processed " INT64_FORMAT " tuples", (int64) reltuples))); + else + ereport(DEBUG1, (errmsg("worker processed " INT64_FORMAT " tuples", (int64) reltuples))); + + /* Notify leader */ + ConditionVariableSignal(&ivfshared->workersdonecv); + + /* We can end tuplesorts immediately */ + tuplesort_end(ivfspool->sortstate); + + FreeBuildState(&buildstate); +} + +/* + * Perform work within a launched parallel process + */ +void +IvfflatParallelBuildMain(dsm_segment *seg, shm_toc *toc) +{ + char *sharedquery; + IvfflatSpool *ivfspool; + IvfflatShared *ivfshared; + Sharedsort *sharedsort; + Vector *ivfcenters; + Relation heapRel; + Relation indexRel; + LOCKMODE heapLockmode; + LOCKMODE indexLockmode; + int sortmem; + + /* Set debug_query_string for individual workers first */ + sharedquery = shm_toc_lookup(toc, PARALLEL_KEY_QUERY_TEXT, true); + debug_query_string = sharedquery; + + /* Report the query string from leader */ + pgstat_report_activity(STATE_RUNNING, debug_query_string); + + /* Look up shared state */ + ivfshared = shm_toc_lookup(toc, PARALLEL_KEY_IVFFLAT_SHARED, false); + + /* Open relations using lock modes known to be obtained by index.c */ + if (!ivfshared->isconcurrent) + { + heapLockmode = ShareLock; + indexLockmode = AccessExclusiveLock; + } + else + { + heapLockmode = ShareUpdateExclusiveLock; + indexLockmode = RowExclusiveLock; + } + + /* Open relations within worker */ +#if PG_VERSION_NUM >= 120000 + heapRel = table_open(ivfshared->heaprelid, heapLockmode); +#else + heapRel = heap_open(ivfshared->heaprelid, heapLockmode); +#endif + indexRel = index_open(ivfshared->indexrelid, indexLockmode); + + /* Initialize worker's own spool */ + ivfspool = (IvfflatSpool *) palloc0(sizeof(IvfflatSpool)); + ivfspool->heap = heapRel; + ivfspool->index = indexRel; + + /* Look up shared state private to tuplesort.c */ + sharedsort = shm_toc_lookup(toc, PARALLEL_KEY_TUPLESORT, false); + tuplesort_attach_shared(sharedsort, seg); + + ivfcenters = shm_toc_lookup(toc, PARALLEL_KEY_IVFFLAT_CENTERS, false); + + /* Perform sorting */ + sortmem = maintenance_work_mem / ivfshared->scantuplesortstates; + IvfflatParallelScanAndSort(ivfspool, ivfshared, sharedsort, ivfcenters, sortmem, false); + + /* Close relations within worker */ + index_close(indexRel, indexLockmode); +#if PG_VERSION_NUM >= 120000 + table_close(heapRel, heapLockmode); +#else + heap_close(heapRel, heapLockmode); +#endif +} + +/* + * End parallel build + */ +static void +IvfflatEndParallel(IvfflatLeader * ivfleader) +{ + /* Shutdown worker processes */ + WaitForParallelWorkersToFinish(ivfleader->pcxt); + + /* Free last reference to MVCC snapshot, if one was used */ + if (IsMVCCSnapshot(ivfleader->snapshot)) + UnregisterSnapshot(ivfleader->snapshot); + DestroyParallelContext(ivfleader->pcxt); + ExitParallelMode(); +} + +/* + * Return size of shared memory required for parallel index build + */ +static Size +ParallelEstimateShared(Relation heap, Snapshot snapshot) +{ +#if PG_VERSION_NUM >= 120000 + return add_size(BUFFERALIGN(sizeof(IvfflatShared)), table_parallelscan_estimate(heap, snapshot)); +#else + if (!IsMVCCSnapshot(snapshot)) + { + Assert(snapshot == SnapshotAny); + return sizeof(IvfflatShared); + } + + return add_size(offsetof(IvfflatShared, heapdesc) + + offsetof(ParallelHeapScanDescData, phs_snapshot_data), + EstimateSnapshotSpace(snapshot)); +#endif +} + +/* + * Within leader, participate as a parallel worker + */ +static void +IvfflatLeaderParticipateAsWorker(IvfflatBuildState * buildstate) +{ + IvfflatLeader *ivfleader = buildstate->ivfleader; + IvfflatSpool *leaderworker; + int sortmem; + + /* Allocate memory and initialize private spool */ + leaderworker = (IvfflatSpool *) palloc0(sizeof(IvfflatSpool)); + leaderworker->heap = buildstate->heap; + leaderworker->index = buildstate->index; + + /* Perform work common to all participants */ + sortmem = maintenance_work_mem / ivfleader->nparticipanttuplesorts; + IvfflatParallelScanAndSort(leaderworker, ivfleader->ivfshared, + ivfleader->sharedsort, ivfleader->ivfcenters, + sortmem, true); +} + +/* + * Begin parallel build + */ +static void +IvfflatBeginParallel(IvfflatBuildState * buildstate, bool isconcurrent, int request) +{ + ParallelContext *pcxt; + int scantuplesortstates; + Snapshot snapshot; + Size estivfshared; + Size estsort; + Size estcenters; + IvfflatShared *ivfshared; + Sharedsort *sharedsort; + Vector *ivfcenters; + IvfflatLeader *ivfleader = (IvfflatLeader *) palloc0(sizeof(IvfflatLeader)); + bool leaderparticipates = true; + int querylen; + +#ifdef DISABLE_LEADER_PARTICIPATION + leaderparticipates = false; +#endif + + /* Enter parallel mode and create context */ + EnterParallelMode(); + Assert(request > 0); +#if PG_VERSION_NUM >= 120000 + pcxt = CreateParallelContext("vector", "IvfflatParallelBuildMain", request); +#else + pcxt = CreateParallelContext("vector", "IvfflatParallelBuildMain", request, true); +#endif + + scantuplesortstates = leaderparticipates ? request + 1 : request; + + /* Get snapshot for table scan */ + if (!isconcurrent) + snapshot = SnapshotAny; + else + snapshot = RegisterSnapshot(GetTransactionSnapshot()); + + /* Estimate size of workspaces */ + estivfshared = ParallelEstimateShared(buildstate->heap, snapshot); + shm_toc_estimate_chunk(&pcxt->estimator, estivfshared); + estsort = tuplesort_estimate_shared(scantuplesortstates); + shm_toc_estimate_chunk(&pcxt->estimator, estsort); + estcenters = VECTOR_SIZE(buildstate->dimensions) * buildstate->lists; + shm_toc_estimate_chunk(&pcxt->estimator, estcenters); + shm_toc_estimate_keys(&pcxt->estimator, 3); + + /* Finally, estimate PARALLEL_KEY_QUERY_TEXT space */ + if (debug_query_string) + { + querylen = strlen(debug_query_string); + shm_toc_estimate_chunk(&pcxt->estimator, querylen + 1); + shm_toc_estimate_keys(&pcxt->estimator, 1); + } + else + querylen = 0; /* keep compiler quiet */ + + /* Everyone's had a chance to ask for space, so now create the DSM */ + InitializeParallelDSM(pcxt); + + /* If no DSM segment was available, back out (do serial build) */ + if (pcxt->seg == NULL) + { + if (IsMVCCSnapshot(snapshot)) + UnregisterSnapshot(snapshot); + DestroyParallelContext(pcxt); + ExitParallelMode(); + return; + } + + /* Store shared build state, for which we reserved space */ + ivfshared = (IvfflatShared *) shm_toc_allocate(pcxt->toc, estivfshared); + /* Initialize immutable state */ + ivfshared->heaprelid = RelationGetRelid(buildstate->heap); + ivfshared->indexrelid = RelationGetRelid(buildstate->index); + ivfshared->isconcurrent = isconcurrent; + ivfshared->scantuplesortstates = scantuplesortstates; + ConditionVariableInit(&ivfshared->workersdonecv); + SpinLockInit(&ivfshared->mutex); + /* Initialize mutable state */ + ivfshared->nparticipantsdone = 0; + ivfshared->reltuples = 0; + ivfshared->indtuples = 0; +#ifdef IVFFLAT_KMEANS_DEBUG + ivfshared->inertia = 0; +#endif +#if PG_VERSION_NUM >= 120000 + table_parallelscan_initialize(buildstate->heap, + ParallelTableScanFromIvfflatShared(ivfshared), + snapshot); +#else + heap_parallelscan_initialize(&ivfshared->heapdesc, buildstate->heap, snapshot); +#endif + + /* Store shared tuplesort-private state, for which we reserved space */ + sharedsort = (Sharedsort *) shm_toc_allocate(pcxt->toc, estsort); + tuplesort_initialize_shared(sharedsort, scantuplesortstates, + pcxt->seg); + + ivfcenters = (Vector *) shm_toc_allocate(pcxt->toc, estcenters); + memcpy(ivfcenters, buildstate->centers->items, estcenters); + + shm_toc_insert(pcxt->toc, PARALLEL_KEY_IVFFLAT_SHARED, ivfshared); + shm_toc_insert(pcxt->toc, PARALLEL_KEY_TUPLESORT, sharedsort); + shm_toc_insert(pcxt->toc, PARALLEL_KEY_IVFFLAT_CENTERS, ivfcenters); + + /* Store query string for workers */ + if (debug_query_string) + { + char *sharedquery; + + sharedquery = (char *) shm_toc_allocate(pcxt->toc, querylen + 1); + memcpy(sharedquery, debug_query_string, querylen + 1); + shm_toc_insert(pcxt->toc, PARALLEL_KEY_QUERY_TEXT, sharedquery); + } + + /* Launch workers, saving status for leader/caller */ + LaunchParallelWorkers(pcxt); + ivfleader->pcxt = pcxt; + ivfleader->nparticipanttuplesorts = pcxt->nworkers_launched; + if (leaderparticipates) + ivfleader->nparticipanttuplesorts++; + ivfleader->ivfshared = ivfshared; + ivfleader->sharedsort = sharedsort; + ivfleader->snapshot = snapshot; + ivfleader->ivfcenters = ivfcenters; + + /* If no workers were successfully launched, back out (do serial build) */ + if (pcxt->nworkers_launched == 0) + { + IvfflatEndParallel(ivfleader); + return; + } + + /* Log participants */ + ereport(DEBUG1, (errmsg("using %d parallel workers", pcxt->nworkers_launched))); + + /* Save leader state now that it's clear build will be parallel */ + buildstate->ivfleader = ivfleader; + + /* Join heap scan ourselves */ + if (leaderparticipates) + IvfflatLeaderParticipateAsWorker(buildstate); + + /* Wait for all launched workers */ + WaitForParallelWorkersToAttach(pcxt); +} + +/* + * Scan table for tuples to index + */ +static void +AssignTuples(IvfflatBuildState * buildstate) +{ + int parallel_workers = 0; + SortCoordinate coordinate = NULL; + + /* Sort options, which must match IvfflatParallelScanAndSort */ + AttrNumber attNums[] = {1}; + Oid sortOperators[] = {Int4LessOperator}; + Oid sortCollations[] = {InvalidOid}; + bool nullsFirstFlags[] = {false}; + + UpdateProgress(PROGRESS_CREATEIDX_SUBPHASE, PROGRESS_IVFFLAT_PHASE_ASSIGN); + + /* Calculate parallel workers */ + if (buildstate->heap != NULL) + parallel_workers = plan_create_index_workers(RelationGetRelid(buildstate->heap), RelationGetRelid(buildstate->index)); + + /* Attempt to launch parallel worker scan when required */ + if (parallel_workers > 0) + IvfflatBeginParallel(buildstate, buildstate->indexInfo->ii_Concurrent, parallel_workers); + + /* Set up coordination state if at least one worker launched */ + if (buildstate->ivfleader) + { + coordinate = (SortCoordinate) palloc0(sizeof(SortCoordinateData)); + coordinate->isWorker = false; + coordinate->nParticipants = buildstate->ivfleader->nparticipanttuplesorts; + coordinate->sharedsort = buildstate->ivfleader->sharedsort; + } + + /* Begin serial/leader tuplesort */ + buildstate->sortstate = tuplesort_begin_heap(buildstate->tupdesc, 1, attNums, sortOperators, sortCollations, nullsFirstFlags, maintenance_work_mem, coordinate, false); + + /* Add tuples to sort */ + if (buildstate->heap != NULL) + { + if (buildstate->ivfleader) + buildstate->reltuples = ParallelHeapScan(buildstate); + else + { +#if PG_VERSION_NUM >= 120000 + buildstate->reltuples = table_index_build_scan(buildstate->heap, buildstate->index, buildstate->indexInfo, + true, true, BuildCallback, (void *) buildstate, NULL); +#else + buildstate->reltuples = IndexBuildHeapScan(buildstate->heap, buildstate->index, buildstate->indexInfo, + true, BuildCallback, (void *) buildstate, NULL); +#endif + } + +#ifdef IVFFLAT_KMEANS_DEBUG + PrintKmeansMetrics(buildstate); +#endif + } +} + +/* + * Create entry pages + */ +static void +CreateEntryPages(IvfflatBuildState * buildstate, ForkNumber forkNum) +{ + /* Assign */ + IvfflatBench("assign tuples", AssignTuples(buildstate)); + + /* Sort */ + IvfflatBench("sort tuples", tuplesort_performsort(buildstate->sortstate)); + + /* Load */ + IvfflatBench("load tuples", InsertTuples(buildstate->index, buildstate, forkNum)); + + /* End sort */ + tuplesort_end(buildstate->sortstate); + + /* End parallel build */ + if (buildstate->ivfleader) + IvfflatEndParallel(buildstate->ivfleader); +} + +/* + * Build the index + */ +static void +BuildIndex(Relation heap, Relation index, IndexInfo *indexInfo, + IvfflatBuildState * buildstate, ForkNumber forkNum) +{ + InitBuildState(buildstate, heap, index, indexInfo); + + ComputeCenters(buildstate); + + /* Create pages */ + CreateMetaPage(index, buildstate->dimensions, buildstate->lists, forkNum); + CreateListPages(index, buildstate->centers, buildstate->dimensions, buildstate->lists, forkNum, &buildstate->listInfo); + CreateEntryPages(buildstate, forkNum); + + FreeBuildState(buildstate); +} + +/* + * Build the index for a logged table + */ +IndexBuildResult * +ivfflatbuild(Relation heap, Relation index, IndexInfo *indexInfo) +{ + IndexBuildResult *result; + IvfflatBuildState buildstate; + + BuildIndex(heap, index, indexInfo, &buildstate, MAIN_FORKNUM); + + result = (IndexBuildResult *) palloc(sizeof(IndexBuildResult)); + result->heap_tuples = buildstate.reltuples; + result->index_tuples = buildstate.indtuples; + + return result; +} + +/* + * Build the index for an unlogged table + */ +void +ivfflatbuildempty(Relation index) +{ + IndexInfo *indexInfo = BuildIndexInfo(index); + IvfflatBuildState buildstate; + + BuildIndex(NULL, index, indexInfo, &buildstate, INIT_FORKNUM); +} diff --git a/packages/server/postgres/extensions/pgvector/src/ivfbuild.o b/packages/server/postgres/extensions/pgvector/src/ivfbuild.o new file mode 100644 index 0000000000000000000000000000000000000000..c514832e50c0b6985dd02c16799805b06302c894 GIT binary patch literal 14592 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 literal 0 HcmV?d00001 diff --git a/packages/server/postgres/extensions/pgvector/src/ivfflat.c b/packages/server/postgres/extensions/pgvector/src/ivfflat.c new file mode 100644 index 00000000000..5057adc2aef --- /dev/null +++ b/packages/server/postgres/extensions/pgvector/src/ivfflat.c @@ -0,0 +1,251 @@ +#include "postgres.h" + +#include + +#include "access/amapi.h" +#include "commands/vacuum.h" +#include "ivfflat.h" +#include "utils/guc.h" +#include "utils/selfuncs.h" +#include "utils/spccache.h" + +#if PG_VERSION_NUM >= 120000 +#include "commands/progress.h" +#endif + +int ivfflat_probes; +static relopt_kind ivfflat_relopt_kind; + +/* + * Initialize index options and variables + */ +void +IvfflatInit(void) +{ + ivfflat_relopt_kind = add_reloption_kind(); + add_int_reloption(ivfflat_relopt_kind, "lists", "Number of inverted lists", + IVFFLAT_DEFAULT_LISTS, IVFFLAT_MIN_LISTS, IVFFLAT_MAX_LISTS +#if PG_VERSION_NUM >= 130000 + ,AccessExclusiveLock +#endif + ); + + DefineCustomIntVariable("ivfflat.probes", "Sets the number of probes", + "Valid range is 1..lists.", &ivfflat_probes, + IVFFLAT_DEFAULT_PROBES, IVFFLAT_MIN_LISTS, IVFFLAT_MAX_LISTS, PGC_USERSET, 0, NULL, NULL, NULL); +} + +/* + * Get the name of index build phase + */ +#if PG_VERSION_NUM >= 120000 +static char * +ivfflatbuildphasename(int64 phasenum) +{ + switch (phasenum) + { + case PROGRESS_CREATEIDX_SUBPHASE_INITIALIZE: + return "initializing"; + case PROGRESS_IVFFLAT_PHASE_KMEANS: + return "performing k-means"; + case PROGRESS_IVFFLAT_PHASE_ASSIGN: + return "assigning tuples"; + case PROGRESS_IVFFLAT_PHASE_LOAD: + return "loading tuples"; + default: + return NULL; + } +} +#endif + +/* + * Estimate the cost of an index scan + */ +static void +ivfflatcostestimate(PlannerInfo *root, IndexPath *path, double loop_count, + Cost *indexStartupCost, Cost *indexTotalCost, + Selectivity *indexSelectivity, double *indexCorrelation, + double *indexPages) +{ + GenericCosts costs; + int lists; + double ratio; + double spc_seq_page_cost; + Relation indexRel; +#if PG_VERSION_NUM < 120000 + List *qinfos; +#endif + + /* Never use index without order */ + if (path->indexorderbys == NULL) + { + *indexStartupCost = DBL_MAX; + *indexTotalCost = DBL_MAX; + *indexSelectivity = 0; + *indexCorrelation = 0; + *indexPages = 0; + return; + } + + MemSet(&costs, 0, sizeof(costs)); + + indexRel = index_open(path->indexinfo->indexoid, NoLock); + lists = IvfflatGetLists(indexRel); + index_close(indexRel, NoLock); + + /* Get the ratio of lists that we need to visit */ + ratio = ((double) ivfflat_probes) / lists; + if (ratio > 1.0) + ratio = 1.0; + + /* + * This gives us the subset of tuples to visit. This value is passed into + * the generic cost estimator to determine the number of pages to visit + * during the index scan. + */ + costs.numIndexTuples = path->indexinfo->tuples * ratio; + +#if PG_VERSION_NUM >= 120000 + genericcostestimate(root, path, loop_count, &costs); +#else + qinfos = deconstruct_indexquals(path); + genericcostestimate(root, path, loop_count, qinfos, &costs); +#endif + + get_tablespace_page_costs(path->indexinfo->reltablespace, NULL, &spc_seq_page_cost); + + /* Adjust cost if needed since TOAST not included in seq scan cost */ + if (costs.numIndexPages > path->indexinfo->rel->pages && ratio < 0.5) + { + /* Change all page cost from random to sequential */ + costs.indexTotalCost -= costs.numIndexPages * (costs.spc_random_page_cost - spc_seq_page_cost); + + /* Remove cost of extra pages */ + costs.indexTotalCost -= (costs.numIndexPages - path->indexinfo->rel->pages) * spc_seq_page_cost; + } + else + { + /* Change some page cost from random to sequential */ + costs.indexTotalCost -= 0.5 * costs.numIndexPages * (costs.spc_random_page_cost - spc_seq_page_cost); + } + + /* + * If the list selectivity is lower than what is returned from the generic + * cost estimator, use that. + */ + if (ratio < costs.indexSelectivity) + costs.indexSelectivity = ratio; + + /* Use total cost since most work happens before first tuple is returned */ + *indexStartupCost = costs.indexTotalCost; + *indexTotalCost = costs.indexTotalCost; + *indexSelectivity = costs.indexSelectivity; + *indexCorrelation = costs.indexCorrelation; + *indexPages = costs.numIndexPages; +} + +/* + * Parse and validate the reloptions + */ +static bytea * +ivfflatoptions(Datum reloptions, bool validate) +{ + static const relopt_parse_elt tab[] = { + {"lists", RELOPT_TYPE_INT, offsetof(IvfflatOptions, lists)}, + }; + +#if PG_VERSION_NUM >= 130000 + return (bytea *) build_reloptions(reloptions, validate, + ivfflat_relopt_kind, + sizeof(IvfflatOptions), + tab, lengthof(tab)); +#else + relopt_value *options; + int numoptions; + IvfflatOptions *rdopts; + + options = parseRelOptions(reloptions, validate, ivfflat_relopt_kind, &numoptions); + rdopts = allocateReloptStruct(sizeof(IvfflatOptions), options, numoptions); + fillRelOptions((void *) rdopts, sizeof(IvfflatOptions), options, numoptions, + validate, tab, lengthof(tab)); + + return (bytea *) rdopts; +#endif +} + +/* + * Validate catalog entries for the specified operator class + */ +static bool +ivfflatvalidate(Oid opclassoid) +{ + return true; +} + +/* + * Define index handler + * + * See https://www.postgresql.org/docs/current/index-api.html + */ +PGDLLEXPORT PG_FUNCTION_INFO_V1(ivfflathandler); +Datum +ivfflathandler(PG_FUNCTION_ARGS) +{ + IndexAmRoutine *amroutine = makeNode(IndexAmRoutine); + + amroutine->amstrategies = 0; + amroutine->amsupport = 4; +#if PG_VERSION_NUM >= 130000 + amroutine->amoptsprocnum = 0; +#endif + amroutine->amcanorder = false; + amroutine->amcanorderbyop = true; + amroutine->amcanbackward = false; /* can change direction mid-scan */ + amroutine->amcanunique = false; + amroutine->amcanmulticol = false; + amroutine->amoptionalkey = true; + amroutine->amsearcharray = false; + amroutine->amsearchnulls = false; + amroutine->amstorage = false; + amroutine->amclusterable = false; + amroutine->ampredlocks = false; + amroutine->amcanparallel = false; + amroutine->amcaninclude = false; +#if PG_VERSION_NUM >= 130000 + amroutine->amusemaintenanceworkmem = false; /* not used during VACUUM */ + amroutine->amparallelvacuumoptions = VACUUM_OPTION_PARALLEL_BULKDEL; +#endif + amroutine->amkeytype = InvalidOid; + + /* Interface functions */ + amroutine->ambuild = ivfflatbuild; + amroutine->ambuildempty = ivfflatbuildempty; + amroutine->aminsert = ivfflatinsert; + amroutine->ambulkdelete = ivfflatbulkdelete; + amroutine->amvacuumcleanup = ivfflatvacuumcleanup; + amroutine->amcanreturn = NULL; /* tuple not included in heapsort */ + amroutine->amcostestimate = ivfflatcostestimate; + amroutine->amoptions = ivfflatoptions; + amroutine->amproperty = NULL; /* TODO AMPROP_DISTANCE_ORDERABLE */ +#if PG_VERSION_NUM >= 120000 + amroutine->ambuildphasename = ivfflatbuildphasename; +#endif + amroutine->amvalidate = ivfflatvalidate; +#if PG_VERSION_NUM >= 140000 + amroutine->amadjustmembers = NULL; +#endif + amroutine->ambeginscan = ivfflatbeginscan; + amroutine->amrescan = ivfflatrescan; + amroutine->amgettuple = ivfflatgettuple; + amroutine->amgetbitmap = NULL; + amroutine->amendscan = ivfflatendscan; + amroutine->ammarkpos = NULL; + amroutine->amrestrpos = NULL; + + /* Interface functions to support parallel index scans */ + amroutine->amestimateparallelscan = NULL; + amroutine->aminitparallelscan = NULL; + amroutine->amparallelrescan = NULL; + + PG_RETURN_POINTER(amroutine); +} diff --git a/packages/server/postgres/extensions/pgvector/src/ivfflat.h b/packages/server/postgres/extensions/pgvector/src/ivfflat.h new file mode 100644 index 00000000000..4e701e2f003 --- /dev/null +++ b/packages/server/postgres/extensions/pgvector/src/ivfflat.h @@ -0,0 +1,306 @@ +#ifndef IVFFLAT_H +#define IVFFLAT_H + +#include "postgres.h" + +#include "access/generic_xlog.h" +#include "access/parallel.h" +#include "access/reloptions.h" +#include "nodes/execnodes.h" +#include "port.h" /* for random() */ +#include "utils/sampling.h" +#include "utils/tuplesort.h" +#include "vector.h" + +#if PG_VERSION_NUM >= 150000 +#include "common/pg_prng.h" +#endif + +#if PG_VERSION_NUM < 120000 +#include "access/relscan.h" +#endif + +#ifdef IVFFLAT_BENCH +#include "portability/instr_time.h" +#endif + +#define IVFFLAT_MAX_DIM 2000 + +/* Support functions */ +#define IVFFLAT_DISTANCE_PROC 1 +#define IVFFLAT_NORM_PROC 2 +#define IVFFLAT_KMEANS_DISTANCE_PROC 3 +#define IVFFLAT_KMEANS_NORM_PROC 4 + +#define IVFFLAT_VERSION 1 +#define IVFFLAT_MAGIC_NUMBER 0x14FF1A7 +#define IVFFLAT_PAGE_ID 0xFF84 + +/* Preserved page numbers */ +#define IVFFLAT_METAPAGE_BLKNO 0 +#define IVFFLAT_HEAD_BLKNO 1 /* first list page */ + +/* IVFFlat parameters */ +#define IVFFLAT_DEFAULT_LISTS 100 +#define IVFFLAT_MIN_LISTS 1 +#define IVFFLAT_MAX_LISTS 32768 +#define IVFFLAT_DEFAULT_PROBES 1 + +/* Build phases */ +/* PROGRESS_CREATEIDX_SUBPHASE_INITIALIZE is 1 */ +#define PROGRESS_IVFFLAT_PHASE_KMEANS 2 +#define PROGRESS_IVFFLAT_PHASE_ASSIGN 3 +#define PROGRESS_IVFFLAT_PHASE_LOAD 4 + +#define IVFFLAT_LIST_SIZE(_dim) (offsetof(IvfflatListData, center) + VECTOR_SIZE(_dim)) + +#define IvfflatPageGetOpaque(page) ((IvfflatPageOpaque) PageGetSpecialPointer(page)) +#define IvfflatPageGetMeta(page) ((IvfflatMetaPageData *) PageGetContents(page)) + +#ifdef IVFFLAT_BENCH +#define IvfflatBench(name, code) \ + do { \ + instr_time start; \ + instr_time duration; \ + INSTR_TIME_SET_CURRENT(start); \ + (code); \ + INSTR_TIME_SET_CURRENT(duration); \ + INSTR_TIME_SUBTRACT(duration, start); \ + elog(INFO, "%s: %.3f ms", name, INSTR_TIME_GET_MILLISEC(duration)); \ + } while (0) +#else +#define IvfflatBench(name, code) (code) +#endif + +#if PG_VERSION_NUM >= 150000 +#define RandomDouble() pg_prng_double(&pg_global_prng_state) +#define RandomInt() pg_prng_uint32(&pg_global_prng_state) +#else +#define RandomDouble() (((double) random()) / MAX_RANDOM_VALUE) +#define RandomInt() random() +#endif + +/* Variables */ +extern int ivfflat_probes; + +typedef struct VectorArrayData +{ + int length; + int maxlen; + int dim; + Vector *items; +} VectorArrayData; + +typedef VectorArrayData * VectorArray; + +typedef struct ListInfo +{ + BlockNumber blkno; + OffsetNumber offno; +} ListInfo; + +/* IVFFlat index options */ +typedef struct IvfflatOptions +{ + int32 vl_len_; /* varlena header (do not touch directly!) */ + int lists; /* number of lists */ +} IvfflatOptions; + +typedef struct IvfflatSpool +{ + Tuplesortstate *sortstate; + Relation heap; + Relation index; +} IvfflatSpool; + +typedef struct IvfflatShared +{ + /* Immutable state */ + Oid heaprelid; + Oid indexrelid; + bool isconcurrent; + int scantuplesortstates; + + /* Worker progress */ + ConditionVariable workersdonecv; + + /* Mutex for mutable state */ + slock_t mutex; + + /* Mutable state */ + int nparticipantsdone; + double reltuples; + double indtuples; + +#ifdef IVFFLAT_KMEANS_DEBUG + double inertia; +#endif + +#if PG_VERSION_NUM < 120000 + ParallelHeapScanDescData heapdesc; /* must come last */ +#endif +} IvfflatShared; + +#if PG_VERSION_NUM >= 120000 +#define ParallelTableScanFromIvfflatShared(shared) \ + (ParallelTableScanDesc) ((char *) (shared) + BUFFERALIGN(sizeof(IvfflatShared))) +#endif + +typedef struct IvfflatLeader +{ + ParallelContext *pcxt; + int nparticipanttuplesorts; + IvfflatShared *ivfshared; + Sharedsort *sharedsort; + Snapshot snapshot; + Vector *ivfcenters; +} IvfflatLeader; + +typedef struct IvfflatBuildState +{ + /* Info */ + Relation heap; + Relation index; + IndexInfo *indexInfo; + + /* Settings */ + int dimensions; + int lists; + + /* Statistics */ + double indtuples; + double reltuples; + + /* Support functions */ + FmgrInfo *procinfo; + FmgrInfo *normprocinfo; + FmgrInfo *kmeansnormprocinfo; + Oid collation; + + /* Variables */ + VectorArray samples; + VectorArray centers; + ListInfo *listInfo; + Vector *normvec; + +#ifdef IVFFLAT_KMEANS_DEBUG + double inertia; + double *listSums; + int *listCounts; +#endif + + /* Sampling */ + BlockSamplerData bs; + ReservoirStateData rstate; + int rowstoskip; + + /* Sorting */ + Tuplesortstate *sortstate; + TupleDesc tupdesc; + TupleTableSlot *slot; + + /* Memory */ + MemoryContext tmpCtx; + + /* Parallel builds */ + IvfflatLeader *ivfleader; +} IvfflatBuildState; + +typedef struct IvfflatMetaPageData +{ + uint32 magicNumber; + uint32 version; + uint16 dimensions; + uint16 lists; +} IvfflatMetaPageData; + +typedef IvfflatMetaPageData * IvfflatMetaPage; + +typedef struct IvfflatPageOpaqueData +{ + BlockNumber nextblkno; + uint16 unused; + uint16 page_id; /* for identification of IVFFlat indexes */ +} IvfflatPageOpaqueData; + +typedef IvfflatPageOpaqueData * IvfflatPageOpaque; + +typedef struct IvfflatListData +{ + BlockNumber startPage; + BlockNumber insertPage; + Vector center; +} IvfflatListData; + +typedef IvfflatListData * IvfflatList; + +typedef struct IvfflatScanList +{ + pairingheap_node ph_node; + BlockNumber startPage; + double distance; +} IvfflatScanList; + +typedef struct IvfflatScanOpaqueData +{ + int probes; + bool first; + Buffer buf; + + /* Sorting */ + Tuplesortstate *sortstate; + TupleDesc tupdesc; + TupleTableSlot *slot; + bool isnull; + + /* Support functions */ + FmgrInfo *procinfo; + FmgrInfo *normprocinfo; + Oid collation; + + /* Lists */ + pairingheap *listQueue; + IvfflatScanList lists[FLEXIBLE_ARRAY_MEMBER]; /* must come last */ +} IvfflatScanOpaqueData; + +typedef IvfflatScanOpaqueData * IvfflatScanOpaque; + +#define VECTOR_ARRAY_SIZE(_length, _dim) (sizeof(VectorArrayData) + (_length) * VECTOR_SIZE(_dim)) +#define VECTOR_ARRAY_OFFSET(_arr, _offset) ((char*) (_arr)->items + (_offset) * VECTOR_SIZE((_arr)->dim)) +#define VectorArrayGet(_arr, _offset) ((Vector *) VECTOR_ARRAY_OFFSET(_arr, _offset)) +#define VectorArraySet(_arr, _offset, _val) memcpy(VECTOR_ARRAY_OFFSET(_arr, _offset), _val, VECTOR_SIZE((_arr)->dim)) + +/* Methods */ +VectorArray VectorArrayInit(int maxlen, int dimensions); +void VectorArrayFree(VectorArray arr); +void PrintVectorArray(char *msg, VectorArray arr); +void IvfflatKmeans(Relation index, VectorArray samples, VectorArray centers); +FmgrInfo *IvfflatOptionalProcInfo(Relation rel, uint16 procnum); +bool IvfflatNormValue(FmgrInfo *procinfo, Oid collation, Datum *value, Vector * result); +int IvfflatGetLists(Relation index); +void IvfflatUpdateList(Relation index, ListInfo listInfo, BlockNumber insertPage, BlockNumber originalInsertPage, BlockNumber startPage, ForkNumber forkNum); +void IvfflatCommitBuffer(Buffer buf, GenericXLogState *state); +void IvfflatAppendPage(Relation index, Buffer *buf, Page *page, GenericXLogState **state, ForkNumber forkNum); +Buffer IvfflatNewBuffer(Relation index, ForkNumber forkNum); +void IvfflatInitPage(Buffer buf, Page page); +void IvfflatInitRegisterPage(Relation index, Buffer *buf, Page *page, GenericXLogState **state); +void IvfflatInit(void); +PGDLLEXPORT void IvfflatParallelBuildMain(dsm_segment *seg, shm_toc *toc); + +/* Index access methods */ +IndexBuildResult *ivfflatbuild(Relation heap, Relation index, IndexInfo *indexInfo); +void ivfflatbuildempty(Relation index); +bool ivfflatinsert(Relation index, Datum *values, bool *isnull, ItemPointer heap_tid, Relation heap, IndexUniqueCheck checkUnique +#if PG_VERSION_NUM >= 140000 + ,bool indexUnchanged +#endif + ,IndexInfo *indexInfo +); +IndexBulkDeleteResult *ivfflatbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats, IndexBulkDeleteCallback callback, void *callback_state); +IndexBulkDeleteResult *ivfflatvacuumcleanup(IndexVacuumInfo *info, IndexBulkDeleteResult *stats); +IndexScanDesc ivfflatbeginscan(Relation index, int nkeys, int norderbys); +void ivfflatrescan(IndexScanDesc scan, ScanKey keys, int nkeys, ScanKey orderbys, int norderbys); +bool ivfflatgettuple(IndexScanDesc scan, ScanDirection dir); +void ivfflatendscan(IndexScanDesc scan); + +#endif diff --git a/packages/server/postgres/extensions/pgvector/src/ivfflat.o b/packages/server/postgres/extensions/pgvector/src/ivfflat.o new file mode 100644 index 0000000000000000000000000000000000000000..3179f3543944215f5531c54039086dc2393887b7 GIT binary patch literal 4344 zcmb7HZ){sv6+iDi$9`$MB!e=tE;uhu%2HU|ZWPF*T63FjOPL2%7mW(lJU`nn$)nHD zmi^KtOPRbs64Cxh&Qu{lmDXwfP#ZKrs>CW$)@cLsVG?UKPzC$ocKML;M@k`#q^lQx z*Y`fhaa19$eDCl6&bjB@bI*P6-RnQx_{XPPh$KQ`O$+K9G8zpPuq;x%cpmEH& z@K+JSi0~JIKXh4H7w&x!bfWUS-i8h;N(i+BbrN<(ATwg))XhXJR<(_25kgQpgv!e2 zMP>1CL%jp_|1GO;)1~UPT&g}xv#>ph$Eb+l&Lh5J578{9Eba$ByQOfrj|zudsCd{f z6%MnptE*I6ZKsu0h0cSsuxFd?5L+=xkrigU1Jw`v-vgO_b$WjZ^Uq#X^gns_tY4je zvs6`Rmc^nS=mI?V_n}l~(%UTaDBG-CcCv zvu}l;o$#Ud$)$}lr7lxX_(Y#(xea3IYpKh+HGjRYo(Rbz18Wr&a$EfMT9&+84?Gx;aLin6Wj$-R7G4=&Idt(~qI z-!+s(kUEW^)ZqkMj~Kz4uiGP4QmtU6o2u6^M@6}_d{yZz)4q7HK3{9({Iy>8j1(?2 zS>>)smD#GT*xz49F7#Y6o+8Z%z9iF#++hc`oW96N>U?{oIy9IJDrz#= zGZ+u{sEJ@te=^ukv{jWCUtW-DWe)XCRC%6uA>X^0PU<)jlvE?w=qE78(lw3G~zv1+SK?mf9?yH8EP2I(6;h!p1wJe zPMA3@Gpbq6aWm(dDUBIOpdxM5o&i*N{?2iOeL(~@2ke$`3O1Etab9+D`3C;pdF4&V}udw_A^mofe*$Jhw)9`HjPW5Yn4E%pJ9u?X;P@Vy*kdw@7o zqPsbU`++!uqJ130T|hju(N2!xtw7`;+QBib0(Zfmk7Mj6lJh0-Wsb2W-~jj{$JhdJ zC-^+aSOwS*{sG6>&wyWm$!Q?YH2W0C*o#1%r_mXX;Yr|K7?%RFe8oA&jskJzh#uh> zW>+U%wW93I1cswPANVlG@E{N$r~%}duq9EAW4H^5R7X2GhPMK-#ApY{@D^Y<><~MB zj;KuoVoVzw|5+d#_Zc97`DwcdguVZ?z=FWIz=%LiAfPs0mE);^eB@|AJ~R@Lhob>m z>kG)8T>*JUdQnp459Erh%J0eVqkTu7L;Gv_q8yNaA)iBgMm~%7NAfAOMfoJO&&e}r zm!+GqU6hv4&Pxl-k5rL-$|>ozbOw0ZcgAmf_v}p_n3b?2;M#Lw@eQ{j5%2OBh#aL>-n9C$6L>@7;lK@EaMIS zUJ>K3i};Ize=7J71ixL(_mtq<1^+SQ$g_8^ydN>OBSx>B6a1tQ{8VuEUa%sbQ%=x+ z0^0_2G9YW@k6^w=u7EW1olW{@n(Y7F#HX9=A8OLS+N8guiL-SDgk`79bk?0BeP|+=GadI)Go8sz4P~6# zUpQ}|<>YaKlYWpN-3M|0l0Ig-x@#nGzT&@ysb`Hb6Mr<=(eI6W*mR?{yMX5>lXWfp zRU*AMvz{~UxL->zIdUi}7wVct6=V9D%Qnm?&7=PSIPE4eeHqQNtEc`xc z<}$am3Z6keyxn5Xb3+_9$Q9P8Soyo8HsiiDCZsbkJNSkEq`6nzlIUcHQ zK}fi08sudOTUEOWk$%8LI?bVpyqifMblk^{oW-`V9<4}-eheY-EY&wj$QoxNecki2 z&$ctk?_dY^*|>k1a3BVYU1qT&aJI9dL9g?5uk$ss-E{V=9z_Rqk4Mn~{fbA?0lm+o P=z!kqQFK6ei#OoEEGi$s literal 0 HcmV?d00001 diff --git a/packages/server/postgres/extensions/pgvector/src/ivfinsert.c b/packages/server/postgres/extensions/pgvector/src/ivfinsert.c new file mode 100644 index 00000000000..cc744af9187 --- /dev/null +++ b/packages/server/postgres/extensions/pgvector/src/ivfinsert.c @@ -0,0 +1,206 @@ +#include "postgres.h" + +#include + +#include "ivfflat.h" +#include "storage/bufmgr.h" +#include "storage/lmgr.h" +#include "utils/memutils.h" + +/* + * Find the list that minimizes the distance function + */ +static void +FindInsertPage(Relation rel, Datum *values, BlockNumber *insertPage, ListInfo * listInfo) +{ + Buffer cbuf; + Page cpage; + IvfflatList list; + double distance; + double minDistance = DBL_MAX; + BlockNumber nextblkno = IVFFLAT_HEAD_BLKNO; + FmgrInfo *procinfo; + Oid collation; + OffsetNumber offno; + OffsetNumber maxoffno; + + /* Avoid compiler warning */ + listInfo->blkno = nextblkno; + listInfo->offno = FirstOffsetNumber; + + procinfo = index_getprocinfo(rel, 1, IVFFLAT_DISTANCE_PROC); + collation = rel->rd_indcollation[0]; + + /* Search all list pages */ + while (BlockNumberIsValid(nextblkno)) + { + cbuf = ReadBuffer(rel, nextblkno); + LockBuffer(cbuf, BUFFER_LOCK_SHARE); + cpage = BufferGetPage(cbuf); + maxoffno = PageGetMaxOffsetNumber(cpage); + + for (offno = FirstOffsetNumber; offno <= maxoffno; offno = OffsetNumberNext(offno)) + { + list = (IvfflatList) PageGetItem(cpage, PageGetItemId(cpage, offno)); + distance = DatumGetFloat8(FunctionCall2Coll(procinfo, collation, values[0], PointerGetDatum(&list->center))); + + if (distance < minDistance || !BlockNumberIsValid(*insertPage)) + { + *insertPage = list->insertPage; + listInfo->blkno = nextblkno; + listInfo->offno = offno; + minDistance = distance; + } + } + + nextblkno = IvfflatPageGetOpaque(cpage)->nextblkno; + + UnlockReleaseBuffer(cbuf); + } +} + +/* + * Insert a tuple into the index + */ +static void +InsertTuple(Relation rel, Datum *values, bool *isnull, ItemPointer heap_tid, Relation heapRel) +{ + IndexTuple itup; + Datum value; + FmgrInfo *normprocinfo; + Buffer buf; + Page page; + GenericXLogState *state; + Size itemsz; + BlockNumber insertPage = InvalidBlockNumber; + ListInfo listInfo; + BlockNumber originalInsertPage; + + /* Detoast once for all calls */ + value = PointerGetDatum(PG_DETOAST_DATUM(values[0])); + + /* Normalize if needed */ + normprocinfo = IvfflatOptionalProcInfo(rel, IVFFLAT_NORM_PROC); + if (normprocinfo != NULL) + { + if (!IvfflatNormValue(normprocinfo, rel->rd_indcollation[0], &value, NULL)) + return; + } + + /* Find the insert page - sets the page and list info */ + FindInsertPage(rel, values, &insertPage, &listInfo); + Assert(BlockNumberIsValid(insertPage)); + originalInsertPage = insertPage; + + /* Form tuple */ + itup = index_form_tuple(RelationGetDescr(rel), &value, isnull); + itup->t_tid = *heap_tid; + + /* Get tuple size */ + itemsz = MAXALIGN(IndexTupleSize(itup)); + Assert(itemsz <= BLCKSZ - MAXALIGN(SizeOfPageHeaderData) - MAXALIGN(sizeof(IvfflatPageOpaqueData))); + + /* Find a page to insert the item */ + for (;;) + { + buf = ReadBuffer(rel, insertPage); + LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE); + + state = GenericXLogStart(rel); + page = GenericXLogRegisterBuffer(state, buf, 0); + + if (PageGetFreeSpace(page) >= itemsz) + break; + + insertPage = IvfflatPageGetOpaque(page)->nextblkno; + + if (BlockNumberIsValid(insertPage)) + { + /* Move to next page */ + GenericXLogAbort(state); + UnlockReleaseBuffer(buf); + } + else + { + Buffer newbuf; + Page newpage; + + /* Add a new page */ + LockRelationForExtension(rel, ExclusiveLock); + newbuf = IvfflatNewBuffer(rel, MAIN_FORKNUM); + UnlockRelationForExtension(rel, ExclusiveLock); + + /* Init new page */ + newpage = GenericXLogRegisterBuffer(state, newbuf, GENERIC_XLOG_FULL_IMAGE); + IvfflatInitPage(newbuf, newpage); + + /* Update insert page */ + insertPage = BufferGetBlockNumber(newbuf); + + /* Update previous buffer */ + IvfflatPageGetOpaque(page)->nextblkno = insertPage; + + /* Commit */ + MarkBufferDirty(newbuf); + MarkBufferDirty(buf); + GenericXLogFinish(state); + + /* Unlock previous buffer */ + UnlockReleaseBuffer(buf); + + /* Prepare new buffer */ + state = GenericXLogStart(rel); + buf = newbuf; + page = GenericXLogRegisterBuffer(state, buf, 0); + break; + } + } + + /* Add to next offset */ + if (PageAddItem(page, (Item) itup, itemsz, InvalidOffsetNumber, false, false) == InvalidOffsetNumber) + elog(ERROR, "failed to add index item to \"%s\"", RelationGetRelationName(rel)); + + IvfflatCommitBuffer(buf, state); + + /* Update the insert page */ + if (insertPage != originalInsertPage) + IvfflatUpdateList(rel, listInfo, insertPage, originalInsertPage, InvalidBlockNumber, MAIN_FORKNUM); +} + +/* + * Insert a tuple into the index + */ +bool +ivfflatinsert(Relation index, Datum *values, bool *isnull, ItemPointer heap_tid, + Relation heap, IndexUniqueCheck checkUnique +#if PG_VERSION_NUM >= 140000 + ,bool indexUnchanged +#endif + ,IndexInfo *indexInfo +) +{ + MemoryContext oldCtx; + MemoryContext insertCtx; + + /* Skip nulls */ + if (isnull[0]) + return false; + + /* + * Use memory context since detoast, IvfflatNormValue, and + * index_form_tuple can allocate + */ + insertCtx = AllocSetContextCreate(CurrentMemoryContext, + "Ivfflat insert temporary context", + ALLOCSET_DEFAULT_SIZES); + oldCtx = MemoryContextSwitchTo(insertCtx); + + /* Insert tuple */ + InsertTuple(index, values, isnull, heap_tid, heap); + + /* Delete memory context */ + MemoryContextSwitchTo(oldCtx); + MemoryContextDelete(insertCtx); + + return false; +} diff --git a/packages/server/postgres/extensions/pgvector/src/ivfinsert.o b/packages/server/postgres/extensions/pgvector/src/ivfinsert.o new file mode 100644 index 0000000000000000000000000000000000000000..113bc1dc1635a68651f77b51cdede2c5e64f0534 GIT binary patch literal 3624 zcmb7HTWl298UA<1cxMCU;v0q#du@}}MyxS6K@u#Du`zbFTxvE6RYJ$h?(BN-?9O&} z9K4C5tQ0?>QdM4>MpcxHrSQP6kct!@?5Gd4;Q>{Za#2*N7b$D#gRQg@Qr+M+IPQ1m z;*5vB6m+BtsrIYyu!jkz;2g-_g!!z~zqv^RsHAXPK~y@(i3NNwKVH!-|hx~S}GHb8PZt#qLPkFQn=48zh>tp+RRD#V{@Alh-_bLT<(Lc5#1`w4=_MS z>qOs-egk%0KxV}B%AnhU!|PFhFadNLoi&Ke&U^*Xo6)~-Tls|he7>zJ^+;P+TuN3K z7bSo3n-_)R!l^)U`L!pC%Wrap0TQ2?+d&8a%k!lQ&y^N=Uul7-QZ3PBmJ=((=*VHR zg{Z(${tKQahN?DC4I!7ezJcCFQ->baQL!DmbBAb#*|lKYYTH)epV@AQZ9U~{1n&M7!IymB)Zb4t&p~gY zmMW-K-JMo3_xB4U)8#$l4D)}e|%kxw?gcvwib#v9UWotLpyuoc9y|=Uyx%8()mxVt_)50_2m49%;We#dhF7uvuLr)Q8oB)q9q2 zEMc9^`aA^9_|4St^--Q8BW+^t)zOh@3AM#RKwY<)@-l3gp5@<~!}s;yw!9MQK)sgu z`qGbprY}1WHugDdhMd;QSlwz%&Yqby<+XM`7A}FuavvUTpDsh!%GJsF@i}}~i`q4{ zScGjE`)HQmQM$`Fl(3hFa7CH5!d-QM)nV4IiNx2BI%nv?OJZ@@N0Irq{tf|qVU`c< z@^ue?#O84qYkm)NhM$^swy%pEJ3|d-o&?_VYd^-B9mLrkz!^S{v)o^Nq#Y~HxAmoP z#tXN2=`YL|%X{S$`h4M(59gc1`KAKf?^9fU2=z11-AEJm`4%e7@i?>C_vi&?=ZtX; zO&$fWrOujHkb++hzLH3&Q6s3Nv$Adk4LOz3^r)T-#ajs zK?Q1z)B1F_m9lzlkMc^wa?ly0LDN%YEThT@>@?gv)a*kgJ&o~MV^0U}qQ~SSIITpX z!$6$5@Bs_EyMXu$@3yeJ9oPoFoj|w@2QBR04BQF+eK0Zq&@JE&;7#CG-~wDnk_$p7@J)2KgQTa(j~~<^ahU{duxsSZIAppkDRTa+uv=E zoYmbe|DA_Ej{M#7_dN0k9{&eDbh}4C?V-Q&&=)-REH>*-3wZL$dFZnqni;$8PkQLD z;n*$zt4Ch+&=)-Zn>=)@M}NXYlXN1gj~*RMBxJo$(TyBQy<@sAr;QVGO4D<__O&V5 z7whwxc+`-?O4cChxSW=CC3ZHfC40_mIyksD+3joHoof`%7*VDW)?!iBigr}hVxy-u z1=q5kCCSCik}7rLzh`oSo|G?I;#E$smP#px!*nGwBqx!ZtUDqEv}Z@s^Jx}xNLJ-& zR(2GIqyjxi6tQB~d7tEnnUvaYAHNo0fB zAtp&ZDyDNrHtZ$p)n%;A;Hnt +#include + +#include "ivfflat.h" +#include "miscadmin.h" + +/* + * Initialize with kmeans++ + * + * https://theory.stanford.edu/~sergei/papers/kMeansPP-soda.pdf + */ +static void +InitCenters(Relation index, VectorArray samples, VectorArray centers, float *lowerBound) +{ + FmgrInfo *procinfo; + Oid collation; + int64 j; + double distance; + double sum; + double choice; + Vector *vec; + float *weight = palloc(samples->length * sizeof(float)); + int numCenters = centers->maxlen; + int numSamples = samples->length; + + procinfo = index_getprocinfo(index, 1, IVFFLAT_KMEANS_DISTANCE_PROC); + collation = index->rd_indcollation[0]; + + /* Choose an initial center uniformly at random */ + VectorArraySet(centers, 0, VectorArrayGet(samples, RandomInt() % samples->length)); + centers->length++; + + for (j = 0; j < numSamples; j++) + weight[j] = DBL_MAX; + + for (int i = 0; i < numCenters; i++) + { + CHECK_FOR_INTERRUPTS(); + + sum = 0.0; + + for (j = 0; j < numSamples; j++) + { + vec = VectorArrayGet(samples, j); + + /* Only need to compute distance for new center */ + /* TODO Use triangle inequality to reduce distance calculations */ + distance = DatumGetFloat8(FunctionCall2Coll(procinfo, collation, PointerGetDatum(vec), PointerGetDatum(VectorArrayGet(centers, i)))); + + /* Set lower bound */ + lowerBound[j * numCenters + i] = distance; + + /* Use distance squared for weighted probability distribution */ + distance *= distance; + + if (distance < weight[j]) + weight[j] = distance; + + sum += weight[j]; + } + + /* Only compute lower bound on last iteration */ + if (i + 1 == numCenters) + break; + + /* Choose new center using weighted probability distribution. */ + choice = sum * RandomDouble(); + for (j = 0; j < numSamples - 1; j++) + { + choice -= weight[j]; + if (choice <= 0) + break; + } + + VectorArraySet(centers, i + 1, VectorArrayGet(samples, j)); + centers->length++; + } + + pfree(weight); +} + +/* + * Apply norm to vector + */ +static inline void +ApplyNorm(FmgrInfo *normprocinfo, Oid collation, Vector * vec) +{ + double norm = DatumGetFloat8(FunctionCall1Coll(normprocinfo, collation, PointerGetDatum(vec))); + + /* TODO Handle zero norm */ + if (norm > 0) + { + for (int i = 0; i < vec->dim; i++) + vec->x[i] /= norm; + } +} + +/* + * Compare vectors + */ +static int +CompareVectors(const void *a, const void *b) +{ + return vector_cmp_internal((Vector *) a, (Vector *) b); +} + +/* + * Quick approach if we have little data + */ +static void +QuickCenters(Relation index, VectorArray samples, VectorArray centers) +{ + Vector *vec; + int dimensions = centers->dim; + Oid collation = index->rd_indcollation[0]; + FmgrInfo *normprocinfo = IvfflatOptionalProcInfo(index, IVFFLAT_KMEANS_NORM_PROC); + + /* Copy existing vectors while avoiding duplicates */ + if (samples->length > 0) + { + qsort(samples->items, samples->length, VECTOR_SIZE(samples->dim), CompareVectors); + for (int i = 0; i < samples->length; i++) + { + vec = VectorArrayGet(samples, i); + + if (i == 0 || CompareVectors(vec, VectorArrayGet(samples, i - 1)) != 0) + { + VectorArraySet(centers, centers->length, vec); + centers->length++; + } + } + } + + /* Fill remaining with random data */ + while (centers->length < centers->maxlen) + { + vec = VectorArrayGet(centers, centers->length); + + SET_VARSIZE(vec, VECTOR_SIZE(dimensions)); + vec->dim = dimensions; + + for (int j = 0; j < dimensions; j++) + vec->x[j] = RandomDouble(); + + /* Normalize if needed (only needed for random centers) */ + if (normprocinfo != NULL) + ApplyNorm(normprocinfo, collation, vec); + + centers->length++; + } +} + +/* + * Use Elkan for performance. This requires distance function to satisfy triangle inequality. + * + * We use L2 distance for L2 (not L2 squared like index scan) + * and angular distance for inner product and cosine distance + * + * https://www.aaai.org/Papers/ICML/2003/ICML03-022.pdf + */ +static void +ElkanKmeans(Relation index, VectorArray samples, VectorArray centers) +{ + FmgrInfo *procinfo; + FmgrInfo *normprocinfo; + Oid collation; + Vector *vec; + Vector *newCenter; + int iteration; + int64 j; + int64 k; + int dimensions = centers->dim; + int numCenters = centers->maxlen; + int numSamples = samples->length; + VectorArray newCenters; + int *centerCounts; + int *closestCenters; + float *lowerBound; + float *upperBound; + float *s; + float *halfcdist; + float *newcdist; + int changes; + double minDistance; + int closestCenter; + double distance; + bool rj; + bool rjreset; + double dxcx; + double dxc; + + /* Calculate allocation sizes */ + Size samplesSize = VECTOR_ARRAY_SIZE(samples->maxlen, samples->dim); + Size centersSize = VECTOR_ARRAY_SIZE(centers->maxlen, centers->dim); + Size newCentersSize = VECTOR_ARRAY_SIZE(numCenters, dimensions); + Size centerCountsSize = sizeof(int) * numCenters; + Size closestCentersSize = sizeof(int) * numSamples; + Size lowerBoundSize = sizeof(float) * numSamples * numCenters; + Size upperBoundSize = sizeof(float) * numSamples; + Size sSize = sizeof(float) * numCenters; + Size halfcdistSize = sizeof(float) * numCenters * numCenters; + Size newcdistSize = sizeof(float) * numCenters; + + /* Calculate total size */ + Size totalSize = samplesSize + centersSize + newCentersSize + centerCountsSize + closestCentersSize + lowerBoundSize + upperBoundSize + sSize + halfcdistSize + newcdistSize; + + /* Check memory requirements */ + /* Add one to error message to ceil */ + if (totalSize > (Size) maintenance_work_mem * 1024L) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("memory required is %zu MB, maintenance_work_mem is %d MB", + totalSize / (1024 * 1024) + 1, maintenance_work_mem / 1024))); + + /* Ensure indexing does not overflow */ + if (numCenters * numCenters > INT_MAX) + elog(ERROR, "Indexing overflow detected. Please report a bug."); + + /* Set support functions */ + procinfo = index_getprocinfo(index, 1, IVFFLAT_KMEANS_DISTANCE_PROC); + normprocinfo = IvfflatOptionalProcInfo(index, IVFFLAT_KMEANS_NORM_PROC); + collation = index->rd_indcollation[0]; + + /* Allocate space */ + /* Use float instead of double to save memory */ + centerCounts = palloc(centerCountsSize); + closestCenters = palloc(closestCentersSize); + lowerBound = palloc_extended(lowerBoundSize, MCXT_ALLOC_HUGE); + upperBound = palloc(upperBoundSize); + s = palloc(sSize); + halfcdist = palloc_extended(halfcdistSize, MCXT_ALLOC_HUGE); + newcdist = palloc(newcdistSize); + + newCenters = VectorArrayInit(numCenters, dimensions); + for (j = 0; j < numCenters; j++) + { + vec = VectorArrayGet(newCenters, j); + SET_VARSIZE(vec, VECTOR_SIZE(dimensions)); + vec->dim = dimensions; + } + + /* Pick initial centers */ + InitCenters(index, samples, centers, lowerBound); + + /* Assign each x to its closest initial center c(x) = argmin d(x,c) */ + for (j = 0; j < numSamples; j++) + { + minDistance = DBL_MAX; + closestCenter = 0; + + /* Find closest center */ + for (k = 0; k < numCenters; k++) + { + /* TODO Use Lemma 1 in k-means++ initialization */ + distance = lowerBound[j * numCenters + k]; + + if (distance < minDistance) + { + minDistance = distance; + closestCenter = k; + } + } + + upperBound[j] = minDistance; + closestCenters[j] = closestCenter; + } + + /* Give 500 iterations to converge */ + for (iteration = 0; iteration < 500; iteration++) + { + /* Can take a while, so ensure we can interrupt */ + CHECK_FOR_INTERRUPTS(); + + changes = 0; + + /* Step 1: For all centers, compute distance */ + for (j = 0; j < numCenters; j++) + { + vec = VectorArrayGet(centers, j); + + for (k = j + 1; k < numCenters; k++) + { + distance = 0.5 * DatumGetFloat8(FunctionCall2Coll(procinfo, collation, PointerGetDatum(vec), PointerGetDatum(VectorArrayGet(centers, k)))); + halfcdist[j * numCenters + k] = distance; + halfcdist[k * numCenters + j] = distance; + } + } + + /* For all centers c, compute s(c) */ + for (j = 0; j < numCenters; j++) + { + minDistance = DBL_MAX; + + for (k = 0; k < numCenters; k++) + { + if (j == k) + continue; + + distance = halfcdist[j * numCenters + k]; + if (distance < minDistance) + minDistance = distance; + } + + s[j] = minDistance; + } + + rjreset = iteration != 0; + + for (j = 0; j < numSamples; j++) + { + /* Step 2: Identify all points x such that u(x) <= s(c(x)) */ + if (upperBound[j] <= s[closestCenters[j]]) + continue; + + rj = rjreset; + + for (k = 0; k < numCenters; k++) + { + /* Step 3: For all remaining points x and centers c */ + if (k == closestCenters[j]) + continue; + + if (upperBound[j] <= lowerBound[j * numCenters + k]) + continue; + + if (upperBound[j] <= halfcdist[closestCenters[j] * numCenters + k]) + continue; + + vec = VectorArrayGet(samples, j); + + /* Step 3a */ + if (rj) + { + dxcx = DatumGetFloat8(FunctionCall2Coll(procinfo, collation, PointerGetDatum(vec), PointerGetDatum(VectorArrayGet(centers, closestCenters[j])))); + + /* d(x,c(x)) computed, which is a form of d(x,c) */ + lowerBound[j * numCenters + closestCenters[j]] = dxcx; + upperBound[j] = dxcx; + + rj = false; + } + else + dxcx = upperBound[j]; + + /* Step 3b */ + if (dxcx > lowerBound[j * numCenters + k] || dxcx > halfcdist[closestCenters[j] * numCenters + k]) + { + dxc = DatumGetFloat8(FunctionCall2Coll(procinfo, collation, PointerGetDatum(vec), PointerGetDatum(VectorArrayGet(centers, k)))); + + /* d(x,c) calculated */ + lowerBound[j * numCenters + k] = dxc; + + if (dxc < dxcx) + { + closestCenters[j] = k; + + /* c(x) changed */ + upperBound[j] = dxc; + + changes++; + } + + } + } + } + + /* Step 4: For each center c, let m(c) be mean of all points assigned */ + for (j = 0; j < numCenters; j++) + { + vec = VectorArrayGet(newCenters, j); + for (k = 0; k < dimensions; k++) + vec->x[k] = 0.0; + + centerCounts[j] = 0; + } + + for (j = 0; j < numSamples; j++) + { + vec = VectorArrayGet(samples, j); + closestCenter = closestCenters[j]; + + /* Increment sum and count of closest center */ + newCenter = VectorArrayGet(newCenters, closestCenter); + for (k = 0; k < dimensions; k++) + newCenter->x[k] += vec->x[k]; + + centerCounts[closestCenter] += 1; + } + + for (j = 0; j < numCenters; j++) + { + vec = VectorArrayGet(newCenters, j); + + if (centerCounts[j] > 0) + { + /* Double avoids overflow, but requires more memory */ + /* TODO Update bounds */ + for (k = 0; k < dimensions; k++) + { + if (isinf(vec->x[k])) + vec->x[k] = vec->x[k] > 0 ? FLT_MAX : -FLT_MAX; + } + + for (k = 0; k < dimensions; k++) + vec->x[k] /= centerCounts[j]; + } + else + { + /* TODO Handle empty centers properly */ + for (k = 0; k < dimensions; k++) + vec->x[k] = RandomDouble(); + } + + /* Normalize if needed */ + if (normprocinfo != NULL) + ApplyNorm(normprocinfo, collation, vec); + } + + /* Step 5 */ + for (j = 0; j < numCenters; j++) + newcdist[j] = DatumGetFloat8(FunctionCall2Coll(procinfo, collation, PointerGetDatum(VectorArrayGet(centers, j)), PointerGetDatum(VectorArrayGet(newCenters, j)))); + + for (j = 0; j < numSamples; j++) + { + for (k = 0; k < numCenters; k++) + { + distance = lowerBound[j * numCenters + k] - newcdist[k]; + + if (distance < 0) + distance = 0; + + lowerBound[j * numCenters + k] = distance; + } + } + + /* Step 6 */ + /* We reset r(x) before Step 3 in the next iteration */ + for (j = 0; j < numSamples; j++) + upperBound[j] += newcdist[closestCenters[j]]; + + /* Step 7 */ + for (j = 0; j < numCenters; j++) + memcpy(VectorArrayGet(centers, j), VectorArrayGet(newCenters, j), VECTOR_SIZE(dimensions)); + + if (changes == 0 && iteration != 0) + break; + } + + VectorArrayFree(newCenters); + pfree(centerCounts); + pfree(closestCenters); + pfree(lowerBound); + pfree(upperBound); + pfree(s); + pfree(halfcdist); + pfree(newcdist); +} + +/* + * Detect issues with centers + */ +static void +CheckCenters(Relation index, VectorArray centers) +{ + FmgrInfo *normprocinfo; + Oid collation; + Vector *vec; + double norm; + + if (centers->length != centers->maxlen) + elog(ERROR, "Not enough centers. Please report a bug."); + + /* Ensure no NaN or infinite values */ + for (int i = 0; i < centers->length; i++) + { + vec = VectorArrayGet(centers, i); + + for (int j = 0; j < vec->dim; j++) + { + if (isnan(vec->x[j])) + elog(ERROR, "NaN detected. Please report a bug."); + + if (isinf(vec->x[j])) + elog(ERROR, "Infinite value detected. Please report a bug."); + } + } + + /* Ensure no duplicate centers */ + /* Fine to sort in-place */ + qsort(centers->items, centers->length, VECTOR_SIZE(centers->dim), CompareVectors); + for (int i = 1; i < centers->length; i++) + { + if (CompareVectors(VectorArrayGet(centers, i), VectorArrayGet(centers, i - 1)) == 0) + elog(ERROR, "Duplicate centers detected. Please report a bug."); + } + + /* Ensure no zero vectors for cosine distance */ + /* Check NORM_PROC instead of KMEANS_NORM_PROC */ + normprocinfo = IvfflatOptionalProcInfo(index, IVFFLAT_NORM_PROC); + if (normprocinfo != NULL) + { + collation = index->rd_indcollation[0]; + + for (int i = 0; i < centers->length; i++) + { + norm = DatumGetFloat8(FunctionCall1Coll(normprocinfo, collation, PointerGetDatum(VectorArrayGet(centers, i)))); + if (norm == 0) + elog(ERROR, "Zero norm detected. Please report a bug."); + } + } +} + +/* + * Perform naive k-means centering + * We use spherical k-means for inner product and cosine + */ +void +IvfflatKmeans(Relation index, VectorArray samples, VectorArray centers) +{ + if (samples->length <= centers->maxlen) + QuickCenters(index, samples, centers); + else + ElkanKmeans(index, samples, centers); + + CheckCenters(index, centers); +} diff --git a/packages/server/postgres/extensions/pgvector/src/ivfkmeans.o b/packages/server/postgres/extensions/pgvector/src/ivfkmeans.o new file mode 100644 index 0000000000000000000000000000000000000000..399ebad55bab1b1cb970ce1993c73160fd28abf3 GIT binary patch literal 9240 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 literal 0 HcmV?d00001 diff --git a/packages/server/postgres/extensions/pgvector/src/ivfscan.c b/packages/server/postgres/extensions/pgvector/src/ivfscan.c new file mode 100644 index 00000000000..6703120aa5d --- /dev/null +++ b/packages/server/postgres/extensions/pgvector/src/ivfscan.c @@ -0,0 +1,381 @@ +#include "postgres.h" + +#include + +#include "access/relscan.h" +#include "catalog/pg_operator_d.h" +#include "catalog/pg_type_d.h" +#include "ivfflat.h" +#include "miscadmin.h" +#include "pgstat.h" +#include "storage/bufmgr.h" + +/* + * Compare list distances + */ +static int +CompareLists(const pairingheap_node *a, const pairingheap_node *b, void *arg) +{ + if (((const IvfflatScanList *) a)->distance > ((const IvfflatScanList *) b)->distance) + return 1; + + if (((const IvfflatScanList *) a)->distance < ((const IvfflatScanList *) b)->distance) + return -1; + + return 0; +} + +/* + * Get lists and sort by distance + */ +static void +GetScanLists(IndexScanDesc scan, Datum value) +{ + Buffer cbuf; + Page cpage; + IvfflatList list; + OffsetNumber offno; + OffsetNumber maxoffno; + BlockNumber nextblkno = IVFFLAT_HEAD_BLKNO; + int listCount = 0; + IvfflatScanOpaque so = (IvfflatScanOpaque) scan->opaque; + double distance; + IvfflatScanList *scanlist; + double maxDistance = DBL_MAX; + + /* Search all list pages */ + while (BlockNumberIsValid(nextblkno)) + { + cbuf = ReadBuffer(scan->indexRelation, nextblkno); + LockBuffer(cbuf, BUFFER_LOCK_SHARE); + cpage = BufferGetPage(cbuf); + + maxoffno = PageGetMaxOffsetNumber(cpage); + + for (offno = FirstOffsetNumber; offno <= maxoffno; offno = OffsetNumberNext(offno)) + { + list = (IvfflatList) PageGetItem(cpage, PageGetItemId(cpage, offno)); + + /* Use procinfo from the index instead of scan key for performance */ + distance = DatumGetFloat8(FunctionCall2Coll(so->procinfo, so->collation, PointerGetDatum(&list->center), value)); + + if (listCount < so->probes) + { + scanlist = &so->lists[listCount]; + scanlist->startPage = list->startPage; + scanlist->distance = distance; + listCount++; + + /* Add to heap */ + pairingheap_add(so->listQueue, &scanlist->ph_node); + + /* Calculate max distance */ + if (listCount == so->probes) + maxDistance = ((IvfflatScanList *) pairingheap_first(so->listQueue))->distance; + } + else if (distance < maxDistance) + { + /* Remove */ + scanlist = (IvfflatScanList *) pairingheap_remove_first(so->listQueue); + + /* Reuse */ + scanlist->startPage = list->startPage; + scanlist->distance = distance; + pairingheap_add(so->listQueue, &scanlist->ph_node); + + /* Update max distance */ + maxDistance = ((IvfflatScanList *) pairingheap_first(so->listQueue))->distance; + } + } + + nextblkno = IvfflatPageGetOpaque(cpage)->nextblkno; + + UnlockReleaseBuffer(cbuf); + } +} + +/* + * Get items + */ +static void +GetScanItems(IndexScanDesc scan, Datum value) +{ + IvfflatScanOpaque so = (IvfflatScanOpaque) scan->opaque; + Buffer buf; + Page page; + IndexTuple itup; + BlockNumber searchPage; + OffsetNumber offno; + OffsetNumber maxoffno; + Datum datum; + bool isnull; + TupleDesc tupdesc = RelationGetDescr(scan->indexRelation); + double tuples = 0; + +#if PG_VERSION_NUM >= 120000 + TupleTableSlot *slot = MakeSingleTupleTableSlot(so->tupdesc, &TTSOpsVirtual); +#else + TupleTableSlot *slot = MakeSingleTupleTableSlot(so->tupdesc); +#endif + + /* + * Reuse same set of shared buffers for scan + * + * See postgres/src/backend/storage/buffer/README for description + */ + BufferAccessStrategy bas = GetAccessStrategy(BAS_BULKREAD); + + /* Search closest probes lists */ + while (!pairingheap_is_empty(so->listQueue)) + { + searchPage = ((IvfflatScanList *) pairingheap_remove_first(so->listQueue))->startPage; + + /* Search all entry pages for list */ + while (BlockNumberIsValid(searchPage)) + { + buf = ReadBufferExtended(scan->indexRelation, MAIN_FORKNUM, searchPage, RBM_NORMAL, bas); + LockBuffer(buf, BUFFER_LOCK_SHARE); + page = BufferGetPage(buf); + maxoffno = PageGetMaxOffsetNumber(page); + + for (offno = FirstOffsetNumber; offno <= maxoffno; offno = OffsetNumberNext(offno)) + { + itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, offno)); + datum = index_getattr(itup, 1, tupdesc, &isnull); + + /* + * Add virtual tuple + * + * Use procinfo from the index instead of scan key for + * performance + */ + ExecClearTuple(slot); + slot->tts_values[0] = FunctionCall2Coll(so->procinfo, so->collation, datum, value); + slot->tts_isnull[0] = false; + slot->tts_values[1] = PointerGetDatum(&itup->t_tid); + slot->tts_isnull[1] = false; + slot->tts_values[2] = Int32GetDatum((int) searchPage); + slot->tts_isnull[2] = false; + ExecStoreVirtualTuple(slot); + + tuplesort_puttupleslot(so->sortstate, slot); + + tuples++; + } + + searchPage = IvfflatPageGetOpaque(page)->nextblkno; + + UnlockReleaseBuffer(buf); + } + } + + FreeAccessStrategy(bas); + + if (tuples < 100) + ereport(DEBUG1, + (errmsg("index scan found few tuples"), + errdetail("Index may have been created with little data."), + errhint("Recreate the index and possibly decrease lists."))); + + tuplesort_performsort(so->sortstate); +} + +/* + * Get dimensions from metapage + */ +static int +GetDimensions(Relation index) +{ + Buffer buf; + Page page; + IvfflatMetaPage metap; + int dimensions; + + buf = ReadBuffer(index, IVFFLAT_METAPAGE_BLKNO); + LockBuffer(buf, BUFFER_LOCK_SHARE); + page = BufferGetPage(buf); + metap = IvfflatPageGetMeta(page); + + dimensions = metap->dimensions; + + UnlockReleaseBuffer(buf); + + return dimensions; +} + +/* + * Prepare for an index scan + */ +IndexScanDesc +ivfflatbeginscan(Relation index, int nkeys, int norderbys) +{ + IndexScanDesc scan; + IvfflatScanOpaque so; + int lists; + AttrNumber attNums[] = {1}; + Oid sortOperators[] = {Float8LessOperator}; + Oid sortCollations[] = {InvalidOid}; + bool nullsFirstFlags[] = {false}; + int probes = ivfflat_probes; + + scan = RelationGetIndexScan(index, nkeys, norderbys); + lists = IvfflatGetLists(scan->indexRelation); + + if (probes > lists) + probes = lists; + + so = (IvfflatScanOpaque) palloc(offsetof(IvfflatScanOpaqueData, lists) + probes * sizeof(IvfflatScanList)); + so->buf = InvalidBuffer; + so->first = true; + so->probes = probes; + + /* Set support functions */ + so->procinfo = index_getprocinfo(index, 1, IVFFLAT_DISTANCE_PROC); + so->normprocinfo = IvfflatOptionalProcInfo(index, IVFFLAT_NORM_PROC); + so->collation = index->rd_indcollation[0]; + + /* Create tuple description for sorting */ +#if PG_VERSION_NUM >= 120000 + so->tupdesc = CreateTemplateTupleDesc(3); +#else + so->tupdesc = CreateTemplateTupleDesc(3, false); +#endif + TupleDescInitEntry(so->tupdesc, (AttrNumber) 1, "distance", FLOAT8OID, -1, 0); + TupleDescInitEntry(so->tupdesc, (AttrNumber) 2, "tid", TIDOID, -1, 0); + TupleDescInitEntry(so->tupdesc, (AttrNumber) 3, "indexblkno", INT4OID, -1, 0); + + /* Prep sort */ + so->sortstate = tuplesort_begin_heap(so->tupdesc, 1, attNums, sortOperators, sortCollations, nullsFirstFlags, work_mem, NULL, false); + +#if PG_VERSION_NUM >= 120000 + so->slot = MakeSingleTupleTableSlot(so->tupdesc, &TTSOpsMinimalTuple); +#else + so->slot = MakeSingleTupleTableSlot(so->tupdesc); +#endif + + so->listQueue = pairingheap_allocate(CompareLists, scan); + + scan->opaque = so; + + return scan; +} + +/* + * Start or restart an index scan + */ +void +ivfflatrescan(IndexScanDesc scan, ScanKey keys, int nkeys, ScanKey orderbys, int norderbys) +{ + IvfflatScanOpaque so = (IvfflatScanOpaque) scan->opaque; + +#if PG_VERSION_NUM >= 130000 + if (!so->first) + tuplesort_reset(so->sortstate); +#endif + + so->first = true; + pairingheap_reset(so->listQueue); + + if (keys && scan->numberOfKeys > 0) + memmove(scan->keyData, keys, scan->numberOfKeys * sizeof(ScanKeyData)); + + if (orderbys && scan->numberOfOrderBys > 0) + memmove(scan->orderByData, orderbys, scan->numberOfOrderBys * sizeof(ScanKeyData)); +} + +/* + * Fetch the next tuple in the given scan + */ +bool +ivfflatgettuple(IndexScanDesc scan, ScanDirection dir) +{ + IvfflatScanOpaque so = (IvfflatScanOpaque) scan->opaque; + + /* + * Index can be used to scan backward, but Postgres doesn't support + * backward scan on operators + */ + Assert(ScanDirectionIsForward(dir)); + + if (so->first) + { + Datum value; + + /* Count index scan for stats */ + pgstat_count_index_scan(scan->indexRelation); + + /* Safety check */ + if (scan->orderByData == NULL) + elog(ERROR, "cannot scan ivfflat index without order"); + + if (scan->orderByData->sk_flags & SK_ISNULL) + value = PointerGetDatum(InitVector(GetDimensions(scan->indexRelation))); + else + { + value = scan->orderByData->sk_argument; + + /* Value should not be compressed or toasted */ + Assert(!VARATT_IS_COMPRESSED(DatumGetPointer(value))); + Assert(!VARATT_IS_EXTENDED(DatumGetPointer(value))); + + /* Fine if normalization fails */ + if (so->normprocinfo != NULL) + IvfflatNormValue(so->normprocinfo, so->collation, &value, NULL); + } + + IvfflatBench("GetScanLists", GetScanLists(scan, value)); + IvfflatBench("GetScanItems", GetScanItems(scan, value)); + so->first = false; + + /* Clean up if we allocated a new value */ + if (value != scan->orderByData->sk_argument) + pfree(DatumGetPointer(value)); + } + + if (tuplesort_gettupleslot(so->sortstate, true, false, so->slot, NULL)) + { + ItemPointer tid = (ItemPointer) DatumGetPointer(slot_getattr(so->slot, 2, &so->isnull)); + BlockNumber indexblkno = DatumGetInt32(slot_getattr(so->slot, 3, &so->isnull)); + +#if PG_VERSION_NUM >= 120000 + scan->xs_heaptid = *tid; +#else + scan->xs_ctup.t_self = *tid; +#endif + + if (BufferIsValid(so->buf)) + ReleaseBuffer(so->buf); + + /* + * An index scan must maintain a pin on the index page holding the + * item last returned by amgettuple + * + * https://www.postgresql.org/docs/current/index-locking.html + */ + so->buf = ReadBuffer(scan->indexRelation, indexblkno); + + scan->xs_recheckorderby = false; + return true; + } + + return false; +} + +/* + * End a scan and release resources + */ +void +ivfflatendscan(IndexScanDesc scan) +{ + IvfflatScanOpaque so = (IvfflatScanOpaque) scan->opaque; + + /* Release pin */ + if (BufferIsValid(so->buf)) + ReleaseBuffer(so->buf); + + pairingheap_free(so->listQueue); + tuplesort_end(so->sortstate); + + pfree(so); + scan->opaque = NULL; +} diff --git a/packages/server/postgres/extensions/pgvector/src/ivfscan.o b/packages/server/postgres/extensions/pgvector/src/ivfscan.o new file mode 100644 index 0000000000000000000000000000000000000000..4e45c4047a1af71918401244dc61867b36fac37a GIT binary patch literal 6344 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 = 0; + res->maxlen = maxlen; + res->dim = dimensions; + res->items = palloc_extended(maxlen * VECTOR_SIZE(dimensions), MCXT_ALLOC_ZERO | MCXT_ALLOC_HUGE); + return res; +} + +/* + * Free a vector array + */ +void +VectorArrayFree(VectorArray arr) +{ + pfree(arr->items); + pfree(arr); +} + +/* + * Print vector array - useful for debugging + */ +void +PrintVectorArray(char *msg, VectorArray arr) +{ + for (int i = 0; i < arr->length; i++) + PrintVector(msg, VectorArrayGet(arr, i)); +} + +/* + * Get the number of lists in the index + */ +int +IvfflatGetLists(Relation index) +{ + IvfflatOptions *opts = (IvfflatOptions *) index->rd_options; + + if (opts) + return opts->lists; + + return IVFFLAT_DEFAULT_LISTS; +} + +/* + * Get proc + */ +FmgrInfo * +IvfflatOptionalProcInfo(Relation rel, uint16 procnum) +{ + if (!OidIsValid(index_getprocid(rel, 1, procnum))) + return NULL; + + return index_getprocinfo(rel, 1, procnum); +} + +/* + * Divide by the norm + * + * Returns false if value should not be indexed + * + * The caller needs to free the pointer stored in value + * if it's different than the original value + */ +bool +IvfflatNormValue(FmgrInfo *procinfo, Oid collation, Datum *value, Vector * result) +{ + double norm = DatumGetFloat8(FunctionCall1Coll(procinfo, collation, *value)); + + if (norm > 0) + { + Vector *v = DatumGetVector(*value); + + if (result == NULL) + result = InitVector(v->dim); + + for (int i = 0; i < v->dim; i++) + result->x[i] = v->x[i] / norm; + + *value = PointerGetDatum(result); + + return true; + } + + return false; +} + +/* + * New buffer + */ +Buffer +IvfflatNewBuffer(Relation index, ForkNumber forkNum) +{ + Buffer buf = ReadBufferExtended(index, forkNum, P_NEW, RBM_NORMAL, NULL); + + LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE); + return buf; +} + +/* + * Init page + */ +void +IvfflatInitPage(Buffer buf, Page page) +{ + PageInit(page, BufferGetPageSize(buf), sizeof(IvfflatPageOpaqueData)); + IvfflatPageGetOpaque(page)->nextblkno = InvalidBlockNumber; + IvfflatPageGetOpaque(page)->page_id = IVFFLAT_PAGE_ID; +} + +/* + * Init and register page + */ +void +IvfflatInitRegisterPage(Relation index, Buffer *buf, Page *page, GenericXLogState **state) +{ + *state = GenericXLogStart(index); + *page = GenericXLogRegisterBuffer(*state, *buf, GENERIC_XLOG_FULL_IMAGE); + IvfflatInitPage(*buf, *page); +} + +/* + * Commit buffer + */ +void +IvfflatCommitBuffer(Buffer buf, GenericXLogState *state) +{ + MarkBufferDirty(buf); + GenericXLogFinish(state); + UnlockReleaseBuffer(buf); +} + +/* + * Add a new page + * + * The order is very important!! + */ +void +IvfflatAppendPage(Relation index, Buffer *buf, Page *page, GenericXLogState **state, ForkNumber forkNum) +{ + /* Get new buffer */ + Buffer newbuf = IvfflatNewBuffer(index, forkNum); + Page newpage = GenericXLogRegisterBuffer(*state, newbuf, GENERIC_XLOG_FULL_IMAGE); + + /* Update the previous buffer */ + IvfflatPageGetOpaque(*page)->nextblkno = BufferGetBlockNumber(newbuf); + + /* Init new page */ + IvfflatInitPage(newbuf, newpage); + + /* Commit */ + MarkBufferDirty(*buf); + MarkBufferDirty(newbuf); + GenericXLogFinish(*state); + + /* Unlock */ + UnlockReleaseBuffer(*buf); + + *state = GenericXLogStart(index); + *page = GenericXLogRegisterBuffer(*state, newbuf, GENERIC_XLOG_FULL_IMAGE); + *buf = newbuf; +} + +/* + * Update the start or insert page of a list + */ +void +IvfflatUpdateList(Relation index, ListInfo listInfo, + BlockNumber insertPage, BlockNumber originalInsertPage, + BlockNumber startPage, ForkNumber forkNum) +{ + Buffer buf; + Page page; + GenericXLogState *state; + IvfflatList list; + bool changed = false; + + buf = ReadBufferExtended(index, forkNum, listInfo.blkno, RBM_NORMAL, NULL); + LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE); + state = GenericXLogStart(index); + page = GenericXLogRegisterBuffer(state, buf, 0); + list = (IvfflatList) PageGetItem(page, PageGetItemId(page, listInfo.offno)); + + if (BlockNumberIsValid(insertPage) && insertPage != list->insertPage) + { + /* Skip update if insert page is lower than original insert page */ + /* This is needed to prevent insert from overwriting vacuum */ + if (!BlockNumberIsValid(originalInsertPage) || insertPage >= originalInsertPage) + { + list->insertPage = insertPage; + changed = true; + } + } + + if (BlockNumberIsValid(startPage) && startPage != list->startPage) + { + list->startPage = startPage; + changed = true; + } + + /* Only commit if changed */ + if (changed) + IvfflatCommitBuffer(buf, state); + else + { + GenericXLogAbort(state); + UnlockReleaseBuffer(buf); + } +} diff --git a/packages/server/postgres/extensions/pgvector/src/ivfutils.o b/packages/server/postgres/extensions/pgvector/src/ivfutils.o new file mode 100644 index 0000000000000000000000000000000000000000..6f2e1db7e185f034a47991f54e0a56be147e0d5a GIT binary patch literal 3552 zcmb7HU2GIp6h6Dt)@k__+R`HS4`bSB6EGU=iZs2zdgM7crB5OApBE+5#UJ;2BZ>W%ABqKGg4&PM@$AHTYm~>9&WKrRSoU9 zAryeBdUhT1EbI16F0^LM$7?srM9;ISfZ`0csA_j46!mo*>OlCI9_~@q_BPwcX!FEH zUZ;_#1iMm9gnC)*)J)|i{8nIC;p;HLfN&7o6IfRZ{CW-14j2K{fx*HtJeYSq#%7HF zD|21nCujD%lG!15GW$M_K9F3g8HrLP#XG@ub{6yM4rpPu$<~?c?)LG}T$c-V zp_fBaGJAzaXCx{0Kys&A$f{i?|L1b8x2jPhkJoM4xW{LSE+tAP26<2J|3%EX2iMR8 z8T%ybX!IkDHK@Ht?;EFyx+KhAH> z4z(hu7Mx3S^6vJlL1*1=if@uoGp_m#-k;B^S)6U|SvlA7*<>(Wbh6X!vhG5lP8lo) zpMIo%jOPKjeCosd=R1qn(O_<`oM-+3eVRd^X2Hifc#;1Fx00?Cd2mi~*#7%o|8^I} z9mhUgAMeL!Z085P^r8Q|&EahBpk}_SO^>`c zC#AyFlkZHKxj)BFqQB^k`7V*`n@-7PN?LwR`BWzP)a@eLi5H)z7|-PEuJXPFk!O)@ zcs6dkxAa|h!-uu!VT;38;!@J8tF-R|=1HPg_?^Rb;}u(4>Z0`WSz31M?k?Z`dG)IK zz9}!Hbc5jaWfwj778kdG$CP*Qi}y8uj`D=sNj3=pZhlm zWPJ)TEJra+-PDLa5%KmtObFe+cL@=1-{TX)Z{PbDh0e*bJcq&j_PxRxnEFX}LT-w9 z`?-A^HjcLi&yY=Jnz|hNtFY#T(-DXNXNP~UL&x_#Z~YyI&fK~DL5Kc4t60$){8EgHwyyuJ%Z`D3H_x;UKh5EvWhWH9NpU z1+ z5UINd!rdIz>I(*Uwnl=%yq4|O!+L*UiEv*e7CPt)4rogj?f`cy&tMmp=V*>bwQ!GR ziuT4dEXnEnZqtxoN)M?~WEJUF@fQ@index; + BlockNumber blkno = IVFFLAT_HEAD_BLKNO; + BufferAccessStrategy bas = GetAccessStrategy(BAS_BULKREAD); + + if (stats == NULL) + stats = (IndexBulkDeleteResult *) palloc0(sizeof(IndexBulkDeleteResult)); + + /* Iterate over list pages */ + while (BlockNumberIsValid(blkno)) + { + Buffer cbuf; + Page cpage; + OffsetNumber coffno; + OffsetNumber cmaxoffno; + BlockNumber startPages[MaxOffsetNumber]; + ListInfo listInfo; + + cbuf = ReadBuffer(index, blkno); + LockBuffer(cbuf, BUFFER_LOCK_SHARE); + cpage = BufferGetPage(cbuf); + + cmaxoffno = PageGetMaxOffsetNumber(cpage); + + /* Iterate over lists */ + for (coffno = FirstOffsetNumber; coffno <= cmaxoffno; coffno = OffsetNumberNext(coffno)) + { + IvfflatList list = (IvfflatList) PageGetItem(cpage, PageGetItemId(cpage, coffno)); + + startPages[coffno - FirstOffsetNumber] = list->startPage; + } + + listInfo.blkno = blkno; + blkno = IvfflatPageGetOpaque(cpage)->nextblkno; + + UnlockReleaseBuffer(cbuf); + + for (coffno = FirstOffsetNumber; coffno <= cmaxoffno; coffno = OffsetNumberNext(coffno)) + { + BlockNumber searchPage = startPages[coffno - FirstOffsetNumber]; + BlockNumber insertPage = InvalidBlockNumber; + + /* Iterate over entry pages */ + while (BlockNumberIsValid(searchPage)) + { + Buffer buf; + Page page; + GenericXLogState *state; + OffsetNumber offno; + OffsetNumber maxoffno; + OffsetNumber deletable[MaxOffsetNumber]; + int ndeletable; + + vacuum_delay_point(); + + buf = ReadBufferExtended(index, MAIN_FORKNUM, searchPage, RBM_NORMAL, bas); + + /* + * ambulkdelete cannot delete entries from pages that are + * pinned by other backends + * + * https://www.postgresql.org/docs/current/index-locking.html + */ + LockBufferForCleanup(buf); + + state = GenericXLogStart(index); + page = GenericXLogRegisterBuffer(state, buf, 0); + + maxoffno = PageGetMaxOffsetNumber(page); + ndeletable = 0; + + /* Find deleted tuples */ + for (offno = FirstOffsetNumber; offno <= maxoffno; offno = OffsetNumberNext(offno)) + { + IndexTuple itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, offno)); + ItemPointer htup = &(itup->t_tid); + + if (callback(htup, callback_state)) + { + deletable[ndeletable++] = offno; + stats->tuples_removed++; + } + else + stats->num_index_tuples++; + } + + /* Set to first free page */ + /* Must be set before searchPage is updated */ + if (!BlockNumberIsValid(insertPage) && ndeletable > 0) + insertPage = searchPage; + + searchPage = IvfflatPageGetOpaque(page)->nextblkno; + + if (ndeletable > 0) + { + /* Delete tuples */ + PageIndexMultiDelete(page, deletable, ndeletable); + MarkBufferDirty(buf); + GenericXLogFinish(state); + } + else + GenericXLogAbort(state); + + UnlockReleaseBuffer(buf); + } + + /* + * Update after all tuples deleted. + * + * We don't add or delete items from lists pages, so offset won't + * change. + */ + if (BlockNumberIsValid(insertPage)) + { + listInfo.offno = coffno; + IvfflatUpdateList(index, listInfo, insertPage, InvalidBlockNumber, InvalidBlockNumber, MAIN_FORKNUM); + } + } + } + + FreeAccessStrategy(bas); + + return stats; +} + +/* + * Clean up after a VACUUM operation + */ +IndexBulkDeleteResult * +ivfflatvacuumcleanup(IndexVacuumInfo *info, IndexBulkDeleteResult *stats) +{ + Relation rel = info->index; + + if (info->analyze_only) + return stats; + + /* stats is NULL if ambulkdelete not called */ + /* OK to return NULL if index not changed */ + if (stats == NULL) + return NULL; + + stats->num_pages = RelationGetNumberOfBlocks(rel); + + return stats; +} diff --git a/packages/server/postgres/extensions/pgvector/src/ivfvacuum.o b/packages/server/postgres/extensions/pgvector/src/ivfvacuum.o new file mode 100644 index 0000000000000000000000000000000000000000..0d358adb993d5d6a380de002b298eaf855a72683 GIT binary patch literal 2664 zcmb7GUu;ul6hF6jTU!R~FLZwl8xBJ^tN{t)GRZD%!wrYbQ6d=U<x&t_V1h;ogka)BM|@}n63nJ)$4dQuy}eqlzW62Q{=Rd* z^PO|P-@WH+e_VR_WF-+t80=9BnP8J<2u3|5nV<{f}|H{;tU=E${Ino^=rY7^^UWV7vgSgv?=|6Ua6(za;bq;29fH0-MS)AAqpI zd<kB-^ zjz3EgVCL8^ifo17l2wSj#d8r`rGMSy3d-1tYVGTVNt>d~CiwbThPTC7tOd+Ft+Yj0 zL+%9M7@Oy7^7ag3t}o=P=hEAY>uI&p8?C2=bGB{vX;4{Pyq4nI+iZuFw7p)K?3C%~ zZyPT*lxHsv`sh-yl`Aj5pSw6XX7779<+#{zkh8ZPgQ1u!iEsS&|zm&d$TmGC}lGW}^_lk;^%f zd`(-@N|{!~Wj0o)6LIW_ldKWqO`P4=lFqq%Z?sO=T@z=UzyalQ?v#V#HkztVb|44# zoM{mzFZf2mna)dsZ^-9j{+H~;y5KzHjYFPvC&lmabuk|EUmNjmv9<&r4IhT=UCkqg z1wKq)hHk49Chg!z0Y`SuUjA~AWq%X6E7>HRpyixLcX1iGbpzoT6_V0+cT_O6K&~xm)GQYzX_MO$lvL|;U=C?D2 z{a7NpjLWHs!!2hT%+AIu5L`3=TK@^_HAJo%AnqBL3Um;uqdE>92IA^-4FGXRxt4J; z;a+wv0xN(IfVf9oP9S>FRR;tou1er)pbc0Je1PQmEpR1(wZOYT^n*()=!>Wi-<0}D z$DzAG6sz9Wap-#>ZdBJCunIT>WN}m=?o-#>z!kt=AgjMy#~~+h4fG8<4mAVy)H;^% z-M#%b%LZo&f1A6@{lb;-kGMrH4NR6jD0_q&^Wz6>S`9JAb*AHNN$|qPap)CA>+C#C zH~9YodeJgHXVUvkx);vH{+CVb@0fIO{FHyYN&nEKUpMJ1O!-*u;{2bO)|007E2eey z{8RgHnf#kg>sw6vC6j*3q>r28k+@e1`gVnfhvi_m64WM1d{x$VdOUI{v|kHKnmhv2 z?w~B2ZGFdvhgC^CFz$s@zY@~ePQM&fJO}#&BRh`-f=^raYZ9{@lRV+@nCO*NX;K^y zD1O-b10G4$YuKd*JiY;DmV+S@i;U2U;t$FrNGS((hkS^_DM&ovXF&#KRhB}s;V>vm zUSqE4rR$}5;N}mH7l+s#2zD7({NWk(g*2b&m4f)ggFjGWNRvE1W)_DfMI{lLBuxqU zLEP)%u_JPD-*EmIp*}w%`JSqKx8heqqa+qeI1*NUphDJU5(lIaxzF#FC-#O_P3bOJ ni_?vSrJ$GPGmg?Tp~=|cB~=?6-;#fBCUIO+LBM9bX`;UX + +#include "catalog/pg_type.h" +#include "fmgr.h" +#include "hnsw.h" +#include "ivfflat.h" +#include "lib/stringinfo.h" +#include "libpq/pqformat.h" +#include "port.h" /* for strtof() */ +#include "utils/array.h" +#include "utils/builtins.h" +#include "utils/lsyscache.h" +#include "utils/numeric.h" +#include "vector.h" + +#if PG_VERSION_NUM >= 160000 +#include "varatt.h" +#endif + +#if PG_VERSION_NUM >= 120000 +#include "common/shortest_dec.h" +#include "utils/float.h" +#else +#include +#endif + +#if PG_VERSION_NUM < 130000 +#define TYPALIGN_DOUBLE 'd' +#define TYPALIGN_INT 'i' +#endif + +#define STATE_DIMS(x) (ARR_DIMS(x)[0] - 1) +#define CreateStateDatums(dim) palloc(sizeof(Datum) * (dim + 1)) + +PG_MODULE_MAGIC; + +/* + * Initialize index options and variables + */ +PGDLLEXPORT void _PG_init(void); +void +_PG_init(void) +{ + HnswInit(); + IvfflatInit(); +} + +/* + * Ensure same dimensions + */ +static inline void +CheckDims(Vector * a, Vector * b) +{ + if (a->dim != b->dim) + ereport(ERROR, + (errcode(ERRCODE_DATA_EXCEPTION), + errmsg("different vector dimensions %d and %d", a->dim, b->dim))); +} + +/* + * Ensure expected dimensions + */ +static inline void +CheckExpectedDim(int32 typmod, int dim) +{ + if (typmod != -1 && typmod != dim) + ereport(ERROR, + (errcode(ERRCODE_DATA_EXCEPTION), + errmsg("expected %d dimensions, not %d", typmod, dim))); +} + +/* + * Ensure valid dimensions + */ +static inline void +CheckDim(int dim) +{ + if (dim < 1) + ereport(ERROR, + (errcode(ERRCODE_DATA_EXCEPTION), + errmsg("vector must have at least 1 dimension"))); + + if (dim > VECTOR_MAX_DIM) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("vector cannot have more than %d dimensions", VECTOR_MAX_DIM))); +} + +/* + * Ensure finite elements + */ +static inline void +CheckElement(float value) +{ + if (isnan(value)) + ereport(ERROR, + (errcode(ERRCODE_DATA_EXCEPTION), + errmsg("NaN not allowed in vector"))); + + if (isinf(value)) + ereport(ERROR, + (errcode(ERRCODE_DATA_EXCEPTION), + errmsg("infinite value not allowed in vector"))); +} + +/* + * Allocate and initialize a new vector + */ +Vector * +InitVector(int dim) +{ + Vector *result; + int size; + + size = VECTOR_SIZE(dim); + result = (Vector *) palloc0(size); + SET_VARSIZE(result, size); + result->dim = dim; + + return result; +} + +/* + * Check for whitespace, since array_isspace() is static + */ +static inline bool +vector_isspace(char ch) +{ + if (ch == ' ' || + ch == '\t' || + ch == '\n' || + ch == '\r' || + ch == '\v' || + ch == '\f') + return true; + return false; +} + +/* + * Check state array + */ +static float8 * +CheckStateArray(ArrayType *statearray, const char *caller) +{ + if (ARR_NDIM(statearray) != 1 || + ARR_DIMS(statearray)[0] < 1 || + ARR_HASNULL(statearray) || + ARR_ELEMTYPE(statearray) != FLOAT8OID) + elog(ERROR, "%s: expected state array", caller); + return (float8 *) ARR_DATA_PTR(statearray); +} + +#if PG_VERSION_NUM < 120003 +static pg_noinline void +float_overflow_error(void) +{ + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("value out of range: overflow"))); +} + +static pg_noinline void +float_underflow_error(void) +{ + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("value out of range: underflow"))); +} +#endif + +/* + * Convert textual representation to internal representation + */ +PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_in); +Datum +vector_in(PG_FUNCTION_ARGS) +{ + char *str = PG_GETARG_CSTRING(0); + int32 typmod = PG_GETARG_INT32(2); + float x[VECTOR_MAX_DIM]; + int dim = 0; + char *pt; + char *stringEnd; + Vector *result; + char *lit = pstrdup(str); + + while (vector_isspace(*str)) + str++; + + if (*str != '[') + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("malformed vector literal: \"%s\"", lit), + errdetail("Vector contents must start with \"[\"."))); + + str++; + pt = strtok(str, ","); + stringEnd = pt; + + while (pt != NULL && *stringEnd != ']') + { + if (dim == VECTOR_MAX_DIM) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("vector cannot have more than %d dimensions", VECTOR_MAX_DIM))); + + while (vector_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 vector: \"%s\"", lit))); + + /* Use strtof like float4in to avoid a double-rounding problem */ + x[dim] = strtof(pt, &stringEnd); + CheckElement(x[dim]); + dim++; + + if (stringEnd == pt) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for type vector: \"%s\"", lit))); + + while (vector_isspace(*stringEnd)) + stringEnd++; + + if (*stringEnd != '\0' && *stringEnd != ']') + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for type vector: \"%s\"", lit))); + + pt = strtok(NULL, ","); + } + + if (stringEnd == NULL || *stringEnd != ']') + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("malformed vector literal: \"%s\"", lit), + errdetail("Unexpected end of input."))); + + stringEnd++; + + /* Only whitespace is allowed after the closing brace */ + while (vector_isspace(*stringEnd)) + stringEnd++; + + if (*stringEnd != '\0') + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("malformed vector literal: \"%s\"", lit), + errdetail("Junk after closing right brace."))); + + /* Ensure no consecutive delimiters since strtok skips */ + for (pt = lit + 1; *pt != '\0'; pt++) + { + if (pt[-1] == ',' && *pt == ',') + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("malformed vector literal: \"%s\"", lit))); + } + + if (dim < 1) + ereport(ERROR, + (errcode(ERRCODE_DATA_EXCEPTION), + errmsg("vector must have at least 1 dimension"))); + + pfree(lit); + + CheckExpectedDim(typmod, dim); + + result = InitVector(dim); + for (int i = 0; i < dim; i++) + result->x[i] = x[i]; + + PG_RETURN_POINTER(result); +} + +/* + * Convert internal representation to textual representation + */ +PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_out); +Datum +vector_out(PG_FUNCTION_ARGS) +{ + Vector *vector = PG_GETARG_VECTOR_P(0); + int dim = vector->dim; + char *buf; + char *ptr; + int n; + +#if PG_VERSION_NUM < 120000 + int ndig = FLT_DIG + extra_float_digits; + + if (ndig < 1) + ndig = 1; + +#define FLOAT_SHORTEST_DECIMAL_LEN (ndig + 10) +#endif + + /* + * Need: + * + * dim * (FLOAT_SHORTEST_DECIMAL_LEN - 1) bytes for + * float_to_shortest_decimal_bufn + * + * dim - 1 bytes for separator + * + * 3 bytes for [, ], and \0 + */ + buf = (char *) palloc(FLOAT_SHORTEST_DECIMAL_LEN * dim + 2); + ptr = buf; + + *ptr = '['; + ptr++; + for (int i = 0; i < dim; i++) + { + if (i > 0) + { + *ptr = ','; + ptr++; + } + +#if PG_VERSION_NUM >= 120000 + n = float_to_shortest_decimal_bufn(vector->x[i], ptr); +#else + n = sprintf(ptr, "%.*g", ndig, vector->x[i]); +#endif + ptr += n; + } + *ptr = ']'; + ptr++; + *ptr = '\0'; + + PG_FREE_IF_COPY(vector, 0); + PG_RETURN_CSTRING(buf); +} + +/* + * Print vector - useful for debugging + */ +void +PrintVector(char *msg, Vector * vector) +{ + char *out = DatumGetPointer(DirectFunctionCall1(vector_out, PointerGetDatum(vector))); + + elog(INFO, "%s = %s", msg, out); + pfree(out); +} + +/* + * Convert type modifier + */ +PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_typmod_in); +Datum +vector_typmod_in(PG_FUNCTION_ARGS) +{ + ArrayType *ta = PG_GETARG_ARRAYTYPE_P(0); + int32 *tl; + int n; + + tl = ArrayGetIntegerTypmods(ta, &n); + + if (n != 1) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid type modifier"))); + + if (*tl < 1) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("dimensions for type vector must be at least 1"))); + + if (*tl > VECTOR_MAX_DIM) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("dimensions for type vector cannot exceed %d", VECTOR_MAX_DIM))); + + PG_RETURN_INT32(*tl); +} + +/* + * Convert external binary representation to internal representation + */ +PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_recv); +Datum +vector_recv(PG_FUNCTION_ARGS) +{ + StringInfo buf = (StringInfo) PG_GETARG_POINTER(0); + int32 typmod = PG_GETARG_INT32(2); + Vector *result; + int16 dim; + int16 unused; + + dim = pq_getmsgint(buf, sizeof(int16)); + unused = pq_getmsgint(buf, sizeof(int16)); + + CheckDim(dim); + CheckExpectedDim(typmod, dim); + + if (unused != 0) + ereport(ERROR, + (errcode(ERRCODE_DATA_EXCEPTION), + errmsg("expected unused to be 0, not %d", unused))); + + result = InitVector(dim); + for (int i = 0; i < dim; i++) + { + result->x[i] = pq_getmsgfloat4(buf); + CheckElement(result->x[i]); + } + + PG_RETURN_POINTER(result); +} + +/* + * Convert internal representation to the external binary representation + */ +PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_send); +Datum +vector_send(PG_FUNCTION_ARGS) +{ + Vector *vec = PG_GETARG_VECTOR_P(0); + StringInfoData buf; + + pq_begintypsend(&buf); + pq_sendint(&buf, vec->dim, sizeof(int16)); + pq_sendint(&buf, vec->unused, sizeof(int16)); + for (int i = 0; i < vec->dim; i++) + pq_sendfloat4(&buf, vec->x[i]); + + PG_RETURN_BYTEA_P(pq_endtypsend(&buf)); +} + +/* + * Convert vector to vector + */ +PGDLLEXPORT PG_FUNCTION_INFO_V1(vector); +Datum +vector(PG_FUNCTION_ARGS) +{ + Vector *arg = PG_GETARG_VECTOR_P(0); + int32 typmod = PG_GETARG_INT32(1); + + CheckExpectedDim(typmod, arg->dim); + + PG_RETURN_POINTER(arg); +} + +/* + * Convert array to vector + */ +PGDLLEXPORT PG_FUNCTION_INFO_V1(array_to_vector); +Datum +array_to_vector(PG_FUNCTION_ARGS) +{ + ArrayType *array = PG_GETARG_ARRAYTYPE_P(0); + int32 typmod = PG_GETARG_INT32(1); + Vector *result; + int16 typlen; + bool typbyval; + char typalign; + Datum *elemsp; + bool *nullsp; + int nelemsp; + + if (ARR_NDIM(array) > 1) + ereport(ERROR, + (errcode(ERRCODE_DATA_EXCEPTION), + errmsg("array must be 1-D"))); + + if (ARR_HASNULL(array) && array_contains_nulls(array)) + ereport(ERROR, + (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("array must not contain nulls"))); + + get_typlenbyvalalign(ARR_ELEMTYPE(array), &typlen, &typbyval, &typalign); + deconstruct_array(array, ARR_ELEMTYPE(array), typlen, typbyval, typalign, &elemsp, &nullsp, &nelemsp); + + CheckDim(nelemsp); + CheckExpectedDim(typmod, nelemsp); + + result = InitVector(nelemsp); + + if (ARR_ELEMTYPE(array) == INT4OID) + { + for (int i = 0; i < nelemsp; i++) + result->x[i] = DatumGetInt32(elemsp[i]); + } + else if (ARR_ELEMTYPE(array) == FLOAT8OID) + { + for (int i = 0; i < nelemsp; i++) + result->x[i] = DatumGetFloat8(elemsp[i]); + } + else if (ARR_ELEMTYPE(array) == FLOAT4OID) + { + for (int i = 0; i < nelemsp; i++) + result->x[i] = DatumGetFloat4(elemsp[i]); + } + else if (ARR_ELEMTYPE(array) == NUMERICOID) + { + for (int i = 0; i < nelemsp; i++) + result->x[i] = DatumGetFloat4(DirectFunctionCall1(numeric_float4, elemsp[i])); + } + else + { + ereport(ERROR, + (errcode(ERRCODE_DATA_EXCEPTION), + errmsg("unsupported array type"))); + } + + /* Check elements */ + for (int i = 0; i < result->dim; i++) + CheckElement(result->x[i]); + + PG_RETURN_POINTER(result); +} + +/* + * Convert vector to float4[] + */ +PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_to_float4); +Datum +vector_to_float4(PG_FUNCTION_ARGS) +{ + Vector *vec = PG_GETARG_VECTOR_P(0); + Datum *datums; + ArrayType *result; + + datums = (Datum *) palloc(sizeof(Datum) * vec->dim); + + for (int i = 0; i < vec->dim; i++) + datums[i] = Float4GetDatum(vec->x[i]); + + /* Use TYPALIGN_INT for float4 */ + result = construct_array(datums, vec->dim, FLOAT4OID, sizeof(float4), true, TYPALIGN_INT); + + pfree(datums); + + PG_RETURN_POINTER(result); +} + +/* + * Get the L2 distance between vectors + */ +PGDLLEXPORT PG_FUNCTION_INFO_V1(l2_distance); +Datum +l2_distance(PG_FUNCTION_ARGS) +{ + Vector *a = PG_GETARG_VECTOR_P(0); + Vector *b = PG_GETARG_VECTOR_P(1); + float *ax = a->x; + float *bx = b->x; + float distance = 0.0; + float diff; + + CheckDims(a, b); + + /* Auto-vectorized */ + for (int i = 0; i < a->dim; i++) + { + diff = ax[i] - bx[i]; + distance += diff * diff; + } + + PG_RETURN_FLOAT8(sqrt((double) distance)); +} + +/* + * Get the L2 squared distance between vectors + * This saves a sqrt calculation + */ +PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_l2_squared_distance); +Datum +vector_l2_squared_distance(PG_FUNCTION_ARGS) +{ + Vector *a = PG_GETARG_VECTOR_P(0); + Vector *b = PG_GETARG_VECTOR_P(1); + float *ax = a->x; + float *bx = b->x; + float distance = 0.0; + float diff; + + CheckDims(a, b); + + /* Auto-vectorized */ + for (int i = 0; i < a->dim; i++) + { + diff = ax[i] - bx[i]; + distance += diff * diff; + } + + PG_RETURN_FLOAT8((double) distance); +} + +/* + * Get the inner product of two vectors + */ +PGDLLEXPORT PG_FUNCTION_INFO_V1(inner_product); +Datum +inner_product(PG_FUNCTION_ARGS) +{ + Vector *a = PG_GETARG_VECTOR_P(0); + Vector *b = PG_GETARG_VECTOR_P(1); + float *ax = a->x; + float *bx = b->x; + float distance = 0.0; + + CheckDims(a, b); + + /* Auto-vectorized */ + for (int i = 0; i < a->dim; i++) + distance += ax[i] * bx[i]; + + PG_RETURN_FLOAT8((double) distance); +} + +/* + * Get the negative inner product of two vectors + */ +PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_negative_inner_product); +Datum +vector_negative_inner_product(PG_FUNCTION_ARGS) +{ + Vector *a = PG_GETARG_VECTOR_P(0); + Vector *b = PG_GETARG_VECTOR_P(1); + float *ax = a->x; + float *bx = b->x; + float distance = 0.0; + + CheckDims(a, b); + + /* Auto-vectorized */ + for (int i = 0; i < a->dim; i++) + distance += ax[i] * bx[i]; + + PG_RETURN_FLOAT8((double) distance * -1); +} + +/* + * Get the cosine distance between two vectors + */ +PGDLLEXPORT PG_FUNCTION_INFO_V1(cosine_distance); +Datum +cosine_distance(PG_FUNCTION_ARGS) +{ + Vector *a = PG_GETARG_VECTOR_P(0); + Vector *b = PG_GETARG_VECTOR_P(1); + float *ax = a->x; + float *bx = b->x; + float distance = 0.0; + float norma = 0.0; + float normb = 0.0; + double similarity; + + CheckDims(a, b); + + /* Auto-vectorized */ + for (int i = 0; i < a->dim; i++) + { + distance += ax[i] * bx[i]; + norma += ax[i] * ax[i]; + normb += bx[i] * bx[i]; + } + + /* Use sqrt(a * b) over sqrt(a) * sqrt(b) */ + similarity = (double) distance / sqrt((double) norma * (double) normb); + +#ifdef _MSC_VER + /* /fp:fast may not propagate NaN */ + if (isnan(similarity)) + PG_RETURN_FLOAT8(NAN); +#endif + + /* Keep in range */ + if (similarity > 1) + similarity = 1.0; + else if (similarity < -1) + similarity = -1.0; + + PG_RETURN_FLOAT8(1.0 - similarity); +} + +/* + * Get the distance for spherical k-means + * Currently uses angular distance since needs to satisfy triangle inequality + * Assumes inputs are unit vectors (skips norm) + */ +PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_spherical_distance); +Datum +vector_spherical_distance(PG_FUNCTION_ARGS) +{ + Vector *a = PG_GETARG_VECTOR_P(0); + Vector *b = PG_GETARG_VECTOR_P(1); + float dp = 0.0; + double distance; + + CheckDims(a, b); + + /* Auto-vectorized */ + for (int i = 0; i < a->dim; i++) + dp += a->x[i] * b->x[i]; + + distance = (double) dp; + + /* Prevent NaN with acos with loss of precision */ + if (distance > 1) + distance = 1; + else if (distance < -1) + distance = -1; + + PG_RETURN_FLOAT8(acos(distance) / M_PI); +} + +/* + * Get the L1 distance between vectors + */ +PGDLLEXPORT PG_FUNCTION_INFO_V1(l1_distance); +Datum +l1_distance(PG_FUNCTION_ARGS) +{ + Vector *a = PG_GETARG_VECTOR_P(0); + Vector *b = PG_GETARG_VECTOR_P(1); + float *ax = a->x; + float *bx = b->x; + float distance = 0.0; + + CheckDims(a, b); + + /* Auto-vectorized */ + for (int i = 0; i < a->dim; i++) + distance += fabsf(ax[i] - bx[i]); + + PG_RETURN_FLOAT8((double) distance); +} + +/* + * Get the dimensions of a vector + */ +PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_dims); +Datum +vector_dims(PG_FUNCTION_ARGS) +{ + Vector *a = PG_GETARG_VECTOR_P(0); + + PG_RETURN_INT32(a->dim); +} + +/* + * Get the L2 norm of a vector + */ +PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_norm); +Datum +vector_norm(PG_FUNCTION_ARGS) +{ + Vector *a = PG_GETARG_VECTOR_P(0); + float *ax = a->x; + double norm = 0.0; + + /* Auto-vectorized */ + for (int i = 0; i < a->dim; i++) + norm += (double) ax[i] * (double) ax[i]; + + PG_RETURN_FLOAT8(sqrt(norm)); +} + +/* + * Add vectors + */ +PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_add); +Datum +vector_add(PG_FUNCTION_ARGS) +{ + Vector *a = PG_GETARG_VECTOR_P(0); + Vector *b = PG_GETARG_VECTOR_P(1); + float *ax = a->x; + float *bx = b->x; + Vector *result; + float *rx; + + CheckDims(a, b); + + result = InitVector(a->dim); + rx = result->x; + + /* Auto-vectorized */ + for (int i = 0, imax = a->dim; i < imax; i++) + rx[i] = ax[i] + bx[i]; + + /* Check for overflow */ + for (int i = 0, imax = a->dim; i < imax; i++) + { + if (isinf(rx[i])) + float_overflow_error(); + } + + PG_RETURN_POINTER(result); +} + +/* + * Subtract vectors + */ +PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_sub); +Datum +vector_sub(PG_FUNCTION_ARGS) +{ + Vector *a = PG_GETARG_VECTOR_P(0); + Vector *b = PG_GETARG_VECTOR_P(1); + float *ax = a->x; + float *bx = b->x; + Vector *result; + float *rx; + + CheckDims(a, b); + + result = InitVector(a->dim); + rx = result->x; + + /* Auto-vectorized */ + for (int i = 0, imax = a->dim; i < imax; i++) + rx[i] = ax[i] - bx[i]; + + /* Check for overflow */ + for (int i = 0, imax = a->dim; i < imax; i++) + { + if (isinf(rx[i])) + float_overflow_error(); + } + + PG_RETURN_POINTER(result); +} + +/* + * Multiply vectors + */ +PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_mul); +Datum +vector_mul(PG_FUNCTION_ARGS) +{ + Vector *a = PG_GETARG_VECTOR_P(0); + Vector *b = PG_GETARG_VECTOR_P(1); + float *ax = a->x; + float *bx = b->x; + Vector *result; + float *rx; + + CheckDims(a, b); + + result = InitVector(a->dim); + rx = result->x; + + /* Auto-vectorized */ + for (int i = 0, imax = a->dim; i < imax; i++) + rx[i] = ax[i] * bx[i]; + + /* Check for overflow and underflow */ + for (int i = 0, imax = a->dim; i < imax; i++) + { + if (isinf(rx[i])) + float_overflow_error(); + + if (rx[i] == 0 && !(ax[i] == 0 || bx[i] == 0)) + float_underflow_error(); + } + + PG_RETURN_POINTER(result); +} + +/* + * Internal helper to compare vectors + */ +int +vector_cmp_internal(Vector * a, Vector * b) +{ + CheckDims(a, b); + + for (int i = 0; i < a->dim; i++) + { + if (a->x[i] < b->x[i]) + return -1; + + if (a->x[i] > b->x[i]) + return 1; + } + return 0; +} + +/* + * Less than + */ +PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_lt); +Datum +vector_lt(PG_FUNCTION_ARGS) +{ + Vector *a = (Vector *) PG_GETARG_VECTOR_P(0); + Vector *b = (Vector *) PG_GETARG_VECTOR_P(1); + + PG_RETURN_BOOL(vector_cmp_internal(a, b) < 0); +} + +/* + * Less than or equal + */ +PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_le); +Datum +vector_le(PG_FUNCTION_ARGS) +{ + Vector *a = (Vector *) PG_GETARG_VECTOR_P(0); + Vector *b = (Vector *) PG_GETARG_VECTOR_P(1); + + PG_RETURN_BOOL(vector_cmp_internal(a, b) <= 0); +} + +/* + * Equal + */ +PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_eq); +Datum +vector_eq(PG_FUNCTION_ARGS) +{ + Vector *a = (Vector *) PG_GETARG_VECTOR_P(0); + Vector *b = (Vector *) PG_GETARG_VECTOR_P(1); + + PG_RETURN_BOOL(vector_cmp_internal(a, b) == 0); +} + +/* + * Not equal + */ +PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_ne); +Datum +vector_ne(PG_FUNCTION_ARGS) +{ + Vector *a = (Vector *) PG_GETARG_VECTOR_P(0); + Vector *b = (Vector *) PG_GETARG_VECTOR_P(1); + + PG_RETURN_BOOL(vector_cmp_internal(a, b) != 0); +} + +/* + * Greater than or equal + */ +PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_ge); +Datum +vector_ge(PG_FUNCTION_ARGS) +{ + Vector *a = (Vector *) PG_GETARG_VECTOR_P(0); + Vector *b = (Vector *) PG_GETARG_VECTOR_P(1); + + PG_RETURN_BOOL(vector_cmp_internal(a, b) >= 0); +} + +/* + * Greater than + */ +PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_gt); +Datum +vector_gt(PG_FUNCTION_ARGS) +{ + Vector *a = (Vector *) PG_GETARG_VECTOR_P(0); + Vector *b = (Vector *) PG_GETARG_VECTOR_P(1); + + PG_RETURN_BOOL(vector_cmp_internal(a, b) > 0); +} + +/* + * Compare vectors + */ +PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_cmp); +Datum +vector_cmp(PG_FUNCTION_ARGS) +{ + Vector *a = (Vector *) PG_GETARG_VECTOR_P(0); + Vector *b = (Vector *) PG_GETARG_VECTOR_P(1); + + PG_RETURN_INT32(vector_cmp_internal(a, b)); +} + +/* + * Accumulate vectors + */ +PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_accum); +Datum +vector_accum(PG_FUNCTION_ARGS) +{ + ArrayType *statearray = PG_GETARG_ARRAYTYPE_P(0); + Vector *newval = PG_GETARG_VECTOR_P(1); + float8 *statevalues; + int16 dim; + bool newarr; + float8 n; + Datum *statedatums; + float *x = newval->x; + ArrayType *result; + + /* Check array before using */ + statevalues = CheckStateArray(statearray, "vector_accum"); + dim = STATE_DIMS(statearray); + newarr = dim == 0; + + if (newarr) + dim = newval->dim; + else + CheckExpectedDim(dim, newval->dim); + + n = statevalues[0] + 1.0; + + statedatums = CreateStateDatums(dim); + statedatums[0] = Float8GetDatum(n); + + if (newarr) + { + for (int i = 0; i < dim; i++) + statedatums[i + 1] = Float8GetDatum((double) x[i]); + } + else + { + for (int i = 0; i < dim; i++) + { + double v = statevalues[i + 1] + x[i]; + + /* Check for overflow */ + if (isinf(v)) + float_overflow_error(); + + statedatums[i + 1] = Float8GetDatum(v); + } + } + + /* Use float8 array like float4_accum */ + result = construct_array(statedatums, dim + 1, + FLOAT8OID, + sizeof(float8), FLOAT8PASSBYVAL, TYPALIGN_DOUBLE); + + pfree(statedatums); + + PG_RETURN_ARRAYTYPE_P(result); +} + +/* + * Combine vectors + */ +PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_combine); +Datum +vector_combine(PG_FUNCTION_ARGS) +{ + ArrayType *statearray1 = PG_GETARG_ARRAYTYPE_P(0); + ArrayType *statearray2 = PG_GETARG_ARRAYTYPE_P(1); + float8 *statevalues1; + float8 *statevalues2; + float8 n; + float8 n1; + float8 n2; + int16 dim; + Datum *statedatums; + ArrayType *result; + + /* Check arrays before using */ + statevalues1 = CheckStateArray(statearray1, "vector_combine"); + statevalues2 = CheckStateArray(statearray2, "vector_combine"); + + n1 = statevalues1[0]; + n2 = statevalues2[0]; + + if (n1 == 0.0) + { + n = n2; + dim = STATE_DIMS(statearray2); + statedatums = CreateStateDatums(dim); + for (int i = 1; i <= dim; i++) + statedatums[i] = Float8GetDatum(statevalues2[i]); + } + else if (n2 == 0.0) + { + n = n1; + dim = STATE_DIMS(statearray1); + statedatums = CreateStateDatums(dim); + for (int i = 1; i <= dim; i++) + statedatums[i] = Float8GetDatum(statevalues1[i]); + } + else + { + n = n1 + n2; + dim = STATE_DIMS(statearray1); + CheckExpectedDim(dim, STATE_DIMS(statearray2)); + statedatums = CreateStateDatums(dim); + for (int i = 1; i <= dim; i++) + { + double v = statevalues1[i] + statevalues2[i]; + + /* Check for overflow */ + if (isinf(v)) + float_overflow_error(); + + statedatums[i] = Float8GetDatum(v); + } + } + + statedatums[0] = Float8GetDatum(n); + + result = construct_array(statedatums, dim + 1, + FLOAT8OID, + sizeof(float8), FLOAT8PASSBYVAL, TYPALIGN_DOUBLE); + + pfree(statedatums); + + PG_RETURN_ARRAYTYPE_P(result); +} + +/* + * Average vectors + */ +PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_avg); +Datum +vector_avg(PG_FUNCTION_ARGS) +{ + ArrayType *statearray = PG_GETARG_ARRAYTYPE_P(0); + float8 *statevalues; + float8 n; + uint16 dim; + Vector *result; + + /* Check array before using */ + statevalues = CheckStateArray(statearray, "vector_avg"); + n = statevalues[0]; + + /* SQL defines AVG of no values to be NULL */ + if (n == 0.0) + PG_RETURN_NULL(); + + /* Create vector */ + dim = STATE_DIMS(statearray); + CheckDim(dim); + result = InitVector(dim); + for (int i = 0; i < dim; i++) + { + result->x[i] = statevalues[i + 1] / n; + CheckElement(result->x[i]); + } + + PG_RETURN_POINTER(result); +} diff --git a/packages/server/postgres/extensions/pgvector/src/vector.h b/packages/server/postgres/extensions/pgvector/src/vector.h new file mode 100644 index 00000000000..e649471eaaa --- /dev/null +++ b/packages/server/postgres/extensions/pgvector/src/vector.h @@ -0,0 +1,23 @@ +#ifndef VECTOR_H +#define VECTOR_H + +#define VECTOR_MAX_DIM 16000 + +#define VECTOR_SIZE(_dim) (offsetof(Vector, x) + sizeof(float)*(_dim)) +#define DatumGetVector(x) ((Vector *) PG_DETOAST_DATUM(x)) +#define PG_GETARG_VECTOR_P(x) DatumGetVector(PG_GETARG_DATUM(x)) +#define PG_RETURN_VECTOR_P(x) PG_RETURN_POINTER(x) + +typedef struct Vector +{ + int32 vl_len_; /* varlena header (do not touch directly!) */ + int16 dim; /* number of dimensions */ + int16 unused; + float x[FLEXIBLE_ARRAY_MEMBER]; +} Vector; + +Vector *InitVector(int dim); +void PrintVector(char *msg, Vector * vector); +int vector_cmp_internal(Vector * a, Vector * b); + +#endif diff --git a/packages/server/postgres/extensions/pgvector/src/vector.o b/packages/server/postgres/extensions/pgvector/src/vector.o new file mode 100644 index 0000000000000000000000000000000000000000..c1b6356d7956ff5bf39256010793224b65075c52 GIT binary patch literal 36336 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 Jths#s{{vhtk68c! literal 0 HcmV?d00001 From 00b92d2bcf672b5685b9e7d20c255ba27d908621 Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Tue, 26 Sep 2023 10:06:41 -0700 Subject: [PATCH 2/6] llama face --- .../graphql/mutations/createReflection.ts | 12 +- .../mutations/helpers/getOpenAIEmbeddings.ts | 19 ++ packages/server/graphql/types/User.ts | 27 +- packages/server/package.json | 3 +- .../1695253321182_reflectionEmbeddings.ts | 31 ++ yarn.lock | 284 ++++++++++++++++-- 6 files changed, 337 insertions(+), 39 deletions(-) create mode 100644 packages/server/graphql/mutations/helpers/getOpenAIEmbeddings.ts create mode 100644 packages/server/postgres/migrations/1695253321182_reflectionEmbeddings.ts diff --git a/packages/server/graphql/mutations/createReflection.ts b/packages/server/graphql/mutations/createReflection.ts index f9e25984da9..a0393fc5863 100644 --- a/packages/server/graphql/mutations/createReflection.ts +++ b/packages/server/graphql/mutations/createReflection.ts @@ -19,6 +19,7 @@ import CreateReflectionInput, {CreateReflectionInputType} from '../types/CreateR import CreateReflectionPayload from '../types/CreateReflectionPayload' import getReflectionEntities from './helpers/getReflectionEntities' import getReflectionSentimentScore from './helpers/getReflectionSentimentScore' +import getOpenAIEmbeddings from './helpers/getOpenAIEmbeddings' export default { type: CreateReflectionPayload, @@ -63,9 +64,10 @@ export default { // RESOLUTION const plaintextContent = extractTextFromDraftString(normalizedContent) - const [entities, sentimentScore] = await Promise.all([ + const [entities, sentimentScore, embeddings] = await Promise.all([ getReflectionEntities(plaintextContent), - tier !== 'starter' ? getReflectionSentimentScore(question, plaintextContent) : undefined + tier !== 'starter' ? getReflectionSentimentScore(question, plaintextContent) : undefined, + getOpenAIEmbeddings(plaintextContent) ]) const reflectionGroupId = generateUID() @@ -93,6 +95,12 @@ export default { await Promise.all([ pg.insertInto('RetroReflectionGroup').values(reflectionGroup).execute(), + embeddings + ? pg + .insertInto('ReflectionEmbeddings') + .values({id: reflection.id, vector: embeddings}) + .execute() + : null, r.table('RetroReflectionGroup').insert(reflectionGroup).run(), r.table('RetroReflection').insert(reflection).run() ]) diff --git a/packages/server/graphql/mutations/helpers/getOpenAIEmbeddings.ts b/packages/server/graphql/mutations/helpers/getOpenAIEmbeddings.ts new file mode 100644 index 00000000000..ce9f23880e2 --- /dev/null +++ b/packages/server/graphql/mutations/helpers/getOpenAIEmbeddings.ts @@ -0,0 +1,19 @@ +import {OpenAIEmbeddings} from 'langchain/embeddings/openai' +import {RecursiveCharacterTextSplitter} from 'langchain/text_splitter' + +export const getOpenAIEmbeddings = async (plaintextContent: string) => { + if (!plaintextContent) return null + const embeddings = new OpenAIEmbeddings( + {openAIApiKey: 'X'}, + {baseURL: 'http://localhost:3002/v1'} + ) + const splitter = new RecursiveCharacterTextSplitter() + const splitText = await splitter.splitText(plaintextContent) + const start = performance.now() + const contentEmbedding = await embeddings.embedDocuments(splitText) + const end = performance.now() + console.log('duration', end - start) + return `[${contentEmbedding.join(',')}]` +} + +export default getOpenAIEmbeddings diff --git a/packages/server/graphql/types/User.ts b/packages/server/graphql/types/User.ts index 4a47a5999cc..c73d60e1af3 100644 --- a/packages/server/graphql/types/User.ts +++ b/packages/server/graphql/types/User.ts @@ -7,6 +7,7 @@ import { GraphQLObjectType, GraphQLString } from 'graphql' +import {sql} from 'kysely' import MeetingMemberId from 'parabol-client/shared/gqlIds/MeetingMemberId' import toTeamMemberId from 'parabol-client/utils/relay/toTeamMemberId' import { @@ -16,11 +17,14 @@ import { } from '../../../client/utils/constants' import groupReflections from '../../../client/utils/smartGroup/groupReflections' import getRethink from '../../database/rethinkDriver' +import {RDatum} from '../../database/stricterR' import MeetingMemberType from '../../database/types/MeetingMember' import OrganizationType from '../../database/types/Organization' import OrganizationUserType from '../../database/types/OrganizationUser' import Reflection from '../../database/types/Reflection' import SuggestedActionType from '../../database/types/SuggestedAction' +import TimelineEvent from '../../database/types/TimelineEvent' +import getKysely from '../../postgres/getKysely' import {getUserId, isSuperUser, isTeamMember} from '../../utils/authorization' import getMonthlyStreak from '../../utils/getMonthlyStreak' import getRedis from '../../utils/getRedis' @@ -28,6 +32,7 @@ import standardError from '../../utils/standardError' import errorFilter from '../errorFilter' import {DataLoaderWorker, GQLContext} from '../graphql' import isValid from '../isValid' +import getOpenAIEmbeddings from '../mutations/helpers/getOpenAIEmbeddings' import invoices from '../queries/invoices' import organization from '../queries/organization' import AuthIdentity from './AuthIdentity' @@ -47,8 +52,6 @@ import TeamMember from './TeamMember' import TierEnum from './TierEnum' import {TimelineEventConnection} from './TimelineEvent' import TimelineEventTypeEnum from './TimelineEventTypeEnum' -import TimelineEvent from '../../database/types/TimelineEvent' -import {RDatum} from '../../database/stricterR' const User: GraphQLObjectType = new GraphQLObjectType({ name: 'User', @@ -498,9 +501,25 @@ const User: GraphQLObjectType = new GraphQLObjectType - plaintextContent.toLowerCase().includes(searchQuery) + const pg = getKysely() + const searchEmbeddings = await getOpenAIEmbeddings(searchQuery) + const vectors = await pg + .selectFrom('ReflectionEmbeddings') + .select([ + 'id', + sql`1 - (vector <=> ${searchEmbeddings})`.as('cosDistance'), + sql`vector <-> ${searchEmbeddings}`.as('l2Distance'), + sql`vector <#> ${searchEmbeddings}`.as('innerProductDifference') + ]) + .orderBy('l2Distance asc') + .limit(5) + .execute() + const l2Alpha = 0.6 + const matchingReflectionIds = new Set( + vectors.filter((vec) => vec.l2Distance < l2Alpha).map(({id}) => id) ) + + const matchedReflections = reflections.filter(({id}) => matchingReflectionIds.has(id)) const relatedReflections = matchedReflections.filter( ({reflectionGroupId: groupId}: Reflection) => groupId !== reflectionGroupId ) diff --git a/packages/server/package.json b/packages/server/package.json index c444212d2b4..17cee319329 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -113,6 +113,7 @@ "ioredis": "^5.2.3", "jsdom": "^20.0.0", "jsonwebtoken": "^9.0.0", + "langchain": "^0.0.152", "mailcomposer": "^4.0.1", "mailgun.js": "^7.0.4", "mime-types": "^2.1.16", @@ -122,7 +123,7 @@ "node-pg-migrate": "^5.9.0", "nodemailer": "^6.4.6", "oauth-1.0a": "^2.2.6", - "openai": "^3.3.0", + "openai": "^4.4.0", "oy-vey": "^0.11.0", "parabol-client": "^6.120.0", "pg": "^8.5.1", diff --git a/packages/server/postgres/migrations/1695253321182_reflectionEmbeddings.ts b/packages/server/postgres/migrations/1695253321182_reflectionEmbeddings.ts new file mode 100644 index 00000000000..6e8adb43fcf --- /dev/null +++ b/packages/server/postgres/migrations/1695253321182_reflectionEmbeddings.ts @@ -0,0 +1,31 @@ +import {Kysely, PostgresDialect, sql} from 'kysely' +import {Client} from 'pg' +import getPg from '../getPg' +import getPgConfig from '../getPgConfig' + +export async function up() { + const pg = new Kysely({ + dialect: new PostgresDialect({ + pool: getPg() + }) + }) + + pg.schema + // I had to normalize domains to its own table to guarantee uniqueness (and make indexing easier) + await sql` + CREATE EXTENSION IF NOT EXISTS "vector"; + CREATE TABLE IF NOT EXISTS "ReflectionEmbeddings" ( + "id" VARCHAR(100) PRIMARY KEY, + "vector" VECTOR NOT NULL, + "meetingId" VARCHAR(100) NOT NULL, + "teamId" VARCHAR(100) NOT NULL, + "orgId" VARCHAR(100) NOT NULL + );`.execute(pg) +} + +export async function down() { + const client = new Client(getPgConfig()) + await client.connect() + await client.query(`DROP TABLE IF EXISTS "ReflectionEmbeddings";`) + await client.end() +} diff --git a/yarn.lock b/yarn.lock index 2bd0456eeb0..fe5b2515efd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -39,6 +39,20 @@ "@jridgewell/gen-mapping" "^0.3.0" "@jridgewell/trace-mapping" "^0.3.9" +"@anthropic-ai/sdk@^0.6.2": + version "0.6.2" + resolved "https://registry.yarnpkg.com/@anthropic-ai/sdk/-/sdk-0.6.2.tgz#4be415e6b1d948df6f8e03af84aedf102ec74b70" + integrity sha512-fB9PUj9RFT+XjkL+E9Ol864ZIJi+1P8WnbHspN3N3/GK2uSzjd0cbVIKTGgf4v3N8MwaQu+UWnU7C4BG/fap/g== + dependencies: + "@types/node" "^18.11.18" + "@types/node-fetch" "^2.6.4" + abort-controller "^3.0.0" + agentkeepalive "^4.2.1" + digest-fetch "^1.3.0" + form-data-encoder "1.7.2" + formdata-node "^4.3.2" + node-fetch "^2.6.7" + "@apideck/better-ajv-errors@^0.3.1": version "0.3.3" resolved "https://registry.yarnpkg.com/@apideck/better-ajv-errors/-/better-ajv-errors-0.3.3.tgz#ab0b1e981e1749bf59736cf7ebe25cfc9f949c15" @@ -7995,6 +8009,14 @@ resolved "https://registry.yarnpkg.com/@types/ms/-/ms-0.7.31.tgz#31b7ca6407128a3d2bbc27fe2d21b345397f6197" integrity sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA== +"@types/node-fetch@^2.6.4": + version "2.6.5" + resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.6.5.tgz#972756a9a0fe354b2886bf3defe667ddb4f0d30a" + integrity sha512-OZsUlr2nxvkqUFLSaY2ZbA+P1q22q+KrlxWOn/38RX+u5kTkYL2mTujEpzUhGkS+K/QCYp9oagfXG39XOzyySg== + dependencies: + "@types/node" "*" + form-data "^4.0.0" + "@types/node@*", "@types/node@>=13.7.0": version "17.0.38" resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.38.tgz#f8bb07c371ccb1903f3752872c89f44006132947" @@ -8010,6 +8032,11 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.62.tgz#bab2e6208531321d147eda20c38e389548cd5ffc" integrity sha512-K/ggecSdwAAy2NUW4WKmF4Rc03GKbsfP+k326UWgckoS+Rzd2PaWbjk76dSmqdLQvLTJAO9axiTUJ6488mFsYQ== +"@types/node@^18.11.18": + version "18.17.18" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.17.18.tgz#acae19ad9011a2ab3d792232501c95085ba1838f" + integrity sha512-/4QOuy3ZpV7Ya1GTRz5CYSz3DgkKpyUptXuQ5PPce7uuyJAOR7r9FhkmxJfvcNUXyklbC63a+YvB3jxy7s9ngw== + "@types/nodemailer@^6.4.4": version "6.4.4" resolved "https://registry.yarnpkg.com/@types/nodemailer/-/nodemailer-6.4.4.tgz#c265f7e7a51df587597b3a49a023acaf0c741f4b" @@ -8181,6 +8208,11 @@ dependencies: "@types/node" "*" +"@types/retry@0.12.0": + version "0.12.0" + resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.0.tgz#2b35eccfcee7d38cd72ad99232fbd58bffb3c84d" + integrity sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA== + "@types/retry@^0.12.0": version "0.12.1" resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.1.tgz#d8f1c0d0dc23afad6dc16a9e993a0865774b4065" @@ -8306,6 +8338,11 @@ resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.3.tgz#c6a60686d953dbd1b1d45e66f4ecdbd5d471b4d0" integrity sha512-0LbEEx1zxrYB3pgpd1M5lEhLcXjKJnYghvhTRgaBeUivLHMDM1TzF3IJ6hXU2+8uA4Xz+5BA63mtZo5DjVT8iA== +"@types/uuid@^9.0.1": + version "9.0.4" + resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-9.0.4.tgz#e884a59338da907bda8d2ed03e01c5c49d036f1c" + integrity sha512-zAuJWQflfx6dYJM62vna+Sn5aeSWhh3OB+wfUEACNcqUSc0AGc5JKl+ycL1vrH7frGTXhJchYjE1Hak8L819dA== + "@types/webpack-sources@*": version "3.2.0" resolved "https://registry.yarnpkg.com/@types/webpack-sources/-/webpack-sources-3.2.0.tgz#16d759ba096c289034b26553d2df1bf45248d38b" @@ -8685,6 +8722,13 @@ abbrev@1: resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== +abort-controller@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" + integrity sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg== + dependencies: + event-target-shim "^5.0.0" + accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.8: version "1.3.8" resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" @@ -9251,13 +9295,6 @@ axios@^0.21.0, axios@^0.21.4: dependencies: follow-redirects "^1.14.0" -axios@^0.26.0: - version "0.26.1" - resolved "https://registry.yarnpkg.com/axios/-/axios-0.26.1.tgz#1ede41c51fcf51bbbd6fd43669caaa4f0495aaa9" - integrity sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA== - dependencies: - follow-redirects "^1.14.8" - axios@^0.27.2: version "0.27.2" resolved "https://registry.yarnpkg.com/axios/-/axios-0.27.2.tgz#207658cc8621606e586c85db4b41a750e756d972" @@ -9475,12 +9512,17 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== +base-64@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/base-64/-/base-64-0.1.0.tgz#780a99c84e7d600260361511c4877613bf24f6bb" + integrity sha512-Y5gU45svrR5tI2Vt/X9GPd3L0HNIKzGu202EjxrXMpuc2V2CiKgemAbUUsqYmZJvPtCXoUKjNZwBJzsNScUbXA== + base-64@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/base-64/-/base-64-1.0.0.tgz#09d0f2084e32a3fd08c2475b973788eee6ae8f4a" integrity sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg== -base64-js@^1.2.0, base64-js@^1.3.0, base64-js@^1.3.1: +base64-js@^1.2.0, base64-js@^1.3.0, base64-js@^1.3.1, base64-js@^1.5.1: version "1.5.1" resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== @@ -9527,11 +9569,16 @@ bin-links@^3.0.0: rimraf "^3.0.0" write-file-atomic "^4.0.0" -binary-extensions@^2.0.0: +binary-extensions@^2.0.0, binary-extensions@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== +binary-search@^1.3.5: + version "1.3.6" + resolved "https://registry.yarnpkg.com/binary-search/-/binary-search-1.3.6.tgz#e32426016a0c5092f0f3598836a1c7da3560565c" + integrity sha512-nbE1WxOTTrUWIfsfZ4aHGYu5DOuNkbxGokjV6Z2kxfJK3uaAb8zNK1muzOeipoLHZjInT4Br88BHpzevc681xA== + bl@^4.0.3, bl@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a" @@ -9884,20 +9931,20 @@ camelcase-keys@^7.0.0: quick-lru "^5.1.1" type-fest "^1.2.1" +camelcase@6, camelcase@^6.2.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" + integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== + camelcase@^5.0.0, camelcase@^5.3.1: version "5.3.1" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== -camelcase@^6.2.0: - version "6.3.0" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" - integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== - caniuse-lite@^1.0.30001286, caniuse-lite@^1.0.30001317, caniuse-lite@^1.0.30001359, caniuse-lite@^1.0.30001400, caniuse-lite@^1.0.30001426, caniuse-lite@^1.0.30001449, caniuse-lite@^1.0.30001517, caniuse-lite@~1.0.0: - version "1.0.30001522" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001522.tgz#44b87a406c901269adcdb834713e23582dd71856" - integrity sha512-TKiyTVZxJGhsTszLuzb+6vUZSjVOAhClszBr2Ta2k9IwtNBT/4dzmL6aywt0HCgEZlmwJzXJd8yNiob6HgwTRg== + version "1.0.30001538" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001538.tgz#9dbc6b9af1ff06b5eb12350c2012b3af56744f3f" + integrity sha512-HWJnhnID+0YMtGlzcp3T9drmBJUVDchPJ08tpUGFLs9CYlwWPH2uLgpHn8fND5pCgXVtnGS3H4QR9XLMHVNkHw== capital-case@^1.0.4: version "1.0.4" @@ -10358,7 +10405,7 @@ commander@2.15.1: resolved "https://registry.yarnpkg.com/commander/-/commander-2.15.1.tgz#df46e867d0fc2aec66a34662b406a9ccafff5b0f" integrity sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag== -commander@^10.0.0: +commander@^10.0.0, commander@^10.0.1: version "10.0.1" resolved "https://registry.yarnpkg.com/commander/-/commander-10.0.1.tgz#881ee46b4f77d1c1dccc5823433aa39b022cbe06" integrity sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug== @@ -11267,6 +11314,14 @@ diff@^4.0.1: resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== +digest-fetch@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/digest-fetch/-/digest-fetch-1.3.0.tgz#898e69264d00012a23cf26e8a3e40320143fc661" + integrity sha512-CGJuv6iKNM7QyZlM2T3sPAdZWd/p9zQiRNS9G+9COUCwzWFTs0Xp8NF5iePx7wtvhDykReiRRrSeNb4oMmB8lA== + dependencies: + base-64 "^0.1.0" + md5 "^2.3.0" + dir-glob@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" @@ -12006,6 +12061,11 @@ event-emitter@^0.3.5: d "1" es5-ext "~0.10.14" +event-target-shim@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" + integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== + eventemitter-asyncresource@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/eventemitter-asyncresource/-/eventemitter-asyncresource-1.0.0.tgz#734ff2e44bf448e627f7748f905d6bdd57bdb65b" @@ -12092,6 +12152,11 @@ expect@^29.0.0, expect@^29.5.0: jest-message-util "^29.5.0" jest-util "^29.5.0" +expr-eval@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/expr-eval/-/expr-eval-2.0.2.tgz#fa6f044a7b0c93fde830954eb9c5b0f7fbc7e201" + integrity sha512-4EMSHGOPSwAfBiibw3ndnP0AvjDWLsMvGOvWEZ2F96IGk0bIVdjQisOHxReSkE13mHcfbuCiXw+G4y0zv6N8Eg== + express@^4.17.3: version "4.18.2" resolved "https://registry.yarnpkg.com/express/-/express-4.18.2.tgz#3fabe08296e930c796c19e3c516979386ba9fd59" @@ -12454,7 +12519,7 @@ follow-redirects@^1.0.0, follow-redirects@^1.14.0: resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.8.tgz#016996fb9a11a100566398b1c6839337d7bfa8fc" integrity sha512-1x0S9UVJHsQprFcEC/qnNzBLcIxsjAV905f/UkQxbclCsoTWlacCNOpQa/anodLl2uaEKFhfWOvM2Qg77+15zA== -follow-redirects@^1.14.8, follow-redirects@^1.14.9, follow-redirects@^1.15.0: +follow-redirects@^1.14.9, follow-redirects@^1.15.0: version "1.15.2" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13" integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA== @@ -12467,6 +12532,11 @@ foreground-child@^3.1.0: cross-spawn "^7.0.0" signal-exit "^4.0.1" +form-data-encoder@1.7.2: + version "1.7.2" + resolved "https://registry.yarnpkg.com/form-data-encoder/-/form-data-encoder-1.7.2.tgz#1f1ae3dccf58ed4690b86d87e4f57c654fbab040" + integrity sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A== + form-data-encoder@^1.7.2: version "1.9.0" resolved "https://registry.yarnpkg.com/form-data-encoder/-/form-data-encoder-1.9.0.tgz#fd18d316b1ec830d2a8be8ad86c1cf0317320b34" @@ -13823,6 +13893,11 @@ is-absolute@^1.0.0: is-relative "^1.0.0" is-windows "^1.0.1" +is-any-array@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-any-array/-/is-any-array-2.0.1.tgz#9233242a9c098220290aa2ec28f82ca7fa79899e" + integrity sha512-UtilS7hLRu++wb/WBAw9bNuP1Eg04Ivn1vERJck8zJthEvXCBEBpGR/33u/xLKWEQf95803oalHrVDptcAvFdQ== + is-arrayish@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" @@ -14704,6 +14779,13 @@ js-git@^0.7.8: git-sha1 "^0.1.2" pako "^0.2.5" +js-tiktoken@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/js-tiktoken/-/js-tiktoken-1.0.7.tgz#56933fcd2093e8304060dfde3071bda91812e6f5" + integrity sha512-biba8u/clw7iesNEWLOLwrNGoBP2lA+hTaBLs/D45pJdUPFXyxD6nhcDVtADChghv4GgyAiMKYMiRx7x6h7Biw== + dependencies: + base64-js "^1.5.1" + "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" @@ -14946,6 +15028,11 @@ jsonpointer@^5.0.0: resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-5.0.0.tgz#f802669a524ec4805fa7389eadbc9921d5dc8072" integrity sha512-PNYZIdMjVIvVgDSYKTT63Y+KZ6IZvGRNNWcxwD+GNnUz1MKPfv30J8ueCjdwcN0nDx2SlshgyB7Oy0epAzVRRg== +jsonpointer@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-5.0.1.tgz#2110e0af0900fd37467b5907ecd13a7884a1b559" + integrity sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ== + jsonwebtoken@^9.0.0: version "9.0.0" resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-9.0.0.tgz#d0faf9ba1cc3a56255fe49c0961a67e520c1926d" @@ -15053,6 +15140,50 @@ kysely@^0.26.3: resolved "https://registry.yarnpkg.com/kysely/-/kysely-0.26.3.tgz#45fdd0153d8c9418b0ea9a6f05ed46b95ed27678" integrity sha512-yWSgGi9bY13b/W06DD2OCDDHQmq1kwTGYlQ4wpZkMOJqMGCstVCFIvxCCVG4KfY1/3G0MhDAcZsip/Lw8/vJWw== +langchain@^0.0.152: + version "0.0.152" + resolved "https://registry.yarnpkg.com/langchain/-/langchain-0.0.152.tgz#9475d3150f2b4092828a9a4dff2f24318681b139" + integrity sha512-/G7eF+ojrA7hCgMs2S9Lb23z3aA+1NhoS0Z1cKNre/mV6hyNTxvt/Lvo1tFsNAPwV8KdH5Jeyp7LFcmjhvMFEw== + dependencies: + "@anthropic-ai/sdk" "^0.6.2" + ansi-styles "^5.0.0" + binary-extensions "^2.2.0" + camelcase "6" + decamelize "^1.2.0" + expr-eval "^2.0.2" + flat "^5.0.2" + js-tiktoken "^1.0.7" + js-yaml "^4.1.0" + jsonpointer "^5.0.1" + langchainhub "~0.0.6" + langsmith "~0.0.31" + ml-distance "^4.0.0" + object-hash "^3.0.0" + openai "~4.4.0" + openapi-types "^12.1.3" + p-queue "^6.6.2" + p-retry "4" + uuid "^9.0.0" + yaml "^2.2.1" + zod "^3.21.4" + zod-to-json-schema "^3.20.4" + +langchainhub@~0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/langchainhub/-/langchainhub-0.0.6.tgz#9d2d06e4ce0807b4e8a31e19611f57aef990b54d" + integrity sha512-SW6105T+YP1cTe0yMf//7kyshCgvCTyFBMTgH2H3s9rTAR4e+78DA/BBrUL/Mt4Q5eMWui7iGuAYb3pgGsdQ9w== + +langsmith@~0.0.31: + version "0.0.38" + resolved "https://registry.yarnpkg.com/langsmith/-/langsmith-0.0.38.tgz#7946b4f6b91c66a450132d8853cf010e3c4a44ac" + integrity sha512-SNeje3mF+90aAX2Br919rraA21+EaNw1IfaXSrWxkqYJjJifWs0knLlAki8Vg1Sg5uv5ay/6MOO8v5mU5mL0yw== + dependencies: + "@types/uuid" "^9.0.1" + commander "^10.0.1" + p-queue "^6.6.2" + p-retry "4" + uuid "^9.0.0" + launch-editor@^2.6.0: version "2.6.0" resolved "https://registry.yarnpkg.com/launch-editor/-/launch-editor-2.6.0.tgz#4c0c1a6ac126c572bd9ff9a30da1d2cae66defd7" @@ -15718,7 +15849,7 @@ marked@^4.3.0: resolved "https://registry.yarnpkg.com/marked/-/marked-4.3.0.tgz#796362821b019f734054582038b116481b456cf3" integrity sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A== -md5@^2.2.1: +md5@^2.2.1, md5@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/md5/-/md5-2.3.0.tgz#c3da9a6aae3a30b46b7b0c349b87b110dc3bda4f" integrity sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g== @@ -16137,6 +16268,42 @@ mkdirp@^0.5.5: dependencies: minimist "^1.2.6" +ml-array-mean@^1.1.6: + version "1.1.6" + resolved "https://registry.yarnpkg.com/ml-array-mean/-/ml-array-mean-1.1.6.tgz#d951a700dc8e3a17b3e0a583c2c64abd0c619c56" + integrity sha512-MIdf7Zc8HznwIisyiJGRH9tRigg3Yf4FldW8DxKxpCCv/g5CafTw0RRu51nojVEOXuCQC7DRVVu5c7XXO/5joQ== + dependencies: + ml-array-sum "^1.1.6" + +ml-array-sum@^1.1.6: + version "1.1.6" + resolved "https://registry.yarnpkg.com/ml-array-sum/-/ml-array-sum-1.1.6.tgz#d1d89c20793cd29c37b09d40e85681aa4515a955" + integrity sha512-29mAh2GwH7ZmiRnup4UyibQZB9+ZLyMShvt4cH4eTK+cL2oEMIZFnSyB3SS8MlsTh6q/w/yh48KmqLxmovN4Dw== + dependencies: + is-any-array "^2.0.0" + +ml-distance-euclidean@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ml-distance-euclidean/-/ml-distance-euclidean-2.0.0.tgz#3a668d236649d1b8fec96380b9435c6f42c9a817" + integrity sha512-yC9/2o8QF0A3m/0IXqCTXCzz2pNEzvmcE/9HFKOZGnTjatvBbsn4lWYJkxENkA4Ug2fnYl7PXQxnPi21sgMy/Q== + +ml-distance@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/ml-distance/-/ml-distance-4.0.1.tgz#4741d17a1735888c5388823762271dfe604bd019" + integrity sha512-feZ5ziXs01zhyFUUUeZV5hwc0f5JW0Sh0ckU1koZe/wdVkJdGxcP06KNQuF0WBTj8FttQUzcvQcpcrOp/XrlEw== + dependencies: + ml-array-mean "^1.1.6" + ml-distance-euclidean "^2.0.0" + ml-tree-similarity "^1.0.0" + +ml-tree-similarity@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/ml-tree-similarity/-/ml-tree-similarity-1.0.0.tgz#24705a107e32829e24d945e87219e892159c53f0" + integrity sha512-XJUyYqjSuUQkNQHMscr6tcjldsOoAekxADTplt40QKfwW6nd++1wHWV9AArl0Zvw/TIHgNaZZNvr8QGvE8wLRg== + dependencies: + binary-search "^1.3.5" + num-sort "^2.0.0" + modify-values@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/modify-values/-/modify-values-1.0.1.tgz#b3939fa605546474e3e3e3c63d64bd43b4ee6022" @@ -16704,6 +16871,11 @@ nullthrows@^1.0.0, nullthrows@^1.1.1: resolved "https://registry.yarnpkg.com/nullthrows/-/nullthrows-1.1.1.tgz#7818258843856ae971eae4208ad7d7eb19a431b1" integrity sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw== +num-sort@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/num-sort/-/num-sort-2.1.0.tgz#1cbb37aed071329fdf41151258bc011898577a9b" + integrity sha512-1MQz1Ed8z2yckoBeSfkQHHO9K1yDRxxtotKSJ9yvcTUUxSvfvzEq5GwBrjjHEpMlq/k5gvXdmJ1SbYxWtpNoVg== + number-is-nan@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" @@ -16972,19 +17144,44 @@ open@^8.0.9, open@^8.4.0: is-docker "^2.1.1" is-wsl "^2.2.0" -openai@^3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/openai/-/openai-3.3.0.tgz#a6408016ad0945738e1febf43f2fccca83a3f532" - integrity sha512-uqxI/Au+aPRnsaQRe8CojU0eCR7I0mBiKjD3sNMzY6DaC1ZVrc85u98mtJW6voDug8fgGN+DIZmTDxTthxb7dQ== +openai@^4.4.0: + version "4.8.0" + resolved "https://registry.yarnpkg.com/openai/-/openai-4.8.0.tgz#42e818e484d5c831e438439ed202b08d67416b1c" + integrity sha512-CnLZvHi2x4pIoGAWCaj3jHi1a6NA4oFBL6mJDSXkIR5A/wv6lven7uL2gxMevjGBLA7OqYqis3Z2PMluiGauVw== dependencies: - axios "^0.26.0" - form-data "^4.0.0" + "@types/node" "^18.11.18" + "@types/node-fetch" "^2.6.4" + abort-controller "^3.0.0" + agentkeepalive "^4.2.1" + digest-fetch "^1.3.0" + form-data-encoder "1.7.2" + formdata-node "^4.3.2" + node-fetch "^2.6.7" + +openai@~4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/openai/-/openai-4.4.0.tgz#dbaab326eb044ddec479951b245850c482678031" + integrity sha512-JN0t628Kh95T0IrXl0HdBqnlJg+4Vq0Bnh55tio+dfCnyzHvMLiWyCM9m726MAJD2YkDU4/8RQB6rNbEq9ct2w== + dependencies: + "@types/node" "^18.11.18" + "@types/node-fetch" "^2.6.4" + abort-controller "^3.0.0" + agentkeepalive "^4.2.1" + digest-fetch "^1.3.0" + form-data-encoder "1.7.2" + formdata-node "^4.3.2" + node-fetch "^2.6.7" openapi-types@^12.1.0: version "12.1.1" resolved "https://registry.yarnpkg.com/openapi-types/-/openapi-types-12.1.1.tgz#0aface4e05ba60efbf51153ed6af23988796617d" integrity sha512-m/DJaEqOUDSU8KoI74E6A3TokccuDOJ81ewZ6kLFwUT1KEIE0GDWvErtnJJDU4sySx8JKF5kk2GzHUuK6f+VHA== +openapi-types@^12.1.3: + version "12.1.3" + resolved "https://registry.yarnpkg.com/openapi-types/-/openapi-types-12.1.3.tgz#471995eb26c4b97b7bd356aacf7b91b73e777dd3" + integrity sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw== + opener@^1.5.2: version "1.5.2" resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.2.tgz#5d37e1f35077b9dcac4301372271afdeb2a13598" @@ -17160,6 +17357,14 @@ p-reduce@^2.0.0, p-reduce@^2.1.0: resolved "https://registry.yarnpkg.com/p-reduce/-/p-reduce-2.1.0.tgz#09408da49507c6c274faa31f28df334bc712b64a" integrity sha512-2USApvnsutq8uoxZBGbbWM0JIYLiEMJ9RlaN7fAzVNb9OZN0SHjjTTfIcb667XynS5Y1VhwDJVDa72TnPzAYWw== +p-retry@4: + version "4.6.2" + resolved "https://registry.yarnpkg.com/p-retry/-/p-retry-4.6.2.tgz#9baae7184057edd4e17231cee04264106e092a16" + integrity sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ== + dependencies: + "@types/retry" "0.12.0" + retry "^0.13.1" + p-retry@^4.5.0: version "4.6.1" resolved "https://registry.yarnpkg.com/p-retry/-/p-retry-4.6.1.tgz#8fcddd5cdf7a67a0911a9cf2ef0e5df7f602316c" @@ -20483,9 +20688,9 @@ tar@^4.4.13: yallist "^3.1.1" tar@^6.0.2: - version "6.1.15" - resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.15.tgz#c9738b0b98845a3b344d334b8fa3041aaba53a69" - integrity sha512-/zKt9UyngnxIT/EAGYuxaMYgOIJiP81ab9ZfkILq4oNLPFX50qyYmu7jRj9qeXoxmJHjGlbH0+cm2uy1WCs10A== + version "6.2.0" + resolved "https://registry.yarnpkg.com/tar/-/tar-6.2.0.tgz#b14ce49a79cb1cd23bc9b016302dea5474493f73" + integrity sha512-/Wo7DcT0u5HUV486xg675HtjNd3BXZ6xDbzsCUZPt5iw8bTQ63bP0Raut3mvro9u+CUyq7YQd8Cx55fsZXxqLQ== dependencies: chownr "^2.0.0" fs-minipass "^2.0.0" @@ -21111,9 +21316,9 @@ unc-path-regex@^0.1.2: integrity sha1-5z3T17DXxe2G+6xrCufYxqadUPo= undici@^5.22.1, undici@^5.8.2: - version "5.23.0" - resolved "https://registry.yarnpkg.com/undici/-/undici-5.23.0.tgz#e7bdb0ed42cebe7b7aca87ced53e6eaafb8f8ca0" - integrity sha512-1D7w+fvRsqlQ9GscLBwcAJinqcZGHUKjbOmXdlE/v8BvEGXjeWAax+341q44EuTcHXXnfyKNbKRq4Lg7OzhMmg== + version "5.25.0" + resolved "https://registry.yarnpkg.com/undici/-/undici-5.25.0.tgz#618318268e1b496bd8ab370ded86745aab3f68e1" + integrity sha512-DgU98Ll+r1ssO8PcGFWIfj6kie0ttV20DSyE/CVYDVeHvfwBwQbjlsIYJIwAoU1WRhGuEEbj+jgZqcKPco5vkQ== dependencies: busboy "^1.6.0" @@ -22200,6 +22405,11 @@ yaml@^1.10.0, yaml@^1.10.2, yaml@^1.7.2: resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== +yaml@^2.2.1: + version "2.3.2" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.3.2.tgz#f522db4313c671a0ca963a75670f1c12ea909144" + integrity sha512-N/lyzTPaJasoDmfV7YTrYCI0G/3ivm/9wdG0aHuheKowWQwGTsK0Eoiw6utmzAnI6pkJa0DUVygvp3spqqEKXg== + yaml@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.3.1.tgz#02fe0975d23cd441242aa7204e09fc28ac2ac33b" @@ -22346,3 +22556,13 @@ zeed-dom@^0.9.19: integrity sha512-HWjX8rA3Y/RI32zby3KIN1D+mgskce+She4K7kRyyx62OiVxJ5FnYm8vWq0YVAja3Tf2S1M0XAc6O2lRFcMgcQ== dependencies: css-what "^6.1.0" + +zod-to-json-schema@^3.20.4: + version "3.21.4" + resolved "https://registry.yarnpkg.com/zod-to-json-schema/-/zod-to-json-schema-3.21.4.tgz#de97c5b6d4a25e9d444618486cb55c0c7fb949fd" + integrity sha512-fjUZh4nQ1s6HMccgIeE0VP4QG/YRGPmyjO9sAh890aQKPEk3nqbfUXhMFaC+Dr5KvYBm8BCyvfpZf2jY9aGSsw== + +zod@^3.21.4: + version "3.22.2" + resolved "https://registry.yarnpkg.com/zod/-/zod-3.22.2.tgz#3add8c682b7077c05ac6f979fea6998b573e157b" + integrity sha512-wvWkphh5WQsJbVk1tbx1l1Ly4yg+XecD+Mq280uBGt9wa5BKSWf4Mhp6GmrkPixhMxmabYY7RbzlwVP32pbGCg== From 02f29e5af34d332a1dbca3752e092d86d89ae09a Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Mon, 9 Oct 2023 16:22:36 -0700 Subject: [PATCH 3/6] chore: docker-compose in CI Signed-off-by: Matt Krick --- .github/workflows/build.yml | 23 +++---------------- docker/dev.yml | 3 +++ .../mutations/helpers/getOpenAIEmbeddings.ts | 2 +- 3 files changed, 7 insertions(+), 21 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9eb0ceb65b4..7d63dd38911 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -11,26 +11,6 @@ jobs: permissions: contents: "read" id-token: "write" - services: - postgres: - image: postgres:12.15-alpine - # This env variables must be the same in the file PARABOL_BUILD_ENV_PATH - env: - POSTGRES_PASSWORD: "temppassword" - POSTGRES_USER: "tempuser" - POSTGRES_DB: "tempdb" - ports: - - 5432:5432 - rethinkdb: - image: rethinkdb:2.4.2 - ports: - - 8080:8080 - - 28015:28015 - - 29015:29015 - redis: - image: redis:6.2.6 - ports: - - 6379:6379 steps: - name: Checkout uses: actions/checkout@v3 @@ -72,6 +52,9 @@ jobs: if: steps.cache.outputs.cache-hit != 'true' run: yarn install --immutable + - name: Install DBs + run: docker compose -f docker/dev.yml up postgres -d + - name: Build the DBs run: | cp ${{ env.PARABOL_BUILD_ENV_PATH }} ./.env diff --git a/docker/dev.yml b/docker/dev.yml index c81337fa7ef..b3be00caa40 100644 --- a/docker/dev.yml +++ b/docker/dev.yml @@ -25,6 +25,9 @@ services: networks: - parabol-network postgres: + depends_on: + - db + - redis build: context: "../packages/server/postgres" restart: unless-stopped diff --git a/packages/server/graphql/mutations/helpers/getOpenAIEmbeddings.ts b/packages/server/graphql/mutations/helpers/getOpenAIEmbeddings.ts index ce9f23880e2..5bcc4d7f894 100644 --- a/packages/server/graphql/mutations/helpers/getOpenAIEmbeddings.ts +++ b/packages/server/graphql/mutations/helpers/getOpenAIEmbeddings.ts @@ -7,7 +7,7 @@ export const getOpenAIEmbeddings = async (plaintextContent: string) => { {openAIApiKey: 'X'}, {baseURL: 'http://localhost:3002/v1'} ) - const splitter = new RecursiveCharacterTextSplitter() + const splitter = new RecursiveCharacterTextSplitter({chunkSize: 1000, chunkOverlap: 200}) const splitText = await splitter.splitText(plaintextContent) const start = performance.now() const contentEmbedding = await embeddings.embedDocuments(splitText) From 14ef403d871d95b31a557df8fb10c5ccc49fce6c Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Mon, 9 Oct 2023 16:28:37 -0700 Subject: [PATCH 4/6] copy env Signed-off-by: Matt Krick --- .github/workflows/build.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7d63dd38911..02f9e47f13b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -53,11 +53,12 @@ jobs: run: yarn install --immutable - name: Install DBs - run: docker compose -f docker/dev.yml up postgres -d + run: | + cp ${{ env.PARABOL_BUILD_ENV_PATH }} ./.env + docker compose -f ./docker/dev.yml up postgres -d - name: Build the DBs run: | - cp ${{ env.PARABOL_BUILD_ENV_PATH }} ./.env yarn db:migrate yarn pg:migrate up yarn pg:build From 4d79321028d1fb37c60d11aeffba338071618102 Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Mon, 9 Oct 2023 16:35:45 -0700 Subject: [PATCH 5/6] use ankane pgvector in CI Signed-off-by: Matt Krick --- .github/workflows/build.yml | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 02f9e47f13b..8f98d5f4e33 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -11,6 +11,27 @@ jobs: permissions: contents: "read" id-token: "write" + services: + postgres: + # Image is pinned to v15, OK since it's just for testing + image: ankane/pgvector + # This env variables must be the same in the file PARABOL_BUILD_ENV_PATH + env: + POSTGRES_PASSWORD: "temppassword" + POSTGRES_USER: "tempuser" + POSTGRES_DB: "tempdb" + ports: + - 5432:5432 + rethinkdb: + image: rethinkdb:2.4.2 + ports: + - 8080:8080 + - 28015:28015 + - 29015:29015 + redis: + image: redis:6.2.6 + ports: + - 6379:6379 steps: - name: Checkout uses: actions/checkout@v3 @@ -52,13 +73,9 @@ jobs: if: steps.cache.outputs.cache-hit != 'true' run: yarn install --immutable - - name: Install DBs - run: | - cp ${{ env.PARABOL_BUILD_ENV_PATH }} ./.env - docker compose -f ./docker/dev.yml up postgres -d - - name: Build the DBs run: | + cp ${{ env.PARABOL_BUILD_ENV_PATH }} ./.env yarn db:migrate yarn pg:migrate up yarn pg:build From ec5d15bd851430cc0856a1455ee8c741ec40b26a Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Mon, 9 Oct 2023 17:52:48 -0700 Subject: [PATCH 6/6] remove pgvector files Signed-off-by: Matt Krick --- packages/server/postgres/Dockerfile | 6 +- .../postgres/extensions/pgvector/Makefile | 76 -- .../pgvector/sql/vector--0.1.0--0.1.1.sql | 39 - .../pgvector/sql/vector--0.1.1--0.1.3.sql | 2 - .../pgvector/sql/vector--0.1.3--0.1.4.sql | 2 - .../pgvector/sql/vector--0.1.4--0.1.5.sql | 2 - .../pgvector/sql/vector--0.1.5--0.1.6.sql | 2 - .../pgvector/sql/vector--0.1.6--0.1.7.sql | 8 - .../pgvector/sql/vector--0.1.7--0.1.8.sql | 8 - .../pgvector/sql/vector--0.1.8--0.2.0.sql | 2 - .../pgvector/sql/vector--0.2.0--0.2.1.sql | 19 - .../pgvector/sql/vector--0.2.1--0.2.2.sql | 2 - .../pgvector/sql/vector--0.2.2--0.2.3.sql | 2 - .../pgvector/sql/vector--0.2.3--0.2.4.sql | 2 - .../pgvector/sql/vector--0.2.4--0.2.5.sql | 2 - .../pgvector/sql/vector--0.2.5--0.2.6.sql | 2 - .../pgvector/sql/vector--0.2.6--0.2.7.sql | 2 - .../pgvector/sql/vector--0.2.7--0.3.0.sql | 2 - .../pgvector/sql/vector--0.3.0--0.3.1.sql | 2 - .../pgvector/sql/vector--0.3.1--0.3.2.sql | 2 - .../pgvector/sql/vector--0.3.2--0.4.0.sql | 23 - .../pgvector/sql/vector--0.4.0--0.4.1.sql | 2 - .../pgvector/sql/vector--0.4.1--0.4.2.sql | 2 - .../pgvector/sql/vector--0.4.2--0.4.3.sql | 2 - .../pgvector/sql/vector--0.4.3--0.4.4.sql | 2 - .../pgvector/sql/vector--0.4.4--0.5.0.sql | 43 - .../extensions/pgvector/sql/vector--0.5.0.sql | 292 ----- .../extensions/pgvector/sql/vector.sql | 292 ----- .../postgres/extensions/pgvector/src/hnsw.c | 224 ---- .../postgres/extensions/pgvector/src/hnsw.h | 305 ----- .../postgres/extensions/pgvector/src/hnsw.o | Bin 4224 -> 0 bytes .../extensions/pgvector/src/hnswbuild.c | 526 -------- .../extensions/pgvector/src/hnswbuild.o | Bin 9192 -> 0 bytes .../extensions/pgvector/src/hnswinsert.c | 590 --------- .../extensions/pgvector/src/hnswinsert.o | Bin 8560 -> 0 bytes .../extensions/pgvector/src/hnswscan.c | 223 ---- .../extensions/pgvector/src/hnswscan.o | Bin 3600 -> 0 bytes .../extensions/pgvector/src/hnswutils.c | 992 -------------- .../extensions/pgvector/src/hnswutils.o | Bin 11504 -> 0 bytes .../extensions/pgvector/src/hnswvacuum.c | 658 ---------- .../extensions/pgvector/src/hnswvacuum.o | Bin 8976 -> 0 bytes .../extensions/pgvector/src/ivfbuild.c | 1111 ---------------- .../extensions/pgvector/src/ivfbuild.o | Bin 14592 -> 0 bytes .../extensions/pgvector/src/ivfflat.c | 251 ---- .../extensions/pgvector/src/ivfflat.h | 306 ----- .../extensions/pgvector/src/ivfflat.o | Bin 4344 -> 0 bytes .../extensions/pgvector/src/ivfinsert.c | 206 --- .../extensions/pgvector/src/ivfinsert.o | Bin 3624 -> 0 bytes .../extensions/pgvector/src/ivfkmeans.c | 528 -------- .../extensions/pgvector/src/ivfkmeans.o | Bin 9240 -> 0 bytes .../extensions/pgvector/src/ivfscan.c | 381 ------ .../extensions/pgvector/src/ivfscan.o | Bin 6344 -> 0 bytes .../extensions/pgvector/src/ivfutils.c | 220 ---- .../extensions/pgvector/src/ivfutils.o | Bin 3552 -> 0 bytes .../extensions/pgvector/src/ivfvacuum.c | 157 --- .../extensions/pgvector/src/ivfvacuum.o | Bin 2664 -> 0 bytes .../postgres/extensions/pgvector/src/vector.c | 1145 ----------------- .../postgres/extensions/pgvector/src/vector.h | 23 - .../postgres/extensions/pgvector/src/vector.o | Bin 36336 -> 0 bytes .../extensions/pgvector/vector.control | 4 - .../postgres/extensions/pgvector/vector.so | Bin 107270 -> 0 bytes 61 files changed, 2 insertions(+), 8690 deletions(-) delete mode 100644 packages/server/postgres/extensions/pgvector/Makefile delete mode 100644 packages/server/postgres/extensions/pgvector/sql/vector--0.1.0--0.1.1.sql delete mode 100644 packages/server/postgres/extensions/pgvector/sql/vector--0.1.1--0.1.3.sql delete mode 100644 packages/server/postgres/extensions/pgvector/sql/vector--0.1.3--0.1.4.sql delete mode 100644 packages/server/postgres/extensions/pgvector/sql/vector--0.1.4--0.1.5.sql delete mode 100644 packages/server/postgres/extensions/pgvector/sql/vector--0.1.5--0.1.6.sql delete mode 100644 packages/server/postgres/extensions/pgvector/sql/vector--0.1.6--0.1.7.sql delete mode 100644 packages/server/postgres/extensions/pgvector/sql/vector--0.1.7--0.1.8.sql delete mode 100644 packages/server/postgres/extensions/pgvector/sql/vector--0.1.8--0.2.0.sql delete mode 100644 packages/server/postgres/extensions/pgvector/sql/vector--0.2.0--0.2.1.sql delete mode 100644 packages/server/postgres/extensions/pgvector/sql/vector--0.2.1--0.2.2.sql delete mode 100644 packages/server/postgres/extensions/pgvector/sql/vector--0.2.2--0.2.3.sql delete mode 100644 packages/server/postgres/extensions/pgvector/sql/vector--0.2.3--0.2.4.sql delete mode 100644 packages/server/postgres/extensions/pgvector/sql/vector--0.2.4--0.2.5.sql delete mode 100644 packages/server/postgres/extensions/pgvector/sql/vector--0.2.5--0.2.6.sql delete mode 100644 packages/server/postgres/extensions/pgvector/sql/vector--0.2.6--0.2.7.sql delete mode 100644 packages/server/postgres/extensions/pgvector/sql/vector--0.2.7--0.3.0.sql delete mode 100644 packages/server/postgres/extensions/pgvector/sql/vector--0.3.0--0.3.1.sql delete mode 100644 packages/server/postgres/extensions/pgvector/sql/vector--0.3.1--0.3.2.sql delete mode 100644 packages/server/postgres/extensions/pgvector/sql/vector--0.3.2--0.4.0.sql delete mode 100644 packages/server/postgres/extensions/pgvector/sql/vector--0.4.0--0.4.1.sql delete mode 100644 packages/server/postgres/extensions/pgvector/sql/vector--0.4.1--0.4.2.sql delete mode 100644 packages/server/postgres/extensions/pgvector/sql/vector--0.4.2--0.4.3.sql delete mode 100644 packages/server/postgres/extensions/pgvector/sql/vector--0.4.3--0.4.4.sql delete mode 100644 packages/server/postgres/extensions/pgvector/sql/vector--0.4.4--0.5.0.sql delete mode 100644 packages/server/postgres/extensions/pgvector/sql/vector--0.5.0.sql delete mode 100644 packages/server/postgres/extensions/pgvector/sql/vector.sql delete mode 100644 packages/server/postgres/extensions/pgvector/src/hnsw.c delete mode 100644 packages/server/postgres/extensions/pgvector/src/hnsw.h delete mode 100644 packages/server/postgres/extensions/pgvector/src/hnsw.o delete mode 100644 packages/server/postgres/extensions/pgvector/src/hnswbuild.c delete mode 100644 packages/server/postgres/extensions/pgvector/src/hnswbuild.o delete mode 100644 packages/server/postgres/extensions/pgvector/src/hnswinsert.c delete mode 100644 packages/server/postgres/extensions/pgvector/src/hnswinsert.o delete mode 100644 packages/server/postgres/extensions/pgvector/src/hnswscan.c delete mode 100644 packages/server/postgres/extensions/pgvector/src/hnswscan.o delete mode 100644 packages/server/postgres/extensions/pgvector/src/hnswutils.c delete mode 100644 packages/server/postgres/extensions/pgvector/src/hnswutils.o delete mode 100644 packages/server/postgres/extensions/pgvector/src/hnswvacuum.c delete mode 100644 packages/server/postgres/extensions/pgvector/src/hnswvacuum.o delete mode 100644 packages/server/postgres/extensions/pgvector/src/ivfbuild.c delete mode 100644 packages/server/postgres/extensions/pgvector/src/ivfbuild.o delete mode 100644 packages/server/postgres/extensions/pgvector/src/ivfflat.c delete mode 100644 packages/server/postgres/extensions/pgvector/src/ivfflat.h delete mode 100644 packages/server/postgres/extensions/pgvector/src/ivfflat.o delete mode 100644 packages/server/postgres/extensions/pgvector/src/ivfinsert.c delete mode 100644 packages/server/postgres/extensions/pgvector/src/ivfinsert.o delete mode 100644 packages/server/postgres/extensions/pgvector/src/ivfkmeans.c delete mode 100644 packages/server/postgres/extensions/pgvector/src/ivfkmeans.o delete mode 100644 packages/server/postgres/extensions/pgvector/src/ivfscan.c delete mode 100644 packages/server/postgres/extensions/pgvector/src/ivfscan.o delete mode 100644 packages/server/postgres/extensions/pgvector/src/ivfutils.c delete mode 100644 packages/server/postgres/extensions/pgvector/src/ivfutils.o delete mode 100644 packages/server/postgres/extensions/pgvector/src/ivfvacuum.c delete mode 100644 packages/server/postgres/extensions/pgvector/src/ivfvacuum.o delete mode 100644 packages/server/postgres/extensions/pgvector/src/vector.c delete mode 100644 packages/server/postgres/extensions/pgvector/src/vector.h delete mode 100644 packages/server/postgres/extensions/pgvector/src/vector.o delete mode 100644 packages/server/postgres/extensions/pgvector/vector.control delete mode 100755 packages/server/postgres/extensions/pgvector/vector.so diff --git a/packages/server/postgres/Dockerfile b/packages/server/postgres/Dockerfile index 104ed26f993..fcc7ce19d4e 100644 --- a/packages/server/postgres/Dockerfile +++ b/packages/server/postgres/Dockerfile @@ -2,12 +2,10 @@ FROM postgres:12.15 ADD extensions /extensions -RUN apt-get update && apt-get install -y build-essential +RUN apt-get update && apt-get install -y --no-install-recommends build-essential postgresql-server-dev-12 git-all RUN cd /extensions/postgres-json-schema && make install && make installcheck -RUN apt-get install -y --no-install-recommends postgresql-server-dev-12 - -RUN cd ./extensions/pgvector && ls && make clean && make && make install +RUN git clone --branch v0.5.0 https://github.com/pgvector/pgvector.git extensions/pgvector && cd extensions/pgvector && make clean && make && make install COPY extensions/install.sql /docker-entrypoint-initdb.d/ diff --git a/packages/server/postgres/extensions/pgvector/Makefile b/packages/server/postgres/extensions/pgvector/Makefile deleted file mode 100644 index 04d39986ed7..00000000000 --- a/packages/server/postgres/extensions/pgvector/Makefile +++ /dev/null @@ -1,76 +0,0 @@ -EXTENSION = vector -EXTVERSION = 0.5.0 - -MODULE_big = vector -DATA = $(wildcard sql/*--*.sql) -OBJS = src/hnsw.o src/hnswbuild.o src/hnswinsert.o src/hnswscan.o src/hnswutils.o src/hnswvacuum.o src/ivfbuild.o src/ivfflat.o src/ivfinsert.o src/ivfkmeans.o src/ivfscan.o src/ivfutils.o src/ivfvacuum.o src/vector.o -HEADERS = src/vector.h - -TESTS = $(wildcard test/sql/*.sql) -REGRESS = $(patsubst test/sql/%.sql,%,$(TESTS)) -REGRESS_OPTS = --inputdir=test --load-extension=vector - -OPTFLAGS = -march=native - -# Mac ARM doesn't support -march=native -ifeq ($(shell uname -s), Darwin) - ifeq ($(shell uname -p), arm) - # no difference with -march=armv8.5-a - OPTFLAGS = - endif -endif - -# PowerPC doesn't support -march=native -ifneq ($(filter ppc64%, $(shell uname -m)), ) - OPTFLAGS = -endif - -# For auto-vectorization: -# - GCC (needs -ftree-vectorize OR -O3) - https://gcc.gnu.org/projects/tree-ssa/vectorization.html -# - Clang (could use pragma instead) - https://llvm.org/docs/Vectorizers.html -PG_CFLAGS += $(OPTFLAGS) -ftree-vectorize -fassociative-math -fno-signed-zeros -fno-trapping-math - -# Debug GCC auto-vectorization -# PG_CFLAGS += -fopt-info-vec - -# Debug Clang auto-vectorization -# PG_CFLAGS += -Rpass=loop-vectorize -Rpass-analysis=loop-vectorize - -all: sql/$(EXTENSION)--$(EXTVERSION).sql - -sql/$(EXTENSION)--$(EXTVERSION).sql: sql/$(EXTENSION).sql - cp $< $@ - -EXTRA_CLEAN = sql/$(EXTENSION)--$(EXTVERSION).sql - -PG_CONFIG ?= pg_config -PGXS := $(shell $(PG_CONFIG) --pgxs) -include $(PGXS) -# for Mac -ifeq ($(PROVE),) - PROVE = prove -endif - -# for Postgres 15 -PROVE_FLAGS += -I ./test/perl - -prove_installcheck: - rm -rf $(CURDIR)/tmp_check - cd $(srcdir) && TESTDIR='$(CURDIR)' PATH="$(bindir):$$PATH" PGPORT='6$(DEF_PGPORT)' PG_REGRESS='$(top_builddir)/src/test/regress/pg_regress' $(PROVE) $(PG_PROVE_FLAGS) $(PROVE_FLAGS) $(if $(PROVE_TESTS),$(PROVE_TESTS),test/t/*.pl) - -.PHONY: dist - -dist: - mkdir -p dist - git archive --format zip --prefix=$(EXTENSION)-$(EXTVERSION)/ --output dist/$(EXTENSION)-$(EXTVERSION).zip master - -.PHONY: docker - -docker: - docker build --pull --no-cache --platform linux/amd64 -t ankane/pgvector:latest . - -.PHONY: docker-release - -docker-release: - docker buildx build --push --pull --no-cache --platform linux/amd64,linux/arm64 -t ankane/pgvector:latest . - docker buildx build --push --platform linux/amd64,linux/arm64 -t ankane/pgvector:v$(EXTVERSION) . diff --git a/packages/server/postgres/extensions/pgvector/sql/vector--0.1.0--0.1.1.sql b/packages/server/postgres/extensions/pgvector/sql/vector--0.1.0--0.1.1.sql deleted file mode 100644 index 959a0d72261..00000000000 --- a/packages/server/postgres/extensions/pgvector/sql/vector--0.1.0--0.1.1.sql +++ /dev/null @@ -1,39 +0,0 @@ --- complain if script is sourced in psql, rather than via CREATE EXTENSION -\echo Use "ALTER EXTENSION vector UPDATE TO '0.1.1'" to load this file. \quit - -CREATE FUNCTION vector_recv(internal, oid, integer) RETURNS vector - AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT; - -CREATE FUNCTION vector_send(vector) RETURNS bytea - AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT; - -ALTER TYPE vector SET ( RECEIVE = vector_recv, SEND = vector_send ); - --- functions - -ALTER FUNCTION vector_in(cstring, oid, integer) PARALLEL SAFE; -ALTER FUNCTION vector_out(vector) PARALLEL SAFE; -ALTER FUNCTION vector_typmod_in(cstring[]) PARALLEL SAFE; -ALTER FUNCTION vector_recv(internal, oid, integer) PARALLEL SAFE; -ALTER FUNCTION vector_send(vector) PARALLEL SAFE; -ALTER FUNCTION l2_distance(vector, vector) PARALLEL SAFE; -ALTER FUNCTION inner_product(vector, vector) PARALLEL SAFE; -ALTER FUNCTION cosine_distance(vector, vector) PARALLEL SAFE; -ALTER FUNCTION vector_dims(vector) PARALLEL SAFE; -ALTER FUNCTION vector_norm(vector) PARALLEL SAFE; -ALTER FUNCTION vector_add(vector, vector) PARALLEL SAFE; -ALTER FUNCTION vector_sub(vector, vector) PARALLEL SAFE; -ALTER FUNCTION vector_lt(vector, vector) PARALLEL SAFE; -ALTER FUNCTION vector_le(vector, vector) PARALLEL SAFE; -ALTER FUNCTION vector_eq(vector, vector) PARALLEL SAFE; -ALTER FUNCTION vector_ne(vector, vector) PARALLEL SAFE; -ALTER FUNCTION vector_ge(vector, vector) PARALLEL SAFE; -ALTER FUNCTION vector_gt(vector, vector) PARALLEL SAFE; -ALTER FUNCTION vector_cmp(vector, vector) PARALLEL SAFE; -ALTER FUNCTION vector_l2_squared_distance(vector, vector) PARALLEL SAFE; -ALTER FUNCTION vector_negative_inner_product(vector, vector) PARALLEL SAFE; -ALTER FUNCTION vector_spherical_distance(vector, vector) PARALLEL SAFE; -ALTER FUNCTION vector(vector, integer, boolean) PARALLEL SAFE; -ALTER FUNCTION array_to_vector(integer[], integer, boolean) PARALLEL SAFE; -ALTER FUNCTION array_to_vector(real[], integer, boolean) PARALLEL SAFE; -ALTER FUNCTION array_to_vector(double precision[], integer, boolean) PARALLEL SAFE; diff --git a/packages/server/postgres/extensions/pgvector/sql/vector--0.1.1--0.1.3.sql b/packages/server/postgres/extensions/pgvector/sql/vector--0.1.1--0.1.3.sql deleted file mode 100644 index 391835f865c..00000000000 --- a/packages/server/postgres/extensions/pgvector/sql/vector--0.1.1--0.1.3.sql +++ /dev/null @@ -1,2 +0,0 @@ --- complain if script is sourced in psql, rather than via CREATE EXTENSION -\echo Use "ALTER EXTENSION vector UPDATE TO '0.1.3'" to load this file. \quit diff --git a/packages/server/postgres/extensions/pgvector/sql/vector--0.1.3--0.1.4.sql b/packages/server/postgres/extensions/pgvector/sql/vector--0.1.3--0.1.4.sql deleted file mode 100644 index 56ab0eb501c..00000000000 --- a/packages/server/postgres/extensions/pgvector/sql/vector--0.1.3--0.1.4.sql +++ /dev/null @@ -1,2 +0,0 @@ --- complain if script is sourced in psql, rather than via CREATE EXTENSION -\echo Use "ALTER EXTENSION vector UPDATE TO '0.1.4'" to load this file. \quit diff --git a/packages/server/postgres/extensions/pgvector/sql/vector--0.1.4--0.1.5.sql b/packages/server/postgres/extensions/pgvector/sql/vector--0.1.4--0.1.5.sql deleted file mode 100644 index 3996b2dcd84..00000000000 --- a/packages/server/postgres/extensions/pgvector/sql/vector--0.1.4--0.1.5.sql +++ /dev/null @@ -1,2 +0,0 @@ --- complain if script is sourced in psql, rather than via CREATE EXTENSION -\echo Use "ALTER EXTENSION vector UPDATE TO '0.1.5'" to load this file. \quit diff --git a/packages/server/postgres/extensions/pgvector/sql/vector--0.1.5--0.1.6.sql b/packages/server/postgres/extensions/pgvector/sql/vector--0.1.5--0.1.6.sql deleted file mode 100644 index fdb605b0b95..00000000000 --- a/packages/server/postgres/extensions/pgvector/sql/vector--0.1.5--0.1.6.sql +++ /dev/null @@ -1,2 +0,0 @@ --- complain if script is sourced in psql, rather than via CREATE EXTENSION -\echo Use "ALTER EXTENSION vector UPDATE TO '0.1.6'" to load this file. \quit diff --git a/packages/server/postgres/extensions/pgvector/sql/vector--0.1.6--0.1.7.sql b/packages/server/postgres/extensions/pgvector/sql/vector--0.1.6--0.1.7.sql deleted file mode 100644 index fcd32f45a90..00000000000 --- a/packages/server/postgres/extensions/pgvector/sql/vector--0.1.6--0.1.7.sql +++ /dev/null @@ -1,8 +0,0 @@ --- complain if script is sourced in psql, rather than via CREATE EXTENSION -\echo Use "ALTER EXTENSION vector UPDATE TO '0.1.7'" to load this file. \quit - -CREATE FUNCTION array_to_vector(numeric[], integer, boolean) RETURNS vector - AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; - -CREATE CAST (numeric[] AS vector) - WITH FUNCTION array_to_vector(numeric[], integer, boolean) AS IMPLICIT; diff --git a/packages/server/postgres/extensions/pgvector/sql/vector--0.1.7--0.1.8.sql b/packages/server/postgres/extensions/pgvector/sql/vector--0.1.7--0.1.8.sql deleted file mode 100644 index 5a387a76b6d..00000000000 --- a/packages/server/postgres/extensions/pgvector/sql/vector--0.1.7--0.1.8.sql +++ /dev/null @@ -1,8 +0,0 @@ --- complain if script is sourced in psql, rather than via CREATE EXTENSION -\echo Use "ALTER EXTENSION vector UPDATE TO '0.1.8'" to load this file. \quit - -CREATE FUNCTION vector_to_float4(vector, integer, boolean) RETURNS real[] - AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; - -CREATE CAST (vector AS real[]) - WITH FUNCTION vector_to_float4(vector, integer, boolean) AS IMPLICIT; diff --git a/packages/server/postgres/extensions/pgvector/sql/vector--0.1.8--0.2.0.sql b/packages/server/postgres/extensions/pgvector/sql/vector--0.1.8--0.2.0.sql deleted file mode 100644 index 1ce0d1efd65..00000000000 --- a/packages/server/postgres/extensions/pgvector/sql/vector--0.1.8--0.2.0.sql +++ /dev/null @@ -1,2 +0,0 @@ --- complain if script is sourced in psql, rather than via CREATE EXTENSION -\echo Use "ALTER EXTENSION vector UPDATE TO '0.2.0'" to load this file. \quit diff --git a/packages/server/postgres/extensions/pgvector/sql/vector--0.2.0--0.2.1.sql b/packages/server/postgres/extensions/pgvector/sql/vector--0.2.0--0.2.1.sql deleted file mode 100644 index 47606deb3ad..00000000000 --- a/packages/server/postgres/extensions/pgvector/sql/vector--0.2.0--0.2.1.sql +++ /dev/null @@ -1,19 +0,0 @@ --- complain if script is sourced in psql, rather than via CREATE EXTENSION -\echo Use "ALTER EXTENSION vector UPDATE TO '0.2.1'" to load this file. \quit - -DROP CAST (integer[] AS vector); -DROP CAST (real[] AS vector); -DROP CAST (double precision[] AS vector); -DROP CAST (numeric[] AS vector); - -CREATE CAST (integer[] AS vector) - WITH FUNCTION array_to_vector(integer[], integer, boolean) AS ASSIGNMENT; - -CREATE CAST (real[] AS vector) - WITH FUNCTION array_to_vector(real[], integer, boolean) AS ASSIGNMENT; - -CREATE CAST (double precision[] AS vector) - WITH FUNCTION array_to_vector(double precision[], integer, boolean) AS ASSIGNMENT; - -CREATE CAST (numeric[] AS vector) - WITH FUNCTION array_to_vector(numeric[], integer, boolean) AS ASSIGNMENT; diff --git a/packages/server/postgres/extensions/pgvector/sql/vector--0.2.1--0.2.2.sql b/packages/server/postgres/extensions/pgvector/sql/vector--0.2.1--0.2.2.sql deleted file mode 100644 index 697c1408d70..00000000000 --- a/packages/server/postgres/extensions/pgvector/sql/vector--0.2.1--0.2.2.sql +++ /dev/null @@ -1,2 +0,0 @@ --- complain if script is sourced in psql, rather than via CREATE EXTENSION -\echo Use "ALTER EXTENSION vector UPDATE TO '0.2.2'" to load this file. \quit diff --git a/packages/server/postgres/extensions/pgvector/sql/vector--0.2.2--0.2.3.sql b/packages/server/postgres/extensions/pgvector/sql/vector--0.2.2--0.2.3.sql deleted file mode 100644 index 32b07dc228f..00000000000 --- a/packages/server/postgres/extensions/pgvector/sql/vector--0.2.2--0.2.3.sql +++ /dev/null @@ -1,2 +0,0 @@ --- complain if script is sourced in psql, rather than via CREATE EXTENSION -\echo Use "ALTER EXTENSION vector UPDATE TO '0.2.3'" to load this file. \quit diff --git a/packages/server/postgres/extensions/pgvector/sql/vector--0.2.3--0.2.4.sql b/packages/server/postgres/extensions/pgvector/sql/vector--0.2.3--0.2.4.sql deleted file mode 100644 index 5d1b34168ba..00000000000 --- a/packages/server/postgres/extensions/pgvector/sql/vector--0.2.3--0.2.4.sql +++ /dev/null @@ -1,2 +0,0 @@ --- complain if script is sourced in psql, rather than via CREATE EXTENSION -\echo Use "ALTER EXTENSION vector UPDATE TO '0.2.4'" to load this file. \quit diff --git a/packages/server/postgres/extensions/pgvector/sql/vector--0.2.4--0.2.5.sql b/packages/server/postgres/extensions/pgvector/sql/vector--0.2.4--0.2.5.sql deleted file mode 100644 index b372ed0c8c3..00000000000 --- a/packages/server/postgres/extensions/pgvector/sql/vector--0.2.4--0.2.5.sql +++ /dev/null @@ -1,2 +0,0 @@ --- complain if script is sourced in psql, rather than via CREATE EXTENSION -\echo Use "ALTER EXTENSION vector UPDATE TO '0.2.5'" to load this file. \quit diff --git a/packages/server/postgres/extensions/pgvector/sql/vector--0.2.5--0.2.6.sql b/packages/server/postgres/extensions/pgvector/sql/vector--0.2.5--0.2.6.sql deleted file mode 100644 index e68c1ac0374..00000000000 --- a/packages/server/postgres/extensions/pgvector/sql/vector--0.2.5--0.2.6.sql +++ /dev/null @@ -1,2 +0,0 @@ --- complain if script is sourced in psql, rather than via CREATE EXTENSION -\echo Use "ALTER EXTENSION vector UPDATE TO '0.2.6'" to load this file. \quit diff --git a/packages/server/postgres/extensions/pgvector/sql/vector--0.2.6--0.2.7.sql b/packages/server/postgres/extensions/pgvector/sql/vector--0.2.6--0.2.7.sql deleted file mode 100644 index 227c2171c41..00000000000 --- a/packages/server/postgres/extensions/pgvector/sql/vector--0.2.6--0.2.7.sql +++ /dev/null @@ -1,2 +0,0 @@ --- complain if script is sourced in psql, rather than via CREATE EXTENSION -\echo Use "ALTER EXTENSION vector UPDATE TO '0.2.7'" to load this file. \quit diff --git a/packages/server/postgres/extensions/pgvector/sql/vector--0.2.7--0.3.0.sql b/packages/server/postgres/extensions/pgvector/sql/vector--0.2.7--0.3.0.sql deleted file mode 100644 index 7e62d39e728..00000000000 --- a/packages/server/postgres/extensions/pgvector/sql/vector--0.2.7--0.3.0.sql +++ /dev/null @@ -1,2 +0,0 @@ --- complain if script is sourced in psql, rather than via CREATE EXTENSION -\echo Use "ALTER EXTENSION vector UPDATE TO '0.3.0'" to load this file. \quit diff --git a/packages/server/postgres/extensions/pgvector/sql/vector--0.3.0--0.3.1.sql b/packages/server/postgres/extensions/pgvector/sql/vector--0.3.0--0.3.1.sql deleted file mode 100644 index f1a8fbce5ae..00000000000 --- a/packages/server/postgres/extensions/pgvector/sql/vector--0.3.0--0.3.1.sql +++ /dev/null @@ -1,2 +0,0 @@ --- complain if script is sourced in psql, rather than via CREATE EXTENSION -\echo Use "ALTER EXTENSION vector UPDATE TO '0.3.1'" to load this file. \quit diff --git a/packages/server/postgres/extensions/pgvector/sql/vector--0.3.1--0.3.2.sql b/packages/server/postgres/extensions/pgvector/sql/vector--0.3.1--0.3.2.sql deleted file mode 100644 index c3461a10339..00000000000 --- a/packages/server/postgres/extensions/pgvector/sql/vector--0.3.1--0.3.2.sql +++ /dev/null @@ -1,2 +0,0 @@ --- complain if script is sourced in psql, rather than via CREATE EXTENSION -\echo Use "ALTER EXTENSION vector UPDATE TO '0.3.2'" to load this file. \quit diff --git a/packages/server/postgres/extensions/pgvector/sql/vector--0.3.2--0.4.0.sql b/packages/server/postgres/extensions/pgvector/sql/vector--0.3.2--0.4.0.sql deleted file mode 100644 index 3652664777c..00000000000 --- a/packages/server/postgres/extensions/pgvector/sql/vector--0.3.2--0.4.0.sql +++ /dev/null @@ -1,23 +0,0 @@ --- complain if script is sourced in psql, rather than via CREATE EXTENSION -\echo Use "ALTER EXTENSION vector UPDATE TO '0.4.0'" to load this file. \quit - --- remove this single line for Postgres < 13 -ALTER TYPE vector SET (STORAGE = extended); - -CREATE FUNCTION vector_accum(double precision[], vector) RETURNS double precision[] - AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; - -CREATE FUNCTION vector_avg(double precision[]) RETURNS vector - AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; - -CREATE FUNCTION vector_combine(double precision[], double precision[]) RETURNS double precision[] - AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; - -CREATE AGGREGATE avg(vector) ( - SFUNC = vector_accum, - STYPE = double precision[], - FINALFUNC = vector_avg, - COMBINEFUNC = vector_combine, - INITCOND = '{0}', - PARALLEL = SAFE -); diff --git a/packages/server/postgres/extensions/pgvector/sql/vector--0.4.0--0.4.1.sql b/packages/server/postgres/extensions/pgvector/sql/vector--0.4.0--0.4.1.sql deleted file mode 100644 index 67ba57ef924..00000000000 --- a/packages/server/postgres/extensions/pgvector/sql/vector--0.4.0--0.4.1.sql +++ /dev/null @@ -1,2 +0,0 @@ --- complain if script is sourced in psql, rather than via CREATE EXTENSION -\echo Use "ALTER EXTENSION vector UPDATE TO '0.4.1'" to load this file. \quit diff --git a/packages/server/postgres/extensions/pgvector/sql/vector--0.4.1--0.4.2.sql b/packages/server/postgres/extensions/pgvector/sql/vector--0.4.1--0.4.2.sql deleted file mode 100644 index 24abacce05f..00000000000 --- a/packages/server/postgres/extensions/pgvector/sql/vector--0.4.1--0.4.2.sql +++ /dev/null @@ -1,2 +0,0 @@ --- complain if script is sourced in psql, rather than via CREATE EXTENSION -\echo Use "ALTER EXTENSION vector UPDATE TO '0.4.2'" to load this file. \quit diff --git a/packages/server/postgres/extensions/pgvector/sql/vector--0.4.2--0.4.3.sql b/packages/server/postgres/extensions/pgvector/sql/vector--0.4.2--0.4.3.sql deleted file mode 100644 index 3db510e557e..00000000000 --- a/packages/server/postgres/extensions/pgvector/sql/vector--0.4.2--0.4.3.sql +++ /dev/null @@ -1,2 +0,0 @@ --- complain if script is sourced in psql, rather than via CREATE EXTENSION -\echo Use "ALTER EXTENSION vector UPDATE TO '0.4.3'" to load this file. \quit diff --git a/packages/server/postgres/extensions/pgvector/sql/vector--0.4.3--0.4.4.sql b/packages/server/postgres/extensions/pgvector/sql/vector--0.4.3--0.4.4.sql deleted file mode 100644 index 49c4ab4ef77..00000000000 --- a/packages/server/postgres/extensions/pgvector/sql/vector--0.4.3--0.4.4.sql +++ /dev/null @@ -1,2 +0,0 @@ --- complain if script is sourced in psql, rather than via CREATE EXTENSION -\echo Use "ALTER EXTENSION vector UPDATE TO '0.4.4'" to load this file. \quit diff --git a/packages/server/postgres/extensions/pgvector/sql/vector--0.4.4--0.5.0.sql b/packages/server/postgres/extensions/pgvector/sql/vector--0.4.4--0.5.0.sql deleted file mode 100644 index 48572bf67a7..00000000000 --- a/packages/server/postgres/extensions/pgvector/sql/vector--0.4.4--0.5.0.sql +++ /dev/null @@ -1,43 +0,0 @@ --- complain if script is sourced in psql, rather than via CREATE EXTENSION -\echo Use "ALTER EXTENSION vector UPDATE TO '0.5.0'" to load this file. \quit - -CREATE FUNCTION l1_distance(vector, vector) RETURNS float8 - AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; - -CREATE FUNCTION vector_mul(vector, vector) RETURNS vector - AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; - -CREATE OPERATOR * ( - LEFTARG = vector, RIGHTARG = vector, PROCEDURE = vector_mul, - COMMUTATOR = * -); - -CREATE AGGREGATE sum(vector) ( - SFUNC = vector_add, - STYPE = vector, - COMBINEFUNC = vector_add, - PARALLEL = SAFE -); - -CREATE FUNCTION hnswhandler(internal) RETURNS index_am_handler - AS 'MODULE_PATHNAME' LANGUAGE C; - -CREATE ACCESS METHOD hnsw TYPE INDEX HANDLER hnswhandler; - -COMMENT ON ACCESS METHOD hnsw IS 'hnsw index access method'; - -CREATE OPERATOR CLASS vector_l2_ops - FOR TYPE vector USING hnsw AS - OPERATOR 1 <-> (vector, vector) FOR ORDER BY float_ops, - FUNCTION 1 vector_l2_squared_distance(vector, vector); - -CREATE OPERATOR CLASS vector_ip_ops - FOR TYPE vector USING hnsw AS - OPERATOR 1 <#> (vector, vector) FOR ORDER BY float_ops, - FUNCTION 1 vector_negative_inner_product(vector, vector); - -CREATE OPERATOR CLASS vector_cosine_ops - FOR TYPE vector USING hnsw AS - OPERATOR 1 <=> (vector, vector) FOR ORDER BY float_ops, - FUNCTION 1 vector_negative_inner_product(vector, vector), - FUNCTION 2 vector_norm(vector); diff --git a/packages/server/postgres/extensions/pgvector/sql/vector--0.5.0.sql b/packages/server/postgres/extensions/pgvector/sql/vector--0.5.0.sql deleted file mode 100644 index 137931fed6d..00000000000 --- a/packages/server/postgres/extensions/pgvector/sql/vector--0.5.0.sql +++ /dev/null @@ -1,292 +0,0 @@ --- complain if script is sourced in psql, rather than via CREATE EXTENSION -\echo Use "CREATE EXTENSION vector" to load this file. \quit - --- type - -CREATE TYPE vector; - -CREATE FUNCTION vector_in(cstring, oid, integer) RETURNS vector - AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; - -CREATE FUNCTION vector_out(vector) RETURNS cstring - AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; - -CREATE FUNCTION vector_typmod_in(cstring[]) RETURNS integer - AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; - -CREATE FUNCTION vector_recv(internal, oid, integer) RETURNS vector - AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; - -CREATE FUNCTION vector_send(vector) RETURNS bytea - AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; - -CREATE TYPE vector ( - INPUT = vector_in, - OUTPUT = vector_out, - TYPMOD_IN = vector_typmod_in, - RECEIVE = vector_recv, - SEND = vector_send, - STORAGE = extended -); - --- functions - -CREATE FUNCTION l2_distance(vector, vector) RETURNS float8 - AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; - -CREATE FUNCTION inner_product(vector, vector) RETURNS float8 - AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; - -CREATE FUNCTION cosine_distance(vector, vector) RETURNS float8 - AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; - -CREATE FUNCTION l1_distance(vector, vector) RETURNS float8 - AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; - -CREATE FUNCTION vector_dims(vector) RETURNS integer - AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; - -CREATE FUNCTION vector_norm(vector) RETURNS float8 - AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; - -CREATE FUNCTION vector_add(vector, vector) RETURNS vector - AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; - -CREATE FUNCTION vector_sub(vector, vector) RETURNS vector - AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; - -CREATE FUNCTION vector_mul(vector, vector) RETURNS vector - AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; - --- private functions - -CREATE FUNCTION vector_lt(vector, vector) RETURNS bool - AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; - -CREATE FUNCTION vector_le(vector, vector) RETURNS bool - AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; - -CREATE FUNCTION vector_eq(vector, vector) RETURNS bool - AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; - -CREATE FUNCTION vector_ne(vector, vector) RETURNS bool - AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; - -CREATE FUNCTION vector_ge(vector, vector) RETURNS bool - AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; - -CREATE FUNCTION vector_gt(vector, vector) RETURNS bool - AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; - -CREATE FUNCTION vector_cmp(vector, vector) RETURNS int4 - AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; - -CREATE FUNCTION vector_l2_squared_distance(vector, vector) RETURNS float8 - AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; - -CREATE FUNCTION vector_negative_inner_product(vector, vector) RETURNS float8 - AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; - -CREATE FUNCTION vector_spherical_distance(vector, vector) RETURNS float8 - AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; - -CREATE FUNCTION vector_accum(double precision[], vector) RETURNS double precision[] - AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; - -CREATE FUNCTION vector_avg(double precision[]) RETURNS vector - AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; - -CREATE FUNCTION vector_combine(double precision[], double precision[]) RETURNS double precision[] - AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; - --- aggregates - -CREATE AGGREGATE avg(vector) ( - SFUNC = vector_accum, - STYPE = double precision[], - FINALFUNC = vector_avg, - COMBINEFUNC = vector_combine, - INITCOND = '{0}', - PARALLEL = SAFE -); - -CREATE AGGREGATE sum(vector) ( - SFUNC = vector_add, - STYPE = vector, - COMBINEFUNC = vector_add, - PARALLEL = SAFE -); - --- cast functions - -CREATE FUNCTION vector(vector, integer, boolean) RETURNS vector - AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; - -CREATE FUNCTION array_to_vector(integer[], integer, boolean) RETURNS vector - AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; - -CREATE FUNCTION array_to_vector(real[], integer, boolean) RETURNS vector - AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; - -CREATE FUNCTION array_to_vector(double precision[], integer, boolean) RETURNS vector - AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; - -CREATE FUNCTION array_to_vector(numeric[], integer, boolean) RETURNS vector - AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; - -CREATE FUNCTION vector_to_float4(vector, integer, boolean) RETURNS real[] - AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; - --- casts - -CREATE CAST (vector AS vector) - WITH FUNCTION vector(vector, integer, boolean) AS IMPLICIT; - -CREATE CAST (vector AS real[]) - WITH FUNCTION vector_to_float4(vector, integer, boolean) AS IMPLICIT; - -CREATE CAST (integer[] AS vector) - WITH FUNCTION array_to_vector(integer[], integer, boolean) AS ASSIGNMENT; - -CREATE CAST (real[] AS vector) - WITH FUNCTION array_to_vector(real[], integer, boolean) AS ASSIGNMENT; - -CREATE CAST (double precision[] AS vector) - WITH FUNCTION array_to_vector(double precision[], integer, boolean) AS ASSIGNMENT; - -CREATE CAST (numeric[] AS vector) - WITH FUNCTION array_to_vector(numeric[], integer, boolean) AS ASSIGNMENT; - --- operators - -CREATE OPERATOR <-> ( - LEFTARG = vector, RIGHTARG = vector, PROCEDURE = l2_distance, - COMMUTATOR = '<->' -); - -CREATE OPERATOR <#> ( - LEFTARG = vector, RIGHTARG = vector, PROCEDURE = vector_negative_inner_product, - COMMUTATOR = '<#>' -); - -CREATE OPERATOR <=> ( - LEFTARG = vector, RIGHTARG = vector, PROCEDURE = cosine_distance, - COMMUTATOR = '<=>' -); - -CREATE OPERATOR + ( - LEFTARG = vector, RIGHTARG = vector, PROCEDURE = vector_add, - COMMUTATOR = + -); - -CREATE OPERATOR - ( - LEFTARG = vector, RIGHTARG = vector, PROCEDURE = vector_sub, - COMMUTATOR = - -); - -CREATE OPERATOR * ( - LEFTARG = vector, RIGHTARG = vector, PROCEDURE = vector_mul, - COMMUTATOR = * -); - -CREATE OPERATOR < ( - LEFTARG = vector, RIGHTARG = vector, PROCEDURE = vector_lt, - COMMUTATOR = > , NEGATOR = >= , - RESTRICT = scalarltsel, JOIN = scalarltjoinsel -); - --- should use scalarlesel and scalarlejoinsel, but not supported in Postgres < 11 -CREATE OPERATOR <= ( - LEFTARG = vector, RIGHTARG = vector, PROCEDURE = vector_le, - COMMUTATOR = >= , NEGATOR = > , - RESTRICT = scalarltsel, JOIN = scalarltjoinsel -); - -CREATE OPERATOR = ( - LEFTARG = vector, RIGHTARG = vector, PROCEDURE = vector_eq, - COMMUTATOR = = , NEGATOR = <> , - RESTRICT = eqsel, JOIN = eqjoinsel -); - -CREATE OPERATOR <> ( - LEFTARG = vector, RIGHTARG = vector, PROCEDURE = vector_ne, - COMMUTATOR = <> , NEGATOR = = , - RESTRICT = eqsel, JOIN = eqjoinsel -); - --- should use scalargesel and scalargejoinsel, but not supported in Postgres < 11 -CREATE OPERATOR >= ( - LEFTARG = vector, RIGHTARG = vector, PROCEDURE = vector_ge, - COMMUTATOR = <= , NEGATOR = < , - RESTRICT = scalargtsel, JOIN = scalargtjoinsel -); - -CREATE OPERATOR > ( - LEFTARG = vector, RIGHTARG = vector, PROCEDURE = vector_gt, - COMMUTATOR = < , NEGATOR = <= , - RESTRICT = scalargtsel, JOIN = scalargtjoinsel -); - --- access methods - -CREATE FUNCTION ivfflathandler(internal) RETURNS index_am_handler - AS 'MODULE_PATHNAME' LANGUAGE C; - -CREATE ACCESS METHOD ivfflat TYPE INDEX HANDLER ivfflathandler; - -COMMENT ON ACCESS METHOD ivfflat IS 'ivfflat index access method'; - -CREATE FUNCTION hnswhandler(internal) RETURNS index_am_handler - AS 'MODULE_PATHNAME' LANGUAGE C; - -CREATE ACCESS METHOD hnsw TYPE INDEX HANDLER hnswhandler; - -COMMENT ON ACCESS METHOD hnsw IS 'hnsw index access method'; - --- opclasses - -CREATE OPERATOR CLASS vector_ops - DEFAULT FOR TYPE vector USING btree AS - OPERATOR 1 < , - OPERATOR 2 <= , - OPERATOR 3 = , - OPERATOR 4 >= , - OPERATOR 5 > , - FUNCTION 1 vector_cmp(vector, vector); - -CREATE OPERATOR CLASS vector_l2_ops - DEFAULT FOR TYPE vector USING ivfflat AS - OPERATOR 1 <-> (vector, vector) FOR ORDER BY float_ops, - FUNCTION 1 vector_l2_squared_distance(vector, vector), - FUNCTION 3 l2_distance(vector, vector); - -CREATE OPERATOR CLASS vector_ip_ops - FOR TYPE vector USING ivfflat AS - OPERATOR 1 <#> (vector, vector) FOR ORDER BY float_ops, - FUNCTION 1 vector_negative_inner_product(vector, vector), - FUNCTION 3 vector_spherical_distance(vector, vector), - FUNCTION 4 vector_norm(vector); - -CREATE OPERATOR CLASS vector_cosine_ops - FOR TYPE vector USING ivfflat AS - OPERATOR 1 <=> (vector, vector) FOR ORDER BY float_ops, - FUNCTION 1 vector_negative_inner_product(vector, vector), - FUNCTION 2 vector_norm(vector), - FUNCTION 3 vector_spherical_distance(vector, vector), - FUNCTION 4 vector_norm(vector); - -CREATE OPERATOR CLASS vector_l2_ops - FOR TYPE vector USING hnsw AS - OPERATOR 1 <-> (vector, vector) FOR ORDER BY float_ops, - FUNCTION 1 vector_l2_squared_distance(vector, vector); - -CREATE OPERATOR CLASS vector_ip_ops - FOR TYPE vector USING hnsw AS - OPERATOR 1 <#> (vector, vector) FOR ORDER BY float_ops, - FUNCTION 1 vector_negative_inner_product(vector, vector); - -CREATE OPERATOR CLASS vector_cosine_ops - FOR TYPE vector USING hnsw AS - OPERATOR 1 <=> (vector, vector) FOR ORDER BY float_ops, - FUNCTION 1 vector_negative_inner_product(vector, vector), - FUNCTION 2 vector_norm(vector); diff --git a/packages/server/postgres/extensions/pgvector/sql/vector.sql b/packages/server/postgres/extensions/pgvector/sql/vector.sql deleted file mode 100644 index 137931fed6d..00000000000 --- a/packages/server/postgres/extensions/pgvector/sql/vector.sql +++ /dev/null @@ -1,292 +0,0 @@ --- complain if script is sourced in psql, rather than via CREATE EXTENSION -\echo Use "CREATE EXTENSION vector" to load this file. \quit - --- type - -CREATE TYPE vector; - -CREATE FUNCTION vector_in(cstring, oid, integer) RETURNS vector - AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; - -CREATE FUNCTION vector_out(vector) RETURNS cstring - AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; - -CREATE FUNCTION vector_typmod_in(cstring[]) RETURNS integer - AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; - -CREATE FUNCTION vector_recv(internal, oid, integer) RETURNS vector - AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; - -CREATE FUNCTION vector_send(vector) RETURNS bytea - AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; - -CREATE TYPE vector ( - INPUT = vector_in, - OUTPUT = vector_out, - TYPMOD_IN = vector_typmod_in, - RECEIVE = vector_recv, - SEND = vector_send, - STORAGE = extended -); - --- functions - -CREATE FUNCTION l2_distance(vector, vector) RETURNS float8 - AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; - -CREATE FUNCTION inner_product(vector, vector) RETURNS float8 - AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; - -CREATE FUNCTION cosine_distance(vector, vector) RETURNS float8 - AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; - -CREATE FUNCTION l1_distance(vector, vector) RETURNS float8 - AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; - -CREATE FUNCTION vector_dims(vector) RETURNS integer - AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; - -CREATE FUNCTION vector_norm(vector) RETURNS float8 - AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; - -CREATE FUNCTION vector_add(vector, vector) RETURNS vector - AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; - -CREATE FUNCTION vector_sub(vector, vector) RETURNS vector - AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; - -CREATE FUNCTION vector_mul(vector, vector) RETURNS vector - AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; - --- private functions - -CREATE FUNCTION vector_lt(vector, vector) RETURNS bool - AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; - -CREATE FUNCTION vector_le(vector, vector) RETURNS bool - AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; - -CREATE FUNCTION vector_eq(vector, vector) RETURNS bool - AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; - -CREATE FUNCTION vector_ne(vector, vector) RETURNS bool - AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; - -CREATE FUNCTION vector_ge(vector, vector) RETURNS bool - AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; - -CREATE FUNCTION vector_gt(vector, vector) RETURNS bool - AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; - -CREATE FUNCTION vector_cmp(vector, vector) RETURNS int4 - AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; - -CREATE FUNCTION vector_l2_squared_distance(vector, vector) RETURNS float8 - AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; - -CREATE FUNCTION vector_negative_inner_product(vector, vector) RETURNS float8 - AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; - -CREATE FUNCTION vector_spherical_distance(vector, vector) RETURNS float8 - AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; - -CREATE FUNCTION vector_accum(double precision[], vector) RETURNS double precision[] - AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; - -CREATE FUNCTION vector_avg(double precision[]) RETURNS vector - AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; - -CREATE FUNCTION vector_combine(double precision[], double precision[]) RETURNS double precision[] - AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; - --- aggregates - -CREATE AGGREGATE avg(vector) ( - SFUNC = vector_accum, - STYPE = double precision[], - FINALFUNC = vector_avg, - COMBINEFUNC = vector_combine, - INITCOND = '{0}', - PARALLEL = SAFE -); - -CREATE AGGREGATE sum(vector) ( - SFUNC = vector_add, - STYPE = vector, - COMBINEFUNC = vector_add, - PARALLEL = SAFE -); - --- cast functions - -CREATE FUNCTION vector(vector, integer, boolean) RETURNS vector - AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; - -CREATE FUNCTION array_to_vector(integer[], integer, boolean) RETURNS vector - AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; - -CREATE FUNCTION array_to_vector(real[], integer, boolean) RETURNS vector - AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; - -CREATE FUNCTION array_to_vector(double precision[], integer, boolean) RETURNS vector - AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; - -CREATE FUNCTION array_to_vector(numeric[], integer, boolean) RETURNS vector - AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; - -CREATE FUNCTION vector_to_float4(vector, integer, boolean) RETURNS real[] - AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; - --- casts - -CREATE CAST (vector AS vector) - WITH FUNCTION vector(vector, integer, boolean) AS IMPLICIT; - -CREATE CAST (vector AS real[]) - WITH FUNCTION vector_to_float4(vector, integer, boolean) AS IMPLICIT; - -CREATE CAST (integer[] AS vector) - WITH FUNCTION array_to_vector(integer[], integer, boolean) AS ASSIGNMENT; - -CREATE CAST (real[] AS vector) - WITH FUNCTION array_to_vector(real[], integer, boolean) AS ASSIGNMENT; - -CREATE CAST (double precision[] AS vector) - WITH FUNCTION array_to_vector(double precision[], integer, boolean) AS ASSIGNMENT; - -CREATE CAST (numeric[] AS vector) - WITH FUNCTION array_to_vector(numeric[], integer, boolean) AS ASSIGNMENT; - --- operators - -CREATE OPERATOR <-> ( - LEFTARG = vector, RIGHTARG = vector, PROCEDURE = l2_distance, - COMMUTATOR = '<->' -); - -CREATE OPERATOR <#> ( - LEFTARG = vector, RIGHTARG = vector, PROCEDURE = vector_negative_inner_product, - COMMUTATOR = '<#>' -); - -CREATE OPERATOR <=> ( - LEFTARG = vector, RIGHTARG = vector, PROCEDURE = cosine_distance, - COMMUTATOR = '<=>' -); - -CREATE OPERATOR + ( - LEFTARG = vector, RIGHTARG = vector, PROCEDURE = vector_add, - COMMUTATOR = + -); - -CREATE OPERATOR - ( - LEFTARG = vector, RIGHTARG = vector, PROCEDURE = vector_sub, - COMMUTATOR = - -); - -CREATE OPERATOR * ( - LEFTARG = vector, RIGHTARG = vector, PROCEDURE = vector_mul, - COMMUTATOR = * -); - -CREATE OPERATOR < ( - LEFTARG = vector, RIGHTARG = vector, PROCEDURE = vector_lt, - COMMUTATOR = > , NEGATOR = >= , - RESTRICT = scalarltsel, JOIN = scalarltjoinsel -); - --- should use scalarlesel and scalarlejoinsel, but not supported in Postgres < 11 -CREATE OPERATOR <= ( - LEFTARG = vector, RIGHTARG = vector, PROCEDURE = vector_le, - COMMUTATOR = >= , NEGATOR = > , - RESTRICT = scalarltsel, JOIN = scalarltjoinsel -); - -CREATE OPERATOR = ( - LEFTARG = vector, RIGHTARG = vector, PROCEDURE = vector_eq, - COMMUTATOR = = , NEGATOR = <> , - RESTRICT = eqsel, JOIN = eqjoinsel -); - -CREATE OPERATOR <> ( - LEFTARG = vector, RIGHTARG = vector, PROCEDURE = vector_ne, - COMMUTATOR = <> , NEGATOR = = , - RESTRICT = eqsel, JOIN = eqjoinsel -); - --- should use scalargesel and scalargejoinsel, but not supported in Postgres < 11 -CREATE OPERATOR >= ( - LEFTARG = vector, RIGHTARG = vector, PROCEDURE = vector_ge, - COMMUTATOR = <= , NEGATOR = < , - RESTRICT = scalargtsel, JOIN = scalargtjoinsel -); - -CREATE OPERATOR > ( - LEFTARG = vector, RIGHTARG = vector, PROCEDURE = vector_gt, - COMMUTATOR = < , NEGATOR = <= , - RESTRICT = scalargtsel, JOIN = scalargtjoinsel -); - --- access methods - -CREATE FUNCTION ivfflathandler(internal) RETURNS index_am_handler - AS 'MODULE_PATHNAME' LANGUAGE C; - -CREATE ACCESS METHOD ivfflat TYPE INDEX HANDLER ivfflathandler; - -COMMENT ON ACCESS METHOD ivfflat IS 'ivfflat index access method'; - -CREATE FUNCTION hnswhandler(internal) RETURNS index_am_handler - AS 'MODULE_PATHNAME' LANGUAGE C; - -CREATE ACCESS METHOD hnsw TYPE INDEX HANDLER hnswhandler; - -COMMENT ON ACCESS METHOD hnsw IS 'hnsw index access method'; - --- opclasses - -CREATE OPERATOR CLASS vector_ops - DEFAULT FOR TYPE vector USING btree AS - OPERATOR 1 < , - OPERATOR 2 <= , - OPERATOR 3 = , - OPERATOR 4 >= , - OPERATOR 5 > , - FUNCTION 1 vector_cmp(vector, vector); - -CREATE OPERATOR CLASS vector_l2_ops - DEFAULT FOR TYPE vector USING ivfflat AS - OPERATOR 1 <-> (vector, vector) FOR ORDER BY float_ops, - FUNCTION 1 vector_l2_squared_distance(vector, vector), - FUNCTION 3 l2_distance(vector, vector); - -CREATE OPERATOR CLASS vector_ip_ops - FOR TYPE vector USING ivfflat AS - OPERATOR 1 <#> (vector, vector) FOR ORDER BY float_ops, - FUNCTION 1 vector_negative_inner_product(vector, vector), - FUNCTION 3 vector_spherical_distance(vector, vector), - FUNCTION 4 vector_norm(vector); - -CREATE OPERATOR CLASS vector_cosine_ops - FOR TYPE vector USING ivfflat AS - OPERATOR 1 <=> (vector, vector) FOR ORDER BY float_ops, - FUNCTION 1 vector_negative_inner_product(vector, vector), - FUNCTION 2 vector_norm(vector), - FUNCTION 3 vector_spherical_distance(vector, vector), - FUNCTION 4 vector_norm(vector); - -CREATE OPERATOR CLASS vector_l2_ops - FOR TYPE vector USING hnsw AS - OPERATOR 1 <-> (vector, vector) FOR ORDER BY float_ops, - FUNCTION 1 vector_l2_squared_distance(vector, vector); - -CREATE OPERATOR CLASS vector_ip_ops - FOR TYPE vector USING hnsw AS - OPERATOR 1 <#> (vector, vector) FOR ORDER BY float_ops, - FUNCTION 1 vector_negative_inner_product(vector, vector); - -CREATE OPERATOR CLASS vector_cosine_ops - FOR TYPE vector USING hnsw AS - OPERATOR 1 <=> (vector, vector) FOR ORDER BY float_ops, - FUNCTION 1 vector_negative_inner_product(vector, vector), - FUNCTION 2 vector_norm(vector); diff --git a/packages/server/postgres/extensions/pgvector/src/hnsw.c b/packages/server/postgres/extensions/pgvector/src/hnsw.c deleted file mode 100644 index 1c3c8acfaca..00000000000 --- a/packages/server/postgres/extensions/pgvector/src/hnsw.c +++ /dev/null @@ -1,224 +0,0 @@ -#include "postgres.h" - -#include -#include - -#include "access/amapi.h" -#include "commands/vacuum.h" -#include "hnsw.h" -#include "utils/guc.h" -#include "utils/selfuncs.h" - -#if PG_VERSION_NUM >= 120000 -#include "commands/progress.h" -#endif - -int hnsw_ef_search; -static relopt_kind hnsw_relopt_kind; - -/* - * Initialize index options and variables - */ -void -HnswInit(void) -{ - hnsw_relopt_kind = add_reloption_kind(); - add_int_reloption(hnsw_relopt_kind, "m", "Max number of connections", - HNSW_DEFAULT_M, HNSW_MIN_M, HNSW_MAX_M -#if PG_VERSION_NUM >= 130000 - ,AccessExclusiveLock -#endif - ); - add_int_reloption(hnsw_relopt_kind, "ef_construction", "Size of the dynamic candidate list for construction", - HNSW_DEFAULT_EF_CONSTRUCTION, HNSW_MIN_EF_CONSTRUCTION, HNSW_MAX_EF_CONSTRUCTION -#if PG_VERSION_NUM >= 130000 - ,AccessExclusiveLock -#endif - ); - - DefineCustomIntVariable("hnsw.ef_search", "Sets the size of the dynamic candidate list for search", - "Valid range is 1..1000.", &hnsw_ef_search, - HNSW_DEFAULT_EF_SEARCH, HNSW_MIN_EF_SEARCH, HNSW_MAX_EF_SEARCH, PGC_USERSET, 0, NULL, NULL, NULL); -} - -/* - * Get the name of index build phase - */ -#if PG_VERSION_NUM >= 120000 -static char * -hnswbuildphasename(int64 phasenum) -{ - switch (phasenum) - { - case PROGRESS_CREATEIDX_SUBPHASE_INITIALIZE: - return "initializing"; - case PROGRESS_HNSW_PHASE_LOAD: - return "loading tuples"; - default: - return NULL; - } -} -#endif - -/* - * Estimate the cost of an index scan - */ -static void -hnswcostestimate(PlannerInfo *root, IndexPath *path, double loop_count, - Cost *indexStartupCost, Cost *indexTotalCost, - Selectivity *indexSelectivity, double *indexCorrelation, - double *indexPages) -{ - GenericCosts costs; - int m; - int entryLevel; - Relation index; -#if PG_VERSION_NUM < 120000 - List *qinfos; -#endif - - /* Never use index without order */ - if (path->indexorderbys == NULL) - { - *indexStartupCost = DBL_MAX; - *indexTotalCost = DBL_MAX; - *indexSelectivity = 0; - *indexCorrelation = 0; - *indexPages = 0; - return; - } - - MemSet(&costs, 0, sizeof(costs)); - - index = index_open(path->indexinfo->indexoid, NoLock); - m = HnswGetM(index); - index_close(index, NoLock); - - /* Approximate entry level */ - entryLevel = (int) -log(1.0 / path->indexinfo->tuples) * HnswGetMl(m); - - /* TODO Improve estimate of visited tuples (currently underestimates) */ - /* Account for number of tuples (or entry level), m, and ef_search */ - costs.numIndexTuples = (entryLevel + 2) * m; - -#if PG_VERSION_NUM >= 120000 - genericcostestimate(root, path, loop_count, &costs); -#else - qinfos = deconstruct_indexquals(path); - genericcostestimate(root, path, loop_count, qinfos, &costs); -#endif - - /* Use total cost since most work happens before first tuple is returned */ - *indexStartupCost = costs.indexTotalCost; - *indexTotalCost = costs.indexTotalCost; - *indexSelectivity = costs.indexSelectivity; - *indexCorrelation = costs.indexCorrelation; - *indexPages = costs.numIndexPages; -} - -/* - * Parse and validate the reloptions - */ -static bytea * -hnswoptions(Datum reloptions, bool validate) -{ - static const relopt_parse_elt tab[] = { - {"m", RELOPT_TYPE_INT, offsetof(HnswOptions, m)}, - {"ef_construction", RELOPT_TYPE_INT, offsetof(HnswOptions, efConstruction)}, - }; - -#if PG_VERSION_NUM >= 130000 - return (bytea *) build_reloptions(reloptions, validate, - hnsw_relopt_kind, - sizeof(HnswOptions), - tab, lengthof(tab)); -#else - relopt_value *options; - int numoptions; - HnswOptions *rdopts; - - options = parseRelOptions(reloptions, validate, hnsw_relopt_kind, &numoptions); - rdopts = allocateReloptStruct(sizeof(HnswOptions), options, numoptions); - fillRelOptions((void *) rdopts, sizeof(HnswOptions), options, numoptions, - validate, tab, lengthof(tab)); - - return (bytea *) rdopts; -#endif -} - -/* - * Validate catalog entries for the specified operator class - */ -static bool -hnswvalidate(Oid opclassoid) -{ - return true; -} - -/* - * Define index handler - * - * See https://www.postgresql.org/docs/current/index-api.html - */ -PGDLLEXPORT PG_FUNCTION_INFO_V1(hnswhandler); -Datum -hnswhandler(PG_FUNCTION_ARGS) -{ - IndexAmRoutine *amroutine = makeNode(IndexAmRoutine); - - amroutine->amstrategies = 0; - amroutine->amsupport = 2; -#if PG_VERSION_NUM >= 130000 - amroutine->amoptsprocnum = 0; -#endif - amroutine->amcanorder = false; - amroutine->amcanorderbyop = true; - amroutine->amcanbackward = false; /* can change direction mid-scan */ - amroutine->amcanunique = false; - amroutine->amcanmulticol = false; - amroutine->amoptionalkey = true; - amroutine->amsearcharray = false; - amroutine->amsearchnulls = false; - amroutine->amstorage = false; - amroutine->amclusterable = false; - amroutine->ampredlocks = false; - amroutine->amcanparallel = false; - amroutine->amcaninclude = false; -#if PG_VERSION_NUM >= 130000 - amroutine->amusemaintenanceworkmem = false; /* not used during VACUUM */ - amroutine->amparallelvacuumoptions = VACUUM_OPTION_PARALLEL_BULKDEL; -#endif - amroutine->amkeytype = InvalidOid; - - /* Interface functions */ - amroutine->ambuild = hnswbuild; - amroutine->ambuildempty = hnswbuildempty; - amroutine->aminsert = hnswinsert; - amroutine->ambulkdelete = hnswbulkdelete; - amroutine->amvacuumcleanup = hnswvacuumcleanup; - amroutine->amcanreturn = NULL; /* tuple not included in heapsort */ - amroutine->amcostestimate = hnswcostestimate; - amroutine->amoptions = hnswoptions; - amroutine->amproperty = NULL; /* TODO AMPROP_DISTANCE_ORDERABLE */ -#if PG_VERSION_NUM >= 120000 - amroutine->ambuildphasename = hnswbuildphasename; -#endif - amroutine->amvalidate = hnswvalidate; -#if PG_VERSION_NUM >= 140000 - amroutine->amadjustmembers = NULL; -#endif - amroutine->ambeginscan = hnswbeginscan; - amroutine->amrescan = hnswrescan; - amroutine->amgettuple = hnswgettuple; - amroutine->amgetbitmap = NULL; - amroutine->amendscan = hnswendscan; - amroutine->ammarkpos = NULL; - amroutine->amrestrpos = NULL; - - /* Interface functions to support parallel index scans */ - amroutine->amestimateparallelscan = NULL; - amroutine->aminitparallelscan = NULL; - amroutine->amparallelrescan = NULL; - - PG_RETURN_POINTER(amroutine); -} diff --git a/packages/server/postgres/extensions/pgvector/src/hnsw.h b/packages/server/postgres/extensions/pgvector/src/hnsw.h deleted file mode 100644 index 3e8bdc2c160..00000000000 --- a/packages/server/postgres/extensions/pgvector/src/hnsw.h +++ /dev/null @@ -1,305 +0,0 @@ -#ifndef HNSW_H -#define HNSW_H - -#include "postgres.h" - -#include "access/generic_xlog.h" -#include "access/reloptions.h" -#include "nodes/execnodes.h" -#include "port.h" /* for random() */ -#include "utils/sampling.h" -#include "vector.h" - -#if PG_VERSION_NUM < 110000 -#error "Requires PostgreSQL 11+" -#endif - -#define HNSW_MAX_DIM 2000 - -/* Support functions */ -#define HNSW_DISTANCE_PROC 1 -#define HNSW_NORM_PROC 2 - -#define HNSW_VERSION 1 -#define HNSW_MAGIC_NUMBER 0xA953A953 -#define HNSW_PAGE_ID 0xFF90 - -/* Preserved page numbers */ -#define HNSW_METAPAGE_BLKNO 0 -#define HNSW_HEAD_BLKNO 1 /* first element page */ - -/* Must correspond to page numbers since page lock is used */ -#define HNSW_UPDATE_LOCK 0 -#define HNSW_SCAN_LOCK 1 - -/* HNSW parameters */ -#define HNSW_DEFAULT_M 16 -#define HNSW_MIN_M 2 -#define HNSW_MAX_M 100 -#define HNSW_DEFAULT_EF_CONSTRUCTION 64 -#define HNSW_MIN_EF_CONSTRUCTION 4 -#define HNSW_MAX_EF_CONSTRUCTION 1000 -#define HNSW_DEFAULT_EF_SEARCH 40 -#define HNSW_MIN_EF_SEARCH 1 -#define HNSW_MAX_EF_SEARCH 1000 - -/* Tuple types */ -#define HNSW_ELEMENT_TUPLE_TYPE 1 -#define HNSW_NEIGHBOR_TUPLE_TYPE 2 - -/* Make graph robust against non-HOT updates */ -#define HNSW_HEAPTIDS 10 - -#define HNSW_UPDATE_ENTRY_GREATER 1 -#define HNSW_UPDATE_ENTRY_ALWAYS 2 - -/* Build phases */ -/* PROGRESS_CREATEIDX_SUBPHASE_INITIALIZE is 1 */ -#define PROGRESS_HNSW_PHASE_LOAD 2 - -#define HNSW_ELEMENT_TUPLE_SIZE(_dim) MAXALIGN(offsetof(HnswElementTupleData, vec) + VECTOR_SIZE(_dim)) -#define HNSW_NEIGHBOR_TUPLE_SIZE(level, m) MAXALIGN(offsetof(HnswNeighborTupleData, indextids) + ((level) + 2) * (m) * sizeof(ItemPointerData)) - -#define HnswPageGetOpaque(page) ((HnswPageOpaque) PageGetSpecialPointer(page)) -#define HnswPageGetMeta(page) ((HnswMetaPageData *) PageGetContents(page)) - -#if PG_VERSION_NUM >= 150000 -#define RandomDouble() pg_prng_double(&pg_global_prng_state) -#else -#define RandomDouble() (((double) random()) / MAX_RANDOM_VALUE) -#endif - -#if PG_VERSION_NUM < 130000 -#define list_delete_last(list) list_truncate(list, list_length(list) - 1) -#define list_sort(list, cmp) list_qsort(list, cmp) -#endif - -#define HnswIsElementTuple(tup) ((tup)->type == HNSW_ELEMENT_TUPLE_TYPE) -#define HnswIsNeighborTuple(tup) ((tup)->type == HNSW_NEIGHBOR_TUPLE_TYPE) - -/* 2 * M connections for ground layer */ -#define HnswGetLayerM(m, layer) (layer == 0 ? (m) * 2 : (m)) - -/* Optimal ML from paper */ -#define HnswGetMl(m) (1 / log(m)) - -/* Ensure fits on page and in uint8 */ -#define HnswGetMaxLevel(m) Min(((BLCKSZ - MAXALIGN(SizeOfPageHeaderData) - MAXALIGN(sizeof(HnswPageOpaqueData)) - offsetof(HnswNeighborTupleData, indextids) - sizeof(ItemIdData)) / (sizeof(ItemPointerData)) / m) - 2, 255) - -/* Variables */ -extern int hnsw_ef_search; - -typedef struct HnswNeighborArray HnswNeighborArray; - -typedef struct HnswElementData -{ - List *heaptids; - uint8 level; - uint8 deleted; - HnswNeighborArray *neighbors; - BlockNumber blkno; - OffsetNumber offno; - OffsetNumber neighborOffno; - BlockNumber neighborPage; - Vector *vec; -} HnswElementData; - -typedef HnswElementData * HnswElement; - -typedef struct HnswCandidate -{ - HnswElement element; - float distance; -} HnswCandidate; - -typedef struct HnswNeighborArray -{ - int length; - HnswCandidate *items; -} HnswNeighborArray; - -typedef struct HnswPairingHeapNode -{ - pairingheap_node ph_node; - HnswCandidate *inner; -} HnswPairingHeapNode; - -/* HNSW index options */ -typedef struct HnswOptions -{ - int32 vl_len_; /* varlena header (do not touch directly!) */ - int m; /* number of connections */ - int efConstruction; /* size of dynamic candidate list */ -} HnswOptions; - -typedef struct HnswBuildState -{ - /* Info */ - Relation heap; - Relation index; - IndexInfo *indexInfo; - ForkNumber forkNum; - - /* Settings */ - int dimensions; - int m; - int efConstruction; - - /* Statistics */ - double indtuples; - double reltuples; - - /* Support functions */ - FmgrInfo *procinfo; - FmgrInfo *normprocinfo; - Oid collation; - - /* Variables */ - List *elements; - HnswElement entryPoint; - double ml; - int maxLevel; - double maxInMemoryElements; - bool flushed; - Vector *normvec; - - /* Memory */ - MemoryContext tmpCtx; -} HnswBuildState; - -typedef struct HnswMetaPageData -{ - uint32 magicNumber; - uint32 version; - uint32 dimensions; - uint16 m; - uint16 efConstruction; - BlockNumber entryBlkno; - OffsetNumber entryOffno; - int16 entryLevel; - BlockNumber insertPage; -} HnswMetaPageData; - -typedef HnswMetaPageData * HnswMetaPage; - -typedef struct HnswPageOpaqueData -{ - BlockNumber nextblkno; - uint16 unused; - uint16 page_id; /* for identification of HNSW indexes */ -} HnswPageOpaqueData; - -typedef HnswPageOpaqueData * HnswPageOpaque; - -typedef struct HnswElementTupleData -{ - uint8 type; - uint8 level; - uint8 deleted; - uint8 unused; - ItemPointerData heaptids[HNSW_HEAPTIDS]; - ItemPointerData neighbortid; - uint16 unused2; - Vector vec; -} HnswElementTupleData; - -typedef HnswElementTupleData * HnswElementTuple; - -typedef struct HnswNeighborTupleData -{ - uint8 type; - uint8 unused; - uint16 count; - ItemPointerData indextids[FLEXIBLE_ARRAY_MEMBER]; -} HnswNeighborTupleData; - -typedef HnswNeighborTupleData * HnswNeighborTuple; - -typedef struct HnswScanOpaqueData -{ - bool first; - Buffer buf; - List *w; - MemoryContext tmpCtx; - - /* Support functions */ - FmgrInfo *procinfo; - FmgrInfo *normprocinfo; - Oid collation; -} HnswScanOpaqueData; - -typedef HnswScanOpaqueData * HnswScanOpaque; - -typedef struct HnswVacuumState -{ - /* Info */ - Relation index; - IndexBulkDeleteResult *stats; - IndexBulkDeleteCallback callback; - void *callback_state; - - /* Settings */ - int m; - int efConstruction; - - /* Support functions */ - FmgrInfo *procinfo; - Oid collation; - - /* Variables */ - HTAB *deleted; - BufferAccessStrategy bas; - HnswNeighborTuple ntup; - HnswElementData highestPoint; - - /* Memory */ - MemoryContext tmpCtx; -} HnswVacuumState; - -/* Methods */ -int HnswGetM(Relation index); -int HnswGetEfConstruction(Relation index); -FmgrInfo *HnswOptionalProcInfo(Relation rel, uint16 procnum); -bool HnswNormValue(FmgrInfo *procinfo, Oid collation, Datum *value, Vector * result); -void HnswCommitBuffer(Buffer buf, GenericXLogState *state); -Buffer HnswNewBuffer(Relation index, ForkNumber forkNum); -void HnswInitPage(Buffer buf, Page page); -void HnswInitRegisterPage(Relation index, Buffer *buf, Page *page, GenericXLogState **state); -void HnswInit(void); -List *HnswSearchLayer(Datum q, List *ep, int ef, int lc, Relation index, FmgrInfo *procinfo, Oid collation, bool inserting, HnswElement skipElement); -HnswElement HnswGetEntryPoint(Relation index); -HnswElement HnswInitElement(ItemPointer tid, int m, double ml, int maxLevel); -void HnswFreeElement(HnswElement element); -HnswElement HnswInitElementFromBlock(BlockNumber blkno, OffsetNumber offno); -void HnswInsertElement(HnswElement element, HnswElement entryPoint, Relation index, FmgrInfo *procinfo, Oid collation, int m, int efConstruction, bool existing); -HnswElement HnswFindDuplicate(HnswElement e); -HnswCandidate *HnswEntryCandidate(HnswElement em, Datum q, Relation rel, FmgrInfo *procinfo, Oid collation, bool loadVec); -void HnswUpdateMetaPage(Relation index, int updateEntry, HnswElement entryPoint, BlockNumber insertPage, ForkNumber forkNum); -void HnswSetNeighborTuple(HnswNeighborTuple ntup, HnswElement e, int m); -void HnswAddHeapTid(HnswElement element, ItemPointer heaptid); -void HnswInitNeighbors(HnswElement element, int m); -bool HnswInsertTuple(Relation index, Datum *values, bool *isnull, ItemPointer heap_tid, Relation heapRel); -void HnswUpdateNeighborPages(Relation index, FmgrInfo *procinfo, Oid collation, HnswElement e, int m, bool checkExisting); -void HnswLoadElementFromTuple(HnswElement element, HnswElementTuple etup, bool loadHeaptids, bool loadVec); -void HnswLoadElement(HnswElement element, float *distance, Datum *q, Relation index, FmgrInfo *procinfo, Oid collation, bool loadVec); -void HnswSetElementTuple(HnswElementTuple etup, HnswElement element); -void HnswUpdateConnection(HnswElement element, HnswCandidate * hc, int m, int lc, int *updateIdx, Relation index, FmgrInfo *procinfo, Oid collation); -void HnswLoadNeighbors(HnswElement element, Relation index); - -/* Index access methods */ -IndexBuildResult *hnswbuild(Relation heap, Relation index, IndexInfo *indexInfo); -void hnswbuildempty(Relation index); -bool hnswinsert(Relation index, Datum *values, bool *isnull, ItemPointer heap_tid, Relation heap, IndexUniqueCheck checkUnique -#if PG_VERSION_NUM >= 140000 - ,bool indexUnchanged -#endif - ,IndexInfo *indexInfo -); -IndexBulkDeleteResult *hnswbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats, IndexBulkDeleteCallback callback, void *callback_state); -IndexBulkDeleteResult *hnswvacuumcleanup(IndexVacuumInfo *info, IndexBulkDeleteResult *stats); -IndexScanDesc hnswbeginscan(Relation index, int nkeys, int norderbys); -void hnswrescan(IndexScanDesc scan, ScanKey keys, int nkeys, ScanKey orderbys, int norderbys); -bool hnswgettuple(IndexScanDesc scan, ScanDirection dir); -void hnswendscan(IndexScanDesc scan); - -#endif diff --git a/packages/server/postgres/extensions/pgvector/src/hnsw.o b/packages/server/postgres/extensions/pgvector/src/hnsw.o deleted file mode 100644 index b7273cc2bb1058737ce6ee77be9cc8bebb339bd6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4224 zcmb7He{5Sv9lv+av0q9_S`k6ohUArI)P*EVw=#wlYtHI)>GaB~G#V4adUouW4-f5+jumNzGU^f& zKKJfk?7VCeoO1WM&-d56``-EP^85RL`_FDdB!a@44%9V@Mny&0hZ<)53(634K>0w} zgar>l#%ho$QFPrg798H^e0t)U=@$2TF`4&RJU665$O$G2kP_GMR@Y|=g*Jh%$Hu3} zTLNz!ftHBJ6WpC}n@-nL1t)LXb3&%;+sEgP>rX=8=S@s-e~J(Fmia@I8gygeSj_&&WxGY{KmrOZ&(SFXf)4Hz3JXeO3B3@kxRS_?W zcx+u!whi|o2zpU@UcZbED#{4z9@GW+T>>&EX4m^ALLLMvUC==YN=~D)@>NmU_}Ttr z{QoVRvUKzAQ?%M#lB>;kNd^9|e1(v5f|9EkWBDK<6@0e*I8dggsTe6u{d9L}Dkznv zm~DbaciJhXsTJ6DoNL)_Zj$O|H)(9jpOZ4gvxKli(Stn`OTQvW-2je$L6QBg}% z$|bU<^pV8@g*3v5uO!I#2KvZV&%YCKjv$6INUJ+#@|H|l4v0Rfa373euwzR$pa!@4 z%5rGSuN7a}k}1obt^R3@HBB1iBa$Vh7sp8Hg|X(+J6G6TH{Yc_L(f#{w32|cqrOf4U#vNXrlj~EY%t^ zsog@(Iwh&H>maE-Chhv<$n!5;yd{TgVM%#WflRt{?xi|xSpJp6$ZH2%Yo6Ep@MrRW z1MBksnkRSd_C}l19=l)dP4qJx5>57Zt{eRcdB)}HzC^!57W$P=vR65G-~f?k`s-gK z&A;GVb$MUyL%Fv`#<_3(XVFZZlIA+_qkBL$M5U=Rh&S!k#bBNY{Z!52U5Q1 z3dkArtif!Yc|%Pv+R2QWQd3DgZKjisp;~6aQD?Jx76Qkt@R_#@&y65Z!ARy)^W==- z6kPwp{}xNQJ)N}7w3<)abB1ab)T1LKM@L6TM~G>gjtSjacBos~WE!YC#hhiZdxM`- z=!n9&IPzQx|0^*N4LBg}jK3iw_Hq9m z@q5L6Irs(oF`*Z8+2KC~J%rVjj>;`9Xfsr2I03pK)5J$BZ;22?-3x=$*@c|>R z0T0412gI2_jJ@ij1{?w(10Db#0}kVF5U2vna6U|kb%A4i1sDb|af~kjzXk4ajOT!d zz%7pPG!Qq9mf#q928gxLra4AfUU9c*lN=*4Ag&@U$}#dN@Y{%YjAMKV_)YLI$M_)d z2)N2I-V1yHyoY0a4-mPxc5{sX8S8+x8h#ImyUV)4G5!`1zcr2R7hvQva6g+r@EfqZ zz%jlG#9C=993v$l&IE0lV`Kq{r-H^_7Qjdv_;tiN4aCz+(>O*ZfdTLs$4C^2A!=hl zW{(`RCDaBvMpPh{MC;=i=>;p<8AF9|FOObCn$R0RTD zdxc(*%2J4~NULa2g4!Sr-ta7^p*ruxhyNRB(I>oOYc#I-lm_@ z5UtaX(7sPUKzp6uK)X(_LHj0MLwkk3f%X!;jP@1!D%uOQOat-_={@Ov_UYhikkXBg zQZPiX1m8gWYVbAW)GZd0(S?b5<>%;kLFT}eY&@vm`4NGPaqqlH2)%bcObG6s2cIyG4HFje zg5rDKf_Do}1&=e1>dpWALhsH0F~(u*jc=6kw)h7bM{>P)<--_nH^$%OuJy@9imUd<0Nib?wif=mx1mE(esAoNi2N?mp;*RbNZ}l&t}~q^LQLs2Bhq} zKTf8K#Z1aFl6Emi^qI3pKKuFhBbh}Wgy_k1S~qQ{^(6wDHWu`3&alONj5!Pz?+{M& zMyqew=@!vjuMpkJ&I#wD^=#U(49DO+l`S|%!7(#<(GY#ku#LQ#+AidC!F$9t;iE}e z*@B4RW+peEEEsqo3E7 - -#include "catalog/index.h" -#include "hnsw.h" -#include "miscadmin.h" -#include "lib/pairingheap.h" -#include "nodes/pg_list.h" -#include "storage/bufmgr.h" -#include "utils/memutils.h" - -#if PG_VERSION_NUM >= 140000 -#include "utils/backend_progress.h" -#elif PG_VERSION_NUM >= 120000 -#include "pgstat.h" -#endif - -#if PG_VERSION_NUM >= 120000 -#include "access/tableam.h" -#include "commands/progress.h" -#else -#define PROGRESS_CREATEIDX_TUPLES_DONE 0 -#endif - -#if PG_VERSION_NUM >= 130000 -#define CALLBACK_ITEM_POINTER ItemPointer tid -#else -#define CALLBACK_ITEM_POINTER HeapTuple hup -#endif - -#if PG_VERSION_NUM >= 120000 -#define UpdateProgress(index, val) pgstat_progress_update_param(index, val) -#else -#define UpdateProgress(index, val) ((void)val) -#endif - -/* - * Create the metapage - */ -static void -CreateMetaPage(HnswBuildState * buildstate) -{ - Relation index = buildstate->index; - ForkNumber forkNum = buildstate->forkNum; - Buffer buf; - Page page; - GenericXLogState *state; - HnswMetaPage metap; - - buf = HnswNewBuffer(index, forkNum); - HnswInitRegisterPage(index, &buf, &page, &state); - - /* Set metapage data */ - metap = HnswPageGetMeta(page); - metap->magicNumber = HNSW_MAGIC_NUMBER; - metap->version = HNSW_VERSION; - metap->dimensions = buildstate->dimensions; - metap->m = buildstate->m; - metap->efConstruction = buildstate->efConstruction; - metap->entryBlkno = InvalidBlockNumber; - metap->entryOffno = InvalidOffsetNumber; - metap->entryLevel = -1; - metap->insertPage = InvalidBlockNumber; - ((PageHeader) page)->pd_lower = - ((char *) metap + sizeof(HnswMetaPageData)) - (char *) page; - - HnswCommitBuffer(buf, state); -} - -/* - * Add a new page - */ -static void -HnswBuildAppendPage(Relation index, Buffer *buf, Page *page, GenericXLogState **state, ForkNumber forkNum) -{ - /* Add a new page */ - Buffer newbuf = HnswNewBuffer(index, forkNum); - - /* Update previous page */ - HnswPageGetOpaque(*page)->nextblkno = BufferGetBlockNumber(newbuf); - - /* Commit */ - MarkBufferDirty(*buf); - GenericXLogFinish(*state); - UnlockReleaseBuffer(*buf); - - /* Can take a while, so ensure we can interrupt */ - /* Needs to be called when no buffer locks are held */ - LockBuffer(newbuf, BUFFER_LOCK_UNLOCK); - CHECK_FOR_INTERRUPTS(); - LockBuffer(newbuf, BUFFER_LOCK_EXCLUSIVE); - - /* Prepare new page */ - *buf = newbuf; - *state = GenericXLogStart(index); - *page = GenericXLogRegisterBuffer(*state, *buf, GENERIC_XLOG_FULL_IMAGE); - HnswInitPage(*buf, *page); -} - -/* - * Create element pages - */ -static void -CreateElementPages(HnswBuildState * buildstate) -{ - Relation index = buildstate->index; - ForkNumber forkNum = buildstate->forkNum; - int dimensions = buildstate->dimensions; - Size etupSize; - Size maxSize; - HnswElementTuple etup; - HnswNeighborTuple ntup; - BlockNumber insertPage; - Buffer buf; - Page page; - GenericXLogState *state; - ListCell *lc; - - /* Calculate sizes */ - maxSize = BLCKSZ - MAXALIGN(SizeOfPageHeaderData) - MAXALIGN(sizeof(HnswPageOpaqueData)); - etupSize = HNSW_ELEMENT_TUPLE_SIZE(dimensions); - - /* Allocate once */ - etup = palloc0(etupSize); - ntup = palloc0(maxSize); - - /* Prepare first page */ - buf = HnswNewBuffer(index, forkNum); - state = GenericXLogStart(index); - page = GenericXLogRegisterBuffer(state, buf, GENERIC_XLOG_FULL_IMAGE); - HnswInitPage(buf, page); - - foreach(lc, buildstate->elements) - { - HnswElement element = lfirst(lc); - Size ntupSize; - Size combinedSize; - - HnswSetElementTuple(etup, element); - - /* Calculate sizes */ - ntupSize = HNSW_NEIGHBOR_TUPLE_SIZE(element->level, buildstate->m); - combinedSize = etupSize + ntupSize + sizeof(ItemIdData); - - /* Keep element and neighbors on the same page if possible */ - if (PageGetFreeSpace(page) < etupSize || (combinedSize <= maxSize && PageGetFreeSpace(page) < combinedSize)) - HnswBuildAppendPage(index, &buf, &page, &state, forkNum); - - /* Calculate offsets */ - element->blkno = BufferGetBlockNumber(buf); - element->offno = OffsetNumberNext(PageGetMaxOffsetNumber(page)); - if (combinedSize <= maxSize) - { - element->neighborPage = element->blkno; - element->neighborOffno = OffsetNumberNext(element->offno); - } - else - { - element->neighborPage = element->blkno + 1; - element->neighborOffno = FirstOffsetNumber; - } - - ItemPointerSet(&etup->neighbortid, element->neighborPage, element->neighborOffno); - - /* Add element */ - if (PageAddItem(page, (Item) etup, etupSize, InvalidOffsetNumber, false, false) != element->offno) - elog(ERROR, "failed to add index item to \"%s\"", RelationGetRelationName(index)); - - /* Add new page if needed */ - if (PageGetFreeSpace(page) < ntupSize) - HnswBuildAppendPage(index, &buf, &page, &state, forkNum); - - /* Add placeholder for neighbors */ - if (PageAddItem(page, (Item) ntup, ntupSize, InvalidOffsetNumber, false, false) != element->neighborOffno) - elog(ERROR, "failed to add index item to \"%s\"", RelationGetRelationName(index)); - } - - insertPage = BufferGetBlockNumber(buf); - - /* Commit */ - MarkBufferDirty(buf); - GenericXLogFinish(state); - UnlockReleaseBuffer(buf); - - HnswUpdateMetaPage(index, HNSW_UPDATE_ENTRY_ALWAYS, buildstate->entryPoint, insertPage, forkNum); - - pfree(etup); - pfree(ntup); -} - -/* - * Create neighbor pages - */ -static void -CreateNeighborPages(HnswBuildState * buildstate) -{ - Relation index = buildstate->index; - ForkNumber forkNum = buildstate->forkNum; - int m = buildstate->m; - ListCell *lc; - HnswNeighborTuple ntup; - - /* Allocate once */ - ntup = palloc0(BLCKSZ); - - foreach(lc, buildstate->elements) - { - HnswElement e = lfirst(lc); - Buffer buf; - Page page; - GenericXLogState *state; - Size ntupSize = HNSW_NEIGHBOR_TUPLE_SIZE(e->level, m); - - /* Can take a while, so ensure we can interrupt */ - /* Needs to be called when no buffer locks are held */ - CHECK_FOR_INTERRUPTS(); - - buf = ReadBufferExtended(index, forkNum, e->neighborPage, RBM_NORMAL, NULL); - LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE); - state = GenericXLogStart(index); - page = GenericXLogRegisterBuffer(state, buf, 0); - - HnswSetNeighborTuple(ntup, e, m); - - if (!PageIndexTupleOverwrite(page, e->neighborOffno, (Item) ntup, ntupSize)) - elog(ERROR, "failed to add index item to \"%s\"", RelationGetRelationName(index)); - - /* Commit */ - MarkBufferDirty(buf); - GenericXLogFinish(state); - UnlockReleaseBuffer(buf); - } - - pfree(ntup); -} - -/* - * Free elements - */ -static void -FreeElements(HnswBuildState * buildstate) -{ - ListCell *lc; - - foreach(lc, buildstate->elements) - HnswFreeElement(lfirst(lc)); - - list_free(buildstate->elements); -} - -/* - * Flush pages - */ -static void -FlushPages(HnswBuildState * buildstate) -{ - CreateMetaPage(buildstate); - CreateElementPages(buildstate); - CreateNeighborPages(buildstate); - - buildstate->flushed = true; - FreeElements(buildstate); -} - -/* - * Insert tuple - */ -static bool -InsertTuple(Relation index, Datum *values, HnswElement element, HnswBuildState * buildstate, HnswElement * dup) -{ - FmgrInfo *procinfo = buildstate->procinfo; - Oid collation = buildstate->collation; - HnswElement entryPoint = buildstate->entryPoint; - int efConstruction = buildstate->efConstruction; - int m = buildstate->m; - - /* Detoast once for all calls */ - Datum value = PointerGetDatum(PG_DETOAST_DATUM(values[0])); - - /* Normalize if needed */ - if (buildstate->normprocinfo != NULL) - { - if (!HnswNormValue(buildstate->normprocinfo, collation, &value, buildstate->normvec)) - return false; - } - - /* Copy value to element so accessible outside of memory context */ - memcpy(element->vec, DatumGetVector(value), VECTOR_SIZE(buildstate->dimensions)); - - /* Insert element in graph */ - HnswInsertElement(element, entryPoint, NULL, procinfo, collation, m, efConstruction, false); - - /* Look for duplicate */ - *dup = HnswFindDuplicate(element); - - /* Update neighbors if needed */ - if (*dup == NULL) - { - for (int lc = element->level; lc >= 0; lc--) - { - int lm = HnswGetLayerM(m, lc); - HnswNeighborArray *neighbors = &element->neighbors[lc]; - - for (int i = 0; i < neighbors->length; i++) - HnswUpdateConnection(element, &neighbors->items[i], lm, lc, NULL, NULL, procinfo, collation); - } - } - - /* Update entry point if needed */ - if (*dup == NULL && (entryPoint == NULL || element->level > entryPoint->level)) - buildstate->entryPoint = element; - - UpdateProgress(PROGRESS_CREATEIDX_TUPLES_DONE, ++buildstate->indtuples); - - return *dup == NULL; -} - -/* - * Callback for table_index_build_scan - */ -static void -BuildCallback(Relation index, CALLBACK_ITEM_POINTER, Datum *values, - bool *isnull, bool tupleIsAlive, void *state) -{ - HnswBuildState *buildstate = (HnswBuildState *) state; - MemoryContext oldCtx; - HnswElement element; - HnswElement dup = NULL; - bool inserted; - -#if PG_VERSION_NUM < 130000 - ItemPointer tid = &hup->t_self; -#endif - - /* Skip nulls */ - if (isnull[0]) - return; - - if (buildstate->indtuples >= buildstate->maxInMemoryElements) - { - if (!buildstate->flushed) - { - ereport(NOTICE, - (errmsg("hnsw graph no longer fits into maintenance_work_mem after " INT64_FORMAT " tuples", (int64) buildstate->indtuples), - errdetail("Building will take significantly more time."), - errhint("Increase maintenance_work_mem to speed up builds."))); - - FlushPages(buildstate); - } - - oldCtx = MemoryContextSwitchTo(buildstate->tmpCtx); - - if (HnswInsertTuple(buildstate->index, values, isnull, tid, buildstate->heap)) - UpdateProgress(PROGRESS_CREATEIDX_TUPLES_DONE, ++buildstate->indtuples); - - /* Reset memory context */ - MemoryContextSwitchTo(oldCtx); - MemoryContextReset(buildstate->tmpCtx); - - return; - } - - /* Allocate necessary memory outside of memory context */ - element = HnswInitElement(tid, buildstate->m, buildstate->ml, buildstate->maxLevel); - element->vec = palloc(VECTOR_SIZE(buildstate->dimensions)); - - /* Use memory context since detoast can allocate */ - oldCtx = MemoryContextSwitchTo(buildstate->tmpCtx); - - /* Insert tuple */ - inserted = InsertTuple(index, values, element, buildstate, &dup); - - /* Reset memory context */ - MemoryContextSwitchTo(oldCtx); - MemoryContextReset(buildstate->tmpCtx); - - /* Add outside memory context */ - if (dup != NULL) - HnswAddHeapTid(dup, tid); - - /* Add to buildstate or free */ - if (inserted) - buildstate->elements = lappend(buildstate->elements, element); - else - HnswFreeElement(element); -} - -/* - * Get the max number of elements that fit into maintenance_work_mem - */ -static double -HnswGetMaxInMemoryElements(int m, double ml, int dimensions) -{ - Size elementSize = sizeof(HnswElementData); - double avgLevel = -log(0.5) * ml; - - elementSize += sizeof(HnswNeighborArray) * (avgLevel + 1); - elementSize += sizeof(HnswCandidate) * (m * (avgLevel + 2)); - elementSize += sizeof(ItemPointerData); - elementSize += VECTOR_SIZE(dimensions); - return (maintenance_work_mem * 1024L) / elementSize; -} - -/* - * Initialize the build state - */ -static void -InitBuildState(HnswBuildState * buildstate, Relation heap, Relation index, IndexInfo *indexInfo, ForkNumber forkNum) -{ - buildstate->heap = heap; - buildstate->index = index; - buildstate->indexInfo = indexInfo; - buildstate->forkNum = forkNum; - - buildstate->m = HnswGetM(index); - buildstate->efConstruction = HnswGetEfConstruction(index); - buildstate->dimensions = TupleDescAttr(index->rd_att, 0)->atttypmod; - - /* Require column to have dimensions to be indexed */ - if (buildstate->dimensions < 0) - elog(ERROR, "column does not have dimensions"); - - if (buildstate->dimensions > HNSW_MAX_DIM) - elog(ERROR, "column cannot have more than %d dimensions for hnsw index", HNSW_MAX_DIM); - - if (buildstate->efConstruction < 2 * buildstate->m) - elog(ERROR, "ef_construction must be greater than or equal to 2 * m"); - - buildstate->reltuples = 0; - buildstate->indtuples = 0; - - /* Get support functions */ - buildstate->procinfo = index_getprocinfo(index, 1, HNSW_DISTANCE_PROC); - buildstate->normprocinfo = HnswOptionalProcInfo(index, HNSW_NORM_PROC); - buildstate->collation = index->rd_indcollation[0]; - - buildstate->elements = NIL; - buildstate->entryPoint = NULL; - buildstate->ml = HnswGetMl(buildstate->m); - buildstate->maxLevel = HnswGetMaxLevel(buildstate->m); - buildstate->maxInMemoryElements = HnswGetMaxInMemoryElements(buildstate->m, buildstate->ml, buildstate->dimensions); - buildstate->flushed = false; - - /* Reuse for each tuple */ - buildstate->normvec = InitVector(buildstate->dimensions); - - buildstate->tmpCtx = AllocSetContextCreate(CurrentMemoryContext, - "Hnsw build temporary context", - ALLOCSET_DEFAULT_SIZES); -} - -/* - * Free resources - */ -static void -FreeBuildState(HnswBuildState * buildstate) -{ - pfree(buildstate->normvec); - MemoryContextDelete(buildstate->tmpCtx); -} - -/* - * Build graph - */ -static void -BuildGraph(HnswBuildState * buildstate, ForkNumber forkNum) -{ - UpdateProgress(PROGRESS_CREATEIDX_SUBPHASE, PROGRESS_HNSW_PHASE_LOAD); - -#if PG_VERSION_NUM >= 120000 - buildstate->reltuples = table_index_build_scan(buildstate->heap, buildstate->index, buildstate->indexInfo, - true, true, BuildCallback, (void *) buildstate, NULL); -#else - buildstate->reltuples = IndexBuildHeapScan(buildstate->heap, buildstate->index, buildstate->indexInfo, - true, BuildCallback, (void *) buildstate, NULL); -#endif -} - -/* - * Build the index - */ -static void -BuildIndex(Relation heap, Relation index, IndexInfo *indexInfo, - HnswBuildState * buildstate, ForkNumber forkNum) -{ - InitBuildState(buildstate, heap, index, indexInfo, forkNum); - - if (buildstate->heap != NULL) - BuildGraph(buildstate, forkNum); - - if (!buildstate->flushed) - FlushPages(buildstate); - - FreeBuildState(buildstate); -} - -/* - * Build the index for a logged table - */ -IndexBuildResult * -hnswbuild(Relation heap, Relation index, IndexInfo *indexInfo) -{ - IndexBuildResult *result; - HnswBuildState buildstate; - - BuildIndex(heap, index, indexInfo, &buildstate, MAIN_FORKNUM); - - result = (IndexBuildResult *) palloc(sizeof(IndexBuildResult)); - result->heap_tuples = buildstate.reltuples; - result->index_tuples = buildstate.indtuples; - - return result; -} - -/* - * Build the index for an unlogged table - */ -void -hnswbuildempty(Relation index) -{ - IndexInfo *indexInfo = BuildIndexInfo(index); - HnswBuildState buildstate; - - BuildIndex(NULL, index, indexInfo, &buildstate, INIT_FORKNUM); -} diff --git a/packages/server/postgres/extensions/pgvector/src/hnswbuild.o b/packages/server/postgres/extensions/pgvector/src/hnswbuild.o deleted file mode 100644 index 35d3a7ffb30d1c15ef6eaf9af6f10d62ec8e0e04..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9192 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 - -#include "hnsw.h" -#include "storage/bufmgr.h" -#include "storage/lmgr.h" -#include "utils/memutils.h" - -/* - * Get the insert page - */ -static BlockNumber -GetInsertPage(Relation index) -{ - Buffer buf; - Page page; - HnswMetaPage metap; - BlockNumber insertPage; - - buf = ReadBuffer(index, HNSW_METAPAGE_BLKNO); - LockBuffer(buf, BUFFER_LOCK_SHARE); - page = BufferGetPage(buf); - metap = HnswPageGetMeta(page); - - insertPage = metap->insertPage; - - UnlockReleaseBuffer(buf); - - return insertPage; -} - -/* - * Check for a free offset - */ -static bool -HnswFreeOffset(Relation index, Buffer buf, Page page, HnswElement element, Size ntupSize, Buffer *nbuf, Page *npage, OffsetNumber *freeOffno, OffsetNumber *freeNeighborOffno, BlockNumber *newInsertPage) -{ - OffsetNumber offno; - OffsetNumber maxoffno = PageGetMaxOffsetNumber(page); - - for (offno = FirstOffsetNumber; offno <= maxoffno; offno = OffsetNumberNext(offno)) - { - HnswElementTuple etup = (HnswElementTuple) PageGetItem(page, PageGetItemId(page, offno)); - - /* Skip neighbor tuples */ - if (!HnswIsElementTuple(etup)) - continue; - - if (etup->deleted) - { - BlockNumber elementPage = BufferGetBlockNumber(buf); - BlockNumber neighborPage = ItemPointerGetBlockNumber(&etup->neighbortid); - OffsetNumber neighborOffno = ItemPointerGetOffsetNumber(&etup->neighbortid); - ItemId itemid; - - if (!BlockNumberIsValid(*newInsertPage)) - *newInsertPage = elementPage; - - if (neighborPage == elementPage) - { - *nbuf = buf; - *npage = page; - } - else - { - *nbuf = ReadBuffer(index, neighborPage); - LockBuffer(*nbuf, BUFFER_LOCK_EXCLUSIVE); - - /* Skip WAL for now */ - *npage = BufferGetPage(*nbuf); - } - - itemid = PageGetItemId(*npage, neighborOffno); - - /* Check for space on neighbor tuple page */ - if (PageGetFreeSpace(*npage) + ItemIdGetLength(itemid) - sizeof(ItemIdData) >= ntupSize) - { - *freeOffno = offno; - *freeNeighborOffno = neighborOffno; - return true; - } - else if (*nbuf != buf) - UnlockReleaseBuffer(*nbuf); - } - } - - return false; -} - -/* - * Add a new page - */ -static void -HnswInsertAppendPage(Relation index, Buffer *nbuf, Page *npage, GenericXLogState *state, Page page) -{ - /* Add a new page */ - LockRelationForExtension(index, ExclusiveLock); - *nbuf = HnswNewBuffer(index, MAIN_FORKNUM); - UnlockRelationForExtension(index, ExclusiveLock); - - /* Init new page */ - *npage = GenericXLogRegisterBuffer(state, *nbuf, GENERIC_XLOG_FULL_IMAGE); - HnswInitPage(*nbuf, *npage); - - /* Update previous buffer */ - HnswPageGetOpaque(page)->nextblkno = BufferGetBlockNumber(*nbuf); -} - -/* - * Add to element and neighbor pages - */ -static void -WriteNewElementPages(Relation index, HnswElement e, int m, BlockNumber insertPage, BlockNumber *updatedInsertPage) -{ - Buffer buf; - Page page; - GenericXLogState *state; - Size etupSize; - Size ntupSize; - Size combinedSize; - Size maxSize; - Size minCombinedSize; - HnswElementTuple etup; - BlockNumber currentPage = insertPage; - int dimensions = e->vec->dim; - HnswNeighborTuple ntup; - Buffer nbuf; - Page npage; - OffsetNumber freeOffno = InvalidOffsetNumber; - OffsetNumber freeNeighborOffno = InvalidOffsetNumber; - BlockNumber newInsertPage = InvalidBlockNumber; - - /* Calculate sizes */ - etupSize = HNSW_ELEMENT_TUPLE_SIZE(dimensions); - ntupSize = HNSW_NEIGHBOR_TUPLE_SIZE(e->level, m); - combinedSize = etupSize + ntupSize + sizeof(ItemIdData); - maxSize = BLCKSZ - MAXALIGN(SizeOfPageHeaderData) - MAXALIGN(sizeof(HnswPageOpaqueData)); - minCombinedSize = etupSize + HNSW_NEIGHBOR_TUPLE_SIZE(0, m) + sizeof(ItemIdData); - - /* Prepare element tuple */ - etup = palloc0(etupSize); - HnswSetElementTuple(etup, e); - - /* Prepare neighbor tuple */ - ntup = palloc0(ntupSize); - HnswSetNeighborTuple(ntup, e, m); - - /* Find a page (or two if needed) to insert the tuples */ - for (;;) - { - buf = ReadBuffer(index, currentPage); - LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE); - - state = GenericXLogStart(index); - page = GenericXLogRegisterBuffer(state, buf, 0); - - /* Keep track of first page where element at level 0 can fit */ - if (!BlockNumberIsValid(newInsertPage) && PageGetFreeSpace(page) >= minCombinedSize) - newInsertPage = currentPage; - - /* First, try the fastest path */ - /* Space for both tuples on the current page */ - /* This can split existing tuples in rare cases */ - if (PageGetFreeSpace(page) >= combinedSize) - { - nbuf = buf; - npage = page; - break; - } - - /* Next, try space from a deleted element */ - if (HnswFreeOffset(index, buf, page, e, ntupSize, &nbuf, &npage, &freeOffno, &freeNeighborOffno, &newInsertPage)) - { - if (nbuf != buf) - npage = GenericXLogRegisterBuffer(state, nbuf, 0); - - break; - } - - /* Finally, try space for element only if last page */ - /* Skip if both tuples can fit on the same page */ - if (combinedSize > maxSize && PageGetFreeSpace(page) >= etupSize && !BlockNumberIsValid(HnswPageGetOpaque(page)->nextblkno)) - { - HnswInsertAppendPage(index, &nbuf, &npage, state, page); - break; - } - - currentPage = HnswPageGetOpaque(page)->nextblkno; - - if (BlockNumberIsValid(currentPage)) - { - /* Move to next page */ - GenericXLogAbort(state); - UnlockReleaseBuffer(buf); - } - else - { - Buffer newbuf; - Page newpage; - - HnswInsertAppendPage(index, &newbuf, &newpage, state, page); - - /* Commit */ - MarkBufferDirty(newbuf); - MarkBufferDirty(buf); - GenericXLogFinish(state); - - /* Unlock previous buffer */ - UnlockReleaseBuffer(buf); - - /* Prepare new buffer */ - state = GenericXLogStart(index); - buf = newbuf; - page = GenericXLogRegisterBuffer(state, buf, 0); - - /* Create new page for neighbors if needed */ - if (PageGetFreeSpace(page) < combinedSize) - HnswInsertAppendPage(index, &nbuf, &npage, state, page); - else - { - nbuf = buf; - npage = page; - } - - break; - } - } - - e->blkno = BufferGetBlockNumber(buf); - e->neighborPage = BufferGetBlockNumber(nbuf); - - /* Added tuple to new page if newInsertPage is not set */ - /* So can set to neighbor page instead of element page */ - if (!BlockNumberIsValid(newInsertPage)) - newInsertPage = e->neighborPage; - - if (OffsetNumberIsValid(freeOffno)) - { - e->offno = freeOffno; - e->neighborOffno = freeNeighborOffno; - } - else - { - e->offno = OffsetNumberNext(PageGetMaxOffsetNumber(page)); - if (nbuf == buf) - e->neighborOffno = OffsetNumberNext(e->offno); - else - e->neighborOffno = FirstOffsetNumber; - } - - ItemPointerSet(&etup->neighbortid, e->neighborPage, e->neighborOffno); - - /* Add element and neighbors */ - if (OffsetNumberIsValid(freeOffno)) - { - if (!PageIndexTupleOverwrite(page, e->offno, (Item) etup, etupSize)) - elog(ERROR, "failed to add index item to \"%s\"", RelationGetRelationName(index)); - - if (!PageIndexTupleOverwrite(npage, e->neighborOffno, (Item) ntup, ntupSize)) - elog(ERROR, "failed to add index item to \"%s\"", RelationGetRelationName(index)); - } - else - { - if (PageAddItem(page, (Item) etup, etupSize, InvalidOffsetNumber, false, false) != e->offno) - elog(ERROR, "failed to add index item to \"%s\"", RelationGetRelationName(index)); - - if (PageAddItem(npage, (Item) ntup, ntupSize, InvalidOffsetNumber, false, false) != e->neighborOffno) - elog(ERROR, "failed to add index item to \"%s\"", RelationGetRelationName(index)); - } - - /* Commit */ - MarkBufferDirty(buf); - if (nbuf != buf) - MarkBufferDirty(nbuf); - GenericXLogFinish(state); - UnlockReleaseBuffer(buf); - if (nbuf != buf) - UnlockReleaseBuffer(nbuf); - - /* Update the insert page */ - if (BlockNumberIsValid(newInsertPage) && newInsertPage != insertPage) - *updatedInsertPage = newInsertPage; -} - -/* - * Check if connection already exists - */ -static bool -ConnectionExists(HnswElement e, HnswNeighborTuple ntup, int startIdx, int lm) -{ - for (int i = 0; i < lm; i++) - { - ItemPointer indextid = &ntup->indextids[startIdx + i]; - - if (!ItemPointerIsValid(indextid)) - break; - - if (ItemPointerGetBlockNumber(indextid) == e->blkno && ItemPointerGetOffsetNumber(indextid) == e->offno) - return true; - } - - return false; -} - -/* - * Update neighbors - */ -void -HnswUpdateNeighborPages(Relation index, FmgrInfo *procinfo, Oid collation, HnswElement e, int m, bool checkExisting) -{ - for (int lc = e->level; lc >= 0; lc--) - { - int lm = HnswGetLayerM(m, lc); - HnswNeighborArray *neighbors = &e->neighbors[lc]; - - for (int i = 0; i < neighbors->length; i++) - { - HnswCandidate *hc = &neighbors->items[i]; - Buffer buf; - Page page; - GenericXLogState *state; - ItemId itemid; - HnswNeighborTuple ntup; - Size ntupSize; - int idx = -1; - int startIdx; - OffsetNumber offno = hc->element->neighborOffno; - - /* Get latest neighbors since they may have changed */ - /* Do not lock yet since selecting neighbors can take time */ - HnswLoadNeighbors(hc->element, index); - - /* - * Could improve performance for vacuuming by checking neighbors - * against list of elements being deleted to find index. It's - * important to exclude already deleted elements for this since - * they can be replaced at any time. - */ - - /* Select neighbors */ - HnswUpdateConnection(e, hc, lm, lc, &idx, index, procinfo, collation); - - /* New element was not selected as a neighbor */ - if (idx == -1) - continue; - - /* Register page */ - buf = ReadBuffer(index, hc->element->neighborPage); - LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE); - state = GenericXLogStart(index); - page = GenericXLogRegisterBuffer(state, buf, 0); - - /* Get tuple */ - itemid = PageGetItemId(page, offno); - ntup = (HnswNeighborTuple) PageGetItem(page, itemid); - ntupSize = ItemIdGetLength(itemid); - - /* Calculate index for update */ - startIdx = (hc->element->level - lc) * m; - - /* Check for existing connection */ - if (checkExisting && ConnectionExists(e, ntup, startIdx, lm)) - idx = -1; - else if (idx == -2) - { - /* Find free offset if still exists */ - /* TODO Retry updating connections if not */ - for (int j = 0; j < lm; j++) - { - if (!ItemPointerIsValid(&ntup->indextids[startIdx + j])) - { - idx = startIdx + j; - break; - } - } - } - else - idx += startIdx; - - /* Make robust to issues */ - if (idx >= 0 && idx < ntup->count) - { - ItemPointer indextid = &ntup->indextids[idx]; - - /* Update neighbor */ - ItemPointerSet(indextid, e->blkno, e->offno); - - /* Overwrite tuple */ - if (!PageIndexTupleOverwrite(page, offno, (Item) ntup, ntupSize)) - elog(ERROR, "failed to add index item to \"%s\"", RelationGetRelationName(index)); - - /* Commit */ - MarkBufferDirty(buf); - GenericXLogFinish(state); - } - else - GenericXLogAbort(state); - - UnlockReleaseBuffer(buf); - } - } -} - -/* - * Add a heap TID to an existing element - */ -static bool -HnswAddDuplicate(Relation index, HnswElement element, HnswElement dup) -{ - Buffer buf; - Page page; - GenericXLogState *state; - Size etupSize = HNSW_ELEMENT_TUPLE_SIZE(dup->vec->dim); - HnswElementTuple etup; - int i; - - /* Read page */ - buf = ReadBuffer(index, dup->blkno); - LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE); - state = GenericXLogStart(index); - page = GenericXLogRegisterBuffer(state, buf, 0); - - /* Find space */ - etup = (HnswElementTuple) PageGetItem(page, PageGetItemId(page, dup->offno)); - for (i = 0; i < HNSW_HEAPTIDS; i++) - { - if (!ItemPointerIsValid(&etup->heaptids[i])) - break; - } - - /* Either being deleted or we lost our chance to another backend */ - if (i == 0 || i == HNSW_HEAPTIDS) - { - GenericXLogAbort(state); - UnlockReleaseBuffer(buf); - return false; - } - - /* Add heap TID */ - etup->heaptids[i] = *((ItemPointer) linitial(element->heaptids)); - - /* Overwrite tuple */ - if (!PageIndexTupleOverwrite(page, dup->offno, (Item) etup, etupSize)) - elog(ERROR, "failed to add index item to \"%s\"", RelationGetRelationName(index)); - - /* Commit */ - MarkBufferDirty(buf); - GenericXLogFinish(state); - UnlockReleaseBuffer(buf); - - return true; -} - -/* - * Write changes to disk - */ -static void -WriteElement(Relation index, FmgrInfo *procinfo, Oid collation, HnswElement element, int m, int efConstruction, HnswElement dup, HnswElement entryPoint) -{ - BlockNumber newInsertPage = InvalidBlockNumber; - - /* Try to add to existing page */ - if (dup != NULL) - { - if (HnswAddDuplicate(index, element, dup)) - return; - } - - /* Write element and neighbor tuples */ - WriteNewElementPages(index, element, m, GetInsertPage(index), &newInsertPage); - - /* Update insert page if needed */ - if (BlockNumberIsValid(newInsertPage)) - HnswUpdateMetaPage(index, 0, NULL, newInsertPage, MAIN_FORKNUM); - - /* Update neighbors */ - HnswUpdateNeighborPages(index, procinfo, collation, element, m, false); - - /* Update metapage if needed */ - if (entryPoint == NULL || element->level > entryPoint->level) - HnswUpdateMetaPage(index, HNSW_UPDATE_ENTRY_GREATER, element, InvalidBlockNumber, MAIN_FORKNUM); -} - -/* - * Insert a tuple into the index - */ -bool -HnswInsertTuple(Relation index, Datum *values, bool *isnull, ItemPointer heap_tid, Relation heapRel) -{ - Datum value; - FmgrInfo *normprocinfo; - HnswElement entryPoint; - HnswElement element; - int m = HnswGetM(index); - int efConstruction = HnswGetEfConstruction(index); - double ml = HnswGetMl(m); - FmgrInfo *procinfo = index_getprocinfo(index, 1, HNSW_DISTANCE_PROC); - Oid collation = index->rd_indcollation[0]; - HnswElement dup; - LOCKMODE lockmode = ShareLock; - - /* Detoast once for all calls */ - value = PointerGetDatum(PG_DETOAST_DATUM(values[0])); - - /* Normalize if needed */ - normprocinfo = HnswOptionalProcInfo(index, HNSW_NORM_PROC); - if (normprocinfo != NULL) - { - if (!HnswNormValue(normprocinfo, collation, &value, NULL)) - return false; - } - - /* Create an element */ - element = HnswInitElement(heap_tid, m, ml, HnswGetMaxLevel(m)); - element->vec = DatumGetVector(value); - - /* - * Get a shared lock. This allows vacuum to ensure no in-flight inserts - * before repairing graph. Use a page lock so it does not interfere with - * buffer lock (or reads when vacuuming). - */ - LockPage(index, HNSW_UPDATE_LOCK, lockmode); - - /* Get entry point */ - entryPoint = HnswGetEntryPoint(index); - - /* Prevent concurrent inserts when likely updating entry point */ - if (entryPoint == NULL || element->level > entryPoint->level) - { - /* Release shared lock */ - UnlockPage(index, HNSW_UPDATE_LOCK, lockmode); - - /* Get exclusive lock */ - lockmode = ExclusiveLock; - LockPage(index, HNSW_UPDATE_LOCK, lockmode); - - /* Get latest entry point after lock is acquired */ - entryPoint = HnswGetEntryPoint(index); - } - - /* Insert element in graph */ - HnswInsertElement(element, entryPoint, index, procinfo, collation, m, efConstruction, false); - - /* Look for duplicate */ - dup = HnswFindDuplicate(element); - - /* Write to disk */ - WriteElement(index, procinfo, collation, element, m, efConstruction, dup, entryPoint); - - /* Release lock */ - UnlockPage(index, HNSW_UPDATE_LOCK, lockmode); - - return true; -} - -/* - * Insert a tuple into the index - */ -bool -hnswinsert(Relation index, Datum *values, bool *isnull, ItemPointer heap_tid, - Relation heap, IndexUniqueCheck checkUnique -#if PG_VERSION_NUM >= 140000 - ,bool indexUnchanged -#endif - ,IndexInfo *indexInfo -) -{ - MemoryContext oldCtx; - MemoryContext insertCtx; - - /* Skip nulls */ - if (isnull[0]) - return false; - - /* Create memory context */ - insertCtx = AllocSetContextCreate(CurrentMemoryContext, - "Hnsw insert temporary context", - ALLOCSET_DEFAULT_SIZES); - oldCtx = MemoryContextSwitchTo(insertCtx); - - /* Insert tuple */ - HnswInsertTuple(index, values, isnull, heap_tid, heap); - - /* Delete memory context */ - MemoryContextSwitchTo(oldCtx); - MemoryContextDelete(insertCtx); - - return false; -} diff --git a/packages/server/postgres/extensions/pgvector/src/hnswinsert.o b/packages/server/postgres/extensions/pgvector/src/hnswinsert.o deleted file mode 100644 index a9e60a05847d711fb15a5be0468e0ce1f36799d9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8560 zcmb`Me{fS*cE_*u41O{OY>@E}*;s~bkcbU&Fc=3^p7|Atm|sRLY^PKx_QO^sOGcI( z)Ie6d&>a(dTfEC3Ovnr-n?EdRfykOC07xj zxDDp;q@=2&$(cx|pP-iTcp~zVh$;Xck9*60_cyZkWS-BT$et08A1q+(mz0#jVg&8- zcpBOwVPCLGP0-uZ?EHdp%Roi(g zm$%JUz?g|E&M^5B(vZbFmd(Wjez;cPN3OIY^9)FZNKZsZ{|T5ZW~?KFu{n6&g&$p{ zPe1C*h4{UI-~aVW9XH;b3T4C}HO-1ocxJ~R?#qmi?-b+HZaPnEQr>qQ2WgVc!?X`@?)`@;{bI{ZvPCgVbL-i&Z9IvjgP~Wm11a zEkA`e*P@-=7jE-Qqz%C@qna*?mQFEG>NnL$KQl5@=SkSGmq|5a3#I<73|8gIWQYB; znR^UF1BLCF6!SBn0z4RbXmcF#+dUw7 z{zV(B)K%f;=u)v|DrACA7If#JOg75SMNH3&PahY$#}UsD1w$W=A&u!IsMYlV&sO2J z#gr~e2NzR%8OC^tI?j)f-~0*U%OLd+V2nwx=Xl`+>1i=Cz}Urie_^;uZ4waEq?2&6 zDwY#m%nF(p+kPnh{G)iR-ewwnTQsqorPIRw(b8DGvGQb50x>#-_@puusH^Ar4<_iD zVa`w;2Ke!=>Wk(x)i(xRaj8^e!rUx1nEay#=GN;cQI6(^mOiYrLpHFMn0p`V74(fQ zW0ONd7Us=D%$uUV5wz(G`1c3Qr_9IY3%I{4;p1F(k=6*&j5%qFMGXZskN7;MzSa6j zOQSXp!In0U1`!XUC@OPFROXV{>B^xvVu!!N8uAh5k`u98TF5@cOUa#;Cq+L-T4^Qw za3BM6nG}7CWEcA|MskG|-A1w(^NHjdDVjxc6y-_Yg7O07Zj=`w??!o&<0wz^0hA~C z3d)o0LwS<_4dqF0M|mTwBLBuS&td#{U)a!(cJvR{XR`~O2l_y~Q_UyAEHDnz+W8SU z8yp2`Eif4n3!oi*>*1T9ZaZM;-gN)i&pt-Jtt{*8=t`_DSo7>A&gFQS@4c{fcMil`sLv&KBwrtK<%V;Kp&rpYO-7SI6f&} zf0&82l+pa+^(B`|lch|&b9TwOKh@Go*`hn|W;3^F!MIGHtM{>|tE5TW)baY?8^t@DlsT)~e1Y5i1?oW`MMZz}C3-*gEab8o^10cp(wYT7 zs`)yUP}}%htd~U%teeIf`!;jOY?fjrVOEkao1hKQ%h&2Q6 z;465C^kzvl9bc!aG+VttmZIl>B1H{&*HF8E{gxE9u(L%|sOurxzkxCTL9|1g&*|^V zVguqsjU6RUut%UcS%f&Dcu&t871l3bgm}@`@xiP-z6NY|c8a;|=IA=~hrx8`AJHG@ zghKzp$rS43`+`BVm(sk*BfkQ$O?{I7ex^0)5Yo-)lRW&$chW@}GxU=rIq((A zr00#pCSRZP`?UALkfM{X&M{6JFXp#hN(fpyU(dCe)cRKIGg=pipsTHc6l3HI#n1!9 z-$tx6>3*d9Nc}xCkM=pmkNu-|Q$J~YA+67}e$afSIMLF0-{kjA!l%Ojp?{vJTkUhL zZ~nJ+YyI&EYmsE3_Y)siX({Wcn9E1JA%=I0h~ZL$_3#q(hkG51?%lwm;?bNv!waQJ zXN*OiOQnA232=iHb#}05?K&w+^YJ$367}Z;>=m)z_2V72rWQIBYs1(B-bQRuzI7H~ z1L;{mZ|%qUytN@tOsh{8xlljFoS1jA$f^1<1V3!5A2!tw8~h+2hT#MD0$AU9oH^ke z)(pSq(~nFv#*6xf;LkApnV}0`N%y=+Iy{!B?jO+_q|SYusmxlxX=$47_#BE`-lr+7 z84}i+B=&wOqv%hN4Rv3E`1|BG#$5AJi7Tv=!|?wB#^yGjN3d4TQrnPRCQS}v@6o$N zsu{(z9s6Bn@1feH_s1~Gpe+m1Z5k*Yds3y(@jU&;4(!jiW1m)s{o1S8w{64T&J`bB z>TJP&PTk*WXMv}4!gCqr4X|UxK2L~GUuE4+hI5LMt^N}Gy>aaKXrAR`J^Bj!wgm0* z@m$7OH?><}C+ahDdXzPQ;&wvOq4xtnH{4eDFcU^f<7WV-_`V5sZbaR+@cmjZ?L|{p z+0F5t2KWJAjoC+};}SHJ~ip7e+NZu(#J;5BB1m>FfatoqUQWk*Ald2R zbi4zSoiiZWInC)f0g{~nNOl@QvU8Br@n;~}+0W^4gJfqHNOtNt9qU1|^BhQaib1kd z!09l9WG9Eykqwd^5hObqoQ`pH2HCjcC2C3c~7*w(|2*MZp}J{4EcB)~_kit7kc1Bma6N;}Sg)Q&EY+OZj#dT8hc9K!~Jy08QkxcjN_gxxr_U) zk_3i>F)zkx9vY@yb}nbQu|Cn|Qf2M^_;=`c=x1D>$McP9zV?2+s^&-3{NJf^xhiAh z&*oul{#^aPLaSPThAKY~UnpPuKZcd^A+J{R1vP)GDt`<&DF3Ib{5w38d{mW-)bg4= z?1k7o{IvgTdr5(S6`>jpZ9h7qRxJaPX2eD{yts) zySn=K>GZej^zm(fMth+?L;jU6ze`u%uan=_=^xS6U#ruTAQx>xtSA_wKL zuVH^(sHqnF`v~)Va{^%LxqBjB%B9WyD{^E@FcOaL3i*O+l1n)Zs2hB)l(Z)P=p$2b&d$=(WYVa%8ou_E1r6eP&|6tAtkNgv(7OA z&kFokmAi}c39olXtLx<^Ut2^DYguqw?N#R48zi^u<$&yMlQo5U+1sc+bE3DD_U&kI zc}+%T%E8OiBu84qp$1>@P>9#Rvo+!i1-*e?NOe6B8R@H7AkxzM5~d4<^op0Bp#}x0RcdOLTopaque; - Relation index = scan->indexRelation; - FmgrInfo *procinfo = so->procinfo; - Oid collation = so->collation; - List *ep; - List *w; - HnswElement entryPoint = HnswGetEntryPoint(index); - - if (entryPoint == NULL) - return NIL; - - ep = list_make1(HnswEntryCandidate(entryPoint, q, index, procinfo, collation, false)); - - for (int lc = entryPoint->level; lc >= 1; lc--) - { - w = HnswSearchLayer(q, ep, 1, lc, index, procinfo, collation, false, NULL); - ep = w; - } - - return HnswSearchLayer(q, ep, hnsw_ef_search, 0, index, procinfo, collation, 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; -} - -/* - * Prepare for an index scan - */ -IndexScanDesc -hnswbeginscan(Relation index, int nkeys, int norderbys) -{ - IndexScanDesc scan; - HnswScanOpaque so; - - scan = RelationGetIndexScan(index, nkeys, norderbys); - - so = (HnswScanOpaque) palloc(sizeof(HnswScanOpaqueData)); - so->buf = InvalidBuffer; - so->first = true; - so->tmpCtx = AllocSetContextCreate(CurrentMemoryContext, - "Hnsw scan temporary context", - ALLOCSET_DEFAULT_SIZES); - - /* Set support functions */ - so->procinfo = index_getprocinfo(index, 1, HNSW_DISTANCE_PROC); - so->normprocinfo = HnswOptionalProcInfo(index, HNSW_NORM_PROC); - so->collation = index->rd_indcollation[0]; - - scan->opaque = so; - - return scan; -} - -/* - * Start or restart an index scan - */ -void -hnswrescan(IndexScanDesc scan, ScanKey keys, int nkeys, ScanKey orderbys, int norderbys) -{ - HnswScanOpaque so = (HnswScanOpaque) scan->opaque; - - so->first = true; - MemoryContextReset(so->tmpCtx); - - if (keys && scan->numberOfKeys > 0) - memmove(scan->keyData, keys, scan->numberOfKeys * sizeof(ScanKeyData)); - - if (orderbys && scan->numberOfOrderBys > 0) - memmove(scan->orderByData, orderbys, scan->numberOfOrderBys * sizeof(ScanKeyData)); -} - -/* - * Fetch the next tuple in the given scan - */ -bool -hnswgettuple(IndexScanDesc scan, ScanDirection dir) -{ - HnswScanOpaque so = (HnswScanOpaque) scan->opaque; - MemoryContext oldCtx = MemoryContextSwitchTo(so->tmpCtx); - - /* - * Index can be used to scan backward, but Postgres doesn't support - * backward scan on operators - */ - Assert(ScanDirectionIsForward(dir)); - - if (so->first) - { - Datum value; - - /* Count index scan for stats */ - pgstat_count_index_scan(scan->indexRelation); - - /* Safety check */ - if (scan->orderByData == NULL) - elog(ERROR, "cannot scan hnsw index without order"); - - if (scan->orderByData->sk_flags & SK_ISNULL) - value = PointerGetDatum(InitVector(GetDimensions(scan->indexRelation))); - else - { - value = scan->orderByData->sk_argument; - - /* Value should not be compressed or toasted */ - Assert(!VARATT_IS_COMPRESSED(DatumGetPointer(value))); - Assert(!VARATT_IS_EXTENDED(DatumGetPointer(value))); - - /* Fine if normalization fails */ - if (so->normprocinfo != NULL) - HnswNormValue(so->normprocinfo, so->collation, &value, NULL); - } - - /* - * Get a shared lock. This allows vacuum to ensure no in-flight scans - * before marking tuples as deleted. - */ - LockPage(scan->indexRelation, HNSW_SCAN_LOCK, ShareLock); - - so->w = GetScanItems(scan, value); - - /* Release shared lock */ - UnlockPage(scan->indexRelation, HNSW_SCAN_LOCK, ShareLock); - - so->first = false; - } - - while (list_length(so->w) > 0) - { - HnswCandidate *hc = llast(so->w); - ItemPointer tid; - BlockNumber indexblkno; - - /* Move to next element if no valid heap tids */ - if (list_length(hc->element->heaptids) == 0) - { - so->w = list_delete_last(so->w); - continue; - } - - tid = llast(hc->element->heaptids); - indexblkno = hc->element->blkno; - - hc->element->heaptids = list_delete_last(hc->element->heaptids); - - MemoryContextSwitchTo(oldCtx); - -#if PG_VERSION_NUM >= 120000 - scan->xs_heaptid = *tid; -#else - scan->xs_ctup.t_self = *tid; -#endif - - if (BufferIsValid(so->buf)) - ReleaseBuffer(so->buf); - - /* - * An index scan must maintain a pin on the index page holding the - * item last returned by amgettuple - * - * https://www.postgresql.org/docs/current/index-locking.html - */ - so->buf = ReadBuffer(scan->indexRelation, indexblkno); - - scan->xs_recheckorderby = false; - return true; - } - - MemoryContextSwitchTo(oldCtx); - return false; -} - -/* - * End a scan and release resources - */ -void -hnswendscan(IndexScanDesc scan) -{ - HnswScanOpaque so = (HnswScanOpaque) scan->opaque; - - /* Release pin */ - if (BufferIsValid(so->buf)) - ReleaseBuffer(so->buf); - - MemoryContextDelete(so->tmpCtx); - - pfree(so); - scan->opaque = NULL; -} diff --git a/packages/server/postgres/extensions/pgvector/src/hnswscan.o b/packages/server/postgres/extensions/pgvector/src/hnswscan.o deleted file mode 100644 index 518eeb55b04be87112f1cd6a008de9f177ec2f76..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3600 zcmb7HTWlQF8UA->*B;ZlWGm0Hz+7sYYEf6k1v z9_NK4pZ(5%|L6bDoHPF5+kao*Mie6q{z+h7ktILcv#X(+wh4ub8m0PGdh?t$2Q(DZZ5GI~us+nk7q2fm>{bDd)KjYiX zY@y1Mx@*1}HU}`-&&&?V>k8?eR7rniRN$*TcMp|c+sE>T`PIp8A6|1}rEkZS(uz=#UmSTRP`et`F6P&6FY;;GYE*7V zU4E__m8UU}t5j)Gjn4EoYERc7&KE}I9MM7@@@-AfbX^_lfcWWK!HXH4eKC4iYc?uZ zYpJ5|GEfs*Xuj7de|nInRn(cE5&j#Vg|AppORfdinq>{Km50L~ai4x!Lyxt-WW1ss zt~-zI*-$O}QfMQa}4(T>%jLnwa9ayQSrdvz1^tnX}w`L z6Q_k0=)4f4_BGUJ2{yb>K2sZE(=h)9 zZ2tk<+A7<|T6*C(*Vl%^{1xn*&=z$WyqO)&iA~S&h)SJ0p5xsZ{f=PwG~%yL{t&%9 zhMxAKw>{`_cj=nbRYK3Fc@J`3*jMrT+JY80${P08y~w3bGb)FlO0Sd@^c}Un2>F(T zQU2BSwf3#(?~fDo*o&V$ztX;!>!kVhor5k80PGE_TvUwmAGa72nLp7)`Z+2iud+eZ z&hfbRSAw1(cKPf9rBg?~e_LC-jpfTHeviF}ceh8rw++~P#hM%T#VzuE2>W^#@7L;N z7oPJy|GiqBe5bUk()5f{9K=5I_Z9q3VNSFEaE)VUsvA7BUiMBW{jbOE!fCybGHu~M*uU+D>4!j$B$d&E@7J<;Oc{V^JKs`W! zFRxXM-niP{aYR!VmCH(8T~sftOKMzQR#(((z=gzx#9N74WmcQhYLz#&8T?LZuWIZ7 zBZIo^K%j7N{yV@6zbA&IJRa_wpD>L%gg)~)63`O?{ZIH}`MH2T7RW64%hu;@5 z=-*NmzbT-@ea!t~dARTE1A115ClfUO*2nW$Rnxq#rukd1n&xkiYI-&z|5rqQK0=?3 z(EJTk?eEP9&9$kP|27hj-^*(GUm|pCME~uGT#5Lfi^z)+`+*3(KVpAAB43R7;{zKi z>*JA#{u>dxE#jZw&uV)TJ>#xx+1^jBoa0XP1V^vPW(uAdv9gwDiL6=hNbpJhBg;Ex zd+x-5ld-|tU^8eHEb#iBl$q@wA04&a?yQqK<*Qn5fkYLvKv~g-?`sl$cE)?$N_h^} z9O0y8x~X)(If0{{eVvk4Bz|VIw?SlN7OarDi8Mz-k$+bRTm^a8NoDL&NBTdJ_cD%c zW(UCS^SOLuJZFt%Y`#HAFzH%1)|NdY3B)ql7B%SOd+4OE?$;@2p0W;!OfH`#VYzOu zFeXqR%S9X#U0Ec4((;1R_PCbmS$*qb=#OUXOd%~p3u{yq{2GwRS-G6^q9qf5)Nyl< zo7r)Y8_M46_ekpaz3DOSk<18UNQbC^ybQBKh|zI7B}5xP<2pa6?0`823$D0a)hEI=L#$wM3Cwy|Lwq|kYQ=>IJ&@=gE% diff --git a/packages/server/postgres/extensions/pgvector/src/hnswutils.c b/packages/server/postgres/extensions/pgvector/src/hnswutils.c deleted file mode 100644 index 8e6f2a9cf6d..00000000000 --- a/packages/server/postgres/extensions/pgvector/src/hnswutils.c +++ /dev/null @@ -1,992 +0,0 @@ -#include "postgres.h" - -#include - -#include "hnsw.h" -#include "storage/bufmgr.h" -#include "vector.h" - -/* - * Get the max number of connections in an upper layer for each element in the index - */ -int -HnswGetM(Relation index) -{ - HnswOptions *opts = (HnswOptions *) index->rd_options; - - if (opts) - return opts->m; - - return HNSW_DEFAULT_M; -} - -/* - * Get the size of the dynamic candidate list in the index - */ -int -HnswGetEfConstruction(Relation index) -{ - HnswOptions *opts = (HnswOptions *) index->rd_options; - - if (opts) - return opts->efConstruction; - - return HNSW_DEFAULT_EF_CONSTRUCTION; -} - -/* - * Get proc - */ -FmgrInfo * -HnswOptionalProcInfo(Relation rel, uint16 procnum) -{ - if (!OidIsValid(index_getprocid(rel, 1, procnum))) - return NULL; - - return index_getprocinfo(rel, 1, procnum); -} - -/* - * Divide by the norm - * - * Returns false if value should not be indexed - * - * The caller needs to free the pointer stored in value - * if it's different than the original value - */ -bool -HnswNormValue(FmgrInfo *procinfo, Oid collation, Datum *value, Vector * result) -{ - double norm = DatumGetFloat8(FunctionCall1Coll(procinfo, collation, *value)); - - if (norm > 0) - { - Vector *v = DatumGetVector(*value); - - if (result == NULL) - result = InitVector(v->dim); - - for (int i = 0; i < v->dim; i++) - result->x[i] = v->x[i] / norm; - - *value = PointerGetDatum(result); - - return true; - } - - return false; -} - -/* - * New buffer - */ -Buffer -HnswNewBuffer(Relation index, ForkNumber forkNum) -{ - Buffer buf = ReadBufferExtended(index, forkNum, P_NEW, RBM_NORMAL, NULL); - - LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE); - return buf; -} - -/* - * Init page - */ -void -HnswInitPage(Buffer buf, Page page) -{ - PageInit(page, BufferGetPageSize(buf), sizeof(HnswPageOpaqueData)); - HnswPageGetOpaque(page)->nextblkno = InvalidBlockNumber; - HnswPageGetOpaque(page)->page_id = HNSW_PAGE_ID; -} - -/* - * Init and register page - */ -void -HnswInitRegisterPage(Relation index, Buffer *buf, Page *page, GenericXLogState **state) -{ - *state = GenericXLogStart(index); - *page = GenericXLogRegisterBuffer(*state, *buf, GENERIC_XLOG_FULL_IMAGE); - HnswInitPage(*buf, *page); -} - -/* - * Commit buffer - */ -void -HnswCommitBuffer(Buffer buf, GenericXLogState *state) -{ - MarkBufferDirty(buf); - GenericXLogFinish(state); - UnlockReleaseBuffer(buf); -} - -/* - * Allocate neighbors - */ -void -HnswInitNeighbors(HnswElement element, int m) -{ - int level = element->level; - - element->neighbors = palloc(sizeof(HnswNeighborArray) * (level + 1)); - - for (int lc = 0; lc <= level; lc++) - { - HnswNeighborArray *a; - int lm = HnswGetLayerM(m, lc); - - a = &element->neighbors[lc]; - a->length = 0; - a->items = palloc(sizeof(HnswCandidate) * lm); - } -} - -/* - * Allocate an element - */ -HnswElement -HnswInitElement(ItemPointer heaptid, int m, double ml, int maxLevel) -{ - HnswElement element = palloc(sizeof(HnswElementData)); - - int level = (int) (-log(RandomDouble()) * ml); - - /* Cap level */ - if (level > maxLevel) - level = maxLevel; - - element->heaptids = NIL; - HnswAddHeapTid(element, heaptid); - - element->level = level; - element->deleted = 0; - - HnswInitNeighbors(element, m); - - return element; -} - -/* - * Free an element - */ -void -HnswFreeElement(HnswElement element) -{ - list_free_deep(element->heaptids); - for (int lc = 0; lc <= element->level; lc++) - pfree(element->neighbors[lc].items); - pfree(element->neighbors); - pfree(element->vec); - pfree(element); -} - -/* - * Add a heap TID to an element - */ -void -HnswAddHeapTid(HnswElement element, ItemPointer heaptid) -{ - ItemPointer copy = palloc(sizeof(ItemPointerData)); - - ItemPointerCopy(heaptid, copy); - element->heaptids = lappend(element->heaptids, copy); -} - -/* - * Allocate an element from block and offset numbers - */ -HnswElement -HnswInitElementFromBlock(BlockNumber blkno, OffsetNumber offno) -{ - HnswElement element = palloc(sizeof(HnswElementData)); - - element->blkno = blkno; - element->offno = offno; - element->neighbors = NULL; - element->vec = NULL; - return element; -} - -/* - * Get the entry point - */ -HnswElement -HnswGetEntryPoint(Relation index) -{ - Buffer buf; - Page page; - HnswMetaPage metap; - HnswElement entryPoint = NULL; - - buf = ReadBuffer(index, HNSW_METAPAGE_BLKNO); - LockBuffer(buf, BUFFER_LOCK_SHARE); - page = BufferGetPage(buf); - metap = HnswPageGetMeta(page); - - if (BlockNumberIsValid(metap->entryBlkno)) - entryPoint = HnswInitElementFromBlock(metap->entryBlkno, metap->entryOffno); - - UnlockReleaseBuffer(buf); - - return entryPoint; -} - -/* - * Update the metapage info - */ -static void -HnswUpdateMetaPageInfo(Page page, int updateEntry, HnswElement entryPoint, BlockNumber insertPage) -{ - HnswMetaPage metap = HnswPageGetMeta(page); - - if (updateEntry) - { - if (entryPoint == NULL) - { - metap->entryBlkno = InvalidBlockNumber; - metap->entryOffno = InvalidOffsetNumber; - metap->entryLevel = -1; - } - else if (entryPoint->level > metap->entryLevel || updateEntry == HNSW_UPDATE_ENTRY_ALWAYS) - { - metap->entryBlkno = entryPoint->blkno; - metap->entryOffno = entryPoint->offno; - metap->entryLevel = entryPoint->level; - } - } - - if (BlockNumberIsValid(insertPage)) - metap->insertPage = insertPage; -} - -/* - * Update the metapage - */ -void -HnswUpdateMetaPage(Relation index, int updateEntry, HnswElement entryPoint, BlockNumber insertPage, ForkNumber forkNum) -{ - Buffer buf; - Page page; - GenericXLogState *state; - - buf = ReadBufferExtended(index, forkNum, HNSW_METAPAGE_BLKNO, RBM_NORMAL, NULL); - LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE); - state = GenericXLogStart(index); - page = GenericXLogRegisterBuffer(state, buf, 0); - - HnswUpdateMetaPageInfo(page, updateEntry, entryPoint, insertPage); - - HnswCommitBuffer(buf, state); -} - -/* - * Set element tuple, except for neighbor info - */ -void -HnswSetElementTuple(HnswElementTuple etup, HnswElement element) -{ - etup->type = HNSW_ELEMENT_TUPLE_TYPE; - etup->level = element->level; - etup->deleted = 0; - for (int i = 0; i < HNSW_HEAPTIDS; i++) - { - if (i < list_length(element->heaptids)) - etup->heaptids[i] = *((ItemPointer) list_nth(element->heaptids, i)); - else - ItemPointerSetInvalid(&etup->heaptids[i]); - } - memcpy(&etup->vec, element->vec, VECTOR_SIZE(element->vec->dim)); -} - -/* - * Set neighbor tuple - */ -void -HnswSetNeighborTuple(HnswNeighborTuple ntup, HnswElement e, int m) -{ - int idx = 0; - - ntup->type = HNSW_NEIGHBOR_TUPLE_TYPE; - - for (int lc = e->level; lc >= 0; lc--) - { - HnswNeighborArray *neighbors = &e->neighbors[lc]; - int lm = HnswGetLayerM(m, lc); - - for (int i = 0; i < lm; i++) - { - ItemPointer indextid = &ntup->indextids[idx++]; - - if (i < neighbors->length) - { - HnswCandidate *hc = &neighbors->items[i]; - - ItemPointerSet(indextid, hc->element->blkno, hc->element->offno); - } - else - ItemPointerSetInvalid(indextid); - } - } - - ntup->count = idx; -} - -/* - * Load neighbors from page - */ -static void -LoadNeighborsFromPage(HnswElement element, Relation index, Page page) -{ - HnswNeighborTuple ntup = (HnswNeighborTuple) PageGetItem(page, PageGetItemId(page, element->neighborOffno)); - int m = HnswGetM(index); - int neighborCount = (element->level + 2) * m; - - Assert(HnswIsNeighborTuple(ntup)); - - HnswInitNeighbors(element, m); - - /* Ensure expected neighbors */ - if (ntup->count != neighborCount) - return; - - for (int i = 0; i < neighborCount; i++) - { - HnswElement e; - int level; - HnswCandidate *hc; - ItemPointer indextid; - HnswNeighborArray *neighbors; - - indextid = &ntup->indextids[i]; - - if (!ItemPointerIsValid(indextid)) - continue; - - e = HnswInitElementFromBlock(ItemPointerGetBlockNumber(indextid), ItemPointerGetOffsetNumber(indextid)); - - /* Calculate level based on offset */ - level = element->level - i / m; - if (level < 0) - level = 0; - - neighbors = &element->neighbors[level]; - hc = &neighbors->items[neighbors->length++]; - hc->element = e; - } -} - -/* - * Load neighbors - */ -void -HnswLoadNeighbors(HnswElement element, Relation index) -{ - Buffer buf; - Page page; - - buf = ReadBuffer(index, element->neighborPage); - LockBuffer(buf, BUFFER_LOCK_SHARE); - page = BufferGetPage(buf); - - LoadNeighborsFromPage(element, index, page); - - UnlockReleaseBuffer(buf); -} - -/* - * Load an element from a tuple - */ -void -HnswLoadElementFromTuple(HnswElement element, HnswElementTuple etup, bool loadHeaptids, bool loadVec) -{ - element->level = etup->level; - element->deleted = etup->deleted; - element->neighborPage = ItemPointerGetBlockNumber(&etup->neighbortid); - element->neighborOffno = ItemPointerGetOffsetNumber(&etup->neighbortid); - element->heaptids = NIL; - - if (loadHeaptids) - { - for (int i = 0; i < HNSW_HEAPTIDS; i++) - { - /* Can stop at first invalid */ - if (!ItemPointerIsValid(&etup->heaptids[i])) - break; - - HnswAddHeapTid(element, &etup->heaptids[i]); - } - } - - if (loadVec) - { - element->vec = palloc(VECTOR_SIZE(etup->vec.dim)); - memcpy(element->vec, &etup->vec, VECTOR_SIZE(etup->vec.dim)); - } -} - -/* - * Load an element and optionally get its distance from q - */ -void -HnswLoadElement(HnswElement element, float *distance, Datum *q, Relation index, FmgrInfo *procinfo, Oid collation, bool loadVec) -{ - Buffer buf; - Page page; - HnswElementTuple etup; - - /* Read vector */ - buf = ReadBuffer(index, element->blkno); - LockBuffer(buf, BUFFER_LOCK_SHARE); - page = BufferGetPage(buf); - - etup = (HnswElementTuple) PageGetItem(page, PageGetItemId(page, element->offno)); - - Assert(HnswIsElementTuple(etup)); - - /* Load element */ - HnswLoadElementFromTuple(element, etup, true, loadVec); - - /* Calculate distance */ - if (distance != NULL) - *distance = (float) DatumGetFloat8(FunctionCall2Coll(procinfo, collation, *q, PointerGetDatum(&etup->vec))); - - UnlockReleaseBuffer(buf); -} - -/* - * Get the distance for a candidate - */ -static float -GetCandidateDistance(HnswCandidate * hc, Datum q, FmgrInfo *procinfo, Oid collation) -{ - return DatumGetFloat8(FunctionCall2Coll(procinfo, collation, q, PointerGetDatum(hc->element->vec))); -} - -/* - * Create a candidate for the entry point - */ -HnswCandidate * -HnswEntryCandidate(HnswElement entryPoint, Datum q, Relation index, FmgrInfo *procinfo, Oid collation, bool loadVec) -{ - HnswCandidate *hc = palloc(sizeof(HnswCandidate)); - - hc->element = entryPoint; - if (index == NULL) - hc->distance = GetCandidateDistance(hc, q, procinfo, collation); - else - HnswLoadElement(hc->element, &hc->distance, &q, index, procinfo, collation, loadVec); - return hc; -} - -/* - * Compare candidate distances - */ -static int -CompareNearestCandidates(const pairingheap_node *a, const pairingheap_node *b, void *arg) -{ - if (((const HnswPairingHeapNode *) a)->inner->distance < ((const HnswPairingHeapNode *) b)->inner->distance) - return 1; - - if (((const HnswPairingHeapNode *) a)->inner->distance > ((const HnswPairingHeapNode *) b)->inner->distance) - return -1; - - return 0; -} - -/* - * Compare candidate distances - */ -static int -CompareFurthestCandidates(const pairingheap_node *a, const pairingheap_node *b, void *arg) -{ - if (((const HnswPairingHeapNode *) a)->inner->distance < ((const HnswPairingHeapNode *) b)->inner->distance) - return -1; - - if (((const HnswPairingHeapNode *) a)->inner->distance > ((const HnswPairingHeapNode *) b)->inner->distance) - return 1; - - return 0; -} - -/* - * Create a pairing heap node for a candidate - */ -static HnswPairingHeapNode * -CreatePairingHeapNode(HnswCandidate * c) -{ - HnswPairingHeapNode *node = palloc(sizeof(HnswPairingHeapNode)); - - node->inner = c; - return node; -} - -/* - * Add to visited - */ -static inline void -AddToVisited(HTAB *v, HnswCandidate * hc, Relation index, bool *found) -{ - if (index == NULL) - hash_search(v, &hc->element, HASH_ENTER, found); - else - { - ItemPointerData indextid; - - ItemPointerSet(&indextid, hc->element->blkno, hc->element->offno); - hash_search(v, &indextid, HASH_ENTER, found); - } -} - -/* - * Algorithm 2 from paper - */ -List * -HnswSearchLayer(Datum q, List *ep, int ef, int lc, Relation index, FmgrInfo *procinfo, Oid collation, bool inserting, HnswElement skipElement) -{ - ListCell *lc2; - - List *w = NIL; - pairingheap *C = pairingheap_allocate(CompareNearestCandidates, NULL); - pairingheap *W = pairingheap_allocate(CompareFurthestCandidates, NULL); - int wlen = 0; - HASHCTL hash_ctl; - HTAB *v; - - /* Create hash table */ - if (index == NULL) - { - hash_ctl.keysize = sizeof(HnswElement *); - hash_ctl.entrysize = sizeof(HnswElement *); - } - else - { - hash_ctl.keysize = sizeof(ItemPointerData); - hash_ctl.entrysize = sizeof(ItemPointerData); - } - - hash_ctl.hcxt = CurrentMemoryContext; - v = hash_create("hnsw visited", 256, &hash_ctl, HASH_ELEM | HASH_BLOBS | HASH_CONTEXT); - - /* Add entry points to v, C, and W */ - foreach(lc2, ep) - { - HnswCandidate *hc = (HnswCandidate *) lfirst(lc2); - - AddToVisited(v, hc, index, NULL); - - pairingheap_add(C, &(CreatePairingHeapNode(hc)->ph_node)); - pairingheap_add(W, &(CreatePairingHeapNode(hc)->ph_node)); - - /* - * Do not count elements being deleted towards ef when vacuuming. It - * would be ideal to do this for inserts as well, but this could - * affect insert performance. - */ - if (skipElement == NULL || list_length(hc->element->heaptids) != 0) - wlen++; - } - - while (!pairingheap_is_empty(C)) - { - HnswNeighborArray *neighborhood; - HnswCandidate *c = ((HnswPairingHeapNode *) pairingheap_remove_first(C))->inner; - HnswCandidate *f = ((HnswPairingHeapNode *) pairingheap_first(W))->inner; - - if (c->distance > f->distance) - break; - - if (c->element->neighbors == NULL) - HnswLoadNeighbors(c->element, index); - - /* Get the neighborhood at layer lc */ - neighborhood = &c->element->neighbors[lc]; - - for (int i = 0; i < neighborhood->length; i++) - { - HnswCandidate *e = &neighborhood->items[i]; - bool visited; - - AddToVisited(v, e, index, &visited); - - if (!visited) - { - float eDistance; - - f = ((HnswPairingHeapNode *) pairingheap_first(W))->inner; - - if (index == NULL) - eDistance = GetCandidateDistance(e, q, procinfo, collation); - else - HnswLoadElement(e->element, &eDistance, &q, index, procinfo, collation, inserting); - - Assert(!e->element->deleted); - - /* Make robust to issues */ - if (e->element->level < lc) - continue; - - if (eDistance < f->distance || wlen < ef) - { - /* Copy e */ - HnswCandidate *ec = palloc(sizeof(HnswCandidate)); - - ec->element = e->element; - ec->distance = eDistance; - - pairingheap_add(C, &(CreatePairingHeapNode(ec)->ph_node)); - pairingheap_add(W, &(CreatePairingHeapNode(ec)->ph_node)); - - /* - * Do not count elements being deleted towards ef when - * vacuuming. It would be ideal to do this for inserts as - * well, but this could affect insert performance. - */ - if (skipElement == NULL || list_length(e->element->heaptids) != 0) - { - wlen++; - - /* No need to decrement wlen */ - if (wlen > ef) - pairingheap_remove_first(W); - } - } - } - } - } - - /* Add each element of W to w */ - while (!pairingheap_is_empty(W)) - { - HnswCandidate *hc = ((HnswPairingHeapNode *) pairingheap_remove_first(W))->inner; - - w = lappend(w, hc); - } - - return w; -} - -/* - * Calculate the distance between elements - */ -static float -HnswGetDistance(HnswElement a, HnswElement b, int lc, FmgrInfo *procinfo, Oid collation) -{ - /* Look for cached distance */ - if (a->neighbors != NULL) - { - Assert(a->level >= lc); - - for (int i = 0; i < a->neighbors[lc].length; i++) - { - if (a->neighbors[lc].items[i].element == b) - return a->neighbors[lc].items[i].distance; - } - } - - if (b->neighbors != NULL) - { - Assert(b->level >= lc); - - for (int i = 0; i < b->neighbors[lc].length; i++) - { - if (b->neighbors[lc].items[i].element == a) - return b->neighbors[lc].items[i].distance; - } - } - - return DatumGetFloat8(FunctionCall2Coll(procinfo, collation, PointerGetDatum(a->vec), PointerGetDatum(b->vec))); -} - -/* - * Check if an element is closer to q than any element from R - */ -static bool -CheckElementCloser(HnswCandidate * e, List *r, int lc, FmgrInfo *procinfo, Oid collation) -{ - ListCell *lc2; - - foreach(lc2, r) - { - HnswCandidate *ri = lfirst(lc2); - float distance = HnswGetDistance(e->element, ri->element, lc, procinfo, collation); - - if (distance <= e->distance) - return false; - } - - return true; -} - -/* - * Algorithm 4 from paper - */ -static List * -SelectNeighbors(List *c, int m, int lc, FmgrInfo *procinfo, Oid collation, HnswCandidate * *pruned) -{ - List *r = NIL; - List *w = list_copy(c); - pairingheap *wd; - - if (list_length(w) <= m) - return w; - - wd = pairingheap_allocate(CompareNearestCandidates, NULL); - - while (list_length(w) > 0 && list_length(r) < m) - { - /* Assumes w is already ordered desc */ - HnswCandidate *e = llast(w); - bool closer; - - w = list_delete_last(w); - - closer = CheckElementCloser(e, r, lc, procinfo, collation); - - if (closer) - r = lappend(r, e); - else - pairingheap_add(wd, &(CreatePairingHeapNode(e)->ph_node)); - } - - /* Keep pruned connections */ - while (!pairingheap_is_empty(wd) && list_length(r) < m) - r = lappend(r, ((HnswPairingHeapNode *) pairingheap_remove_first(wd))->inner); - - /* Return pruned for update connections */ - if (pruned != NULL) - { - if (!pairingheap_is_empty(wd)) - *pruned = ((HnswPairingHeapNode *) pairingheap_first(wd))->inner; - else - *pruned = linitial(w); - } - - return r; -} - -/* - * Find duplicate element - */ -HnswElement -HnswFindDuplicate(HnswElement e) -{ - HnswNeighborArray *neighbors = &e->neighbors[0]; - - for (int i = 0; i < neighbors->length; i++) - { - HnswCandidate *neighbor = &neighbors->items[i]; - - /* Exit early since ordered by distance */ - if (vector_cmp_internal(e->vec, neighbor->element->vec) != 0) - break; - - /* Check for space */ - if (list_length(neighbor->element->heaptids) < HNSW_HEAPTIDS) - return neighbor->element; - } - - return NULL; -} - -/* - * Add connections - */ -static void -AddConnections(HnswElement element, List *neighbors, int m, int lc) -{ - ListCell *lc2; - HnswNeighborArray *a = &element->neighbors[lc]; - - foreach(lc2, neighbors) - a->items[a->length++] = *((HnswCandidate *) lfirst(lc2)); -} - -/* - * Compare candidate distances - */ -static int -#if PG_VERSION_NUM >= 130000 -CompareCandidateDistances(const ListCell *a, const ListCell *b) -#else -CompareCandidateDistances(const void *a, const void *b) -#endif -{ - HnswCandidate *hca = lfirst((ListCell *) a); - HnswCandidate *hcb = lfirst((ListCell *) b); - - if (hca->distance < hcb->distance) - return 1; - - if (hca->distance > hcb->distance) - return -1; - - return 0; -} - -/* - * Update connections - */ -void -HnswUpdateConnection(HnswElement element, HnswCandidate * hc, int m, int lc, int *updateIdx, Relation index, FmgrInfo *procinfo, Oid collation) -{ - HnswNeighborArray *currentNeighbors = &hc->element->neighbors[lc]; - - HnswCandidate hc2; - - hc2.element = element; - hc2.distance = hc->distance; - - if (currentNeighbors->length < m) - { - currentNeighbors->items[currentNeighbors->length++] = hc2; - - /* Track update */ - if (updateIdx != NULL) - *updateIdx = -2; - } - else - { - /* Shrink connections */ - HnswCandidate *pruned = NULL; - - /* Load elements on insert */ - if (index != NULL) - { - Datum q = PointerGetDatum(hc->element->vec); - - for (int i = 0; i < currentNeighbors->length; i++) - { - HnswCandidate *hc3 = ¤tNeighbors->items[i]; - - if (hc3->element->vec == NULL) - HnswLoadElement(hc3->element, &hc3->distance, &q, index, procinfo, collation, true); - else - hc3->distance = GetCandidateDistance(hc3, q, procinfo, collation); - - /* Prune element if being deleted */ - if (list_length(hc3->element->heaptids) == 0) - { - pruned = ¤tNeighbors->items[i]; - break; - } - } - } - - if (pruned == NULL) - { - List *c = NIL; - - /* Add and sort candidates */ - for (int i = 0; i < currentNeighbors->length; i++) - c = lappend(c, ¤tNeighbors->items[i]); - c = lappend(c, &hc2); - list_sort(c, CompareCandidateDistances); - - SelectNeighbors(c, m, lc, procinfo, collation, &pruned); - - /* Should not happen */ - if (pruned == NULL) - return; - } - - /* Find and replace the pruned element */ - for (int i = 0; i < currentNeighbors->length; i++) - { - if (currentNeighbors->items[i].element == pruned->element) - { - currentNeighbors->items[i] = hc2; - - /* Track update */ - if (updateIdx != NULL) - *updateIdx = i; - - break; - } - } - } -} - -/* - * Remove elements being deleted or skipped - */ -static List * -RemoveElements(List *w, HnswElement skipElement) -{ - ListCell *lc2; - List *w2 = NIL; - - foreach(lc2, w) - { - HnswCandidate *hc = (HnswCandidate *) lfirst(lc2); - - /* Skip self for vacuuming update */ - if (skipElement != NULL && hc->element->blkno == skipElement->blkno && hc->element->offno == skipElement->offno) - continue; - - if (list_length(hc->element->heaptids) != 0) - w2 = lappend(w2, hc); - } - - return w2; -} - -/* - * Algorithm 1 from paper - */ -void -HnswInsertElement(HnswElement element, HnswElement entryPoint, Relation index, FmgrInfo *procinfo, Oid collation, int m, int efConstruction, bool existing) -{ - List *ep; - List *w; - int level = element->level; - int entryLevel; - Datum q = PointerGetDatum(element->vec); - HnswElement skipElement = existing ? element : NULL; - - /* No neighbors if no entry point */ - if (entryPoint == NULL) - return; - - /* Get entry point and level */ - ep = list_make1(HnswEntryCandidate(entryPoint, q, index, procinfo, collation, true)); - entryLevel = entryPoint->level; - - /* 1st phase: greedy search to insert level */ - for (int lc = entryLevel; lc >= level + 1; lc--) - { - w = HnswSearchLayer(q, ep, 1, lc, index, procinfo, collation, true, skipElement); - ep = w; - } - - if (level > entryLevel) - level = entryLevel; - - /* Add one for existing element */ - if (existing) - efConstruction++; - - /* 2nd phase */ - for (int lc = level; lc >= 0; lc--) - { - int lm = HnswGetLayerM(m, lc); - List *neighbors; - List *lw; - - w = HnswSearchLayer(q, ep, efConstruction, lc, index, procinfo, collation, true, skipElement); - - /* Elements being deleted or skipped can help with search */ - /* but should be removed before selecting neighbors */ - if (index != NULL) - lw = RemoveElements(w, skipElement); - else - lw = w; - - neighbors = SelectNeighbors(lw, lm, lc, procinfo, collation, NULL); - - AddConnections(element, neighbors, lm, lc); - - ep = w; - } -} diff --git a/packages/server/postgres/extensions/pgvector/src/hnswutils.o b/packages/server/postgres/extensions/pgvector/src/hnswutils.o deleted file mode 100644 index 52d8670f33aefa86f0df8af8f0d317f8bf5ce54d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11504 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 - -#include "commands/vacuum.h" -#include "hnsw.h" -#include "storage/bufmgr.h" -#include "storage/lmgr.h" -#include "utils/memutils.h" - -/* - * Check if deleted list contains an index TID - */ -static bool -DeletedContains(HTAB *deleted, ItemPointer indextid) -{ - bool found; - - hash_search(deleted, indextid, HASH_FIND, &found); - return found; -} - -/* - * Remove deleted heap TIDs - * - * OK to remove for entry point, since always considered for searches and inserts - */ -static void -RemoveHeapTids(HnswVacuumState * vacuumstate) -{ - BlockNumber blkno = HNSW_HEAD_BLKNO; - HnswElement highestPoint = &vacuumstate->highestPoint; - Relation index = vacuumstate->index; - BufferAccessStrategy bas = vacuumstate->bas; - HnswElement entryPoint = HnswGetEntryPoint(vacuumstate->index); - IndexBulkDeleteResult *stats = vacuumstate->stats; - - /* Store separately since highestPoint.level is uint8 */ - int highestLevel = -1; - - /* Initialize highest point */ - highestPoint->blkno = InvalidBlockNumber; - highestPoint->offno = InvalidOffsetNumber; - - while (BlockNumberIsValid(blkno)) - { - Buffer buf; - Page page; - GenericXLogState *state; - OffsetNumber offno; - OffsetNumber maxoffno; - bool updated = false; - - vacuum_delay_point(); - - buf = ReadBufferExtended(index, MAIN_FORKNUM, blkno, RBM_NORMAL, bas); - LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE); - state = GenericXLogStart(index); - page = GenericXLogRegisterBuffer(state, buf, 0); - maxoffno = PageGetMaxOffsetNumber(page); - - /* Iterate over nodes */ - for (offno = FirstOffsetNumber; offno <= maxoffno; offno = OffsetNumberNext(offno)) - { - HnswElementTuple etup = (HnswElementTuple) PageGetItem(page, PageGetItemId(page, offno)); - int idx = 0; - bool itemUpdated = false; - - /* Skip neighbor tuples */ - if (!HnswIsElementTuple(etup)) - continue; - - if (ItemPointerIsValid(&etup->heaptids[0])) - { - for (int i = 0; i < HNSW_HEAPTIDS; i++) - { - /* Stop at first unused */ - if (!ItemPointerIsValid(&etup->heaptids[i])) - break; - - if (vacuumstate->callback(&etup->heaptids[i], vacuumstate->callback_state)) - { - itemUpdated = true; - stats->tuples_removed++; - } - else - { - /* Move to front of list */ - etup->heaptids[idx++] = etup->heaptids[i]; - stats->num_index_tuples++; - } - } - - if (itemUpdated) - { - Size etupSize = HNSW_ELEMENT_TUPLE_SIZE(etup->vec.dim); - - /* Mark rest as invalid */ - for (int i = idx; i < HNSW_HEAPTIDS; i++) - ItemPointerSetInvalid(&etup->heaptids[i]); - - if (!PageIndexTupleOverwrite(page, offno, (Item) etup, etupSize)) - elog(ERROR, "failed to add index item to \"%s\"", RelationGetRelationName(index)); - - updated = true; - } - } - - if (!ItemPointerIsValid(&etup->heaptids[0])) - { - ItemPointerData ip; - - /* Add to deleted list */ - ItemPointerSet(&ip, blkno, offno); - - (void) hash_search(vacuumstate->deleted, &ip, HASH_ENTER, NULL); - } - else if (etup->level > highestLevel && !(entryPoint != NULL && blkno == entryPoint->blkno && offno == entryPoint->offno)) - { - /* Keep track of highest non-entry point */ - highestPoint->blkno = blkno; - highestPoint->offno = offno; - highestPoint->level = etup->level; - highestLevel = etup->level; - } - } - - blkno = HnswPageGetOpaque(page)->nextblkno; - - if (updated) - { - MarkBufferDirty(buf); - GenericXLogFinish(state); - } - else - GenericXLogAbort(state); - - UnlockReleaseBuffer(buf); - } -} - -/* - * Check for deleted neighbors - */ -static bool -NeedsUpdated(HnswVacuumState * vacuumstate, HnswElement element) -{ - Relation index = vacuumstate->index; - BufferAccessStrategy bas = vacuumstate->bas; - Buffer buf; - Page page; - HnswNeighborTuple ntup; - bool needsUpdated = false; - - buf = ReadBufferExtended(index, MAIN_FORKNUM, element->neighborPage, RBM_NORMAL, bas); - LockBuffer(buf, BUFFER_LOCK_SHARE); - page = BufferGetPage(buf); - ntup = (HnswNeighborTuple) PageGetItem(page, PageGetItemId(page, element->neighborOffno)); - - Assert(HnswIsNeighborTuple(ntup)); - - /* Check neighbors */ - for (int i = 0; i < ntup->count; i++) - { - ItemPointer indextid = &ntup->indextids[i]; - - if (!ItemPointerIsValid(indextid)) - continue; - - /* Check if in deleted list */ - if (DeletedContains(vacuumstate->deleted, indextid)) - { - needsUpdated = true; - break; - } - } - - /* Also update if layer 0 is not full */ - /* This could indicate too many candidates being deleted during insert */ - if (!needsUpdated) - needsUpdated = !ItemPointerIsValid(&ntup->indextids[ntup->count - 1]); - - UnlockReleaseBuffer(buf); - - return needsUpdated; -} - -/* - * Repair graph for a single element - */ -static void -RepairGraphElement(HnswVacuumState * vacuumstate, HnswElement element, HnswElement entryPoint) -{ - Relation index = vacuumstate->index; - Buffer buf; - Page page; - GenericXLogState *state; - int m = vacuumstate->m; - int efConstruction = vacuumstate->efConstruction; - FmgrInfo *procinfo = vacuumstate->procinfo; - Oid collation = vacuumstate->collation; - BufferAccessStrategy bas = vacuumstate->bas; - HnswNeighborTuple ntup = vacuumstate->ntup; - Size ntupSize = HNSW_NEIGHBOR_TUPLE_SIZE(element->level, m); - - /* Skip if element is entry point */ - if (entryPoint != NULL && element->blkno == entryPoint->blkno && element->offno == entryPoint->offno) - return; - - /* Init fields */ - HnswInitNeighbors(element, m); - element->heaptids = NIL; - - /* Add element to graph, skipping itself */ - HnswInsertElement(element, entryPoint, index, procinfo, collation, m, efConstruction, true); - - /* Update neighbor tuple */ - /* Do this before getting page to minimize locking */ - HnswSetNeighborTuple(ntup, element, m); - - /* Get neighbor page */ - buf = ReadBufferExtended(index, MAIN_FORKNUM, element->neighborPage, RBM_NORMAL, bas); - LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE); - state = GenericXLogStart(index); - page = GenericXLogRegisterBuffer(state, buf, 0); - - /* Overwrite tuple */ - if (!PageIndexTupleOverwrite(page, element->neighborOffno, (Item) ntup, ntupSize)) - elog(ERROR, "failed to add index item to \"%s\"", RelationGetRelationName(index)); - - /* Commit */ - MarkBufferDirty(buf); - GenericXLogFinish(state); - UnlockReleaseBuffer(buf); - - /* Update neighbors */ - HnswUpdateNeighborPages(index, procinfo, collation, element, m, true); -} - -/* - * Repair graph entry point - */ -static void -RepairGraphEntryPoint(HnswVacuumState * vacuumstate) -{ - Relation index = vacuumstate->index; - HnswElement highestPoint = &vacuumstate->highestPoint; - HnswElement entryPoint; - MemoryContext oldCtx = MemoryContextSwitchTo(vacuumstate->tmpCtx); - - if (!BlockNumberIsValid(highestPoint->blkno)) - highestPoint = NULL; - - /* - * Repair graph for highest non-entry point. Highest point may be outdated - * due to inserts that happen during and after RemoveHeapTids. - */ - if (highestPoint != NULL) - { - /* Get a shared lock */ - LockPage(index, HNSW_UPDATE_LOCK, ShareLock); - - /* Load element */ - HnswLoadElement(highestPoint, NULL, NULL, index, vacuumstate->procinfo, vacuumstate->collation, true); - - /* Repair if needed */ - if (NeedsUpdated(vacuumstate, highestPoint)) - RepairGraphElement(vacuumstate, highestPoint, HnswGetEntryPoint(index)); - - /* Release lock */ - UnlockPage(index, HNSW_UPDATE_LOCK, ShareLock); - } - - /* Prevent concurrent inserts when possibly updating entry point */ - LockPage(index, HNSW_UPDATE_LOCK, ExclusiveLock); - - /* Get latest entry point */ - entryPoint = HnswGetEntryPoint(index); - - if (entryPoint != NULL) - { - ItemPointerData epData; - - ItemPointerSet(&epData, entryPoint->blkno, entryPoint->offno); - - if (DeletedContains(vacuumstate->deleted, &epData)) - { - /* - * Replace the entry point with the highest point. If highest - * point is outdated and empty, the entry point will be empty - * until an element is repaired. - */ - HnswUpdateMetaPage(index, HNSW_UPDATE_ENTRY_ALWAYS, highestPoint, InvalidBlockNumber, MAIN_FORKNUM); - } - else - { - /* - * Repair the entry point with the highest point. If highest point - * is outdated, this can remove connections at higher levels in - * the graph until they are repaired, but this should be fine. - */ - HnswLoadElement(entryPoint, NULL, NULL, index, vacuumstate->procinfo, vacuumstate->collation, true); - - if (NeedsUpdated(vacuumstate, entryPoint)) - { - /* Reset neighbors from previous update */ - if (highestPoint != NULL) - highestPoint->neighbors = NULL; - - RepairGraphElement(vacuumstate, entryPoint, highestPoint); - } - } - } - - /* Release lock */ - UnlockPage(index, HNSW_UPDATE_LOCK, ExclusiveLock); - - /* Reset memory context */ - MemoryContextSwitchTo(oldCtx); - MemoryContextReset(vacuumstate->tmpCtx); -} - -/* - * Repair graph for all elements - */ -static void -RepairGraph(HnswVacuumState * vacuumstate) -{ - Relation index = vacuumstate->index; - BufferAccessStrategy bas = vacuumstate->bas; - BlockNumber blkno = HNSW_HEAD_BLKNO; - - /* Wait for inserts to complete */ - LockPage(index, HNSW_UPDATE_LOCK, ExclusiveLock); - UnlockPage(index, HNSW_UPDATE_LOCK, ExclusiveLock); - - /* Repair entry point first */ - RepairGraphEntryPoint(vacuumstate); - - while (BlockNumberIsValid(blkno)) - { - Buffer buf; - Page page; - OffsetNumber offno; - OffsetNumber maxoffno; - List *elements = NIL; - ListCell *lc2; - MemoryContext oldCtx; - - vacuum_delay_point(); - - oldCtx = MemoryContextSwitchTo(vacuumstate->tmpCtx); - - buf = ReadBufferExtended(index, MAIN_FORKNUM, blkno, RBM_NORMAL, bas); - LockBuffer(buf, BUFFER_LOCK_SHARE); - page = BufferGetPage(buf); - maxoffno = PageGetMaxOffsetNumber(page); - - /* Load items into memory to minimize locking */ - for (offno = FirstOffsetNumber; offno <= maxoffno; offno = OffsetNumberNext(offno)) - { - HnswElementTuple etup = (HnswElementTuple) PageGetItem(page, PageGetItemId(page, offno)); - HnswElement element; - - /* Skip neighbor tuples */ - if (!HnswIsElementTuple(etup)) - continue; - - /* Skip updating neighbors if being deleted */ - if (!ItemPointerIsValid(&etup->heaptids[0])) - continue; - - /* Create an element */ - element = HnswInitElementFromBlock(blkno, offno); - HnswLoadElementFromTuple(element, etup, false, true); - - elements = lappend(elements, element); - } - - blkno = HnswPageGetOpaque(page)->nextblkno; - - UnlockReleaseBuffer(buf); - - /* Update neighbor pages */ - foreach(lc2, elements) - { - HnswElement element = (HnswElement) lfirst(lc2); - HnswElement entryPoint; - LOCKMODE lockmode = ShareLock; - - /* Check if any neighbors point to deleted values */ - if (!NeedsUpdated(vacuumstate, element)) - continue; - - /* Get a shared lock */ - LockPage(index, HNSW_UPDATE_LOCK, lockmode); - - /* Refresh entry point for each element */ - entryPoint = HnswGetEntryPoint(index); - - /* Prevent concurrent inserts when likely updating entry point */ - if (entryPoint == NULL || element->level > entryPoint->level) - { - /* Release shared lock */ - UnlockPage(index, HNSW_UPDATE_LOCK, lockmode); - - /* Get exclusive lock */ - lockmode = ExclusiveLock; - LockPage(index, HNSW_UPDATE_LOCK, lockmode); - - /* Get latest entry point after lock is acquired */ - entryPoint = HnswGetEntryPoint(index); - } - - /* Repair connections */ - RepairGraphElement(vacuumstate, element, entryPoint); - - /* - * Update metapage if needed. Should only happen if entry point - * was replaced and highest point was outdated. - */ - if (entryPoint == NULL || element->level > entryPoint->level) - HnswUpdateMetaPage(index, HNSW_UPDATE_ENTRY_GREATER, element, InvalidBlockNumber, MAIN_FORKNUM); - - /* Release lock */ - UnlockPage(index, HNSW_UPDATE_LOCK, lockmode); - } - - /* Reset memory context */ - MemoryContextSwitchTo(oldCtx); - MemoryContextReset(vacuumstate->tmpCtx); - } -} - -/* - * Mark items as deleted - */ -static void -MarkDeleted(HnswVacuumState * vacuumstate) -{ - BlockNumber blkno = HNSW_HEAD_BLKNO; - BlockNumber insertPage = InvalidBlockNumber; - Relation index = vacuumstate->index; - BufferAccessStrategy bas = vacuumstate->bas; - - /* Wait for selects to complete */ - LockPage(index, HNSW_SCAN_LOCK, ExclusiveLock); - UnlockPage(index, HNSW_SCAN_LOCK, ExclusiveLock); - - while (BlockNumberIsValid(blkno)) - { - Buffer buf; - Page page; - GenericXLogState *state; - OffsetNumber offno; - OffsetNumber maxoffno; - - vacuum_delay_point(); - - buf = ReadBufferExtended(index, MAIN_FORKNUM, blkno, RBM_NORMAL, bas); - - /* - * ambulkdelete cannot delete entries from pages that are pinned by - * other backends - * - * https://www.postgresql.org/docs/current/index-locking.html - */ - LockBufferForCleanup(buf); - - state = GenericXLogStart(index); - page = GenericXLogRegisterBuffer(state, buf, 0); - maxoffno = PageGetMaxOffsetNumber(page); - - /* Update element and neighbors together */ - for (offno = FirstOffsetNumber; offno <= maxoffno; offno = OffsetNumberNext(offno)) - { - HnswElementTuple etup = (HnswElementTuple) PageGetItem(page, PageGetItemId(page, offno)); - HnswNeighborTuple ntup; - Size etupSize; - Size ntupSize; - Buffer nbuf; - Page npage; - BlockNumber neighborPage; - OffsetNumber neighborOffno; - - /* Skip neighbor tuples */ - if (!HnswIsElementTuple(etup)) - continue; - - /* Skip deleted tuples */ - if (etup->deleted) - { - /* Set to first free page */ - if (!BlockNumberIsValid(insertPage)) - insertPage = blkno; - - continue; - } - - /* Skip live tuples */ - if (ItemPointerIsValid(&etup->heaptids[0])) - continue; - - /* Calculate sizes */ - etupSize = HNSW_ELEMENT_TUPLE_SIZE(etup->vec.dim); - ntupSize = HNSW_NEIGHBOR_TUPLE_SIZE(etup->level, vacuumstate->m); - - /* Get neighbor page */ - neighborPage = ItemPointerGetBlockNumber(&etup->neighbortid); - neighborOffno = ItemPointerGetOffsetNumber(&etup->neighbortid); - - if (neighborPage == blkno) - { - nbuf = buf; - npage = page; - } - else - { - nbuf = ReadBufferExtended(index, MAIN_FORKNUM, neighborPage, RBM_NORMAL, bas); - LockBuffer(nbuf, BUFFER_LOCK_EXCLUSIVE); - npage = GenericXLogRegisterBuffer(state, nbuf, 0); - } - - ntup = (HnswNeighborTuple) PageGetItem(npage, PageGetItemId(npage, neighborOffno)); - - /* Overwrite element */ - etup->deleted = 1; - MemSet(&etup->vec.x, 0, etup->vec.dim * sizeof(float)); - - /* Overwrite neighbors */ - for (int i = 0; i < ntup->count; i++) - ItemPointerSetInvalid(&ntup->indextids[i]); - - /* Overwrite element tuple */ - if (!PageIndexTupleOverwrite(page, offno, (Item) etup, etupSize)) - elog(ERROR, "failed to add index item to \"%s\"", RelationGetRelationName(index)); - - /* Overwrite neighbor tuple */ - if (!PageIndexTupleOverwrite(npage, neighborOffno, (Item) ntup, ntupSize)) - elog(ERROR, "failed to add index item to \"%s\"", RelationGetRelationName(index)); - - /* Commit */ - MarkBufferDirty(buf); - if (nbuf != buf) - MarkBufferDirty(nbuf); - GenericXLogFinish(state); - if (nbuf != buf) - UnlockReleaseBuffer(nbuf); - - /* Set to first free page */ - if (!BlockNumberIsValid(insertPage)) - insertPage = blkno; - - /* Prepare new xlog */ - state = GenericXLogStart(index); - page = GenericXLogRegisterBuffer(state, buf, 0); - } - - blkno = HnswPageGetOpaque(page)->nextblkno; - - GenericXLogAbort(state); - UnlockReleaseBuffer(buf); - } - - /* Update insert page last, after everything has been marked as deleted */ - HnswUpdateMetaPage(index, 0, NULL, insertPage, MAIN_FORKNUM); -} - -/* - * Initialize the vacuum state - */ -static void -InitVacuumState(HnswVacuumState * vacuumstate, IndexVacuumInfo *info, IndexBulkDeleteResult *stats, IndexBulkDeleteCallback callback, void *callback_state) -{ - Relation index = info->index; - HASHCTL hash_ctl; - - if (stats == NULL) - stats = (IndexBulkDeleteResult *) palloc0(sizeof(IndexBulkDeleteResult)); - - vacuumstate->index = index; - vacuumstate->stats = stats; - vacuumstate->callback = callback; - vacuumstate->callback_state = callback_state; - vacuumstate->m = HnswGetM(index); - vacuumstate->efConstruction = HnswGetEfConstruction(index); - vacuumstate->bas = GetAccessStrategy(BAS_BULKREAD); - vacuumstate->procinfo = index_getprocinfo(index, 1, HNSW_DISTANCE_PROC); - vacuumstate->collation = index->rd_indcollation[0]; - vacuumstate->ntup = palloc0(BLCKSZ); - vacuumstate->tmpCtx = AllocSetContextCreate(CurrentMemoryContext, - "Hnsw vacuum temporary context", - ALLOCSET_DEFAULT_SIZES); - - /* Create hash table */ - hash_ctl.keysize = sizeof(ItemPointerData); - hash_ctl.entrysize = sizeof(ItemPointerData); - hash_ctl.hcxt = CurrentMemoryContext; - vacuumstate->deleted = hash_create("hnswbulkdelete indextids", 256, &hash_ctl, HASH_ELEM | HASH_BLOBS | HASH_CONTEXT); -} - -/* - * Free resources - */ -static void -FreeVacuumState(HnswVacuumState * vacuumstate) -{ - hash_destroy(vacuumstate->deleted); - FreeAccessStrategy(vacuumstate->bas); - pfree(vacuumstate->ntup); - MemoryContextDelete(vacuumstate->tmpCtx); -} - -/* - * Bulk delete tuples from the index - */ -IndexBulkDeleteResult * -hnswbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats, - IndexBulkDeleteCallback callback, void *callback_state) -{ - HnswVacuumState vacuumstate; - - InitVacuumState(&vacuumstate, info, stats, callback, callback_state); - - /* Pass 1: Remove heap TIDs */ - RemoveHeapTids(&vacuumstate); - - /* Pass 2: Repair graph */ - RepairGraph(&vacuumstate); - - /* Pass 3: Mark as deleted */ - MarkDeleted(&vacuumstate); - - FreeVacuumState(&vacuumstate); - - return vacuumstate.stats; -} - -/* - * Clean up after a VACUUM operation - */ -IndexBulkDeleteResult * -hnswvacuumcleanup(IndexVacuumInfo *info, IndexBulkDeleteResult *stats) -{ - Relation rel = info->index; - - if (info->analyze_only) - return stats; - - /* stats is NULL if ambulkdelete not called */ - /* OK to return NULL if index not changed */ - if (stats == NULL) - return NULL; - - stats->num_pages = RelationGetNumberOfBlocks(rel); - - return stats; -} diff --git a/packages/server/postgres/extensions/pgvector/src/hnswvacuum.o b/packages/server/postgres/extensions/pgvector/src/hnswvacuum.o deleted file mode 100644 index c2391ee0765ca88ee417530e954670977ed3c1e5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8976 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 diff --git a/packages/server/postgres/extensions/pgvector/src/ivfbuild.c b/packages/server/postgres/extensions/pgvector/src/ivfbuild.c deleted file mode 100644 index cc4f7a3a99e..00000000000 --- a/packages/server/postgres/extensions/pgvector/src/ivfbuild.c +++ /dev/null @@ -1,1111 +0,0 @@ -#include "postgres.h" - -#include - -#include "access/parallel.h" -#include "access/xact.h" -#include "catalog/index.h" -#include "catalog/pg_operator_d.h" -#include "catalog/pg_type_d.h" -#include "ivfflat.h" -#include "miscadmin.h" -#include "storage/bufmgr.h" -#include "utils/memutils.h" -#include "tcop/tcopprot.h" - -#if PG_VERSION_NUM >= 140000 -#include "utils/backend_progress.h" -#elif PG_VERSION_NUM >= 120000 -#include "pgstat.h" -#endif - -#if PG_VERSION_NUM >= 120000 -#include "access/tableam.h" -#include "commands/progress.h" -#else -#define PROGRESS_CREATEIDX_SUBPHASE 0 -#define PROGRESS_CREATEIDX_TUPLES_TOTAL 0 -#define PROGRESS_CREATEIDX_TUPLES_DONE 0 -#endif - -#if PG_VERSION_NUM >= 130000 -#define CALLBACK_ITEM_POINTER ItemPointer tid -#else -#define CALLBACK_ITEM_POINTER HeapTuple hup -#endif - -#if PG_VERSION_NUM >= 120000 -#define UpdateProgress(index, val) pgstat_progress_update_param(index, val) -#else -#define UpdateProgress(index, val) ((void)val) -#endif - -#if PG_VERSION_NUM >= 140000 -#include "utils/backend_status.h" -#include "utils/wait_event.h" -#endif - -#if PG_VERSION_NUM >= 120000 -#include "access/table.h" -#include "optimizer/optimizer.h" -#else -#include "access/heapam.h" -#include "optimizer/planner.h" -#include "pgstat.h" -#endif - -#define PARALLEL_KEY_IVFFLAT_SHARED UINT64CONST(0xA000000000000001) -#define PARALLEL_KEY_TUPLESORT UINT64CONST(0xA000000000000002) -#define PARALLEL_KEY_IVFFLAT_CENTERS UINT64CONST(0xA000000000000003) -#define PARALLEL_KEY_QUERY_TEXT UINT64CONST(0xA000000000000004) - -/* - * Add sample - */ -static void -AddSample(Datum *values, IvfflatBuildState * buildstate) -{ - VectorArray samples = buildstate->samples; - int targsamples = samples->maxlen; - - /* Detoast once for all calls */ - Datum value = PointerGetDatum(PG_DETOAST_DATUM(values[0])); - - /* - * Normalize with KMEANS_NORM_PROC since spherical distance function - * expects unit vectors - */ - if (buildstate->kmeansnormprocinfo != NULL) - { - if (!IvfflatNormValue(buildstate->kmeansnormprocinfo, buildstate->collation, &value, buildstate->normvec)) - return; - } - - if (samples->length < targsamples) - { - VectorArraySet(samples, samples->length, DatumGetVector(value)); - samples->length++; - } - else - { - if (buildstate->rowstoskip < 0) - buildstate->rowstoskip = reservoir_get_next_S(&buildstate->rstate, samples->length, targsamples); - - if (buildstate->rowstoskip <= 0) - { -#if PG_VERSION_NUM >= 150000 - int k = (int) (targsamples * sampler_random_fract(&buildstate->rstate.randstate)); -#else - int k = (int) (targsamples * sampler_random_fract(buildstate->rstate.randstate)); -#endif - - Assert(k >= 0 && k < targsamples); - VectorArraySet(samples, k, DatumGetVector(value)); - } - - buildstate->rowstoskip -= 1; - } -} - -/* - * Callback for sampling - */ -static void -SampleCallback(Relation index, CALLBACK_ITEM_POINTER, Datum *values, - bool *isnull, bool tupleIsAlive, void *state) -{ - IvfflatBuildState *buildstate = (IvfflatBuildState *) state; - MemoryContext oldCtx; - - /* Skip nulls */ - if (isnull[0]) - return; - - /* Use memory context since detoast can allocate */ - oldCtx = MemoryContextSwitchTo(buildstate->tmpCtx); - - /* Add sample */ - AddSample(values, state); - - /* Reset memory context */ - MemoryContextSwitchTo(oldCtx); - MemoryContextReset(buildstate->tmpCtx); -} - -/* - * Sample rows with same logic as ANALYZE - */ -static void -SampleRows(IvfflatBuildState * buildstate) -{ - int targsamples = buildstate->samples->maxlen; - BlockNumber totalblocks = RelationGetNumberOfBlocks(buildstate->heap); - - buildstate->rowstoskip = -1; - - BlockSampler_Init(&buildstate->bs, totalblocks, targsamples, RandomInt()); - - reservoir_init_selection_state(&buildstate->rstate, targsamples); - while (BlockSampler_HasMore(&buildstate->bs)) - { - BlockNumber targblock = BlockSampler_Next(&buildstate->bs); - -#if PG_VERSION_NUM >= 120000 - table_index_build_range_scan(buildstate->heap, buildstate->index, buildstate->indexInfo, - false, true, false, targblock, 1, SampleCallback, (void *) buildstate, NULL); -#else - IndexBuildHeapRangeScan(buildstate->heap, buildstate->index, buildstate->indexInfo, - false, true, targblock, 1, SampleCallback, (void *) buildstate, NULL); -#endif - } -} - -/* - * Add tuple to sort - */ -static void -AddTupleToSort(Relation index, ItemPointer tid, Datum *values, IvfflatBuildState * buildstate) -{ - double distance; - double minDistance = DBL_MAX; - int closestCenter = 0; - VectorArray centers = buildstate->centers; - TupleTableSlot *slot = buildstate->slot; - - /* Detoast once for all calls */ - Datum value = PointerGetDatum(PG_DETOAST_DATUM(values[0])); - - /* Normalize if needed */ - if (buildstate->normprocinfo != NULL) - { - if (!IvfflatNormValue(buildstate->normprocinfo, buildstate->collation, &value, buildstate->normvec)) - return; - } - - /* Find the list that minimizes the distance */ - for (int i = 0; i < centers->length; i++) - { - distance = DatumGetFloat8(FunctionCall2Coll(buildstate->procinfo, buildstate->collation, value, PointerGetDatum(VectorArrayGet(centers, i)))); - - if (distance < minDistance) - { - minDistance = distance; - closestCenter = i; - } - } - -#ifdef IVFFLAT_KMEANS_DEBUG - buildstate->inertia += minDistance; - buildstate->listSums[closestCenter] += minDistance; - buildstate->listCounts[closestCenter]++; -#endif - - /* Create a virtual tuple */ - ExecClearTuple(slot); - slot->tts_values[0] = Int32GetDatum(closestCenter); - slot->tts_isnull[0] = false; - slot->tts_values[1] = PointerGetDatum(tid); - slot->tts_isnull[1] = false; - slot->tts_values[2] = value; - slot->tts_isnull[2] = false; - ExecStoreVirtualTuple(slot); - - /* - * Add tuple to sort - * - * tuplesort_puttupleslot comment: Input data is always copied; the caller - * need not save it. - */ - tuplesort_puttupleslot(buildstate->sortstate, slot); - - buildstate->indtuples++; -} - -/* - * Callback for table_index_build_scan - */ -static void -BuildCallback(Relation index, CALLBACK_ITEM_POINTER, Datum *values, - bool *isnull, bool tupleIsAlive, void *state) -{ - IvfflatBuildState *buildstate = (IvfflatBuildState *) state; - MemoryContext oldCtx; - -#if PG_VERSION_NUM < 130000 - ItemPointer tid = &hup->t_self; -#endif - - /* Skip nulls */ - if (isnull[0]) - return; - - /* Use memory context since detoast can allocate */ - oldCtx = MemoryContextSwitchTo(buildstate->tmpCtx); - - /* Add tuple to sort */ - AddTupleToSort(index, tid, values, buildstate); - - /* Reset memory context */ - MemoryContextSwitchTo(oldCtx); - MemoryContextReset(buildstate->tmpCtx); -} - -/* - * Get index tuple from sort state - */ -static inline void -GetNextTuple(Tuplesortstate *sortstate, TupleDesc tupdesc, TupleTableSlot *slot, IndexTuple *itup, int *list) -{ - Datum value; - bool isnull; - - if (tuplesort_gettupleslot(sortstate, true, false, slot, NULL)) - { - *list = DatumGetInt32(slot_getattr(slot, 1, &isnull)); - value = slot_getattr(slot, 3, &isnull); - - /* Form the index tuple */ - *itup = index_form_tuple(tupdesc, &value, &isnull); - (*itup)->t_tid = *((ItemPointer) DatumGetPointer(slot_getattr(slot, 2, &isnull))); - } - else - *list = -1; -} - -/* - * Create initial entry pages - */ -static void -InsertTuples(Relation index, IvfflatBuildState * buildstate, ForkNumber forkNum) -{ - int list; - IndexTuple itup = NULL; /* silence compiler warning */ - int64 inserted = 0; - -#if PG_VERSION_NUM >= 120000 - TupleTableSlot *slot = MakeSingleTupleTableSlot(buildstate->tupdesc, &TTSOpsMinimalTuple); -#else - TupleTableSlot *slot = MakeSingleTupleTableSlot(buildstate->tupdesc); -#endif - TupleDesc tupdesc = RelationGetDescr(index); - - UpdateProgress(PROGRESS_CREATEIDX_SUBPHASE, PROGRESS_IVFFLAT_PHASE_LOAD); - - UpdateProgress(PROGRESS_CREATEIDX_TUPLES_TOTAL, buildstate->indtuples); - - GetNextTuple(buildstate->sortstate, tupdesc, slot, &itup, &list); - - for (int i = 0; i < buildstate->centers->length; i++) - { - Buffer buf; - Page page; - GenericXLogState *state; - BlockNumber startPage; - BlockNumber insertPage; - - /* Can take a while, so ensure we can interrupt */ - /* Needs to be called when no buffer locks are held */ - CHECK_FOR_INTERRUPTS(); - - buf = IvfflatNewBuffer(index, forkNum); - IvfflatInitRegisterPage(index, &buf, &page, &state); - - startPage = BufferGetBlockNumber(buf); - - /* Get all tuples for list */ - while (list == i) - { - /* Check for free space */ - Size itemsz = MAXALIGN(IndexTupleSize(itup)); - - if (PageGetFreeSpace(page) < itemsz) - IvfflatAppendPage(index, &buf, &page, &state, forkNum); - - /* Add the item */ - if (PageAddItem(page, (Item) itup, itemsz, InvalidOffsetNumber, false, false) == InvalidOffsetNumber) - elog(ERROR, "failed to add index item to \"%s\"", RelationGetRelationName(index)); - - pfree(itup); - - UpdateProgress(PROGRESS_CREATEIDX_TUPLES_DONE, ++inserted); - - GetNextTuple(buildstate->sortstate, tupdesc, slot, &itup, &list); - } - - insertPage = BufferGetBlockNumber(buf); - - IvfflatCommitBuffer(buf, state); - - /* Set the start and insert pages */ - IvfflatUpdateList(index, buildstate->listInfo[i], insertPage, InvalidBlockNumber, startPage, forkNum); - } -} - -/* - * Initialize the build state - */ -static void -InitBuildState(IvfflatBuildState * buildstate, Relation heap, Relation index, IndexInfo *indexInfo) -{ - buildstate->heap = heap; - buildstate->index = index; - buildstate->indexInfo = indexInfo; - - buildstate->lists = IvfflatGetLists(index); - buildstate->dimensions = TupleDescAttr(index->rd_att, 0)->atttypmod; - - /* Require column to have dimensions to be indexed */ - if (buildstate->dimensions < 0) - elog(ERROR, "column does not have dimensions"); - - if (buildstate->dimensions > IVFFLAT_MAX_DIM) - elog(ERROR, "column cannot have more than %d dimensions for ivfflat index", IVFFLAT_MAX_DIM); - - buildstate->reltuples = 0; - buildstate->indtuples = 0; - - /* Get support functions */ - buildstate->procinfo = index_getprocinfo(index, 1, IVFFLAT_DISTANCE_PROC); - buildstate->normprocinfo = IvfflatOptionalProcInfo(index, IVFFLAT_NORM_PROC); - buildstate->kmeansnormprocinfo = IvfflatOptionalProcInfo(index, IVFFLAT_KMEANS_NORM_PROC); - buildstate->collation = index->rd_indcollation[0]; - - /* Require more than one dimension for spherical k-means */ - if (buildstate->kmeansnormprocinfo != NULL && buildstate->dimensions == 1) - elog(ERROR, "dimensions must be greater than one for this opclass"); - - /* Create tuple description for sorting */ -#if PG_VERSION_NUM >= 120000 - buildstate->tupdesc = CreateTemplateTupleDesc(3); -#else - buildstate->tupdesc = CreateTemplateTupleDesc(3, false); -#endif - TupleDescInitEntry(buildstate->tupdesc, (AttrNumber) 1, "list", INT4OID, -1, 0); - TupleDescInitEntry(buildstate->tupdesc, (AttrNumber) 2, "tid", TIDOID, -1, 0); - TupleDescInitEntry(buildstate->tupdesc, (AttrNumber) 3, "vector", RelationGetDescr(index)->attrs[0].atttypid, -1, 0); - -#if PG_VERSION_NUM >= 120000 - buildstate->slot = MakeSingleTupleTableSlot(buildstate->tupdesc, &TTSOpsVirtual); -#else - buildstate->slot = MakeSingleTupleTableSlot(buildstate->tupdesc); -#endif - - buildstate->centers = VectorArrayInit(buildstate->lists, buildstate->dimensions); - buildstate->listInfo = palloc(sizeof(ListInfo) * buildstate->lists); - - /* Reuse for each tuple */ - buildstate->normvec = InitVector(buildstate->dimensions); - - buildstate->tmpCtx = AllocSetContextCreate(CurrentMemoryContext, - "Ivfflat build temporary context", - ALLOCSET_DEFAULT_SIZES); - -#ifdef IVFFLAT_KMEANS_DEBUG - buildstate->inertia = 0; - buildstate->listSums = palloc0(sizeof(double) * buildstate->lists); - buildstate->listCounts = palloc0(sizeof(int) * buildstate->lists); -#endif - - buildstate->ivfleader = NULL; -} - -/* - * Free resources - */ -static void -FreeBuildState(IvfflatBuildState * buildstate) -{ - VectorArrayFree(buildstate->centers); - pfree(buildstate->listInfo); - pfree(buildstate->normvec); - -#ifdef IVFFLAT_KMEANS_DEBUG - pfree(buildstate->listSums); - pfree(buildstate->listCounts); -#endif - - MemoryContextDelete(buildstate->tmpCtx); -} - -/* - * Compute centers - */ -static void -ComputeCenters(IvfflatBuildState * buildstate) -{ - int numSamples; - - UpdateProgress(PROGRESS_CREATEIDX_SUBPHASE, PROGRESS_IVFFLAT_PHASE_KMEANS); - - /* Target 50 samples per list, with at least 10000 samples */ - /* The number of samples has a large effect on index build time */ - numSamples = buildstate->lists * 50; - if (numSamples < 10000) - numSamples = 10000; - - /* Skip samples for unlogged table */ - if (buildstate->heap == NULL) - numSamples = 1; - - /* Sample rows */ - /* TODO Ensure within maintenance_work_mem */ - buildstate->samples = VectorArrayInit(numSamples, buildstate->dimensions); - if (buildstate->heap != NULL) - { - SampleRows(buildstate); - - if (buildstate->samples->length < buildstate->lists) - { - ereport(NOTICE, - (errmsg("ivfflat index created with little data"), - errdetail("This will cause low recall."), - errhint("Drop the index until the table has more data."))); - } - } - - /* Calculate centers */ - IvfflatBench("k-means", IvfflatKmeans(buildstate->index, buildstate->samples, buildstate->centers)); - - /* Free samples before we allocate more memory */ - VectorArrayFree(buildstate->samples); -} - -/* - * Create the metapage - */ -static void -CreateMetaPage(Relation index, int dimensions, int lists, ForkNumber forkNum) -{ - Buffer buf; - Page page; - GenericXLogState *state; - IvfflatMetaPage metap; - - buf = IvfflatNewBuffer(index, forkNum); - IvfflatInitRegisterPage(index, &buf, &page, &state); - - /* Set metapage data */ - metap = IvfflatPageGetMeta(page); - metap->magicNumber = IVFFLAT_MAGIC_NUMBER; - metap->version = IVFFLAT_VERSION; - metap->dimensions = dimensions; - metap->lists = lists; - ((PageHeader) page)->pd_lower = - ((char *) metap + sizeof(IvfflatMetaPageData)) - (char *) page; - - IvfflatCommitBuffer(buf, state); -} - -/* - * Create list pages - */ -static void -CreateListPages(Relation index, VectorArray centers, int dimensions, - int lists, ForkNumber forkNum, ListInfo * *listInfo) -{ - Buffer buf; - Page page; - GenericXLogState *state; - OffsetNumber offno; - Size itemsz; - IvfflatList list; - - itemsz = MAXALIGN(IVFFLAT_LIST_SIZE(dimensions)); - list = palloc(itemsz); - - buf = IvfflatNewBuffer(index, forkNum); - IvfflatInitRegisterPage(index, &buf, &page, &state); - - for (int i = 0; i < lists; i++) - { - /* Load list */ - list->startPage = InvalidBlockNumber; - list->insertPage = InvalidBlockNumber; - memcpy(&list->center, VectorArrayGet(centers, i), VECTOR_SIZE(dimensions)); - - /* Ensure free space */ - if (PageGetFreeSpace(page) < itemsz) - IvfflatAppendPage(index, &buf, &page, &state, forkNum); - - /* Add the item */ - offno = PageAddItem(page, (Item) list, itemsz, InvalidOffsetNumber, false, false); - if (offno == InvalidOffsetNumber) - elog(ERROR, "failed to add index item to \"%s\"", RelationGetRelationName(index)); - - /* Save location info */ - (*listInfo)[i].blkno = BufferGetBlockNumber(buf); - (*listInfo)[i].offno = offno; - } - - IvfflatCommitBuffer(buf, state); - - pfree(list); -} - -/* - * Print k-means metrics - */ -#ifdef IVFFLAT_KMEANS_DEBUG -static void -PrintKmeansMetrics(IvfflatBuildState * buildstate) -{ - elog(INFO, "inertia: %.3e", buildstate->inertia); - - /* Calculate Davies-Bouldin index */ - if (buildstate->lists > 1 && !buildstate->ivfleader) - { - double db = 0.0; - - /* Calculate average distance */ - for (int i = 0; i < buildstate->lists; i++) - { - if (buildstate->listCounts[i] > 0) - buildstate->listSums[i] /= buildstate->listCounts[i]; - } - - for (int i = 0; i < buildstate->lists; i++) - { - double max = 0.0; - double distance; - - for (int j = 0; j < buildstate->lists; j++) - { - if (j == i) - continue; - - distance = DatumGetFloat8(FunctionCall2Coll(buildstate->procinfo, buildstate->collation, PointerGetDatum(VectorArrayGet(buildstate->centers, i)), PointerGetDatum(VectorArrayGet(buildstate->centers, j)))); - distance = (buildstate->listSums[i] + buildstate->listSums[j]) / distance; - - if (distance > max) - max = distance; - } - db += max; - } - db /= buildstate->lists; - elog(INFO, "davies-bouldin: %.3f", db); - } -} -#endif - -/* - * Within leader, wait for end of heap scan - */ -static double -ParallelHeapScan(IvfflatBuildState * buildstate) -{ - IvfflatShared *ivfshared = buildstate->ivfleader->ivfshared; - int nparticipanttuplesorts; - double reltuples; - - nparticipanttuplesorts = buildstate->ivfleader->nparticipanttuplesorts; - for (;;) - { - SpinLockAcquire(&ivfshared->mutex); - if (ivfshared->nparticipantsdone == nparticipanttuplesorts) - { - buildstate->indtuples = ivfshared->indtuples; - reltuples = ivfshared->reltuples; -#ifdef IVFFLAT_KMEANS_DEBUG - buildstate->inertia = ivfshared->inertia; -#endif - SpinLockRelease(&ivfshared->mutex); - break; - } - SpinLockRelease(&ivfshared->mutex); - - ConditionVariableSleep(&ivfshared->workersdonecv, - WAIT_EVENT_PARALLEL_CREATE_INDEX_SCAN); - } - - ConditionVariableCancelSleep(); - - return reltuples; -} - -/* - * Perform a worker's portion of a parallel sort - */ -static void -IvfflatParallelScanAndSort(IvfflatSpool * ivfspool, IvfflatShared * ivfshared, Sharedsort *sharedsort, Vector * ivfcenters, int sortmem, bool progress) -{ - SortCoordinate coordinate; - IvfflatBuildState buildstate; -#if PG_VERSION_NUM >= 120000 - TableScanDesc scan; -#else - HeapScanDesc scan; -#endif - double reltuples; - IndexInfo *indexInfo; - - /* Sort options, which must match AssignTuples */ - AttrNumber attNums[] = {1}; - Oid sortOperators[] = {Int4LessOperator}; - Oid sortCollations[] = {InvalidOid}; - bool nullsFirstFlags[] = {false}; - - /* Initialize local tuplesort coordination state */ - coordinate = palloc0(sizeof(SortCoordinateData)); - coordinate->isWorker = true; - coordinate->nParticipants = -1; - coordinate->sharedsort = sharedsort; - - /* Join parallel scan */ - indexInfo = BuildIndexInfo(ivfspool->index); - indexInfo->ii_Concurrent = ivfshared->isconcurrent; - InitBuildState(&buildstate, ivfspool->heap, ivfspool->index, indexInfo); - memcpy(buildstate.centers->items, ivfcenters, VECTOR_SIZE(buildstate.centers->dim) * buildstate.centers->maxlen); - buildstate.centers->length = buildstate.centers->maxlen; - ivfspool->sortstate = tuplesort_begin_heap(buildstate.tupdesc, 1, attNums, sortOperators, sortCollations, nullsFirstFlags, sortmem, coordinate, false); - buildstate.sortstate = ivfspool->sortstate; -#if PG_VERSION_NUM >= 120000 - scan = table_beginscan_parallel(ivfspool->heap, - ParallelTableScanFromIvfflatShared(ivfshared)); - reltuples = table_index_build_scan(ivfspool->heap, ivfspool->index, indexInfo, - true, progress, BuildCallback, - (void *) &buildstate, scan); -#else - scan = heap_beginscan_parallel(ivfspool->heap, &ivfshared->heapdesc); - reltuples = IndexBuildHeapScan(ivfspool->heap, ivfspool->index, indexInfo, - true, BuildCallback, - (void *) &buildstate, scan); -#endif - - /* Execute this worker's part of the sort */ - tuplesort_performsort(ivfspool->sortstate); - - /* Record statistics */ - SpinLockAcquire(&ivfshared->mutex); - ivfshared->nparticipantsdone++; - ivfshared->reltuples += reltuples; - ivfshared->indtuples += buildstate.indtuples; -#ifdef IVFFLAT_KMEANS_DEBUG - ivfshared->inertia += buildstate.inertia; -#endif - SpinLockRelease(&ivfshared->mutex); - - /* Log statistics */ - if (progress) - ereport(DEBUG1, (errmsg("leader processed " INT64_FORMAT " tuples", (int64) reltuples))); - else - ereport(DEBUG1, (errmsg("worker processed " INT64_FORMAT " tuples", (int64) reltuples))); - - /* Notify leader */ - ConditionVariableSignal(&ivfshared->workersdonecv); - - /* We can end tuplesorts immediately */ - tuplesort_end(ivfspool->sortstate); - - FreeBuildState(&buildstate); -} - -/* - * Perform work within a launched parallel process - */ -void -IvfflatParallelBuildMain(dsm_segment *seg, shm_toc *toc) -{ - char *sharedquery; - IvfflatSpool *ivfspool; - IvfflatShared *ivfshared; - Sharedsort *sharedsort; - Vector *ivfcenters; - Relation heapRel; - Relation indexRel; - LOCKMODE heapLockmode; - LOCKMODE indexLockmode; - int sortmem; - - /* Set debug_query_string for individual workers first */ - sharedquery = shm_toc_lookup(toc, PARALLEL_KEY_QUERY_TEXT, true); - debug_query_string = sharedquery; - - /* Report the query string from leader */ - pgstat_report_activity(STATE_RUNNING, debug_query_string); - - /* Look up shared state */ - ivfshared = shm_toc_lookup(toc, PARALLEL_KEY_IVFFLAT_SHARED, false); - - /* Open relations using lock modes known to be obtained by index.c */ - if (!ivfshared->isconcurrent) - { - heapLockmode = ShareLock; - indexLockmode = AccessExclusiveLock; - } - else - { - heapLockmode = ShareUpdateExclusiveLock; - indexLockmode = RowExclusiveLock; - } - - /* Open relations within worker */ -#if PG_VERSION_NUM >= 120000 - heapRel = table_open(ivfshared->heaprelid, heapLockmode); -#else - heapRel = heap_open(ivfshared->heaprelid, heapLockmode); -#endif - indexRel = index_open(ivfshared->indexrelid, indexLockmode); - - /* Initialize worker's own spool */ - ivfspool = (IvfflatSpool *) palloc0(sizeof(IvfflatSpool)); - ivfspool->heap = heapRel; - ivfspool->index = indexRel; - - /* Look up shared state private to tuplesort.c */ - sharedsort = shm_toc_lookup(toc, PARALLEL_KEY_TUPLESORT, false); - tuplesort_attach_shared(sharedsort, seg); - - ivfcenters = shm_toc_lookup(toc, PARALLEL_KEY_IVFFLAT_CENTERS, false); - - /* Perform sorting */ - sortmem = maintenance_work_mem / ivfshared->scantuplesortstates; - IvfflatParallelScanAndSort(ivfspool, ivfshared, sharedsort, ivfcenters, sortmem, false); - - /* Close relations within worker */ - index_close(indexRel, indexLockmode); -#if PG_VERSION_NUM >= 120000 - table_close(heapRel, heapLockmode); -#else - heap_close(heapRel, heapLockmode); -#endif -} - -/* - * End parallel build - */ -static void -IvfflatEndParallel(IvfflatLeader * ivfleader) -{ - /* Shutdown worker processes */ - WaitForParallelWorkersToFinish(ivfleader->pcxt); - - /* Free last reference to MVCC snapshot, if one was used */ - if (IsMVCCSnapshot(ivfleader->snapshot)) - UnregisterSnapshot(ivfleader->snapshot); - DestroyParallelContext(ivfleader->pcxt); - ExitParallelMode(); -} - -/* - * Return size of shared memory required for parallel index build - */ -static Size -ParallelEstimateShared(Relation heap, Snapshot snapshot) -{ -#if PG_VERSION_NUM >= 120000 - return add_size(BUFFERALIGN(sizeof(IvfflatShared)), table_parallelscan_estimate(heap, snapshot)); -#else - if (!IsMVCCSnapshot(snapshot)) - { - Assert(snapshot == SnapshotAny); - return sizeof(IvfflatShared); - } - - return add_size(offsetof(IvfflatShared, heapdesc) + - offsetof(ParallelHeapScanDescData, phs_snapshot_data), - EstimateSnapshotSpace(snapshot)); -#endif -} - -/* - * Within leader, participate as a parallel worker - */ -static void -IvfflatLeaderParticipateAsWorker(IvfflatBuildState * buildstate) -{ - IvfflatLeader *ivfleader = buildstate->ivfleader; - IvfflatSpool *leaderworker; - int sortmem; - - /* Allocate memory and initialize private spool */ - leaderworker = (IvfflatSpool *) palloc0(sizeof(IvfflatSpool)); - leaderworker->heap = buildstate->heap; - leaderworker->index = buildstate->index; - - /* Perform work common to all participants */ - sortmem = maintenance_work_mem / ivfleader->nparticipanttuplesorts; - IvfflatParallelScanAndSort(leaderworker, ivfleader->ivfshared, - ivfleader->sharedsort, ivfleader->ivfcenters, - sortmem, true); -} - -/* - * Begin parallel build - */ -static void -IvfflatBeginParallel(IvfflatBuildState * buildstate, bool isconcurrent, int request) -{ - ParallelContext *pcxt; - int scantuplesortstates; - Snapshot snapshot; - Size estivfshared; - Size estsort; - Size estcenters; - IvfflatShared *ivfshared; - Sharedsort *sharedsort; - Vector *ivfcenters; - IvfflatLeader *ivfleader = (IvfflatLeader *) palloc0(sizeof(IvfflatLeader)); - bool leaderparticipates = true; - int querylen; - -#ifdef DISABLE_LEADER_PARTICIPATION - leaderparticipates = false; -#endif - - /* Enter parallel mode and create context */ - EnterParallelMode(); - Assert(request > 0); -#if PG_VERSION_NUM >= 120000 - pcxt = CreateParallelContext("vector", "IvfflatParallelBuildMain", request); -#else - pcxt = CreateParallelContext("vector", "IvfflatParallelBuildMain", request, true); -#endif - - scantuplesortstates = leaderparticipates ? request + 1 : request; - - /* Get snapshot for table scan */ - if (!isconcurrent) - snapshot = SnapshotAny; - else - snapshot = RegisterSnapshot(GetTransactionSnapshot()); - - /* Estimate size of workspaces */ - estivfshared = ParallelEstimateShared(buildstate->heap, snapshot); - shm_toc_estimate_chunk(&pcxt->estimator, estivfshared); - estsort = tuplesort_estimate_shared(scantuplesortstates); - shm_toc_estimate_chunk(&pcxt->estimator, estsort); - estcenters = VECTOR_SIZE(buildstate->dimensions) * buildstate->lists; - shm_toc_estimate_chunk(&pcxt->estimator, estcenters); - shm_toc_estimate_keys(&pcxt->estimator, 3); - - /* Finally, estimate PARALLEL_KEY_QUERY_TEXT space */ - if (debug_query_string) - { - querylen = strlen(debug_query_string); - shm_toc_estimate_chunk(&pcxt->estimator, querylen + 1); - shm_toc_estimate_keys(&pcxt->estimator, 1); - } - else - querylen = 0; /* keep compiler quiet */ - - /* Everyone's had a chance to ask for space, so now create the DSM */ - InitializeParallelDSM(pcxt); - - /* If no DSM segment was available, back out (do serial build) */ - if (pcxt->seg == NULL) - { - if (IsMVCCSnapshot(snapshot)) - UnregisterSnapshot(snapshot); - DestroyParallelContext(pcxt); - ExitParallelMode(); - return; - } - - /* Store shared build state, for which we reserved space */ - ivfshared = (IvfflatShared *) shm_toc_allocate(pcxt->toc, estivfshared); - /* Initialize immutable state */ - ivfshared->heaprelid = RelationGetRelid(buildstate->heap); - ivfshared->indexrelid = RelationGetRelid(buildstate->index); - ivfshared->isconcurrent = isconcurrent; - ivfshared->scantuplesortstates = scantuplesortstates; - ConditionVariableInit(&ivfshared->workersdonecv); - SpinLockInit(&ivfshared->mutex); - /* Initialize mutable state */ - ivfshared->nparticipantsdone = 0; - ivfshared->reltuples = 0; - ivfshared->indtuples = 0; -#ifdef IVFFLAT_KMEANS_DEBUG - ivfshared->inertia = 0; -#endif -#if PG_VERSION_NUM >= 120000 - table_parallelscan_initialize(buildstate->heap, - ParallelTableScanFromIvfflatShared(ivfshared), - snapshot); -#else - heap_parallelscan_initialize(&ivfshared->heapdesc, buildstate->heap, snapshot); -#endif - - /* Store shared tuplesort-private state, for which we reserved space */ - sharedsort = (Sharedsort *) shm_toc_allocate(pcxt->toc, estsort); - tuplesort_initialize_shared(sharedsort, scantuplesortstates, - pcxt->seg); - - ivfcenters = (Vector *) shm_toc_allocate(pcxt->toc, estcenters); - memcpy(ivfcenters, buildstate->centers->items, estcenters); - - shm_toc_insert(pcxt->toc, PARALLEL_KEY_IVFFLAT_SHARED, ivfshared); - shm_toc_insert(pcxt->toc, PARALLEL_KEY_TUPLESORT, sharedsort); - shm_toc_insert(pcxt->toc, PARALLEL_KEY_IVFFLAT_CENTERS, ivfcenters); - - /* Store query string for workers */ - if (debug_query_string) - { - char *sharedquery; - - sharedquery = (char *) shm_toc_allocate(pcxt->toc, querylen + 1); - memcpy(sharedquery, debug_query_string, querylen + 1); - shm_toc_insert(pcxt->toc, PARALLEL_KEY_QUERY_TEXT, sharedquery); - } - - /* Launch workers, saving status for leader/caller */ - LaunchParallelWorkers(pcxt); - ivfleader->pcxt = pcxt; - ivfleader->nparticipanttuplesorts = pcxt->nworkers_launched; - if (leaderparticipates) - ivfleader->nparticipanttuplesorts++; - ivfleader->ivfshared = ivfshared; - ivfleader->sharedsort = sharedsort; - ivfleader->snapshot = snapshot; - ivfleader->ivfcenters = ivfcenters; - - /* If no workers were successfully launched, back out (do serial build) */ - if (pcxt->nworkers_launched == 0) - { - IvfflatEndParallel(ivfleader); - return; - } - - /* Log participants */ - ereport(DEBUG1, (errmsg("using %d parallel workers", pcxt->nworkers_launched))); - - /* Save leader state now that it's clear build will be parallel */ - buildstate->ivfleader = ivfleader; - - /* Join heap scan ourselves */ - if (leaderparticipates) - IvfflatLeaderParticipateAsWorker(buildstate); - - /* Wait for all launched workers */ - WaitForParallelWorkersToAttach(pcxt); -} - -/* - * Scan table for tuples to index - */ -static void -AssignTuples(IvfflatBuildState * buildstate) -{ - int parallel_workers = 0; - SortCoordinate coordinate = NULL; - - /* Sort options, which must match IvfflatParallelScanAndSort */ - AttrNumber attNums[] = {1}; - Oid sortOperators[] = {Int4LessOperator}; - Oid sortCollations[] = {InvalidOid}; - bool nullsFirstFlags[] = {false}; - - UpdateProgress(PROGRESS_CREATEIDX_SUBPHASE, PROGRESS_IVFFLAT_PHASE_ASSIGN); - - /* Calculate parallel workers */ - if (buildstate->heap != NULL) - parallel_workers = plan_create_index_workers(RelationGetRelid(buildstate->heap), RelationGetRelid(buildstate->index)); - - /* Attempt to launch parallel worker scan when required */ - if (parallel_workers > 0) - IvfflatBeginParallel(buildstate, buildstate->indexInfo->ii_Concurrent, parallel_workers); - - /* Set up coordination state if at least one worker launched */ - if (buildstate->ivfleader) - { - coordinate = (SortCoordinate) palloc0(sizeof(SortCoordinateData)); - coordinate->isWorker = false; - coordinate->nParticipants = buildstate->ivfleader->nparticipanttuplesorts; - coordinate->sharedsort = buildstate->ivfleader->sharedsort; - } - - /* Begin serial/leader tuplesort */ - buildstate->sortstate = tuplesort_begin_heap(buildstate->tupdesc, 1, attNums, sortOperators, sortCollations, nullsFirstFlags, maintenance_work_mem, coordinate, false); - - /* Add tuples to sort */ - if (buildstate->heap != NULL) - { - if (buildstate->ivfleader) - buildstate->reltuples = ParallelHeapScan(buildstate); - else - { -#if PG_VERSION_NUM >= 120000 - buildstate->reltuples = table_index_build_scan(buildstate->heap, buildstate->index, buildstate->indexInfo, - true, true, BuildCallback, (void *) buildstate, NULL); -#else - buildstate->reltuples = IndexBuildHeapScan(buildstate->heap, buildstate->index, buildstate->indexInfo, - true, BuildCallback, (void *) buildstate, NULL); -#endif - } - -#ifdef IVFFLAT_KMEANS_DEBUG - PrintKmeansMetrics(buildstate); -#endif - } -} - -/* - * Create entry pages - */ -static void -CreateEntryPages(IvfflatBuildState * buildstate, ForkNumber forkNum) -{ - /* Assign */ - IvfflatBench("assign tuples", AssignTuples(buildstate)); - - /* Sort */ - IvfflatBench("sort tuples", tuplesort_performsort(buildstate->sortstate)); - - /* Load */ - IvfflatBench("load tuples", InsertTuples(buildstate->index, buildstate, forkNum)); - - /* End sort */ - tuplesort_end(buildstate->sortstate); - - /* End parallel build */ - if (buildstate->ivfleader) - IvfflatEndParallel(buildstate->ivfleader); -} - -/* - * Build the index - */ -static void -BuildIndex(Relation heap, Relation index, IndexInfo *indexInfo, - IvfflatBuildState * buildstate, ForkNumber forkNum) -{ - InitBuildState(buildstate, heap, index, indexInfo); - - ComputeCenters(buildstate); - - /* Create pages */ - CreateMetaPage(index, buildstate->dimensions, buildstate->lists, forkNum); - CreateListPages(index, buildstate->centers, buildstate->dimensions, buildstate->lists, forkNum, &buildstate->listInfo); - CreateEntryPages(buildstate, forkNum); - - FreeBuildState(buildstate); -} - -/* - * Build the index for a logged table - */ -IndexBuildResult * -ivfflatbuild(Relation heap, Relation index, IndexInfo *indexInfo) -{ - IndexBuildResult *result; - IvfflatBuildState buildstate; - - BuildIndex(heap, index, indexInfo, &buildstate, MAIN_FORKNUM); - - result = (IndexBuildResult *) palloc(sizeof(IndexBuildResult)); - result->heap_tuples = buildstate.reltuples; - result->index_tuples = buildstate.indtuples; - - return result; -} - -/* - * Build the index for an unlogged table - */ -void -ivfflatbuildempty(Relation index) -{ - IndexInfo *indexInfo = BuildIndexInfo(index); - IvfflatBuildState buildstate; - - BuildIndex(NULL, index, indexInfo, &buildstate, INIT_FORKNUM); -} diff --git a/packages/server/postgres/extensions/pgvector/src/ivfbuild.o b/packages/server/postgres/extensions/pgvector/src/ivfbuild.o deleted file mode 100644 index c514832e50c0b6985dd02c16799805b06302c894..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14592 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 diff --git a/packages/server/postgres/extensions/pgvector/src/ivfflat.c b/packages/server/postgres/extensions/pgvector/src/ivfflat.c deleted file mode 100644 index 5057adc2aef..00000000000 --- a/packages/server/postgres/extensions/pgvector/src/ivfflat.c +++ /dev/null @@ -1,251 +0,0 @@ -#include "postgres.h" - -#include - -#include "access/amapi.h" -#include "commands/vacuum.h" -#include "ivfflat.h" -#include "utils/guc.h" -#include "utils/selfuncs.h" -#include "utils/spccache.h" - -#if PG_VERSION_NUM >= 120000 -#include "commands/progress.h" -#endif - -int ivfflat_probes; -static relopt_kind ivfflat_relopt_kind; - -/* - * Initialize index options and variables - */ -void -IvfflatInit(void) -{ - ivfflat_relopt_kind = add_reloption_kind(); - add_int_reloption(ivfflat_relopt_kind, "lists", "Number of inverted lists", - IVFFLAT_DEFAULT_LISTS, IVFFLAT_MIN_LISTS, IVFFLAT_MAX_LISTS -#if PG_VERSION_NUM >= 130000 - ,AccessExclusiveLock -#endif - ); - - DefineCustomIntVariable("ivfflat.probes", "Sets the number of probes", - "Valid range is 1..lists.", &ivfflat_probes, - IVFFLAT_DEFAULT_PROBES, IVFFLAT_MIN_LISTS, IVFFLAT_MAX_LISTS, PGC_USERSET, 0, NULL, NULL, NULL); -} - -/* - * Get the name of index build phase - */ -#if PG_VERSION_NUM >= 120000 -static char * -ivfflatbuildphasename(int64 phasenum) -{ - switch (phasenum) - { - case PROGRESS_CREATEIDX_SUBPHASE_INITIALIZE: - return "initializing"; - case PROGRESS_IVFFLAT_PHASE_KMEANS: - return "performing k-means"; - case PROGRESS_IVFFLAT_PHASE_ASSIGN: - return "assigning tuples"; - case PROGRESS_IVFFLAT_PHASE_LOAD: - return "loading tuples"; - default: - return NULL; - } -} -#endif - -/* - * Estimate the cost of an index scan - */ -static void -ivfflatcostestimate(PlannerInfo *root, IndexPath *path, double loop_count, - Cost *indexStartupCost, Cost *indexTotalCost, - Selectivity *indexSelectivity, double *indexCorrelation, - double *indexPages) -{ - GenericCosts costs; - int lists; - double ratio; - double spc_seq_page_cost; - Relation indexRel; -#if PG_VERSION_NUM < 120000 - List *qinfos; -#endif - - /* Never use index without order */ - if (path->indexorderbys == NULL) - { - *indexStartupCost = DBL_MAX; - *indexTotalCost = DBL_MAX; - *indexSelectivity = 0; - *indexCorrelation = 0; - *indexPages = 0; - return; - } - - MemSet(&costs, 0, sizeof(costs)); - - indexRel = index_open(path->indexinfo->indexoid, NoLock); - lists = IvfflatGetLists(indexRel); - index_close(indexRel, NoLock); - - /* Get the ratio of lists that we need to visit */ - ratio = ((double) ivfflat_probes) / lists; - if (ratio > 1.0) - ratio = 1.0; - - /* - * This gives us the subset of tuples to visit. This value is passed into - * the generic cost estimator to determine the number of pages to visit - * during the index scan. - */ - costs.numIndexTuples = path->indexinfo->tuples * ratio; - -#if PG_VERSION_NUM >= 120000 - genericcostestimate(root, path, loop_count, &costs); -#else - qinfos = deconstruct_indexquals(path); - genericcostestimate(root, path, loop_count, qinfos, &costs); -#endif - - get_tablespace_page_costs(path->indexinfo->reltablespace, NULL, &spc_seq_page_cost); - - /* Adjust cost if needed since TOAST not included in seq scan cost */ - if (costs.numIndexPages > path->indexinfo->rel->pages && ratio < 0.5) - { - /* Change all page cost from random to sequential */ - costs.indexTotalCost -= costs.numIndexPages * (costs.spc_random_page_cost - spc_seq_page_cost); - - /* Remove cost of extra pages */ - costs.indexTotalCost -= (costs.numIndexPages - path->indexinfo->rel->pages) * spc_seq_page_cost; - } - else - { - /* Change some page cost from random to sequential */ - costs.indexTotalCost -= 0.5 * costs.numIndexPages * (costs.spc_random_page_cost - spc_seq_page_cost); - } - - /* - * If the list selectivity is lower than what is returned from the generic - * cost estimator, use that. - */ - if (ratio < costs.indexSelectivity) - costs.indexSelectivity = ratio; - - /* Use total cost since most work happens before first tuple is returned */ - *indexStartupCost = costs.indexTotalCost; - *indexTotalCost = costs.indexTotalCost; - *indexSelectivity = costs.indexSelectivity; - *indexCorrelation = costs.indexCorrelation; - *indexPages = costs.numIndexPages; -} - -/* - * Parse and validate the reloptions - */ -static bytea * -ivfflatoptions(Datum reloptions, bool validate) -{ - static const relopt_parse_elt tab[] = { - {"lists", RELOPT_TYPE_INT, offsetof(IvfflatOptions, lists)}, - }; - -#if PG_VERSION_NUM >= 130000 - return (bytea *) build_reloptions(reloptions, validate, - ivfflat_relopt_kind, - sizeof(IvfflatOptions), - tab, lengthof(tab)); -#else - relopt_value *options; - int numoptions; - IvfflatOptions *rdopts; - - options = parseRelOptions(reloptions, validate, ivfflat_relopt_kind, &numoptions); - rdopts = allocateReloptStruct(sizeof(IvfflatOptions), options, numoptions); - fillRelOptions((void *) rdopts, sizeof(IvfflatOptions), options, numoptions, - validate, tab, lengthof(tab)); - - return (bytea *) rdopts; -#endif -} - -/* - * Validate catalog entries for the specified operator class - */ -static bool -ivfflatvalidate(Oid opclassoid) -{ - return true; -} - -/* - * Define index handler - * - * See https://www.postgresql.org/docs/current/index-api.html - */ -PGDLLEXPORT PG_FUNCTION_INFO_V1(ivfflathandler); -Datum -ivfflathandler(PG_FUNCTION_ARGS) -{ - IndexAmRoutine *amroutine = makeNode(IndexAmRoutine); - - amroutine->amstrategies = 0; - amroutine->amsupport = 4; -#if PG_VERSION_NUM >= 130000 - amroutine->amoptsprocnum = 0; -#endif - amroutine->amcanorder = false; - amroutine->amcanorderbyop = true; - amroutine->amcanbackward = false; /* can change direction mid-scan */ - amroutine->amcanunique = false; - amroutine->amcanmulticol = false; - amroutine->amoptionalkey = true; - amroutine->amsearcharray = false; - amroutine->amsearchnulls = false; - amroutine->amstorage = false; - amroutine->amclusterable = false; - amroutine->ampredlocks = false; - amroutine->amcanparallel = false; - amroutine->amcaninclude = false; -#if PG_VERSION_NUM >= 130000 - amroutine->amusemaintenanceworkmem = false; /* not used during VACUUM */ - amroutine->amparallelvacuumoptions = VACUUM_OPTION_PARALLEL_BULKDEL; -#endif - amroutine->amkeytype = InvalidOid; - - /* Interface functions */ - amroutine->ambuild = ivfflatbuild; - amroutine->ambuildempty = ivfflatbuildempty; - amroutine->aminsert = ivfflatinsert; - amroutine->ambulkdelete = ivfflatbulkdelete; - amroutine->amvacuumcleanup = ivfflatvacuumcleanup; - amroutine->amcanreturn = NULL; /* tuple not included in heapsort */ - amroutine->amcostestimate = ivfflatcostestimate; - amroutine->amoptions = ivfflatoptions; - amroutine->amproperty = NULL; /* TODO AMPROP_DISTANCE_ORDERABLE */ -#if PG_VERSION_NUM >= 120000 - amroutine->ambuildphasename = ivfflatbuildphasename; -#endif - amroutine->amvalidate = ivfflatvalidate; -#if PG_VERSION_NUM >= 140000 - amroutine->amadjustmembers = NULL; -#endif - amroutine->ambeginscan = ivfflatbeginscan; - amroutine->amrescan = ivfflatrescan; - amroutine->amgettuple = ivfflatgettuple; - amroutine->amgetbitmap = NULL; - amroutine->amendscan = ivfflatendscan; - amroutine->ammarkpos = NULL; - amroutine->amrestrpos = NULL; - - /* Interface functions to support parallel index scans */ - amroutine->amestimateparallelscan = NULL; - amroutine->aminitparallelscan = NULL; - amroutine->amparallelrescan = NULL; - - PG_RETURN_POINTER(amroutine); -} diff --git a/packages/server/postgres/extensions/pgvector/src/ivfflat.h b/packages/server/postgres/extensions/pgvector/src/ivfflat.h deleted file mode 100644 index 4e701e2f003..00000000000 --- a/packages/server/postgres/extensions/pgvector/src/ivfflat.h +++ /dev/null @@ -1,306 +0,0 @@ -#ifndef IVFFLAT_H -#define IVFFLAT_H - -#include "postgres.h" - -#include "access/generic_xlog.h" -#include "access/parallel.h" -#include "access/reloptions.h" -#include "nodes/execnodes.h" -#include "port.h" /* for random() */ -#include "utils/sampling.h" -#include "utils/tuplesort.h" -#include "vector.h" - -#if PG_VERSION_NUM >= 150000 -#include "common/pg_prng.h" -#endif - -#if PG_VERSION_NUM < 120000 -#include "access/relscan.h" -#endif - -#ifdef IVFFLAT_BENCH -#include "portability/instr_time.h" -#endif - -#define IVFFLAT_MAX_DIM 2000 - -/* Support functions */ -#define IVFFLAT_DISTANCE_PROC 1 -#define IVFFLAT_NORM_PROC 2 -#define IVFFLAT_KMEANS_DISTANCE_PROC 3 -#define IVFFLAT_KMEANS_NORM_PROC 4 - -#define IVFFLAT_VERSION 1 -#define IVFFLAT_MAGIC_NUMBER 0x14FF1A7 -#define IVFFLAT_PAGE_ID 0xFF84 - -/* Preserved page numbers */ -#define IVFFLAT_METAPAGE_BLKNO 0 -#define IVFFLAT_HEAD_BLKNO 1 /* first list page */ - -/* IVFFlat parameters */ -#define IVFFLAT_DEFAULT_LISTS 100 -#define IVFFLAT_MIN_LISTS 1 -#define IVFFLAT_MAX_LISTS 32768 -#define IVFFLAT_DEFAULT_PROBES 1 - -/* Build phases */ -/* PROGRESS_CREATEIDX_SUBPHASE_INITIALIZE is 1 */ -#define PROGRESS_IVFFLAT_PHASE_KMEANS 2 -#define PROGRESS_IVFFLAT_PHASE_ASSIGN 3 -#define PROGRESS_IVFFLAT_PHASE_LOAD 4 - -#define IVFFLAT_LIST_SIZE(_dim) (offsetof(IvfflatListData, center) + VECTOR_SIZE(_dim)) - -#define IvfflatPageGetOpaque(page) ((IvfflatPageOpaque) PageGetSpecialPointer(page)) -#define IvfflatPageGetMeta(page) ((IvfflatMetaPageData *) PageGetContents(page)) - -#ifdef IVFFLAT_BENCH -#define IvfflatBench(name, code) \ - do { \ - instr_time start; \ - instr_time duration; \ - INSTR_TIME_SET_CURRENT(start); \ - (code); \ - INSTR_TIME_SET_CURRENT(duration); \ - INSTR_TIME_SUBTRACT(duration, start); \ - elog(INFO, "%s: %.3f ms", name, INSTR_TIME_GET_MILLISEC(duration)); \ - } while (0) -#else -#define IvfflatBench(name, code) (code) -#endif - -#if PG_VERSION_NUM >= 150000 -#define RandomDouble() pg_prng_double(&pg_global_prng_state) -#define RandomInt() pg_prng_uint32(&pg_global_prng_state) -#else -#define RandomDouble() (((double) random()) / MAX_RANDOM_VALUE) -#define RandomInt() random() -#endif - -/* Variables */ -extern int ivfflat_probes; - -typedef struct VectorArrayData -{ - int length; - int maxlen; - int dim; - Vector *items; -} VectorArrayData; - -typedef VectorArrayData * VectorArray; - -typedef struct ListInfo -{ - BlockNumber blkno; - OffsetNumber offno; -} ListInfo; - -/* IVFFlat index options */ -typedef struct IvfflatOptions -{ - int32 vl_len_; /* varlena header (do not touch directly!) */ - int lists; /* number of lists */ -} IvfflatOptions; - -typedef struct IvfflatSpool -{ - Tuplesortstate *sortstate; - Relation heap; - Relation index; -} IvfflatSpool; - -typedef struct IvfflatShared -{ - /* Immutable state */ - Oid heaprelid; - Oid indexrelid; - bool isconcurrent; - int scantuplesortstates; - - /* Worker progress */ - ConditionVariable workersdonecv; - - /* Mutex for mutable state */ - slock_t mutex; - - /* Mutable state */ - int nparticipantsdone; - double reltuples; - double indtuples; - -#ifdef IVFFLAT_KMEANS_DEBUG - double inertia; -#endif - -#if PG_VERSION_NUM < 120000 - ParallelHeapScanDescData heapdesc; /* must come last */ -#endif -} IvfflatShared; - -#if PG_VERSION_NUM >= 120000 -#define ParallelTableScanFromIvfflatShared(shared) \ - (ParallelTableScanDesc) ((char *) (shared) + BUFFERALIGN(sizeof(IvfflatShared))) -#endif - -typedef struct IvfflatLeader -{ - ParallelContext *pcxt; - int nparticipanttuplesorts; - IvfflatShared *ivfshared; - Sharedsort *sharedsort; - Snapshot snapshot; - Vector *ivfcenters; -} IvfflatLeader; - -typedef struct IvfflatBuildState -{ - /* Info */ - Relation heap; - Relation index; - IndexInfo *indexInfo; - - /* Settings */ - int dimensions; - int lists; - - /* Statistics */ - double indtuples; - double reltuples; - - /* Support functions */ - FmgrInfo *procinfo; - FmgrInfo *normprocinfo; - FmgrInfo *kmeansnormprocinfo; - Oid collation; - - /* Variables */ - VectorArray samples; - VectorArray centers; - ListInfo *listInfo; - Vector *normvec; - -#ifdef IVFFLAT_KMEANS_DEBUG - double inertia; - double *listSums; - int *listCounts; -#endif - - /* Sampling */ - BlockSamplerData bs; - ReservoirStateData rstate; - int rowstoskip; - - /* Sorting */ - Tuplesortstate *sortstate; - TupleDesc tupdesc; - TupleTableSlot *slot; - - /* Memory */ - MemoryContext tmpCtx; - - /* Parallel builds */ - IvfflatLeader *ivfleader; -} IvfflatBuildState; - -typedef struct IvfflatMetaPageData -{ - uint32 magicNumber; - uint32 version; - uint16 dimensions; - uint16 lists; -} IvfflatMetaPageData; - -typedef IvfflatMetaPageData * IvfflatMetaPage; - -typedef struct IvfflatPageOpaqueData -{ - BlockNumber nextblkno; - uint16 unused; - uint16 page_id; /* for identification of IVFFlat indexes */ -} IvfflatPageOpaqueData; - -typedef IvfflatPageOpaqueData * IvfflatPageOpaque; - -typedef struct IvfflatListData -{ - BlockNumber startPage; - BlockNumber insertPage; - Vector center; -} IvfflatListData; - -typedef IvfflatListData * IvfflatList; - -typedef struct IvfflatScanList -{ - pairingheap_node ph_node; - BlockNumber startPage; - double distance; -} IvfflatScanList; - -typedef struct IvfflatScanOpaqueData -{ - int probes; - bool first; - Buffer buf; - - /* Sorting */ - Tuplesortstate *sortstate; - TupleDesc tupdesc; - TupleTableSlot *slot; - bool isnull; - - /* Support functions */ - FmgrInfo *procinfo; - FmgrInfo *normprocinfo; - Oid collation; - - /* Lists */ - pairingheap *listQueue; - IvfflatScanList lists[FLEXIBLE_ARRAY_MEMBER]; /* must come last */ -} IvfflatScanOpaqueData; - -typedef IvfflatScanOpaqueData * IvfflatScanOpaque; - -#define VECTOR_ARRAY_SIZE(_length, _dim) (sizeof(VectorArrayData) + (_length) * VECTOR_SIZE(_dim)) -#define VECTOR_ARRAY_OFFSET(_arr, _offset) ((char*) (_arr)->items + (_offset) * VECTOR_SIZE((_arr)->dim)) -#define VectorArrayGet(_arr, _offset) ((Vector *) VECTOR_ARRAY_OFFSET(_arr, _offset)) -#define VectorArraySet(_arr, _offset, _val) memcpy(VECTOR_ARRAY_OFFSET(_arr, _offset), _val, VECTOR_SIZE((_arr)->dim)) - -/* Methods */ -VectorArray VectorArrayInit(int maxlen, int dimensions); -void VectorArrayFree(VectorArray arr); -void PrintVectorArray(char *msg, VectorArray arr); -void IvfflatKmeans(Relation index, VectorArray samples, VectorArray centers); -FmgrInfo *IvfflatOptionalProcInfo(Relation rel, uint16 procnum); -bool IvfflatNormValue(FmgrInfo *procinfo, Oid collation, Datum *value, Vector * result); -int IvfflatGetLists(Relation index); -void IvfflatUpdateList(Relation index, ListInfo listInfo, BlockNumber insertPage, BlockNumber originalInsertPage, BlockNumber startPage, ForkNumber forkNum); -void IvfflatCommitBuffer(Buffer buf, GenericXLogState *state); -void IvfflatAppendPage(Relation index, Buffer *buf, Page *page, GenericXLogState **state, ForkNumber forkNum); -Buffer IvfflatNewBuffer(Relation index, ForkNumber forkNum); -void IvfflatInitPage(Buffer buf, Page page); -void IvfflatInitRegisterPage(Relation index, Buffer *buf, Page *page, GenericXLogState **state); -void IvfflatInit(void); -PGDLLEXPORT void IvfflatParallelBuildMain(dsm_segment *seg, shm_toc *toc); - -/* Index access methods */ -IndexBuildResult *ivfflatbuild(Relation heap, Relation index, IndexInfo *indexInfo); -void ivfflatbuildempty(Relation index); -bool ivfflatinsert(Relation index, Datum *values, bool *isnull, ItemPointer heap_tid, Relation heap, IndexUniqueCheck checkUnique -#if PG_VERSION_NUM >= 140000 - ,bool indexUnchanged -#endif - ,IndexInfo *indexInfo -); -IndexBulkDeleteResult *ivfflatbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats, IndexBulkDeleteCallback callback, void *callback_state); -IndexBulkDeleteResult *ivfflatvacuumcleanup(IndexVacuumInfo *info, IndexBulkDeleteResult *stats); -IndexScanDesc ivfflatbeginscan(Relation index, int nkeys, int norderbys); -void ivfflatrescan(IndexScanDesc scan, ScanKey keys, int nkeys, ScanKey orderbys, int norderbys); -bool ivfflatgettuple(IndexScanDesc scan, ScanDirection dir); -void ivfflatendscan(IndexScanDesc scan); - -#endif diff --git a/packages/server/postgres/extensions/pgvector/src/ivfflat.o b/packages/server/postgres/extensions/pgvector/src/ivfflat.o deleted file mode 100644 index 3179f3543944215f5531c54039086dc2393887b7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4344 zcmb7HZ){sv6+iDi$9`$MB!e=tE;uhu%2HU|ZWPF*T63FjOPL2%7mW(lJU`nn$)nHD zmi^KtOPRbs64Cxh&Qu{lmDXwfP#ZKrs>CW$)@cLsVG?UKPzC$ocKML;M@k`#q^lQx z*Y`fhaa19$eDCl6&bjB@bI*P6-RnQx_{XPPh$KQ`O$+K9G8zpPuq;x%cpmEH& z@K+JSi0~JIKXh4H7w&x!bfWUS-i8h;N(i+BbrN<(ATwg))XhXJR<(_25kgQpgv!e2 zMP>1CL%jp_|1GO;)1~UPT&g}xv#>ph$Eb+l&Lh5J578{9Eba$ByQOfrj|zudsCd{f z6%MnptE*I6ZKsu0h0cSsuxFd?5L+=xkrigU1Jw`v-vgO_b$WjZ^Uq#X^gns_tY4je zvs6`Rmc^nS=mI?V_n}l~(%UTaDBG-CcCv zvu}l;o$#Ud$)$}lr7lxX_(Y#(xea3IYpKh+HGjRYo(Rbz18Wr&a$EfMT9&+84?Gx;aLin6Wj$-R7G4=&Idt(~qI z-!+s(kUEW^)ZqkMj~Kz4uiGP4QmtU6o2u6^M@6}_d{yZz)4q7HK3{9({Iy>8j1(?2 zS>>)smD#GT*xz49F7#Y6o+8Z%z9iF#++hc`oW96N>U?{oIy9IJDrz#= zGZ+u{sEJ@te=^ukv{jWCUtW-DWe)XCRC%6uA>X^0PU<)jlvE?w=qE78(lw3G~zv1+SK?mf9?yH8EP2I(6;h!p1wJe zPMA3@Gpbq6aWm(dDUBIOpdxM5o&i*N{?2iOeL(~@2ke$`3O1Etab9+D`3C;pdF4&V}udw_A^mofe*$Jhw)9`HjPW5Yn4E%pJ9u?X;P@Vy*kdw@7o zqPsbU`++!uqJ130T|hju(N2!xtw7`;+QBib0(Zfmk7Mj6lJh0-Wsb2W-~jj{$JhdJ zC-^+aSOwS*{sG6>&wyWm$!Q?YH2W0C*o#1%r_mXX;Yr|K7?%RFe8oA&jskJzh#uh> zW>+U%wW93I1cswPANVlG@E{N$r~%}duq9EAW4H^5R7X2GhPMK-#ApY{@D^Y<><~MB zj;KuoVoVzw|5+d#_Zc97`DwcdguVZ?z=FWIz=%LiAfPs0mE);^eB@|AJ~R@Lhob>m z>kG)8T>*JUdQnp459Erh%J0eVqkTu7L;Gv_q8yNaA)iBgMm~%7NAfAOMfoJO&&e}r zm!+GqU6hv4&Pxl-k5rL-$|>ozbOw0ZcgAmf_v}p_n3b?2;M#Lw@eQ{j5%2OBh#aL>-n9C$6L>@7;lK@EaMIS zUJ>K3i};Ize=7J71ixL(_mtq<1^+SQ$g_8^ydN>OBSx>B6a1tQ{8VuEUa%sbQ%=x+ z0^0_2G9YW@k6^w=u7EW1olW{@n(Y7F#HX9=A8OLS+N8guiL-SDgk`79bk?0BeP|+=GadI)Go8sz4P~6# zUpQ}|<>YaKlYWpN-3M|0l0Ig-x@#nGzT&@ysb`Hb6Mr<=(eI6W*mR?{yMX5>lXWfp zRU*AMvz{~UxL->zIdUi}7wVct6=V9D%Qnm?&7=PSIPE4eeHqQNtEc`xc z<}$am3Z6keyxn5Xb3+_9$Q9P8Soyo8HsiiDCZsbkJNSkEq`6nzlIUcHQ zK}fi08sudOTUEOWk$%8LI?bVpyqifMblk^{oW-`V9<4}-eheY-EY&wj$QoxNecki2 z&$ctk?_dY^*|>k1a3BVYU1qT&aJI9dL9g?5uk$ss-E{V=9z_Rqk4Mn~{fbA?0lm+o P=z!kqQFK6ei#OoEEGi$s diff --git a/packages/server/postgres/extensions/pgvector/src/ivfinsert.c b/packages/server/postgres/extensions/pgvector/src/ivfinsert.c deleted file mode 100644 index cc744af9187..00000000000 --- a/packages/server/postgres/extensions/pgvector/src/ivfinsert.c +++ /dev/null @@ -1,206 +0,0 @@ -#include "postgres.h" - -#include - -#include "ivfflat.h" -#include "storage/bufmgr.h" -#include "storage/lmgr.h" -#include "utils/memutils.h" - -/* - * Find the list that minimizes the distance function - */ -static void -FindInsertPage(Relation rel, Datum *values, BlockNumber *insertPage, ListInfo * listInfo) -{ - Buffer cbuf; - Page cpage; - IvfflatList list; - double distance; - double minDistance = DBL_MAX; - BlockNumber nextblkno = IVFFLAT_HEAD_BLKNO; - FmgrInfo *procinfo; - Oid collation; - OffsetNumber offno; - OffsetNumber maxoffno; - - /* Avoid compiler warning */ - listInfo->blkno = nextblkno; - listInfo->offno = FirstOffsetNumber; - - procinfo = index_getprocinfo(rel, 1, IVFFLAT_DISTANCE_PROC); - collation = rel->rd_indcollation[0]; - - /* Search all list pages */ - while (BlockNumberIsValid(nextblkno)) - { - cbuf = ReadBuffer(rel, nextblkno); - LockBuffer(cbuf, BUFFER_LOCK_SHARE); - cpage = BufferGetPage(cbuf); - maxoffno = PageGetMaxOffsetNumber(cpage); - - for (offno = FirstOffsetNumber; offno <= maxoffno; offno = OffsetNumberNext(offno)) - { - list = (IvfflatList) PageGetItem(cpage, PageGetItemId(cpage, offno)); - distance = DatumGetFloat8(FunctionCall2Coll(procinfo, collation, values[0], PointerGetDatum(&list->center))); - - if (distance < minDistance || !BlockNumberIsValid(*insertPage)) - { - *insertPage = list->insertPage; - listInfo->blkno = nextblkno; - listInfo->offno = offno; - minDistance = distance; - } - } - - nextblkno = IvfflatPageGetOpaque(cpage)->nextblkno; - - UnlockReleaseBuffer(cbuf); - } -} - -/* - * Insert a tuple into the index - */ -static void -InsertTuple(Relation rel, Datum *values, bool *isnull, ItemPointer heap_tid, Relation heapRel) -{ - IndexTuple itup; - Datum value; - FmgrInfo *normprocinfo; - Buffer buf; - Page page; - GenericXLogState *state; - Size itemsz; - BlockNumber insertPage = InvalidBlockNumber; - ListInfo listInfo; - BlockNumber originalInsertPage; - - /* Detoast once for all calls */ - value = PointerGetDatum(PG_DETOAST_DATUM(values[0])); - - /* Normalize if needed */ - normprocinfo = IvfflatOptionalProcInfo(rel, IVFFLAT_NORM_PROC); - if (normprocinfo != NULL) - { - if (!IvfflatNormValue(normprocinfo, rel->rd_indcollation[0], &value, NULL)) - return; - } - - /* Find the insert page - sets the page and list info */ - FindInsertPage(rel, values, &insertPage, &listInfo); - Assert(BlockNumberIsValid(insertPage)); - originalInsertPage = insertPage; - - /* Form tuple */ - itup = index_form_tuple(RelationGetDescr(rel), &value, isnull); - itup->t_tid = *heap_tid; - - /* Get tuple size */ - itemsz = MAXALIGN(IndexTupleSize(itup)); - Assert(itemsz <= BLCKSZ - MAXALIGN(SizeOfPageHeaderData) - MAXALIGN(sizeof(IvfflatPageOpaqueData))); - - /* Find a page to insert the item */ - for (;;) - { - buf = ReadBuffer(rel, insertPage); - LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE); - - state = GenericXLogStart(rel); - page = GenericXLogRegisterBuffer(state, buf, 0); - - if (PageGetFreeSpace(page) >= itemsz) - break; - - insertPage = IvfflatPageGetOpaque(page)->nextblkno; - - if (BlockNumberIsValid(insertPage)) - { - /* Move to next page */ - GenericXLogAbort(state); - UnlockReleaseBuffer(buf); - } - else - { - Buffer newbuf; - Page newpage; - - /* Add a new page */ - LockRelationForExtension(rel, ExclusiveLock); - newbuf = IvfflatNewBuffer(rel, MAIN_FORKNUM); - UnlockRelationForExtension(rel, ExclusiveLock); - - /* Init new page */ - newpage = GenericXLogRegisterBuffer(state, newbuf, GENERIC_XLOG_FULL_IMAGE); - IvfflatInitPage(newbuf, newpage); - - /* Update insert page */ - insertPage = BufferGetBlockNumber(newbuf); - - /* Update previous buffer */ - IvfflatPageGetOpaque(page)->nextblkno = insertPage; - - /* Commit */ - MarkBufferDirty(newbuf); - MarkBufferDirty(buf); - GenericXLogFinish(state); - - /* Unlock previous buffer */ - UnlockReleaseBuffer(buf); - - /* Prepare new buffer */ - state = GenericXLogStart(rel); - buf = newbuf; - page = GenericXLogRegisterBuffer(state, buf, 0); - break; - } - } - - /* Add to next offset */ - if (PageAddItem(page, (Item) itup, itemsz, InvalidOffsetNumber, false, false) == InvalidOffsetNumber) - elog(ERROR, "failed to add index item to \"%s\"", RelationGetRelationName(rel)); - - IvfflatCommitBuffer(buf, state); - - /* Update the insert page */ - if (insertPage != originalInsertPage) - IvfflatUpdateList(rel, listInfo, insertPage, originalInsertPage, InvalidBlockNumber, MAIN_FORKNUM); -} - -/* - * Insert a tuple into the index - */ -bool -ivfflatinsert(Relation index, Datum *values, bool *isnull, ItemPointer heap_tid, - Relation heap, IndexUniqueCheck checkUnique -#if PG_VERSION_NUM >= 140000 - ,bool indexUnchanged -#endif - ,IndexInfo *indexInfo -) -{ - MemoryContext oldCtx; - MemoryContext insertCtx; - - /* Skip nulls */ - if (isnull[0]) - return false; - - /* - * Use memory context since detoast, IvfflatNormValue, and - * index_form_tuple can allocate - */ - insertCtx = AllocSetContextCreate(CurrentMemoryContext, - "Ivfflat insert temporary context", - ALLOCSET_DEFAULT_SIZES); - oldCtx = MemoryContextSwitchTo(insertCtx); - - /* Insert tuple */ - InsertTuple(index, values, isnull, heap_tid, heap); - - /* Delete memory context */ - MemoryContextSwitchTo(oldCtx); - MemoryContextDelete(insertCtx); - - return false; -} diff --git a/packages/server/postgres/extensions/pgvector/src/ivfinsert.o b/packages/server/postgres/extensions/pgvector/src/ivfinsert.o deleted file mode 100644 index 113bc1dc1635a68651f77b51cdede2c5e64f0534..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3624 zcmb7HTWl298UA<1cxMCU;v0q#du@}}MyxS6K@u#Du`zbFTxvE6RYJ$h?(BN-?9O&} z9K4C5tQ0?>QdM4>MpcxHrSQP6kct!@?5Gd4;Q>{Za#2*N7b$D#gRQg@Qr+M+IPQ1m z;*5vB6m+BtsrIYyu!jkz;2g-_g!!z~zqv^RsHAXPK~y@(i3NNwKVH!-|hx~S}GHb8PZt#qLPkFQn=48zh>tp+RRD#V{@Alh-_bLT<(Lc5#1`w4=_MS z>qOs-egk%0KxV}B%AnhU!|PFhFadNLoi&Ke&U^*Xo6)~-Tls|he7>zJ^+;P+TuN3K z7bSo3n-_)R!l^)U`L!pC%Wrap0TQ2?+d&8a%k!lQ&y^N=Uul7-QZ3PBmJ=((=*VHR zg{Z(${tKQahN?DC4I!7ezJcCFQ->baQL!DmbBAb#*|lKYYTH)epV@AQZ9U~{1n&M7!IymB)Zb4t&p~gY zmMW-K-JMo3_xB4U)8#$l4D)}e|%kxw?gcvwib#v9UWotLpyuoc9y|=Uyx%8()mxVt_)50_2m49%;We#dhF7uvuLr)Q8oB)q9q2 zEMc9^`aA^9_|4St^--Q8BW+^t)zOh@3AM#RKwY<)@-l3gp5@<~!}s;yw!9MQK)sgu z`qGbprY}1WHugDdhMd;QSlwz%&Yqby<+XM`7A}FuavvUTpDsh!%GJsF@i}}~i`q4{ zScGjE`)HQmQM$`Fl(3hFa7CH5!d-QM)nV4IiNx2BI%nv?OJZ@@N0Irq{tf|qVU`c< z@^ue?#O84qYkm)NhM$^swy%pEJ3|d-o&?_VYd^-B9mLrkz!^S{v)o^Nq#Y~HxAmoP z#tXN2=`YL|%X{S$`h4M(59gc1`KAKf?^9fU2=z11-AEJm`4%e7@i?>C_vi&?=ZtX; zO&$fWrOujHkb++hzLH3&Q6s3Nv$Adk4LOz3^r)T-#ajs zK?Q1z)B1F_m9lzlkMc^wa?ly0LDN%YEThT@>@?gv)a*kgJ&o~MV^0U}qQ~SSIITpX z!$6$5@Bs_EyMXu$@3yeJ9oPoFoj|w@2QBR04BQF+eK0Zq&@JE&;7#CG-~wDnk_$p7@J)2KgQTa(j~~<^ahU{duxsSZIAppkDRTa+uv=E zoYmbe|DA_Ej{M#7_dN0k9{&eDbh}4C?V-Q&&=)-REH>*-3wZL$dFZnqni;$8PkQLD z;n*$zt4Ch+&=)-Zn>=)@M}NXYlXN1gj~*RMBxJo$(TyBQy<@sAr;QVGO4D<__O&V5 z7whwxc+`-?O4cChxSW=CC3ZHfC40_mIyksD+3joHoof`%7*VDW)?!iBigr}hVxy-u z1=q5kCCSCik}7rLzh`oSo|G?I;#E$smP#px!*nGwBqx!ZtUDqEv}Z@s^Jx}xNLJ-& zR(2GIqyjxi6tQB~d7tEnnUvaYAHNo0fB zAtp&ZDyDNrHtZ$p)n%;A;Hnt -#include - -#include "ivfflat.h" -#include "miscadmin.h" - -/* - * Initialize with kmeans++ - * - * https://theory.stanford.edu/~sergei/papers/kMeansPP-soda.pdf - */ -static void -InitCenters(Relation index, VectorArray samples, VectorArray centers, float *lowerBound) -{ - FmgrInfo *procinfo; - Oid collation; - int64 j; - double distance; - double sum; - double choice; - Vector *vec; - float *weight = palloc(samples->length * sizeof(float)); - int numCenters = centers->maxlen; - int numSamples = samples->length; - - procinfo = index_getprocinfo(index, 1, IVFFLAT_KMEANS_DISTANCE_PROC); - collation = index->rd_indcollation[0]; - - /* Choose an initial center uniformly at random */ - VectorArraySet(centers, 0, VectorArrayGet(samples, RandomInt() % samples->length)); - centers->length++; - - for (j = 0; j < numSamples; j++) - weight[j] = DBL_MAX; - - for (int i = 0; i < numCenters; i++) - { - CHECK_FOR_INTERRUPTS(); - - sum = 0.0; - - for (j = 0; j < numSamples; j++) - { - vec = VectorArrayGet(samples, j); - - /* Only need to compute distance for new center */ - /* TODO Use triangle inequality to reduce distance calculations */ - distance = DatumGetFloat8(FunctionCall2Coll(procinfo, collation, PointerGetDatum(vec), PointerGetDatum(VectorArrayGet(centers, i)))); - - /* Set lower bound */ - lowerBound[j * numCenters + i] = distance; - - /* Use distance squared for weighted probability distribution */ - distance *= distance; - - if (distance < weight[j]) - weight[j] = distance; - - sum += weight[j]; - } - - /* Only compute lower bound on last iteration */ - if (i + 1 == numCenters) - break; - - /* Choose new center using weighted probability distribution. */ - choice = sum * RandomDouble(); - for (j = 0; j < numSamples - 1; j++) - { - choice -= weight[j]; - if (choice <= 0) - break; - } - - VectorArraySet(centers, i + 1, VectorArrayGet(samples, j)); - centers->length++; - } - - pfree(weight); -} - -/* - * Apply norm to vector - */ -static inline void -ApplyNorm(FmgrInfo *normprocinfo, Oid collation, Vector * vec) -{ - double norm = DatumGetFloat8(FunctionCall1Coll(normprocinfo, collation, PointerGetDatum(vec))); - - /* TODO Handle zero norm */ - if (norm > 0) - { - for (int i = 0; i < vec->dim; i++) - vec->x[i] /= norm; - } -} - -/* - * Compare vectors - */ -static int -CompareVectors(const void *a, const void *b) -{ - return vector_cmp_internal((Vector *) a, (Vector *) b); -} - -/* - * Quick approach if we have little data - */ -static void -QuickCenters(Relation index, VectorArray samples, VectorArray centers) -{ - Vector *vec; - int dimensions = centers->dim; - Oid collation = index->rd_indcollation[0]; - FmgrInfo *normprocinfo = IvfflatOptionalProcInfo(index, IVFFLAT_KMEANS_NORM_PROC); - - /* Copy existing vectors while avoiding duplicates */ - if (samples->length > 0) - { - qsort(samples->items, samples->length, VECTOR_SIZE(samples->dim), CompareVectors); - for (int i = 0; i < samples->length; i++) - { - vec = VectorArrayGet(samples, i); - - if (i == 0 || CompareVectors(vec, VectorArrayGet(samples, i - 1)) != 0) - { - VectorArraySet(centers, centers->length, vec); - centers->length++; - } - } - } - - /* Fill remaining with random data */ - while (centers->length < centers->maxlen) - { - vec = VectorArrayGet(centers, centers->length); - - SET_VARSIZE(vec, VECTOR_SIZE(dimensions)); - vec->dim = dimensions; - - for (int j = 0; j < dimensions; j++) - vec->x[j] = RandomDouble(); - - /* Normalize if needed (only needed for random centers) */ - if (normprocinfo != NULL) - ApplyNorm(normprocinfo, collation, vec); - - centers->length++; - } -} - -/* - * Use Elkan for performance. This requires distance function to satisfy triangle inequality. - * - * We use L2 distance for L2 (not L2 squared like index scan) - * and angular distance for inner product and cosine distance - * - * https://www.aaai.org/Papers/ICML/2003/ICML03-022.pdf - */ -static void -ElkanKmeans(Relation index, VectorArray samples, VectorArray centers) -{ - FmgrInfo *procinfo; - FmgrInfo *normprocinfo; - Oid collation; - Vector *vec; - Vector *newCenter; - int iteration; - int64 j; - int64 k; - int dimensions = centers->dim; - int numCenters = centers->maxlen; - int numSamples = samples->length; - VectorArray newCenters; - int *centerCounts; - int *closestCenters; - float *lowerBound; - float *upperBound; - float *s; - float *halfcdist; - float *newcdist; - int changes; - double minDistance; - int closestCenter; - double distance; - bool rj; - bool rjreset; - double dxcx; - double dxc; - - /* Calculate allocation sizes */ - Size samplesSize = VECTOR_ARRAY_SIZE(samples->maxlen, samples->dim); - Size centersSize = VECTOR_ARRAY_SIZE(centers->maxlen, centers->dim); - Size newCentersSize = VECTOR_ARRAY_SIZE(numCenters, dimensions); - Size centerCountsSize = sizeof(int) * numCenters; - Size closestCentersSize = sizeof(int) * numSamples; - Size lowerBoundSize = sizeof(float) * numSamples * numCenters; - Size upperBoundSize = sizeof(float) * numSamples; - Size sSize = sizeof(float) * numCenters; - Size halfcdistSize = sizeof(float) * numCenters * numCenters; - Size newcdistSize = sizeof(float) * numCenters; - - /* Calculate total size */ - Size totalSize = samplesSize + centersSize + newCentersSize + centerCountsSize + closestCentersSize + lowerBoundSize + upperBoundSize + sSize + halfcdistSize + newcdistSize; - - /* Check memory requirements */ - /* Add one to error message to ceil */ - if (totalSize > (Size) maintenance_work_mem * 1024L) - ereport(ERROR, - (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), - errmsg("memory required is %zu MB, maintenance_work_mem is %d MB", - totalSize / (1024 * 1024) + 1, maintenance_work_mem / 1024))); - - /* Ensure indexing does not overflow */ - if (numCenters * numCenters > INT_MAX) - elog(ERROR, "Indexing overflow detected. Please report a bug."); - - /* Set support functions */ - procinfo = index_getprocinfo(index, 1, IVFFLAT_KMEANS_DISTANCE_PROC); - normprocinfo = IvfflatOptionalProcInfo(index, IVFFLAT_KMEANS_NORM_PROC); - collation = index->rd_indcollation[0]; - - /* Allocate space */ - /* Use float instead of double to save memory */ - centerCounts = palloc(centerCountsSize); - closestCenters = palloc(closestCentersSize); - lowerBound = palloc_extended(lowerBoundSize, MCXT_ALLOC_HUGE); - upperBound = palloc(upperBoundSize); - s = palloc(sSize); - halfcdist = palloc_extended(halfcdistSize, MCXT_ALLOC_HUGE); - newcdist = palloc(newcdistSize); - - newCenters = VectorArrayInit(numCenters, dimensions); - for (j = 0; j < numCenters; j++) - { - vec = VectorArrayGet(newCenters, j); - SET_VARSIZE(vec, VECTOR_SIZE(dimensions)); - vec->dim = dimensions; - } - - /* Pick initial centers */ - InitCenters(index, samples, centers, lowerBound); - - /* Assign each x to its closest initial center c(x) = argmin d(x,c) */ - for (j = 0; j < numSamples; j++) - { - minDistance = DBL_MAX; - closestCenter = 0; - - /* Find closest center */ - for (k = 0; k < numCenters; k++) - { - /* TODO Use Lemma 1 in k-means++ initialization */ - distance = lowerBound[j * numCenters + k]; - - if (distance < minDistance) - { - minDistance = distance; - closestCenter = k; - } - } - - upperBound[j] = minDistance; - closestCenters[j] = closestCenter; - } - - /* Give 500 iterations to converge */ - for (iteration = 0; iteration < 500; iteration++) - { - /* Can take a while, so ensure we can interrupt */ - CHECK_FOR_INTERRUPTS(); - - changes = 0; - - /* Step 1: For all centers, compute distance */ - for (j = 0; j < numCenters; j++) - { - vec = VectorArrayGet(centers, j); - - for (k = j + 1; k < numCenters; k++) - { - distance = 0.5 * DatumGetFloat8(FunctionCall2Coll(procinfo, collation, PointerGetDatum(vec), PointerGetDatum(VectorArrayGet(centers, k)))); - halfcdist[j * numCenters + k] = distance; - halfcdist[k * numCenters + j] = distance; - } - } - - /* For all centers c, compute s(c) */ - for (j = 0; j < numCenters; j++) - { - minDistance = DBL_MAX; - - for (k = 0; k < numCenters; k++) - { - if (j == k) - continue; - - distance = halfcdist[j * numCenters + k]; - if (distance < minDistance) - minDistance = distance; - } - - s[j] = minDistance; - } - - rjreset = iteration != 0; - - for (j = 0; j < numSamples; j++) - { - /* Step 2: Identify all points x such that u(x) <= s(c(x)) */ - if (upperBound[j] <= s[closestCenters[j]]) - continue; - - rj = rjreset; - - for (k = 0; k < numCenters; k++) - { - /* Step 3: For all remaining points x and centers c */ - if (k == closestCenters[j]) - continue; - - if (upperBound[j] <= lowerBound[j * numCenters + k]) - continue; - - if (upperBound[j] <= halfcdist[closestCenters[j] * numCenters + k]) - continue; - - vec = VectorArrayGet(samples, j); - - /* Step 3a */ - if (rj) - { - dxcx = DatumGetFloat8(FunctionCall2Coll(procinfo, collation, PointerGetDatum(vec), PointerGetDatum(VectorArrayGet(centers, closestCenters[j])))); - - /* d(x,c(x)) computed, which is a form of d(x,c) */ - lowerBound[j * numCenters + closestCenters[j]] = dxcx; - upperBound[j] = dxcx; - - rj = false; - } - else - dxcx = upperBound[j]; - - /* Step 3b */ - if (dxcx > lowerBound[j * numCenters + k] || dxcx > halfcdist[closestCenters[j] * numCenters + k]) - { - dxc = DatumGetFloat8(FunctionCall2Coll(procinfo, collation, PointerGetDatum(vec), PointerGetDatum(VectorArrayGet(centers, k)))); - - /* d(x,c) calculated */ - lowerBound[j * numCenters + k] = dxc; - - if (dxc < dxcx) - { - closestCenters[j] = k; - - /* c(x) changed */ - upperBound[j] = dxc; - - changes++; - } - - } - } - } - - /* Step 4: For each center c, let m(c) be mean of all points assigned */ - for (j = 0; j < numCenters; j++) - { - vec = VectorArrayGet(newCenters, j); - for (k = 0; k < dimensions; k++) - vec->x[k] = 0.0; - - centerCounts[j] = 0; - } - - for (j = 0; j < numSamples; j++) - { - vec = VectorArrayGet(samples, j); - closestCenter = closestCenters[j]; - - /* Increment sum and count of closest center */ - newCenter = VectorArrayGet(newCenters, closestCenter); - for (k = 0; k < dimensions; k++) - newCenter->x[k] += vec->x[k]; - - centerCounts[closestCenter] += 1; - } - - for (j = 0; j < numCenters; j++) - { - vec = VectorArrayGet(newCenters, j); - - if (centerCounts[j] > 0) - { - /* Double avoids overflow, but requires more memory */ - /* TODO Update bounds */ - for (k = 0; k < dimensions; k++) - { - if (isinf(vec->x[k])) - vec->x[k] = vec->x[k] > 0 ? FLT_MAX : -FLT_MAX; - } - - for (k = 0; k < dimensions; k++) - vec->x[k] /= centerCounts[j]; - } - else - { - /* TODO Handle empty centers properly */ - for (k = 0; k < dimensions; k++) - vec->x[k] = RandomDouble(); - } - - /* Normalize if needed */ - if (normprocinfo != NULL) - ApplyNorm(normprocinfo, collation, vec); - } - - /* Step 5 */ - for (j = 0; j < numCenters; j++) - newcdist[j] = DatumGetFloat8(FunctionCall2Coll(procinfo, collation, PointerGetDatum(VectorArrayGet(centers, j)), PointerGetDatum(VectorArrayGet(newCenters, j)))); - - for (j = 0; j < numSamples; j++) - { - for (k = 0; k < numCenters; k++) - { - distance = lowerBound[j * numCenters + k] - newcdist[k]; - - if (distance < 0) - distance = 0; - - lowerBound[j * numCenters + k] = distance; - } - } - - /* Step 6 */ - /* We reset r(x) before Step 3 in the next iteration */ - for (j = 0; j < numSamples; j++) - upperBound[j] += newcdist[closestCenters[j]]; - - /* Step 7 */ - for (j = 0; j < numCenters; j++) - memcpy(VectorArrayGet(centers, j), VectorArrayGet(newCenters, j), VECTOR_SIZE(dimensions)); - - if (changes == 0 && iteration != 0) - break; - } - - VectorArrayFree(newCenters); - pfree(centerCounts); - pfree(closestCenters); - pfree(lowerBound); - pfree(upperBound); - pfree(s); - pfree(halfcdist); - pfree(newcdist); -} - -/* - * Detect issues with centers - */ -static void -CheckCenters(Relation index, VectorArray centers) -{ - FmgrInfo *normprocinfo; - Oid collation; - Vector *vec; - double norm; - - if (centers->length != centers->maxlen) - elog(ERROR, "Not enough centers. Please report a bug."); - - /* Ensure no NaN or infinite values */ - for (int i = 0; i < centers->length; i++) - { - vec = VectorArrayGet(centers, i); - - for (int j = 0; j < vec->dim; j++) - { - if (isnan(vec->x[j])) - elog(ERROR, "NaN detected. Please report a bug."); - - if (isinf(vec->x[j])) - elog(ERROR, "Infinite value detected. Please report a bug."); - } - } - - /* Ensure no duplicate centers */ - /* Fine to sort in-place */ - qsort(centers->items, centers->length, VECTOR_SIZE(centers->dim), CompareVectors); - for (int i = 1; i < centers->length; i++) - { - if (CompareVectors(VectorArrayGet(centers, i), VectorArrayGet(centers, i - 1)) == 0) - elog(ERROR, "Duplicate centers detected. Please report a bug."); - } - - /* Ensure no zero vectors for cosine distance */ - /* Check NORM_PROC instead of KMEANS_NORM_PROC */ - normprocinfo = IvfflatOptionalProcInfo(index, IVFFLAT_NORM_PROC); - if (normprocinfo != NULL) - { - collation = index->rd_indcollation[0]; - - for (int i = 0; i < centers->length; i++) - { - norm = DatumGetFloat8(FunctionCall1Coll(normprocinfo, collation, PointerGetDatum(VectorArrayGet(centers, i)))); - if (norm == 0) - elog(ERROR, "Zero norm detected. Please report a bug."); - } - } -} - -/* - * Perform naive k-means centering - * We use spherical k-means for inner product and cosine - */ -void -IvfflatKmeans(Relation index, VectorArray samples, VectorArray centers) -{ - if (samples->length <= centers->maxlen) - QuickCenters(index, samples, centers); - else - ElkanKmeans(index, samples, centers); - - CheckCenters(index, centers); -} diff --git a/packages/server/postgres/extensions/pgvector/src/ivfkmeans.o b/packages/server/postgres/extensions/pgvector/src/ivfkmeans.o deleted file mode 100644 index 399ebad55bab1b1cb970ce1993c73160fd28abf3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9240 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 diff --git a/packages/server/postgres/extensions/pgvector/src/ivfscan.c b/packages/server/postgres/extensions/pgvector/src/ivfscan.c deleted file mode 100644 index 6703120aa5d..00000000000 --- a/packages/server/postgres/extensions/pgvector/src/ivfscan.c +++ /dev/null @@ -1,381 +0,0 @@ -#include "postgres.h" - -#include - -#include "access/relscan.h" -#include "catalog/pg_operator_d.h" -#include "catalog/pg_type_d.h" -#include "ivfflat.h" -#include "miscadmin.h" -#include "pgstat.h" -#include "storage/bufmgr.h" - -/* - * Compare list distances - */ -static int -CompareLists(const pairingheap_node *a, const pairingheap_node *b, void *arg) -{ - if (((const IvfflatScanList *) a)->distance > ((const IvfflatScanList *) b)->distance) - return 1; - - if (((const IvfflatScanList *) a)->distance < ((const IvfflatScanList *) b)->distance) - return -1; - - return 0; -} - -/* - * Get lists and sort by distance - */ -static void -GetScanLists(IndexScanDesc scan, Datum value) -{ - Buffer cbuf; - Page cpage; - IvfflatList list; - OffsetNumber offno; - OffsetNumber maxoffno; - BlockNumber nextblkno = IVFFLAT_HEAD_BLKNO; - int listCount = 0; - IvfflatScanOpaque so = (IvfflatScanOpaque) scan->opaque; - double distance; - IvfflatScanList *scanlist; - double maxDistance = DBL_MAX; - - /* Search all list pages */ - while (BlockNumberIsValid(nextblkno)) - { - cbuf = ReadBuffer(scan->indexRelation, nextblkno); - LockBuffer(cbuf, BUFFER_LOCK_SHARE); - cpage = BufferGetPage(cbuf); - - maxoffno = PageGetMaxOffsetNumber(cpage); - - for (offno = FirstOffsetNumber; offno <= maxoffno; offno = OffsetNumberNext(offno)) - { - list = (IvfflatList) PageGetItem(cpage, PageGetItemId(cpage, offno)); - - /* Use procinfo from the index instead of scan key for performance */ - distance = DatumGetFloat8(FunctionCall2Coll(so->procinfo, so->collation, PointerGetDatum(&list->center), value)); - - if (listCount < so->probes) - { - scanlist = &so->lists[listCount]; - scanlist->startPage = list->startPage; - scanlist->distance = distance; - listCount++; - - /* Add to heap */ - pairingheap_add(so->listQueue, &scanlist->ph_node); - - /* Calculate max distance */ - if (listCount == so->probes) - maxDistance = ((IvfflatScanList *) pairingheap_first(so->listQueue))->distance; - } - else if (distance < maxDistance) - { - /* Remove */ - scanlist = (IvfflatScanList *) pairingheap_remove_first(so->listQueue); - - /* Reuse */ - scanlist->startPage = list->startPage; - scanlist->distance = distance; - pairingheap_add(so->listQueue, &scanlist->ph_node); - - /* Update max distance */ - maxDistance = ((IvfflatScanList *) pairingheap_first(so->listQueue))->distance; - } - } - - nextblkno = IvfflatPageGetOpaque(cpage)->nextblkno; - - UnlockReleaseBuffer(cbuf); - } -} - -/* - * Get items - */ -static void -GetScanItems(IndexScanDesc scan, Datum value) -{ - IvfflatScanOpaque so = (IvfflatScanOpaque) scan->opaque; - Buffer buf; - Page page; - IndexTuple itup; - BlockNumber searchPage; - OffsetNumber offno; - OffsetNumber maxoffno; - Datum datum; - bool isnull; - TupleDesc tupdesc = RelationGetDescr(scan->indexRelation); - double tuples = 0; - -#if PG_VERSION_NUM >= 120000 - TupleTableSlot *slot = MakeSingleTupleTableSlot(so->tupdesc, &TTSOpsVirtual); -#else - TupleTableSlot *slot = MakeSingleTupleTableSlot(so->tupdesc); -#endif - - /* - * Reuse same set of shared buffers for scan - * - * See postgres/src/backend/storage/buffer/README for description - */ - BufferAccessStrategy bas = GetAccessStrategy(BAS_BULKREAD); - - /* Search closest probes lists */ - while (!pairingheap_is_empty(so->listQueue)) - { - searchPage = ((IvfflatScanList *) pairingheap_remove_first(so->listQueue))->startPage; - - /* Search all entry pages for list */ - while (BlockNumberIsValid(searchPage)) - { - buf = ReadBufferExtended(scan->indexRelation, MAIN_FORKNUM, searchPage, RBM_NORMAL, bas); - LockBuffer(buf, BUFFER_LOCK_SHARE); - page = BufferGetPage(buf); - maxoffno = PageGetMaxOffsetNumber(page); - - for (offno = FirstOffsetNumber; offno <= maxoffno; offno = OffsetNumberNext(offno)) - { - itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, offno)); - datum = index_getattr(itup, 1, tupdesc, &isnull); - - /* - * Add virtual tuple - * - * Use procinfo from the index instead of scan key for - * performance - */ - ExecClearTuple(slot); - slot->tts_values[0] = FunctionCall2Coll(so->procinfo, so->collation, datum, value); - slot->tts_isnull[0] = false; - slot->tts_values[1] = PointerGetDatum(&itup->t_tid); - slot->tts_isnull[1] = false; - slot->tts_values[2] = Int32GetDatum((int) searchPage); - slot->tts_isnull[2] = false; - ExecStoreVirtualTuple(slot); - - tuplesort_puttupleslot(so->sortstate, slot); - - tuples++; - } - - searchPage = IvfflatPageGetOpaque(page)->nextblkno; - - UnlockReleaseBuffer(buf); - } - } - - FreeAccessStrategy(bas); - - if (tuples < 100) - ereport(DEBUG1, - (errmsg("index scan found few tuples"), - errdetail("Index may have been created with little data."), - errhint("Recreate the index and possibly decrease lists."))); - - tuplesort_performsort(so->sortstate); -} - -/* - * Get dimensions from metapage - */ -static int -GetDimensions(Relation index) -{ - Buffer buf; - Page page; - IvfflatMetaPage metap; - int dimensions; - - buf = ReadBuffer(index, IVFFLAT_METAPAGE_BLKNO); - LockBuffer(buf, BUFFER_LOCK_SHARE); - page = BufferGetPage(buf); - metap = IvfflatPageGetMeta(page); - - dimensions = metap->dimensions; - - UnlockReleaseBuffer(buf); - - return dimensions; -} - -/* - * Prepare for an index scan - */ -IndexScanDesc -ivfflatbeginscan(Relation index, int nkeys, int norderbys) -{ - IndexScanDesc scan; - IvfflatScanOpaque so; - int lists; - AttrNumber attNums[] = {1}; - Oid sortOperators[] = {Float8LessOperator}; - Oid sortCollations[] = {InvalidOid}; - bool nullsFirstFlags[] = {false}; - int probes = ivfflat_probes; - - scan = RelationGetIndexScan(index, nkeys, norderbys); - lists = IvfflatGetLists(scan->indexRelation); - - if (probes > lists) - probes = lists; - - so = (IvfflatScanOpaque) palloc(offsetof(IvfflatScanOpaqueData, lists) + probes * sizeof(IvfflatScanList)); - so->buf = InvalidBuffer; - so->first = true; - so->probes = probes; - - /* Set support functions */ - so->procinfo = index_getprocinfo(index, 1, IVFFLAT_DISTANCE_PROC); - so->normprocinfo = IvfflatOptionalProcInfo(index, IVFFLAT_NORM_PROC); - so->collation = index->rd_indcollation[0]; - - /* Create tuple description for sorting */ -#if PG_VERSION_NUM >= 120000 - so->tupdesc = CreateTemplateTupleDesc(3); -#else - so->tupdesc = CreateTemplateTupleDesc(3, false); -#endif - TupleDescInitEntry(so->tupdesc, (AttrNumber) 1, "distance", FLOAT8OID, -1, 0); - TupleDescInitEntry(so->tupdesc, (AttrNumber) 2, "tid", TIDOID, -1, 0); - TupleDescInitEntry(so->tupdesc, (AttrNumber) 3, "indexblkno", INT4OID, -1, 0); - - /* Prep sort */ - so->sortstate = tuplesort_begin_heap(so->tupdesc, 1, attNums, sortOperators, sortCollations, nullsFirstFlags, work_mem, NULL, false); - -#if PG_VERSION_NUM >= 120000 - so->slot = MakeSingleTupleTableSlot(so->tupdesc, &TTSOpsMinimalTuple); -#else - so->slot = MakeSingleTupleTableSlot(so->tupdesc); -#endif - - so->listQueue = pairingheap_allocate(CompareLists, scan); - - scan->opaque = so; - - return scan; -} - -/* - * Start or restart an index scan - */ -void -ivfflatrescan(IndexScanDesc scan, ScanKey keys, int nkeys, ScanKey orderbys, int norderbys) -{ - IvfflatScanOpaque so = (IvfflatScanOpaque) scan->opaque; - -#if PG_VERSION_NUM >= 130000 - if (!so->first) - tuplesort_reset(so->sortstate); -#endif - - so->first = true; - pairingheap_reset(so->listQueue); - - if (keys && scan->numberOfKeys > 0) - memmove(scan->keyData, keys, scan->numberOfKeys * sizeof(ScanKeyData)); - - if (orderbys && scan->numberOfOrderBys > 0) - memmove(scan->orderByData, orderbys, scan->numberOfOrderBys * sizeof(ScanKeyData)); -} - -/* - * Fetch the next tuple in the given scan - */ -bool -ivfflatgettuple(IndexScanDesc scan, ScanDirection dir) -{ - IvfflatScanOpaque so = (IvfflatScanOpaque) scan->opaque; - - /* - * Index can be used to scan backward, but Postgres doesn't support - * backward scan on operators - */ - Assert(ScanDirectionIsForward(dir)); - - if (so->first) - { - Datum value; - - /* Count index scan for stats */ - pgstat_count_index_scan(scan->indexRelation); - - /* Safety check */ - if (scan->orderByData == NULL) - elog(ERROR, "cannot scan ivfflat index without order"); - - if (scan->orderByData->sk_flags & SK_ISNULL) - value = PointerGetDatum(InitVector(GetDimensions(scan->indexRelation))); - else - { - value = scan->orderByData->sk_argument; - - /* Value should not be compressed or toasted */ - Assert(!VARATT_IS_COMPRESSED(DatumGetPointer(value))); - Assert(!VARATT_IS_EXTENDED(DatumGetPointer(value))); - - /* Fine if normalization fails */ - if (so->normprocinfo != NULL) - IvfflatNormValue(so->normprocinfo, so->collation, &value, NULL); - } - - IvfflatBench("GetScanLists", GetScanLists(scan, value)); - IvfflatBench("GetScanItems", GetScanItems(scan, value)); - so->first = false; - - /* Clean up if we allocated a new value */ - if (value != scan->orderByData->sk_argument) - pfree(DatumGetPointer(value)); - } - - if (tuplesort_gettupleslot(so->sortstate, true, false, so->slot, NULL)) - { - ItemPointer tid = (ItemPointer) DatumGetPointer(slot_getattr(so->slot, 2, &so->isnull)); - BlockNumber indexblkno = DatumGetInt32(slot_getattr(so->slot, 3, &so->isnull)); - -#if PG_VERSION_NUM >= 120000 - scan->xs_heaptid = *tid; -#else - scan->xs_ctup.t_self = *tid; -#endif - - if (BufferIsValid(so->buf)) - ReleaseBuffer(so->buf); - - /* - * An index scan must maintain a pin on the index page holding the - * item last returned by amgettuple - * - * https://www.postgresql.org/docs/current/index-locking.html - */ - so->buf = ReadBuffer(scan->indexRelation, indexblkno); - - scan->xs_recheckorderby = false; - return true; - } - - return false; -} - -/* - * End a scan and release resources - */ -void -ivfflatendscan(IndexScanDesc scan) -{ - IvfflatScanOpaque so = (IvfflatScanOpaque) scan->opaque; - - /* Release pin */ - if (BufferIsValid(so->buf)) - ReleaseBuffer(so->buf); - - pairingheap_free(so->listQueue); - tuplesort_end(so->sortstate); - - pfree(so); - scan->opaque = NULL; -} diff --git a/packages/server/postgres/extensions/pgvector/src/ivfscan.o b/packages/server/postgres/extensions/pgvector/src/ivfscan.o deleted file mode 100644 index 4e45c4047a1af71918401244dc61867b36fac37a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6344 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 = 0; - res->maxlen = maxlen; - res->dim = dimensions; - res->items = palloc_extended(maxlen * VECTOR_SIZE(dimensions), MCXT_ALLOC_ZERO | MCXT_ALLOC_HUGE); - return res; -} - -/* - * Free a vector array - */ -void -VectorArrayFree(VectorArray arr) -{ - pfree(arr->items); - pfree(arr); -} - -/* - * Print vector array - useful for debugging - */ -void -PrintVectorArray(char *msg, VectorArray arr) -{ - for (int i = 0; i < arr->length; i++) - PrintVector(msg, VectorArrayGet(arr, i)); -} - -/* - * Get the number of lists in the index - */ -int -IvfflatGetLists(Relation index) -{ - IvfflatOptions *opts = (IvfflatOptions *) index->rd_options; - - if (opts) - return opts->lists; - - return IVFFLAT_DEFAULT_LISTS; -} - -/* - * Get proc - */ -FmgrInfo * -IvfflatOptionalProcInfo(Relation rel, uint16 procnum) -{ - if (!OidIsValid(index_getprocid(rel, 1, procnum))) - return NULL; - - return index_getprocinfo(rel, 1, procnum); -} - -/* - * Divide by the norm - * - * Returns false if value should not be indexed - * - * The caller needs to free the pointer stored in value - * if it's different than the original value - */ -bool -IvfflatNormValue(FmgrInfo *procinfo, Oid collation, Datum *value, Vector * result) -{ - double norm = DatumGetFloat8(FunctionCall1Coll(procinfo, collation, *value)); - - if (norm > 0) - { - Vector *v = DatumGetVector(*value); - - if (result == NULL) - result = InitVector(v->dim); - - for (int i = 0; i < v->dim; i++) - result->x[i] = v->x[i] / norm; - - *value = PointerGetDatum(result); - - return true; - } - - return false; -} - -/* - * New buffer - */ -Buffer -IvfflatNewBuffer(Relation index, ForkNumber forkNum) -{ - Buffer buf = ReadBufferExtended(index, forkNum, P_NEW, RBM_NORMAL, NULL); - - LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE); - return buf; -} - -/* - * Init page - */ -void -IvfflatInitPage(Buffer buf, Page page) -{ - PageInit(page, BufferGetPageSize(buf), sizeof(IvfflatPageOpaqueData)); - IvfflatPageGetOpaque(page)->nextblkno = InvalidBlockNumber; - IvfflatPageGetOpaque(page)->page_id = IVFFLAT_PAGE_ID; -} - -/* - * Init and register page - */ -void -IvfflatInitRegisterPage(Relation index, Buffer *buf, Page *page, GenericXLogState **state) -{ - *state = GenericXLogStart(index); - *page = GenericXLogRegisterBuffer(*state, *buf, GENERIC_XLOG_FULL_IMAGE); - IvfflatInitPage(*buf, *page); -} - -/* - * Commit buffer - */ -void -IvfflatCommitBuffer(Buffer buf, GenericXLogState *state) -{ - MarkBufferDirty(buf); - GenericXLogFinish(state); - UnlockReleaseBuffer(buf); -} - -/* - * Add a new page - * - * The order is very important!! - */ -void -IvfflatAppendPage(Relation index, Buffer *buf, Page *page, GenericXLogState **state, ForkNumber forkNum) -{ - /* Get new buffer */ - Buffer newbuf = IvfflatNewBuffer(index, forkNum); - Page newpage = GenericXLogRegisterBuffer(*state, newbuf, GENERIC_XLOG_FULL_IMAGE); - - /* Update the previous buffer */ - IvfflatPageGetOpaque(*page)->nextblkno = BufferGetBlockNumber(newbuf); - - /* Init new page */ - IvfflatInitPage(newbuf, newpage); - - /* Commit */ - MarkBufferDirty(*buf); - MarkBufferDirty(newbuf); - GenericXLogFinish(*state); - - /* Unlock */ - UnlockReleaseBuffer(*buf); - - *state = GenericXLogStart(index); - *page = GenericXLogRegisterBuffer(*state, newbuf, GENERIC_XLOG_FULL_IMAGE); - *buf = newbuf; -} - -/* - * Update the start or insert page of a list - */ -void -IvfflatUpdateList(Relation index, ListInfo listInfo, - BlockNumber insertPage, BlockNumber originalInsertPage, - BlockNumber startPage, ForkNumber forkNum) -{ - Buffer buf; - Page page; - GenericXLogState *state; - IvfflatList list; - bool changed = false; - - buf = ReadBufferExtended(index, forkNum, listInfo.blkno, RBM_NORMAL, NULL); - LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE); - state = GenericXLogStart(index); - page = GenericXLogRegisterBuffer(state, buf, 0); - list = (IvfflatList) PageGetItem(page, PageGetItemId(page, listInfo.offno)); - - if (BlockNumberIsValid(insertPage) && insertPage != list->insertPage) - { - /* Skip update if insert page is lower than original insert page */ - /* This is needed to prevent insert from overwriting vacuum */ - if (!BlockNumberIsValid(originalInsertPage) || insertPage >= originalInsertPage) - { - list->insertPage = insertPage; - changed = true; - } - } - - if (BlockNumberIsValid(startPage) && startPage != list->startPage) - { - list->startPage = startPage; - changed = true; - } - - /* Only commit if changed */ - if (changed) - IvfflatCommitBuffer(buf, state); - else - { - GenericXLogAbort(state); - UnlockReleaseBuffer(buf); - } -} diff --git a/packages/server/postgres/extensions/pgvector/src/ivfutils.o b/packages/server/postgres/extensions/pgvector/src/ivfutils.o deleted file mode 100644 index 6f2e1db7e185f034a47991f54e0a56be147e0d5a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3552 zcmb7HU2GIp6h6Dt)@k__+R`HS4`bSB6EGU=iZs2zdgM7crB5OApBE+5#UJ;2BZ>W%ABqKGg4&PM@$AHTYm~>9&WKrRSoU9 zAryeBdUhT1EbI16F0^LM$7?srM9;ISfZ`0csA_j46!mo*>OlCI9_~@q_BPwcX!FEH zUZ;_#1iMm9gnC)*)J)|i{8nIC;p;HLfN&7o6IfRZ{CW-14j2K{fx*HtJeYSq#%7HF zD|21nCujD%lG!15GW$M_K9F3g8HrLP#XG@ub{6yM4rpPu$<~?c?)LG}T$c-V zp_fBaGJAzaXCx{0Kys&A$f{i?|L1b8x2jPhkJoM4xW{LSE+tAP26<2J|3%EX2iMR8 z8T%ybX!IkDHK@Ht?;EFyx+KhAH> z4z(hu7Mx3S^6vJlL1*1=if@uoGp_m#-k;B^S)6U|SvlA7*<>(Wbh6X!vhG5lP8lo) zpMIo%jOPKjeCosd=R1qn(O_<`oM-+3eVRd^X2Hifc#;1Fx00?Cd2mi~*#7%o|8^I} z9mhUgAMeL!Z085P^r8Q|&EahBpk}_SO^>`c zC#AyFlkZHKxj)BFqQB^k`7V*`n@-7PN?LwR`BWzP)a@eLi5H)z7|-PEuJXPFk!O)@ zcs6dkxAa|h!-uu!VT;38;!@J8tF-R|=1HPg_?^Rb;}u(4>Z0`WSz31M?k?Z`dG)IK zz9}!Hbc5jaWfwj778kdG$CP*Qi}y8uj`D=sNj3=pZhlm zWPJ)TEJra+-PDLa5%KmtObFe+cL@=1-{TX)Z{PbDh0e*bJcq&j_PxRxnEFX}LT-w9 z`?-A^HjcLi&yY=Jnz|hNtFY#T(-DXNXNP~UL&x_#Z~YyI&fK~DL5Kc4t60$){8EgHwyyuJ%Z`D3H_x;UKh5EvWhWH9NpU z1+ z5UINd!rdIz>I(*Uwnl=%yq4|O!+L*UiEv*e7CPt)4rogj?f`cy&tMmp=V*>bwQ!GR ziuT4dEXnEnZqtxoN)M?~WEJUF@fQ@index; - BlockNumber blkno = IVFFLAT_HEAD_BLKNO; - BufferAccessStrategy bas = GetAccessStrategy(BAS_BULKREAD); - - if (stats == NULL) - stats = (IndexBulkDeleteResult *) palloc0(sizeof(IndexBulkDeleteResult)); - - /* Iterate over list pages */ - while (BlockNumberIsValid(blkno)) - { - Buffer cbuf; - Page cpage; - OffsetNumber coffno; - OffsetNumber cmaxoffno; - BlockNumber startPages[MaxOffsetNumber]; - ListInfo listInfo; - - cbuf = ReadBuffer(index, blkno); - LockBuffer(cbuf, BUFFER_LOCK_SHARE); - cpage = BufferGetPage(cbuf); - - cmaxoffno = PageGetMaxOffsetNumber(cpage); - - /* Iterate over lists */ - for (coffno = FirstOffsetNumber; coffno <= cmaxoffno; coffno = OffsetNumberNext(coffno)) - { - IvfflatList list = (IvfflatList) PageGetItem(cpage, PageGetItemId(cpage, coffno)); - - startPages[coffno - FirstOffsetNumber] = list->startPage; - } - - listInfo.blkno = blkno; - blkno = IvfflatPageGetOpaque(cpage)->nextblkno; - - UnlockReleaseBuffer(cbuf); - - for (coffno = FirstOffsetNumber; coffno <= cmaxoffno; coffno = OffsetNumberNext(coffno)) - { - BlockNumber searchPage = startPages[coffno - FirstOffsetNumber]; - BlockNumber insertPage = InvalidBlockNumber; - - /* Iterate over entry pages */ - while (BlockNumberIsValid(searchPage)) - { - Buffer buf; - Page page; - GenericXLogState *state; - OffsetNumber offno; - OffsetNumber maxoffno; - OffsetNumber deletable[MaxOffsetNumber]; - int ndeletable; - - vacuum_delay_point(); - - buf = ReadBufferExtended(index, MAIN_FORKNUM, searchPage, RBM_NORMAL, bas); - - /* - * ambulkdelete cannot delete entries from pages that are - * pinned by other backends - * - * https://www.postgresql.org/docs/current/index-locking.html - */ - LockBufferForCleanup(buf); - - state = GenericXLogStart(index); - page = GenericXLogRegisterBuffer(state, buf, 0); - - maxoffno = PageGetMaxOffsetNumber(page); - ndeletable = 0; - - /* Find deleted tuples */ - for (offno = FirstOffsetNumber; offno <= maxoffno; offno = OffsetNumberNext(offno)) - { - IndexTuple itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, offno)); - ItemPointer htup = &(itup->t_tid); - - if (callback(htup, callback_state)) - { - deletable[ndeletable++] = offno; - stats->tuples_removed++; - } - else - stats->num_index_tuples++; - } - - /* Set to first free page */ - /* Must be set before searchPage is updated */ - if (!BlockNumberIsValid(insertPage) && ndeletable > 0) - insertPage = searchPage; - - searchPage = IvfflatPageGetOpaque(page)->nextblkno; - - if (ndeletable > 0) - { - /* Delete tuples */ - PageIndexMultiDelete(page, deletable, ndeletable); - MarkBufferDirty(buf); - GenericXLogFinish(state); - } - else - GenericXLogAbort(state); - - UnlockReleaseBuffer(buf); - } - - /* - * Update after all tuples deleted. - * - * We don't add or delete items from lists pages, so offset won't - * change. - */ - if (BlockNumberIsValid(insertPage)) - { - listInfo.offno = coffno; - IvfflatUpdateList(index, listInfo, insertPage, InvalidBlockNumber, InvalidBlockNumber, MAIN_FORKNUM); - } - } - } - - FreeAccessStrategy(bas); - - return stats; -} - -/* - * Clean up after a VACUUM operation - */ -IndexBulkDeleteResult * -ivfflatvacuumcleanup(IndexVacuumInfo *info, IndexBulkDeleteResult *stats) -{ - Relation rel = info->index; - - if (info->analyze_only) - return stats; - - /* stats is NULL if ambulkdelete not called */ - /* OK to return NULL if index not changed */ - if (stats == NULL) - return NULL; - - stats->num_pages = RelationGetNumberOfBlocks(rel); - - return stats; -} diff --git a/packages/server/postgres/extensions/pgvector/src/ivfvacuum.o b/packages/server/postgres/extensions/pgvector/src/ivfvacuum.o deleted file mode 100644 index 0d358adb993d5d6a380de002b298eaf855a72683..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2664 zcmb7GUu;ul6hF6jTU!R~FLZwl8xBJ^tN{t)GRZD%!wrYbQ6d=U<x&t_V1h;ogka)BM|@}n63nJ)$4dQuy}eqlzW62Q{=Rd* z^PO|P-@WH+e_VR_WF-+t80=9BnP8J<2u3|5nV<{f}|H{;tU=E${Ino^=rY7^^UWV7vgSgv?=|6Ua6(za;bq;29fH0-MS)AAqpI zd<kB-^ zjz3EgVCL8^ifo17l2wSj#d8r`rGMSy3d-1tYVGTVNt>d~CiwbThPTC7tOd+Ft+Yj0 zL+%9M7@Oy7^7ag3t}o=P=hEAY>uI&p8?C2=bGB{vX;4{Pyq4nI+iZuFw7p)K?3C%~ zZyPT*lxHsv`sh-yl`Aj5pSw6XX7779<+#{zkh8ZPgQ1u!iEsS&|zm&d$TmGC}lGW}^_lk;^%f zd`(-@N|{!~Wj0o)6LIW_ldKWqO`P4=lFqq%Z?sO=T@z=UzyalQ?v#V#HkztVb|44# zoM{mzFZf2mna)dsZ^-9j{+H~;y5KzHjYFPvC&lmabuk|EUmNjmv9<&r4IhT=UCkqg z1wKq)hHk49Chg!z0Y`SuUjA~AWq%X6E7>HRpyixLcX1iGbpzoT6_V0+cT_O6K&~xm)GQYzX_MO$lvL|;U=C?D2 z{a7NpjLWHs!!2hT%+AIu5L`3=TK@^_HAJo%AnqBL3Um;uqdE>92IA^-4FGXRxt4J; z;a+wv0xN(IfVf9oP9S>FRR;tou1er)pbc0Je1PQmEpR1(wZOYT^n*()=!>Wi-<0}D z$DzAG6sz9Wap-#>ZdBJCunIT>WN}m=?o-#>z!kt=AgjMy#~~+h4fG8<4mAVy)H;^% z-M#%b%LZo&f1A6@{lb;-kGMrH4NR6jD0_q&^Wz6>S`9JAb*AHNN$|qPap)CA>+C#C zH~9YodeJgHXVUvkx);vH{+CVb@0fIO{FHyYN&nEKUpMJ1O!-*u;{2bO)|007E2eey z{8RgHnf#kg>sw6vC6j*3q>r28k+@e1`gVnfhvi_m64WM1d{x$VdOUI{v|kHKnmhv2 z?w~B2ZGFdvhgC^CFz$s@zY@~ePQM&fJO}#&BRh`-f=^raYZ9{@lRV+@nCO*NX;K^y zD1O-b10G4$YuKd*JiY;DmV+S@i;U2U;t$FrNGS((hkS^_DM&ovXF&#KRhB}s;V>vm zUSqE4rR$}5;N}mH7l+s#2zD7({NWk(g*2b&m4f)ggFjGWNRvE1W)_DfMI{lLBuxqU zLEP)%u_JPD-*EmIp*}w%`JSqKx8heqqa+qeI1*NUphDJU5(lIaxzF#FC-#O_P3bOJ ni_?vSrJ$GPGmg?Tp~=|cB~=?6-;#fBCUIO+LBM9bX`;UX - -#include "catalog/pg_type.h" -#include "fmgr.h" -#include "hnsw.h" -#include "ivfflat.h" -#include "lib/stringinfo.h" -#include "libpq/pqformat.h" -#include "port.h" /* for strtof() */ -#include "utils/array.h" -#include "utils/builtins.h" -#include "utils/lsyscache.h" -#include "utils/numeric.h" -#include "vector.h" - -#if PG_VERSION_NUM >= 160000 -#include "varatt.h" -#endif - -#if PG_VERSION_NUM >= 120000 -#include "common/shortest_dec.h" -#include "utils/float.h" -#else -#include -#endif - -#if PG_VERSION_NUM < 130000 -#define TYPALIGN_DOUBLE 'd' -#define TYPALIGN_INT 'i' -#endif - -#define STATE_DIMS(x) (ARR_DIMS(x)[0] - 1) -#define CreateStateDatums(dim) palloc(sizeof(Datum) * (dim + 1)) - -PG_MODULE_MAGIC; - -/* - * Initialize index options and variables - */ -PGDLLEXPORT void _PG_init(void); -void -_PG_init(void) -{ - HnswInit(); - IvfflatInit(); -} - -/* - * Ensure same dimensions - */ -static inline void -CheckDims(Vector * a, Vector * b) -{ - if (a->dim != b->dim) - ereport(ERROR, - (errcode(ERRCODE_DATA_EXCEPTION), - errmsg("different vector dimensions %d and %d", a->dim, b->dim))); -} - -/* - * Ensure expected dimensions - */ -static inline void -CheckExpectedDim(int32 typmod, int dim) -{ - if (typmod != -1 && typmod != dim) - ereport(ERROR, - (errcode(ERRCODE_DATA_EXCEPTION), - errmsg("expected %d dimensions, not %d", typmod, dim))); -} - -/* - * Ensure valid dimensions - */ -static inline void -CheckDim(int dim) -{ - if (dim < 1) - ereport(ERROR, - (errcode(ERRCODE_DATA_EXCEPTION), - errmsg("vector must have at least 1 dimension"))); - - if (dim > VECTOR_MAX_DIM) - ereport(ERROR, - (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), - errmsg("vector cannot have more than %d dimensions", VECTOR_MAX_DIM))); -} - -/* - * Ensure finite elements - */ -static inline void -CheckElement(float value) -{ - if (isnan(value)) - ereport(ERROR, - (errcode(ERRCODE_DATA_EXCEPTION), - errmsg("NaN not allowed in vector"))); - - if (isinf(value)) - ereport(ERROR, - (errcode(ERRCODE_DATA_EXCEPTION), - errmsg("infinite value not allowed in vector"))); -} - -/* - * Allocate and initialize a new vector - */ -Vector * -InitVector(int dim) -{ - Vector *result; - int size; - - size = VECTOR_SIZE(dim); - result = (Vector *) palloc0(size); - SET_VARSIZE(result, size); - result->dim = dim; - - return result; -} - -/* - * Check for whitespace, since array_isspace() is static - */ -static inline bool -vector_isspace(char ch) -{ - if (ch == ' ' || - ch == '\t' || - ch == '\n' || - ch == '\r' || - ch == '\v' || - ch == '\f') - return true; - return false; -} - -/* - * Check state array - */ -static float8 * -CheckStateArray(ArrayType *statearray, const char *caller) -{ - if (ARR_NDIM(statearray) != 1 || - ARR_DIMS(statearray)[0] < 1 || - ARR_HASNULL(statearray) || - ARR_ELEMTYPE(statearray) != FLOAT8OID) - elog(ERROR, "%s: expected state array", caller); - return (float8 *) ARR_DATA_PTR(statearray); -} - -#if PG_VERSION_NUM < 120003 -static pg_noinline void -float_overflow_error(void) -{ - ereport(ERROR, - (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), - errmsg("value out of range: overflow"))); -} - -static pg_noinline void -float_underflow_error(void) -{ - ereport(ERROR, - (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), - errmsg("value out of range: underflow"))); -} -#endif - -/* - * Convert textual representation to internal representation - */ -PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_in); -Datum -vector_in(PG_FUNCTION_ARGS) -{ - char *str = PG_GETARG_CSTRING(0); - int32 typmod = PG_GETARG_INT32(2); - float x[VECTOR_MAX_DIM]; - int dim = 0; - char *pt; - char *stringEnd; - Vector *result; - char *lit = pstrdup(str); - - while (vector_isspace(*str)) - str++; - - if (*str != '[') - ereport(ERROR, - (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), - errmsg("malformed vector literal: \"%s\"", lit), - errdetail("Vector contents must start with \"[\"."))); - - str++; - pt = strtok(str, ","); - stringEnd = pt; - - while (pt != NULL && *stringEnd != ']') - { - if (dim == VECTOR_MAX_DIM) - ereport(ERROR, - (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), - errmsg("vector cannot have more than %d dimensions", VECTOR_MAX_DIM))); - - while (vector_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 vector: \"%s\"", lit))); - - /* Use strtof like float4in to avoid a double-rounding problem */ - x[dim] = strtof(pt, &stringEnd); - CheckElement(x[dim]); - dim++; - - if (stringEnd == pt) - ereport(ERROR, - (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), - errmsg("invalid input syntax for type vector: \"%s\"", lit))); - - while (vector_isspace(*stringEnd)) - stringEnd++; - - if (*stringEnd != '\0' && *stringEnd != ']') - ereport(ERROR, - (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), - errmsg("invalid input syntax for type vector: \"%s\"", lit))); - - pt = strtok(NULL, ","); - } - - if (stringEnd == NULL || *stringEnd != ']') - ereport(ERROR, - (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), - errmsg("malformed vector literal: \"%s\"", lit), - errdetail("Unexpected end of input."))); - - stringEnd++; - - /* Only whitespace is allowed after the closing brace */ - while (vector_isspace(*stringEnd)) - stringEnd++; - - if (*stringEnd != '\0') - ereport(ERROR, - (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), - errmsg("malformed vector literal: \"%s\"", lit), - errdetail("Junk after closing right brace."))); - - /* Ensure no consecutive delimiters since strtok skips */ - for (pt = lit + 1; *pt != '\0'; pt++) - { - if (pt[-1] == ',' && *pt == ',') - ereport(ERROR, - (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), - errmsg("malformed vector literal: \"%s\"", lit))); - } - - if (dim < 1) - ereport(ERROR, - (errcode(ERRCODE_DATA_EXCEPTION), - errmsg("vector must have at least 1 dimension"))); - - pfree(lit); - - CheckExpectedDim(typmod, dim); - - result = InitVector(dim); - for (int i = 0; i < dim; i++) - result->x[i] = x[i]; - - PG_RETURN_POINTER(result); -} - -/* - * Convert internal representation to textual representation - */ -PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_out); -Datum -vector_out(PG_FUNCTION_ARGS) -{ - Vector *vector = PG_GETARG_VECTOR_P(0); - int dim = vector->dim; - char *buf; - char *ptr; - int n; - -#if PG_VERSION_NUM < 120000 - int ndig = FLT_DIG + extra_float_digits; - - if (ndig < 1) - ndig = 1; - -#define FLOAT_SHORTEST_DECIMAL_LEN (ndig + 10) -#endif - - /* - * Need: - * - * dim * (FLOAT_SHORTEST_DECIMAL_LEN - 1) bytes for - * float_to_shortest_decimal_bufn - * - * dim - 1 bytes for separator - * - * 3 bytes for [, ], and \0 - */ - buf = (char *) palloc(FLOAT_SHORTEST_DECIMAL_LEN * dim + 2); - ptr = buf; - - *ptr = '['; - ptr++; - for (int i = 0; i < dim; i++) - { - if (i > 0) - { - *ptr = ','; - ptr++; - } - -#if PG_VERSION_NUM >= 120000 - n = float_to_shortest_decimal_bufn(vector->x[i], ptr); -#else - n = sprintf(ptr, "%.*g", ndig, vector->x[i]); -#endif - ptr += n; - } - *ptr = ']'; - ptr++; - *ptr = '\0'; - - PG_FREE_IF_COPY(vector, 0); - PG_RETURN_CSTRING(buf); -} - -/* - * Print vector - useful for debugging - */ -void -PrintVector(char *msg, Vector * vector) -{ - char *out = DatumGetPointer(DirectFunctionCall1(vector_out, PointerGetDatum(vector))); - - elog(INFO, "%s = %s", msg, out); - pfree(out); -} - -/* - * Convert type modifier - */ -PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_typmod_in); -Datum -vector_typmod_in(PG_FUNCTION_ARGS) -{ - ArrayType *ta = PG_GETARG_ARRAYTYPE_P(0); - int32 *tl; - int n; - - tl = ArrayGetIntegerTypmods(ta, &n); - - if (n != 1) - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("invalid type modifier"))); - - if (*tl < 1) - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("dimensions for type vector must be at least 1"))); - - if (*tl > VECTOR_MAX_DIM) - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("dimensions for type vector cannot exceed %d", VECTOR_MAX_DIM))); - - PG_RETURN_INT32(*tl); -} - -/* - * Convert external binary representation to internal representation - */ -PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_recv); -Datum -vector_recv(PG_FUNCTION_ARGS) -{ - StringInfo buf = (StringInfo) PG_GETARG_POINTER(0); - int32 typmod = PG_GETARG_INT32(2); - Vector *result; - int16 dim; - int16 unused; - - dim = pq_getmsgint(buf, sizeof(int16)); - unused = pq_getmsgint(buf, sizeof(int16)); - - CheckDim(dim); - CheckExpectedDim(typmod, dim); - - if (unused != 0) - ereport(ERROR, - (errcode(ERRCODE_DATA_EXCEPTION), - errmsg("expected unused to be 0, not %d", unused))); - - result = InitVector(dim); - for (int i = 0; i < dim; i++) - { - result->x[i] = pq_getmsgfloat4(buf); - CheckElement(result->x[i]); - } - - PG_RETURN_POINTER(result); -} - -/* - * Convert internal representation to the external binary representation - */ -PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_send); -Datum -vector_send(PG_FUNCTION_ARGS) -{ - Vector *vec = PG_GETARG_VECTOR_P(0); - StringInfoData buf; - - pq_begintypsend(&buf); - pq_sendint(&buf, vec->dim, sizeof(int16)); - pq_sendint(&buf, vec->unused, sizeof(int16)); - for (int i = 0; i < vec->dim; i++) - pq_sendfloat4(&buf, vec->x[i]); - - PG_RETURN_BYTEA_P(pq_endtypsend(&buf)); -} - -/* - * Convert vector to vector - */ -PGDLLEXPORT PG_FUNCTION_INFO_V1(vector); -Datum -vector(PG_FUNCTION_ARGS) -{ - Vector *arg = PG_GETARG_VECTOR_P(0); - int32 typmod = PG_GETARG_INT32(1); - - CheckExpectedDim(typmod, arg->dim); - - PG_RETURN_POINTER(arg); -} - -/* - * Convert array to vector - */ -PGDLLEXPORT PG_FUNCTION_INFO_V1(array_to_vector); -Datum -array_to_vector(PG_FUNCTION_ARGS) -{ - ArrayType *array = PG_GETARG_ARRAYTYPE_P(0); - int32 typmod = PG_GETARG_INT32(1); - Vector *result; - int16 typlen; - bool typbyval; - char typalign; - Datum *elemsp; - bool *nullsp; - int nelemsp; - - if (ARR_NDIM(array) > 1) - ereport(ERROR, - (errcode(ERRCODE_DATA_EXCEPTION), - errmsg("array must be 1-D"))); - - if (ARR_HASNULL(array) && array_contains_nulls(array)) - ereport(ERROR, - (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), - errmsg("array must not contain nulls"))); - - get_typlenbyvalalign(ARR_ELEMTYPE(array), &typlen, &typbyval, &typalign); - deconstruct_array(array, ARR_ELEMTYPE(array), typlen, typbyval, typalign, &elemsp, &nullsp, &nelemsp); - - CheckDim(nelemsp); - CheckExpectedDim(typmod, nelemsp); - - result = InitVector(nelemsp); - - if (ARR_ELEMTYPE(array) == INT4OID) - { - for (int i = 0; i < nelemsp; i++) - result->x[i] = DatumGetInt32(elemsp[i]); - } - else if (ARR_ELEMTYPE(array) == FLOAT8OID) - { - for (int i = 0; i < nelemsp; i++) - result->x[i] = DatumGetFloat8(elemsp[i]); - } - else if (ARR_ELEMTYPE(array) == FLOAT4OID) - { - for (int i = 0; i < nelemsp; i++) - result->x[i] = DatumGetFloat4(elemsp[i]); - } - else if (ARR_ELEMTYPE(array) == NUMERICOID) - { - for (int i = 0; i < nelemsp; i++) - result->x[i] = DatumGetFloat4(DirectFunctionCall1(numeric_float4, elemsp[i])); - } - else - { - ereport(ERROR, - (errcode(ERRCODE_DATA_EXCEPTION), - errmsg("unsupported array type"))); - } - - /* Check elements */ - for (int i = 0; i < result->dim; i++) - CheckElement(result->x[i]); - - PG_RETURN_POINTER(result); -} - -/* - * Convert vector to float4[] - */ -PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_to_float4); -Datum -vector_to_float4(PG_FUNCTION_ARGS) -{ - Vector *vec = PG_GETARG_VECTOR_P(0); - Datum *datums; - ArrayType *result; - - datums = (Datum *) palloc(sizeof(Datum) * vec->dim); - - for (int i = 0; i < vec->dim; i++) - datums[i] = Float4GetDatum(vec->x[i]); - - /* Use TYPALIGN_INT for float4 */ - result = construct_array(datums, vec->dim, FLOAT4OID, sizeof(float4), true, TYPALIGN_INT); - - pfree(datums); - - PG_RETURN_POINTER(result); -} - -/* - * Get the L2 distance between vectors - */ -PGDLLEXPORT PG_FUNCTION_INFO_V1(l2_distance); -Datum -l2_distance(PG_FUNCTION_ARGS) -{ - Vector *a = PG_GETARG_VECTOR_P(0); - Vector *b = PG_GETARG_VECTOR_P(1); - float *ax = a->x; - float *bx = b->x; - float distance = 0.0; - float diff; - - CheckDims(a, b); - - /* Auto-vectorized */ - for (int i = 0; i < a->dim; i++) - { - diff = ax[i] - bx[i]; - distance += diff * diff; - } - - PG_RETURN_FLOAT8(sqrt((double) distance)); -} - -/* - * Get the L2 squared distance between vectors - * This saves a sqrt calculation - */ -PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_l2_squared_distance); -Datum -vector_l2_squared_distance(PG_FUNCTION_ARGS) -{ - Vector *a = PG_GETARG_VECTOR_P(0); - Vector *b = PG_GETARG_VECTOR_P(1); - float *ax = a->x; - float *bx = b->x; - float distance = 0.0; - float diff; - - CheckDims(a, b); - - /* Auto-vectorized */ - for (int i = 0; i < a->dim; i++) - { - diff = ax[i] - bx[i]; - distance += diff * diff; - } - - PG_RETURN_FLOAT8((double) distance); -} - -/* - * Get the inner product of two vectors - */ -PGDLLEXPORT PG_FUNCTION_INFO_V1(inner_product); -Datum -inner_product(PG_FUNCTION_ARGS) -{ - Vector *a = PG_GETARG_VECTOR_P(0); - Vector *b = PG_GETARG_VECTOR_P(1); - float *ax = a->x; - float *bx = b->x; - float distance = 0.0; - - CheckDims(a, b); - - /* Auto-vectorized */ - for (int i = 0; i < a->dim; i++) - distance += ax[i] * bx[i]; - - PG_RETURN_FLOAT8((double) distance); -} - -/* - * Get the negative inner product of two vectors - */ -PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_negative_inner_product); -Datum -vector_negative_inner_product(PG_FUNCTION_ARGS) -{ - Vector *a = PG_GETARG_VECTOR_P(0); - Vector *b = PG_GETARG_VECTOR_P(1); - float *ax = a->x; - float *bx = b->x; - float distance = 0.0; - - CheckDims(a, b); - - /* Auto-vectorized */ - for (int i = 0; i < a->dim; i++) - distance += ax[i] * bx[i]; - - PG_RETURN_FLOAT8((double) distance * -1); -} - -/* - * Get the cosine distance between two vectors - */ -PGDLLEXPORT PG_FUNCTION_INFO_V1(cosine_distance); -Datum -cosine_distance(PG_FUNCTION_ARGS) -{ - Vector *a = PG_GETARG_VECTOR_P(0); - Vector *b = PG_GETARG_VECTOR_P(1); - float *ax = a->x; - float *bx = b->x; - float distance = 0.0; - float norma = 0.0; - float normb = 0.0; - double similarity; - - CheckDims(a, b); - - /* Auto-vectorized */ - for (int i = 0; i < a->dim; i++) - { - distance += ax[i] * bx[i]; - norma += ax[i] * ax[i]; - normb += bx[i] * bx[i]; - } - - /* Use sqrt(a * b) over sqrt(a) * sqrt(b) */ - similarity = (double) distance / sqrt((double) norma * (double) normb); - -#ifdef _MSC_VER - /* /fp:fast may not propagate NaN */ - if (isnan(similarity)) - PG_RETURN_FLOAT8(NAN); -#endif - - /* Keep in range */ - if (similarity > 1) - similarity = 1.0; - else if (similarity < -1) - similarity = -1.0; - - PG_RETURN_FLOAT8(1.0 - similarity); -} - -/* - * Get the distance for spherical k-means - * Currently uses angular distance since needs to satisfy triangle inequality - * Assumes inputs are unit vectors (skips norm) - */ -PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_spherical_distance); -Datum -vector_spherical_distance(PG_FUNCTION_ARGS) -{ - Vector *a = PG_GETARG_VECTOR_P(0); - Vector *b = PG_GETARG_VECTOR_P(1); - float dp = 0.0; - double distance; - - CheckDims(a, b); - - /* Auto-vectorized */ - for (int i = 0; i < a->dim; i++) - dp += a->x[i] * b->x[i]; - - distance = (double) dp; - - /* Prevent NaN with acos with loss of precision */ - if (distance > 1) - distance = 1; - else if (distance < -1) - distance = -1; - - PG_RETURN_FLOAT8(acos(distance) / M_PI); -} - -/* - * Get the L1 distance between vectors - */ -PGDLLEXPORT PG_FUNCTION_INFO_V1(l1_distance); -Datum -l1_distance(PG_FUNCTION_ARGS) -{ - Vector *a = PG_GETARG_VECTOR_P(0); - Vector *b = PG_GETARG_VECTOR_P(1); - float *ax = a->x; - float *bx = b->x; - float distance = 0.0; - - CheckDims(a, b); - - /* Auto-vectorized */ - for (int i = 0; i < a->dim; i++) - distance += fabsf(ax[i] - bx[i]); - - PG_RETURN_FLOAT8((double) distance); -} - -/* - * Get the dimensions of a vector - */ -PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_dims); -Datum -vector_dims(PG_FUNCTION_ARGS) -{ - Vector *a = PG_GETARG_VECTOR_P(0); - - PG_RETURN_INT32(a->dim); -} - -/* - * Get the L2 norm of a vector - */ -PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_norm); -Datum -vector_norm(PG_FUNCTION_ARGS) -{ - Vector *a = PG_GETARG_VECTOR_P(0); - float *ax = a->x; - double norm = 0.0; - - /* Auto-vectorized */ - for (int i = 0; i < a->dim; i++) - norm += (double) ax[i] * (double) ax[i]; - - PG_RETURN_FLOAT8(sqrt(norm)); -} - -/* - * Add vectors - */ -PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_add); -Datum -vector_add(PG_FUNCTION_ARGS) -{ - Vector *a = PG_GETARG_VECTOR_P(0); - Vector *b = PG_GETARG_VECTOR_P(1); - float *ax = a->x; - float *bx = b->x; - Vector *result; - float *rx; - - CheckDims(a, b); - - result = InitVector(a->dim); - rx = result->x; - - /* Auto-vectorized */ - for (int i = 0, imax = a->dim; i < imax; i++) - rx[i] = ax[i] + bx[i]; - - /* Check for overflow */ - for (int i = 0, imax = a->dim; i < imax; i++) - { - if (isinf(rx[i])) - float_overflow_error(); - } - - PG_RETURN_POINTER(result); -} - -/* - * Subtract vectors - */ -PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_sub); -Datum -vector_sub(PG_FUNCTION_ARGS) -{ - Vector *a = PG_GETARG_VECTOR_P(0); - Vector *b = PG_GETARG_VECTOR_P(1); - float *ax = a->x; - float *bx = b->x; - Vector *result; - float *rx; - - CheckDims(a, b); - - result = InitVector(a->dim); - rx = result->x; - - /* Auto-vectorized */ - for (int i = 0, imax = a->dim; i < imax; i++) - rx[i] = ax[i] - bx[i]; - - /* Check for overflow */ - for (int i = 0, imax = a->dim; i < imax; i++) - { - if (isinf(rx[i])) - float_overflow_error(); - } - - PG_RETURN_POINTER(result); -} - -/* - * Multiply vectors - */ -PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_mul); -Datum -vector_mul(PG_FUNCTION_ARGS) -{ - Vector *a = PG_GETARG_VECTOR_P(0); - Vector *b = PG_GETARG_VECTOR_P(1); - float *ax = a->x; - float *bx = b->x; - Vector *result; - float *rx; - - CheckDims(a, b); - - result = InitVector(a->dim); - rx = result->x; - - /* Auto-vectorized */ - for (int i = 0, imax = a->dim; i < imax; i++) - rx[i] = ax[i] * bx[i]; - - /* Check for overflow and underflow */ - for (int i = 0, imax = a->dim; i < imax; i++) - { - if (isinf(rx[i])) - float_overflow_error(); - - if (rx[i] == 0 && !(ax[i] == 0 || bx[i] == 0)) - float_underflow_error(); - } - - PG_RETURN_POINTER(result); -} - -/* - * Internal helper to compare vectors - */ -int -vector_cmp_internal(Vector * a, Vector * b) -{ - CheckDims(a, b); - - for (int i = 0; i < a->dim; i++) - { - if (a->x[i] < b->x[i]) - return -1; - - if (a->x[i] > b->x[i]) - return 1; - } - return 0; -} - -/* - * Less than - */ -PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_lt); -Datum -vector_lt(PG_FUNCTION_ARGS) -{ - Vector *a = (Vector *) PG_GETARG_VECTOR_P(0); - Vector *b = (Vector *) PG_GETARG_VECTOR_P(1); - - PG_RETURN_BOOL(vector_cmp_internal(a, b) < 0); -} - -/* - * Less than or equal - */ -PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_le); -Datum -vector_le(PG_FUNCTION_ARGS) -{ - Vector *a = (Vector *) PG_GETARG_VECTOR_P(0); - Vector *b = (Vector *) PG_GETARG_VECTOR_P(1); - - PG_RETURN_BOOL(vector_cmp_internal(a, b) <= 0); -} - -/* - * Equal - */ -PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_eq); -Datum -vector_eq(PG_FUNCTION_ARGS) -{ - Vector *a = (Vector *) PG_GETARG_VECTOR_P(0); - Vector *b = (Vector *) PG_GETARG_VECTOR_P(1); - - PG_RETURN_BOOL(vector_cmp_internal(a, b) == 0); -} - -/* - * Not equal - */ -PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_ne); -Datum -vector_ne(PG_FUNCTION_ARGS) -{ - Vector *a = (Vector *) PG_GETARG_VECTOR_P(0); - Vector *b = (Vector *) PG_GETARG_VECTOR_P(1); - - PG_RETURN_BOOL(vector_cmp_internal(a, b) != 0); -} - -/* - * Greater than or equal - */ -PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_ge); -Datum -vector_ge(PG_FUNCTION_ARGS) -{ - Vector *a = (Vector *) PG_GETARG_VECTOR_P(0); - Vector *b = (Vector *) PG_GETARG_VECTOR_P(1); - - PG_RETURN_BOOL(vector_cmp_internal(a, b) >= 0); -} - -/* - * Greater than - */ -PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_gt); -Datum -vector_gt(PG_FUNCTION_ARGS) -{ - Vector *a = (Vector *) PG_GETARG_VECTOR_P(0); - Vector *b = (Vector *) PG_GETARG_VECTOR_P(1); - - PG_RETURN_BOOL(vector_cmp_internal(a, b) > 0); -} - -/* - * Compare vectors - */ -PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_cmp); -Datum -vector_cmp(PG_FUNCTION_ARGS) -{ - Vector *a = (Vector *) PG_GETARG_VECTOR_P(0); - Vector *b = (Vector *) PG_GETARG_VECTOR_P(1); - - PG_RETURN_INT32(vector_cmp_internal(a, b)); -} - -/* - * Accumulate vectors - */ -PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_accum); -Datum -vector_accum(PG_FUNCTION_ARGS) -{ - ArrayType *statearray = PG_GETARG_ARRAYTYPE_P(0); - Vector *newval = PG_GETARG_VECTOR_P(1); - float8 *statevalues; - int16 dim; - bool newarr; - float8 n; - Datum *statedatums; - float *x = newval->x; - ArrayType *result; - - /* Check array before using */ - statevalues = CheckStateArray(statearray, "vector_accum"); - dim = STATE_DIMS(statearray); - newarr = dim == 0; - - if (newarr) - dim = newval->dim; - else - CheckExpectedDim(dim, newval->dim); - - n = statevalues[0] + 1.0; - - statedatums = CreateStateDatums(dim); - statedatums[0] = Float8GetDatum(n); - - if (newarr) - { - for (int i = 0; i < dim; i++) - statedatums[i + 1] = Float8GetDatum((double) x[i]); - } - else - { - for (int i = 0; i < dim; i++) - { - double v = statevalues[i + 1] + x[i]; - - /* Check for overflow */ - if (isinf(v)) - float_overflow_error(); - - statedatums[i + 1] = Float8GetDatum(v); - } - } - - /* Use float8 array like float4_accum */ - result = construct_array(statedatums, dim + 1, - FLOAT8OID, - sizeof(float8), FLOAT8PASSBYVAL, TYPALIGN_DOUBLE); - - pfree(statedatums); - - PG_RETURN_ARRAYTYPE_P(result); -} - -/* - * Combine vectors - */ -PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_combine); -Datum -vector_combine(PG_FUNCTION_ARGS) -{ - ArrayType *statearray1 = PG_GETARG_ARRAYTYPE_P(0); - ArrayType *statearray2 = PG_GETARG_ARRAYTYPE_P(1); - float8 *statevalues1; - float8 *statevalues2; - float8 n; - float8 n1; - float8 n2; - int16 dim; - Datum *statedatums; - ArrayType *result; - - /* Check arrays before using */ - statevalues1 = CheckStateArray(statearray1, "vector_combine"); - statevalues2 = CheckStateArray(statearray2, "vector_combine"); - - n1 = statevalues1[0]; - n2 = statevalues2[0]; - - if (n1 == 0.0) - { - n = n2; - dim = STATE_DIMS(statearray2); - statedatums = CreateStateDatums(dim); - for (int i = 1; i <= dim; i++) - statedatums[i] = Float8GetDatum(statevalues2[i]); - } - else if (n2 == 0.0) - { - n = n1; - dim = STATE_DIMS(statearray1); - statedatums = CreateStateDatums(dim); - for (int i = 1; i <= dim; i++) - statedatums[i] = Float8GetDatum(statevalues1[i]); - } - else - { - n = n1 + n2; - dim = STATE_DIMS(statearray1); - CheckExpectedDim(dim, STATE_DIMS(statearray2)); - statedatums = CreateStateDatums(dim); - for (int i = 1; i <= dim; i++) - { - double v = statevalues1[i] + statevalues2[i]; - - /* Check for overflow */ - if (isinf(v)) - float_overflow_error(); - - statedatums[i] = Float8GetDatum(v); - } - } - - statedatums[0] = Float8GetDatum(n); - - result = construct_array(statedatums, dim + 1, - FLOAT8OID, - sizeof(float8), FLOAT8PASSBYVAL, TYPALIGN_DOUBLE); - - pfree(statedatums); - - PG_RETURN_ARRAYTYPE_P(result); -} - -/* - * Average vectors - */ -PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_avg); -Datum -vector_avg(PG_FUNCTION_ARGS) -{ - ArrayType *statearray = PG_GETARG_ARRAYTYPE_P(0); - float8 *statevalues; - float8 n; - uint16 dim; - Vector *result; - - /* Check array before using */ - statevalues = CheckStateArray(statearray, "vector_avg"); - n = statevalues[0]; - - /* SQL defines AVG of no values to be NULL */ - if (n == 0.0) - PG_RETURN_NULL(); - - /* Create vector */ - dim = STATE_DIMS(statearray); - CheckDim(dim); - result = InitVector(dim); - for (int i = 0; i < dim; i++) - { - result->x[i] = statevalues[i + 1] / n; - CheckElement(result->x[i]); - } - - PG_RETURN_POINTER(result); -} diff --git a/packages/server/postgres/extensions/pgvector/src/vector.h b/packages/server/postgres/extensions/pgvector/src/vector.h deleted file mode 100644 index e649471eaaa..00000000000 --- a/packages/server/postgres/extensions/pgvector/src/vector.h +++ /dev/null @@ -1,23 +0,0 @@ -#ifndef VECTOR_H -#define VECTOR_H - -#define VECTOR_MAX_DIM 16000 - -#define VECTOR_SIZE(_dim) (offsetof(Vector, x) + sizeof(float)*(_dim)) -#define DatumGetVector(x) ((Vector *) PG_DETOAST_DATUM(x)) -#define PG_GETARG_VECTOR_P(x) DatumGetVector(PG_GETARG_DATUM(x)) -#define PG_RETURN_VECTOR_P(x) PG_RETURN_POINTER(x) - -typedef struct Vector -{ - int32 vl_len_; /* varlena header (do not touch directly!) */ - int16 dim; /* number of dimensions */ - int16 unused; - float x[FLEXIBLE_ARRAY_MEMBER]; -} Vector; - -Vector *InitVector(int dim); -void PrintVector(char *msg, Vector * vector); -int vector_cmp_internal(Vector * a, Vector * b); - -#endif diff --git a/packages/server/postgres/extensions/pgvector/src/vector.o b/packages/server/postgres/extensions/pgvector/src/vector.o deleted file mode 100644 index c1b6356d7956ff5bf39256010793224b65075c52..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 36336 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 Jths#s{{vhtk68c!