Compare commits

..

1 Commits

Author SHA1 Message Date
Andrew Kane
7c6694e0ef Added bound option 2022-02-13 01:41:06 -08:00
50 changed files with 310 additions and 786 deletions

View File

@@ -1,6 +1,6 @@
root = true root = true
[*.{c,h,pl,pm}] [*.{c,h,pl}]
indent_style = tab indent_style = tab
indent_size = tab indent_size = tab
tab_width = 4 tab_width = 4

View File

@@ -1,65 +1,38 @@
name: build name: build
on: [push, pull_request] on: [push, pull_request]
jobs: jobs:
ubuntu: build:
runs-on: ubuntu-latest runs-on: ${{ matrix.os }}
if: ${{ !startsWith(github.ref_name, 'windows') }}
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
postgres: [15, 14, 13, 12, 11, 10] os: [ubuntu-latest]
postgres: [14, 13, 12, 11, 10, 9.6]
include:
- os: macos-latest
postgres: 14
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v2
- uses: ankane/setup-postgres@v1 - uses: ankane/setup-postgres@v1
with: with:
postgres-version: ${{ matrix.postgres }} postgres-version: ${{ matrix.postgres }}
dev-files: true - if: ${{ startsWith(matrix.os, 'ubuntu') }}
- run: make run: sudo apt-get update && sudo apt-get install postgresql-server-dev-${{ matrix.postgres }} libipc-run-perl
- run: | - run: make
export PG_CONFIG=`which pg_config` - if: ${{ startsWith(matrix.os, 'ubuntu') }}
sudo --preserve-env=PG_CONFIG make install run: |
- run: make installcheck export PG_CONFIG=`which pg_config`
- if: ${{ failure() }} sudo --preserve-env=PG_CONFIG make install
run: cat regression.diffs - if: ${{ startsWith(matrix.os, 'macos') }}
- run: | run: make install
sudo apt-get update - run: make installcheck
sudo apt-get install libipc-run-perl - if: ${{ failure() }}
make prove_installcheck run: cat regression.diffs
mac: - if: ${{ startsWith(matrix.os, 'ubuntu') }}
runs-on: macos-latest run: make prove_installcheck
if: ${{ !startsWith(github.ref_name, 'windows') }} - if: ${{ startsWith(matrix.os, 'macos') }}
steps: run: |
- uses: actions/checkout@v3 brew install cpanm && cpanm IPC::Run
- uses: ankane/setup-postgres@v1 wget -q https://github.com/postgres/postgres/archive/refs/tags/REL_14_1.tar.gz
with: tar xf REL_14_1.tar.gz
postgres-version: 14 make prove_installcheck PROVE=prove PERL5LIB=postgres-REL_14_1/src/test/perl
- run: make
- run: make install
- run: make installcheck
- if: ${{ failure() }}
run: cat regression.diffs
- run: |
brew install cpanm
cpanm IPC::Run
wget -q https://github.com/postgres/postgres/archive/refs/tags/REL_14_5.tar.gz
tar xf REL_14_5.tar.gz
make prove_installcheck PROVE=prove PROVE_FLAGS="-I ./postgres-REL_14_5/src/test/perl" PERL5LIB="/Users/runner/perl5/lib/perl5"
windows:
runs-on: windows-latest
steps:
- uses: actions/checkout@v3
- uses: ankane/setup-postgres@v1
with:
postgres-version: 14
- run: |
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat"
nmake /NOLOGO /F Makefile.win
nmake /NOLOGO /F Makefile.win install
curl -Ls -o REL_14_5.tar.gz https://github.com/postgres/postgres/archive/refs/tags/REL_14_5.tar.gz
7z x REL_14_5.tar.gz
7z x REL_14_5.tar
ls ./postgres-REL_14_5/src/test/perl
set PROVE=prove
set PROVE_FLAGS="-I ./postgres-REL_14_5/src/test/perl"
nmake /NOLOGO /F Makefile.win prove_installcheck
shell: cmd

1
.gitignore vendored
View File

@@ -5,4 +5,3 @@
regression.* regression.*
*.o *.o
*.so *.so
*.bc

View File

@@ -1,33 +1,3 @@
## 0.4.0 (unreleased)
- Changed text representation for vector elements to match `real`
- Improved accuracy of text parsing for certain inputs
- Added experimental support for Windows
## 0.3.2 (2022-11-22)
- Fixed `invalid memory alloc request size` error
## 0.3.1 (2022-11-02)
If upgrading from 0.2.7 or 0.3.0, [recreate](https://github.com/pgvector/pgvector#031) all `ivfflat` indexes after upgrading to ensure all data is indexed.
- Fixed issue with inserts silently corrupting `ivfflat` indexes (introduced in 0.2.7)
- Fixed segmentation fault with index creation when lists > 6500
## 0.3.0 (2022-10-15)
- Added support for Postgres 15
- Dropped support for Postgres 9.6
## 0.2.7 (2022-07-31)
- Fixed `unexpected data beyond EOF` error
## 0.2.6 (2022-05-22)
- Improved performance of index creation for Postgres < 12
## 0.2.5 (2022-02-11) ## 0.2.5 (2022-02-11)
- Reduced memory usage during index creation - Reduced memory usage during index creation

View File

@@ -1,9 +1,9 @@
FROM postgres:15 FROM postgres:14
COPY . /tmp/pgvector COPY . /tmp/pgvector
RUN apt-get update && \ RUN apt-get update && \
apt-get install -y --no-install-recommends build-essential postgresql-server-dev-15 && \ apt-get install -y --no-install-recommends build-essential postgresql-server-dev-14 && \
cd /tmp/pgvector && \ cd /tmp/pgvector && \
make clean && \ make clean && \
make OPTFLAGS="" && \ make OPTFLAGS="" && \
@@ -11,6 +11,6 @@ RUN apt-get update && \
mkdir /usr/share/doc/pgvector && \ mkdir /usr/share/doc/pgvector && \
cp LICENSE README.md /usr/share/doc/pgvector && \ cp LICENSE README.md /usr/share/doc/pgvector && \
rm -r /tmp/pgvector && \ rm -r /tmp/pgvector && \
apt-get remove -y build-essential postgresql-server-dev-15 && \ apt-get remove -y build-essential postgresql-server-dev-14 && \
apt-get autoremove -y && \ apt-get autoremove -y && \
rm -rf /var/lib/apt/lists/* rm -rf /var/lib/apt/lists/*

View File

@@ -2,7 +2,7 @@
"name": "vector", "name": "vector",
"abstract": "Open-source vector similarity search for Postgres", "abstract": "Open-source vector similarity search for Postgres",
"description": "Supports L2 distance, inner product, and cosine distance", "description": "Supports L2 distance, inner product, and cosine distance",
"version": "0.3.2", "version": "0.2.5",
"maintainer": [ "maintainer": [
"Andrew Kane <andrew@ankane.org>" "Andrew Kane <andrew@ankane.org>"
], ],
@@ -12,7 +12,7 @@
"prereqs": { "prereqs": {
"runtime": { "runtime": {
"requires": { "requires": {
"PostgreSQL": "10.0.0" "PostgreSQL": "9.6.0"
} }
} }
}, },
@@ -20,7 +20,7 @@
"vector": { "vector": {
"file": "sql/vector.sql", "file": "sql/vector.sql",
"docfile": "README.md", "docfile": "README.md",
"version": "0.3.2", "version": "0.2.5",
"abstract": "Open-source vector similarity search for Postgres" "abstract": "Open-source vector similarity search for Postgres"
} }
}, },

View File

@@ -1,5 +1,5 @@
EXTENSION = vector EXTENSION = vector
EXTVERSION = 0.3.2 EXTVERSION = 0.2.5
MODULE_big = vector MODULE_big = vector
DATA = $(wildcard sql/*--*.sql) DATA = $(wildcard sql/*--*.sql)
@@ -7,7 +7,7 @@ OBJS = src/ivfbuild.o src/ivfflat.o src/ivfinsert.o src/ivfkmeans.o src/ivfscan.
TESTS = $(wildcard test/sql/*.sql) TESTS = $(wildcard test/sql/*.sql)
REGRESS = $(patsubst test/sql/%.sql,%,$(TESTS)) REGRESS = $(patsubst test/sql/%.sql,%,$(TESTS))
REGRESS_OPTS = --inputdir=test --load-extension=vector REGRESS_OPTS = --inputdir=test
OPTFLAGS = -march=native OPTFLAGS = -march=native
@@ -40,9 +40,6 @@ PG_CONFIG ?= pg_config
PGXS := $(shell $(PG_CONFIG) --pgxs) PGXS := $(shell $(PG_CONFIG) --pgxs)
include $(PGXS) include $(PGXS)
# for Postgres 15
PROVE_FLAGS += -I ./test/perl
prove_installcheck: prove_installcheck:
rm -rf $(CURDIR)/tmp_check rm -rf $(CURDIR)/tmp_check
cd $(srcdir) && TESTDIR='$(CURDIR)' PATH="$(bindir):$$PATH" PGPORT='6$(DEF_PGPORT)' PG_REGRESS='$(top_builddir)/src/test/regress/pg_regress' $(PROVE) $(PG_PROVE_FLAGS) $(PROVE_FLAGS) $(if $(PROVE_TESTS),$(PROVE_TESTS),test/t/*.pl) cd $(srcdir) && TESTDIR='$(CURDIR)' PATH="$(bindir):$$PATH" PGPORT='6$(DEF_PGPORT)' PG_REGRESS='$(top_builddir)/src/test/regress/pg_regress' $(PROVE) $(PG_PROVE_FLAGS) $(PROVE_FLAGS) $(if $(PROVE_TESTS),$(PROVE_TESTS),test/t/*.pl)

View File

@@ -1,62 +0,0 @@
EXTENSION = vector
EXTVERSION = 0.3.2
OBJS = src\ivfbuild.obj src\ivfflat.obj src\ivfinsert.obj src\ivfkmeans.obj src\ivfscan.obj src\ivfutils.obj src\ivfvacuum.obj src\vector.obj
REGRESS = btree cast copy functions input ivfflat_cosine ivfflat_ip ivfflat_l2 ivfflat_options ivfflat_unlogged
REGRESS_OPTS = --inputdir=test --load-extension=vector
# For /arch flags
# https://learn.microsoft.com/en-us/cpp/build/reference/arch-minimum-cpu-architecture
OPTFLAGS =
# For auto-vectorization:
# - MSVC (needs /O2 /fp:fast) - https://learn.microsoft.com/en-us/cpp/parallel/auto-parallelization-and-auto-vectorization?#auto-vectorizer
PG_CFLAGS = $(PG_CFLAGS) $(OPTFLAGS) /O2 /fp:fast
# Debug MSVC auto-vectorization
# https://learn.microsoft.com/en-us/cpp/error-messages/tool-errors/vectorizer-and-parallelizer-messages
# PG_CFLAGS = $(PG_CFLAGS) /Qvec-report:2
all: sql\$(EXTENSION)--$(EXTVERSION).sql
sql\$(EXTENSION)--$(EXTVERSION).sql: sql\$(EXTENSION).sql
copy sql\$(EXTENSION).sql $@
# TODO use pg_config
BINDIR = $(PGROOT)\bin
INCLUDEDIR = $(PGROOT)\include
INCLUDEDIR_SERVER = $(PGROOT)\include\server
LIBDIR = $(PGROOT)\lib
PKGLIBDIR = $(PGROOT)\lib
SHAREDIR = $(PGROOT)\share
CFLAGS = /nologo /I"$(INCLUDEDIR_SERVER)\port\win32_msvc" /I"$(INCLUDEDIR_SERVER)\port\win32" /I"$(INCLUDEDIR_SERVER)" /I"$(INCLUDEDIR)"
CFLAGS = $(CFLAGS) $(PG_CFLAGS)
SHLIB = src\$(EXTENSION).dll
LIBS = "$(LIBDIR)\postgres.lib"
.c.obj:
$(CC) $(CFLAGS) /c $< /Fo$@
$(SHLIB): $(OBJS)
$(CC) $(CFLAGS) $(OBJS) $(LIBS) /link /DLL /OUT:$(SHLIB)
all: $(SHLIB)
install:
copy $(SHLIB) "$(PKGLIBDIR)"
copy $(EXTENSION).control "$(SHAREDIR)\extension"
copy sql\$(EXTENSION)--*.sql "$(SHAREDIR)\extension"
installcheck:
"$(BINDIR)\pg_regress" --bindir="$(BINDIR)" $(REGRESS_OPTS) $(REGRESS)
prove_installcheck:
rm -rf tmp_check
set PGPORT=65432
set PG_REGRESS="$(BINDIR)\pg_regress"
$(PROVE) $(PG_PROVE_FLAGS) $(PROVE_FLAGS) test/t/*.pl

View File

@@ -3,9 +3,9 @@
Open-source vector similarity search for Postgres Open-source vector similarity search for Postgres
```sql ```sql
CREATE TABLE items (embedding vector(3)); CREATE TABLE table (column vector(3));
CREATE INDEX ON items USING ivfflat (embedding vector_l2_ops); CREATE INDEX ON table USING ivfflat (column vector_l2_ops);
SELECT * FROM items ORDER BY embedding <-> '[1,2,3]' LIMIT 5; SELECT * FROM table ORDER BY column <-> '[1,2,3]' LIMIT 5;
``` ```
Supports L2 distance, inner product, and cosine distance Supports L2 distance, inner product, and cosine distance
@@ -14,10 +14,10 @@ Supports L2 distance, inner product, and cosine distance
## Installation ## Installation
Compile and install the extension (supports Postgres 10+) Compile and install the extension (supports Postgres 9.6+)
```sh ```sh
git clone --branch v0.3.2 https://github.com/pgvector/pgvector.git git clone --branch v0.2.5 https://github.com/pgvector/pgvector.git
cd pgvector cd pgvector
make make
make install # may need sudo make install # may need sudo
@@ -33,22 +33,22 @@ You can also install it with [Docker](#docker), [Homebrew](#homebrew), or [PGXN]
## Getting Started ## Getting Started
Create a vector column with 3 dimensions Create a vector column with 3 dimensions (replace `table` and `column` with non-reserved names)
```sql ```sql
CREATE TABLE items (embedding vector(3)); CREATE TABLE table (column vector(3));
``` ```
Insert values Insert values
```sql ```sql
INSERT INTO items VALUES ('[1,2,3]'), ('[4,5,6]'); INSERT INTO table VALUES ('[1,2,3]'), ('[4,5,6]');
``` ```
Get the nearest neighbor by L2 distance Get the nearest neighbor by L2 distance
```sql ```sql
SELECT * FROM items ORDER BY embedding <-> '[3,1,2]' LIMIT 1; SELECT * FROM table ORDER BY column <-> '[3,1,2]' LIMIT 1;
``` ```
Also supports inner product (`<#>`) and cosine distance (`<=>`) Also supports inner product (`<#>`) and cosine distance (`<=>`)
@@ -62,19 +62,19 @@ Speed up queries with an approximate index. Add an index for each distance funct
L2 distance L2 distance
```sql ```sql
CREATE INDEX ON items USING ivfflat (embedding vector_l2_ops); CREATE INDEX ON table USING ivfflat (column vector_l2_ops);
``` ```
Inner product Inner product
```sql ```sql
CREATE INDEX ON items USING ivfflat (embedding vector_ip_ops); CREATE INDEX ON table USING ivfflat (column vector_ip_ops);
``` ```
Cosine distance Cosine distance
```sql ```sql
CREATE INDEX ON items USING ivfflat (embedding vector_cosine_ops); CREATE INDEX ON table USING ivfflat (column vector_cosine_ops);
``` ```
Indexes should be created after the table has some data for optimal clustering. Also, unlike typical indexes which only affect performance, you may see different results for queries after adding an approximate index. Indexes should be created after the table has some data for optimal clustering. Also, unlike typical indexes which only affect performance, you may see different results for queries after adding an approximate index.
@@ -84,7 +84,7 @@ Indexes should be created after the table has some data for optimal clustering.
Specify the number of inverted lists (100 by default) Specify the number of inverted lists (100 by default)
```sql ```sql
CREATE INDEX ON items USING ivfflat (embedding vector_l2_ops) WITH (lists = 100); CREATE INDEX ON table USING ivfflat (column opclass) WITH (lists = 100);
``` ```
A [good place to start](https://github.com/facebookresearch/faiss/issues/112) is `4 * sqrt(rows)` A [good place to start](https://github.com/facebookresearch/faiss/issues/112) is `4 * sqrt(rows)`
@@ -131,20 +131,10 @@ Note: `tuples_done` and `tuples_total` are only populated during the `loading tu
Consider [partial indexes](https://www.postgresql.org/docs/current/indexes-partial.html) for queries with a `WHERE` clause Consider [partial indexes](https://www.postgresql.org/docs/current/indexes-partial.html) for queries with a `WHERE` clause
```sql ```sql
SELECT * FROM items WHERE category_id = 123 ORDER BY embedding <-> '[3,1,2]' LIMIT 5; CREATE INDEX ON table USING ivfflat (column opclass) WHERE (other_column = 123);
``` ```
can be indexed with: To index many different values of `other_column`, consider [partitioning](https://www.postgresql.org/docs/current/ddl-partitioning.html) on `other_column`.
```sql
CREATE INDEX ON items USING ivfflat (embedding vector_l2_ops) WHERE (category_id = 123);
```
To index many different values of `category_id`, consider [partitioning](https://www.postgresql.org/docs/current/ddl-partitioning.html) on `category_id`.
```sql
CREATE TABLE items (embedding vector(3), category_id int) PARTITION BY LIST(category_id);
```
## Performance ## Performance
@@ -157,14 +147,14 @@ SET max_parallel_workers_per_gather = 4;
To speed up queries with an index, increase the number of inverted lists (at the expense of recall). To speed up queries with an index, increase the number of inverted lists (at the expense of recall).
```sql ```sql
CREATE INDEX ON items USING ivfflat (embedding vector_l2_ops) WITH (lists = 1000); CREATE INDEX ON table USING ivfflat (column opclass) WITH (lists = 1000);
``` ```
## Reference ## Reference
### Vector Type ### Vector Type
Each vector takes `4 * dimensions + 8` bytes of storage. Each element is a single precision floating-point number (like the `real` type in Postgres), and all elements must be finite (no `NaN`, `Infinity` or `-Infinity`). Vectors can have up to 1024 dimensions. Each vector takes `4 * dimensions + 8` bytes of storage. Each element is a float, and all elements must be finite (no `NaN`, `Infinity` or `-Infinity`). Vectors can have up to 1024 dimensions.
### Vector Operators ### Vector Operators
@@ -195,10 +185,8 @@ Libraries that use pgvector:
- [pgvector-ruby](https://github.com/pgvector/pgvector-ruby) (Ruby) - [pgvector-ruby](https://github.com/pgvector/pgvector-ruby) (Ruby)
- [pgvector-node](https://github.com/pgvector/pgvector-node) (Node.js) - [pgvector-node](https://github.com/pgvector/pgvector-node) (Node.js)
- [pgvector-go](https://github.com/pgvector/pgvector-go) (Go) - [pgvector-go](https://github.com/pgvector/pgvector-go) (Go)
- [pgvector-php](https://github.com/pgvector/pgvector-php) (PHP)
- [pgvector-rust](https://github.com/pgvector/pgvector-rust) (Rust) - [pgvector-rust](https://github.com/pgvector/pgvector-rust) (Rust)
- [pgvector-cpp](https://github.com/pgvector/pgvector-cpp) (C++) - [pgvector-cpp](https://github.com/pgvector/pgvector-cpp) (C++)
- [pgvector-elixir](https://github.com/pgvector/pgvector-elixir) (Elixir)
## Frequently Asked Questions ## Frequently Asked Questions
@@ -232,14 +220,14 @@ This adds pgvector to the [Postgres image](https://hub.docker.com/_/postgres).
You can also build the image manually You can also build the image manually
```sh ```sh
git clone --branch v0.3.2 https://github.com/pgvector/pgvector.git git clone --branch v0.2.5 https://github.com/pgvector/pgvector.git
cd pgvector cd pgvector
docker build -t pgvector . docker build -t pgvector .
``` ```
### Homebrew ### Homebrew
With Homebrew Postgres, you can use: On Mac with Homebrew Postgres, you can use:
```sh ```sh
brew install pgvector/brew/pgvector brew install pgvector/brew/pgvector
@@ -270,22 +258,6 @@ Install the latest version and run:
ALTER EXTENSION vector UPDATE; ALTER EXTENSION vector UPDATE;
``` ```
## Upgrade Notes
### 0.3.1
If upgrading from 0.2.7 or 0.3.0, recreate all `ivfflat` indexes after upgrading to ensure all data is indexed.
```sql
-- Postgres 12+
REINDEX INDEX CONCURRENTLY index_name;
-- Postgres < 12
CREATE INDEX CONCURRENTLY temp_name ON table USING ivfflat (column opclass);
DROP INDEX CONCURRENTLY index_name;
ALTER INDEX temp_name RENAME TO index_name;
```
## Thanks ## Thanks
Thanks to: Thanks to:

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -80,11 +80,7 @@ SampleCallback(Relation index, CALLBACK_ITEM_POINTER, Datum *values,
if (buildstate->rowstoskip <= 0) if (buildstate->rowstoskip <= 0)
{ {
#if PG_VERSION_NUM >= 150000
int k = (int) (targsamples * sampler_random_fract(&buildstate->rstate.randstate));
#else
int k = (int) (targsamples * sampler_random_fract(buildstate->rstate.randstate)); int k = (int) (targsamples * sampler_random_fract(buildstate->rstate.randstate));
#endif
Assert(k >= 0 && k < targsamples); Assert(k >= 0 && k < targsamples);
VectorArraySet(samples, k, DatumGetVector(value)); VectorArraySet(samples, k, DatumGetVector(value));
@@ -107,7 +103,7 @@ SampleRows(IvfflatBuildState * buildstate)
buildstate->rowstoskip = -1; buildstate->rowstoskip = -1;
BlockSampler_Init(&buildstate->bs, totalblocks, targsamples, RandomInt()); BlockSampler_Init(&buildstate->bs, totalblocks, targsamples, random());
reservoir_init_selection_state(&buildstate->rstate, targsamples); reservoir_init_selection_state(&buildstate->rstate, targsamples);
while (BlockSampler_HasMore(&buildstate->bs)) while (BlockSampler_HasMore(&buildstate->bs))
@@ -116,13 +112,13 @@ SampleRows(IvfflatBuildState * buildstate)
#if PG_VERSION_NUM >= 120000 #if PG_VERSION_NUM >= 120000
table_index_build_range_scan(buildstate->heap, buildstate->index, buildstate->indexInfo, table_index_build_range_scan(buildstate->heap, buildstate->index, buildstate->indexInfo,
false, true, false, targblock, 1, SampleCallback, (void *) buildstate, NULL); false, true, true, targblock, 1, SampleCallback, (void *) buildstate, NULL);
#elif PG_VERSION_NUM >= 110000 #elif PG_VERSION_NUM >= 110000
IndexBuildHeapRangeScan(buildstate->heap, buildstate->index, buildstate->indexInfo, IndexBuildHeapRangeScan(buildstate->heap, buildstate->index, buildstate->indexInfo,
false, true, targblock, 1, SampleCallback, (void *) buildstate, NULL); true, true, targblock, 1, SampleCallback, (void *) buildstate, NULL);
#else #else
IndexBuildHeapRangeScan(buildstate->heap, buildstate->index, buildstate->indexInfo, IndexBuildHeapRangeScan(buildstate->heap, buildstate->index, buildstate->indexInfo,
false, true, targblock, 1, SampleCallback, (void *) buildstate); true, true, targblock, 1, SampleCallback, (void *) buildstate);
#endif #endif
} }
} }
@@ -171,18 +167,18 @@ BuildCallback(Relation index, CALLBACK_ITEM_POINTER, Datum *values,
#ifdef IVFFLAT_KMEANS_DEBUG #ifdef IVFFLAT_KMEANS_DEBUG
buildstate->inertia += minDistance; buildstate->inertia += minDistance;
buildstate->listSums[closestCenter] += minDistance;
buildstate->listCounts[closestCenter]++;
#endif #endif
/* Create a virtual tuple */ /* Create a virtual tuple */
ExecClearTuple(slot); ExecClearTuple(slot);
slot->tts_values[0] = Int32GetDatum(closestCenter); slot->tts_values[0] = Int32GetDatum(closestCenter);
slot->tts_isnull[0] = false; slot->tts_isnull[0] = false;
slot->tts_values[1] = PointerGetDatum(tid); slot->tts_values[1] = Int32GetDatum(ItemPointerGetBlockNumberNoCheck(tid));
slot->tts_isnull[1] = false; slot->tts_isnull[1] = false;
slot->tts_values[2] = value; slot->tts_values[2] = Int32GetDatum(ItemPointerGetOffsetNumberNoCheck(tid));
slot->tts_isnull[2] = false; slot->tts_isnull[2] = false;
slot->tts_values[3] = value;
slot->tts_isnull[3] = false;
ExecStoreVirtualTuple(slot); ExecStoreVirtualTuple(slot);
/* /*
@@ -204,6 +200,8 @@ GetNextTuple(Tuplesortstate *sortstate, TupleDesc tupdesc, TupleTableSlot *slot,
{ {
Datum value; Datum value;
bool isnull; bool isnull;
int tupblk;
int tupoff;
#if PG_VERSION_NUM >= 100000 #if PG_VERSION_NUM >= 100000
if (tuplesort_gettupleslot(sortstate, true, false, slot, NULL)) if (tuplesort_gettupleslot(sortstate, true, false, slot, NULL))
@@ -212,11 +210,13 @@ GetNextTuple(Tuplesortstate *sortstate, TupleDesc tupdesc, TupleTableSlot *slot,
#endif #endif
{ {
*list = DatumGetInt32(slot_getattr(slot, 1, &isnull)); *list = DatumGetInt32(slot_getattr(slot, 1, &isnull));
value = slot_getattr(slot, 3, &isnull); tupblk = DatumGetInt32(slot_getattr(slot, 2, &isnull));
tupoff = DatumGetInt32(slot_getattr(slot, 3, &isnull));
value = slot_getattr(slot, 4, &isnull);
/* Form the index tuple */ /* Form the index tuple */
*itup = index_form_tuple(tupdesc, &value, &isnull); *itup = index_form_tuple(tupdesc, &value, &isnull);
(*itup)->t_tid = *((ItemPointer) DatumGetPointer(slot_getattr(slot, 2, &isnull))); ItemPointerSet(&(*itup)->t_tid, tupblk, tupoff);
} }
else else
*list = -1; *list = -1;
@@ -233,8 +233,8 @@ InsertTuples(Relation index, IvfflatBuildState * buildstate, ForkNumber forkNum)
GenericXLogState *state; GenericXLogState *state;
int list; int list;
IndexTuple itup = NULL; /* silence compiler warning */ IndexTuple itup = NULL; /* silence compiler warning */
BlockNumber startPage; BlockNumber startPage = InvalidBlockNumber;
BlockNumber insertPage; BlockNumber insertPage = InvalidBlockNumber;
Size itemsz; Size itemsz;
int i; int i;
int64 inserted = 0; int64 inserted = 0;
@@ -259,7 +259,7 @@ InsertTuples(Relation index, IvfflatBuildState * buildstate, ForkNumber forkNum)
CHECK_FOR_INTERRUPTS(); CHECK_FOR_INTERRUPTS();
buf = IvfflatNewBuffer(index, forkNum); buf = IvfflatNewBuffer(index, forkNum);
IvfflatInitRegisterPage(index, &buf, &page, &state); IvfflatInitPage(index, &buf, &page, &state);
startPage = BufferGetBlockNumber(buf); startPage = BufferGetBlockNumber(buf);
@@ -325,16 +325,17 @@ InitBuildState(IvfflatBuildState * buildstate, Relation heap, Relation index, In
/* Create tuple description for sorting */ /* Create tuple description for sorting */
#if PG_VERSION_NUM >= 120000 #if PG_VERSION_NUM >= 120000
buildstate->tupdesc = CreateTemplateTupleDesc(3); buildstate->tupdesc = CreateTemplateTupleDesc(4);
#else #else
buildstate->tupdesc = CreateTemplateTupleDesc(3, false); buildstate->tupdesc = CreateTemplateTupleDesc(4, false);
#endif #endif
TupleDescInitEntry(buildstate->tupdesc, (AttrNumber) 1, "list", INT4OID, -1, 0); TupleDescInitEntry(buildstate->tupdesc, (AttrNumber) 1, "list", INT4OID, -1, 0);
TupleDescInitEntry(buildstate->tupdesc, (AttrNumber) 2, "tid", TIDOID, -1, 0); TupleDescInitEntry(buildstate->tupdesc, (AttrNumber) 2, "blkno", INT4OID, -1, 0);
TupleDescInitEntry(buildstate->tupdesc, (AttrNumber) 3, "offset", INT4OID, -1, 0);
#if PG_VERSION_NUM >= 110000 #if PG_VERSION_NUM >= 110000
TupleDescInitEntry(buildstate->tupdesc, (AttrNumber) 3, "vector", RelationGetDescr(index)->attrs[0].atttypid, -1, 0); TupleDescInitEntry(buildstate->tupdesc, (AttrNumber) 4, "vector", RelationGetDescr(index)->attrs[0].atttypid, -1, 0);
#else #else
TupleDescInitEntry(buildstate->tupdesc, (AttrNumber) 3, "vector", RelationGetDescr(index)->attrs[0]->atttypid, -1, 0); TupleDescInitEntry(buildstate->tupdesc, (AttrNumber) 4, "vector", RelationGetDescr(index)->attrs[0]->atttypid, -1, 0);
#endif #endif
#if PG_VERSION_NUM >= 120000 #if PG_VERSION_NUM >= 120000
@@ -351,8 +352,6 @@ InitBuildState(IvfflatBuildState * buildstate, Relation heap, Relation index, In
#ifdef IVFFLAT_KMEANS_DEBUG #ifdef IVFFLAT_KMEANS_DEBUG
buildstate->inertia = 0; buildstate->inertia = 0;
buildstate->listSums = palloc0(sizeof(double) * buildstate->lists);
buildstate->listCounts = palloc0(sizeof(int) * buildstate->lists);
#endif #endif
} }
@@ -362,14 +361,9 @@ InitBuildState(IvfflatBuildState * buildstate, Relation heap, Relation index, In
static void static void
FreeBuildState(IvfflatBuildState * buildstate) FreeBuildState(IvfflatBuildState * buildstate)
{ {
VectorArrayFree(buildstate->centers); pfree(buildstate->centers);
pfree(buildstate->listInfo); pfree(buildstate->listInfo);
pfree(buildstate->normvec); pfree(buildstate->normvec);
#ifdef IVFFLAT_KMEANS_DEBUG
pfree(buildstate->listSums);
pfree(buildstate->listCounts);
#endif
} }
/* /*
@@ -401,7 +395,7 @@ ComputeCenters(IvfflatBuildState * buildstate)
IvfflatBench("k-means", IvfflatKmeans(buildstate->index, buildstate->samples, buildstate->centers)); IvfflatBench("k-means", IvfflatKmeans(buildstate->index, buildstate->samples, buildstate->centers));
/* Free samples before we allocate more memory */ /* Free samples before we allocate more memory */
VectorArrayFree(buildstate->samples); pfree(buildstate->samples);
} }
/* /*
@@ -416,7 +410,7 @@ CreateMetaPage(Relation index, int dimensions, int lists, ForkNumber forkNum)
IvfflatMetaPage metap; IvfflatMetaPage metap;
buf = IvfflatNewBuffer(index, forkNum); buf = IvfflatNewBuffer(index, forkNum);
IvfflatInitRegisterPage(index, &buf, &page, &state); IvfflatInitPage(index, &buf, &page, &state);
/* Set metapage data */ /* Set metapage data */
metap = IvfflatPageGetMeta(page); metap = IvfflatPageGetMeta(page);
@@ -449,7 +443,7 @@ CreateListPages(Relation index, VectorArray centers, int dimensions,
list = palloc(itemsz); list = palloc(itemsz);
buf = IvfflatNewBuffer(index, forkNum); buf = IvfflatNewBuffer(index, forkNum);
IvfflatInitRegisterPage(index, &buf, &page, &state); IvfflatInitPage(index, &buf, &page, &state);
for (i = 0; i < lists; i++) for (i = 0; i < lists; i++)
{ {
@@ -477,51 +471,6 @@ CreateListPages(Relation index, VectorArray centers, int dimensions,
pfree(list); pfree(list);
} }
/*
* Print k-means metrics
*/
#ifdef IVFFLAT_KMEANS_DEBUG
static void
PrintKmeansMetrics(IvfflatBuildState * buildstate)
{
elog(INFO, "inertia: %.3e", buildstate->inertia);
/* Calculate Davies-Bouldin index */
if (buildstate->lists > 1)
{
double db = 0.0;
/* Calculate average distance */
for (int i = 0; i < buildstate->lists; i++)
{
if (buildstate->listCounts[i] > 0)
buildstate->listSums[i] /= buildstate->listCounts[i];
}
for (int i = 0; i < buildstate->lists; i++)
{
double max = 0.0;
double distance;
for (int j = 0; j < buildstate->lists; j++)
{
if (j == i)
continue;
distance = DatumGetFloat8(FunctionCall2Coll(buildstate->procinfo, buildstate->collation, PointerGetDatum(VectorArrayGet(buildstate->centers, i)), PointerGetDatum(VectorArrayGet(buildstate->centers, j))));
distance = (buildstate->listSums[i] + buildstate->listSums[j]) / distance;
if (distance > max)
max = distance;
}
db += max;
}
db /= buildstate->lists;
elog(INFO, "davies-bouldin: %.3f", db);
}
}
#endif
/* /*
* Create entry pages * Create entry pages
*/ */
@@ -560,7 +509,7 @@ CreateEntryPages(IvfflatBuildState * buildstate, ForkNumber forkNum)
tuplesort_performsort(buildstate->sortstate); tuplesort_performsort(buildstate->sortstate);
#ifdef IVFFLAT_KMEANS_DEBUG #ifdef IVFFLAT_KMEANS_DEBUG
PrintKmeansMetrics(buildstate); elog(INFO, "inertia: %.3e", buildstate->inertia);
#endif #endif
/* Insert */ /* Insert */

View File

@@ -13,6 +13,7 @@
#endif #endif
int ivfflat_probes; int ivfflat_probes;
int ivfflat_bound;
static relopt_kind ivfflat_relopt_kind; static relopt_kind ivfflat_relopt_kind;
/* /*
@@ -32,6 +33,10 @@ _PG_init(void)
DefineCustomIntVariable("ivfflat.probes", "Sets the number of probes", DefineCustomIntVariable("ivfflat.probes", "Sets the number of probes",
"Valid range is 1..lists.", &ivfflat_probes, "Valid range is 1..lists.", &ivfflat_probes,
1, 1, IVFFLAT_MAX_LISTS, PGC_USERSET, 0, NULL, NULL, NULL); 1, 1, IVFFLAT_MAX_LISTS, PGC_USERSET, 0, NULL, NULL, NULL);
DefineCustomIntVariable("ivfflat.bound", "Sets the max results from index (experimental)",
NULL, &ivfflat_bound,
0, 0, INT_MAX, PGC_USERSET, 0, NULL, NULL, NULL);
} }
/* /*
@@ -164,7 +169,7 @@ ivfflatvalidate(Oid opclassoid)
* *
* See https://www.postgresql.org/docs/current/index-api.html * See https://www.postgresql.org/docs/current/index-api.html
*/ */
PGDLLEXPORT PG_FUNCTION_INFO_V1(ivfflathandler); PG_FUNCTION_INFO_V1(ivfflathandler);
Datum Datum
ivfflathandler(PG_FUNCTION_ARGS) ivfflathandler(PG_FUNCTION_ARGS)
{ {

View File

@@ -3,26 +3,21 @@
#include "postgres.h" #include "postgres.h"
#if PG_VERSION_NUM < 100000
#error "Requires PostgreSQL 10+"
#endif
#include "access/generic_xlog.h" #include "access/generic_xlog.h"
#include "access/reloptions.h" #include "access/reloptions.h"
#include "nodes/execnodes.h" #include "nodes/execnodes.h"
#include "port.h" /* for strtof() and random() */
#include "utils/sampling.h" #include "utils/sampling.h"
#include "utils/tuplesort.h" #include "utils/tuplesort.h"
#include "vector.h" #include "vector.h"
#if PG_VERSION_NUM >= 150000
#include "common/pg_prng.h"
#endif
#ifdef IVFFLAT_BENCH #ifdef IVFFLAT_BENCH
#include "portability/instr_time.h" #include "portability/instr_time.h"
#endif #endif
#if PG_VERSION_NUM < 90600
#error "Requires PostgreSQL 9.6+"
#endif
/* Support functions */ /* Support functions */
#define IVFFLAT_DISTANCE_PROC 1 #define IVFFLAT_DISTANCE_PROC 1
#define IVFFLAT_NORM_PROC 2 #define IVFFLAT_NORM_PROC 2
@@ -67,26 +62,21 @@
#define IvfflatBench(name, code) (code) #define IvfflatBench(name, code) (code)
#endif #endif
#if PG_VERSION_NUM >= 150000 #if PG_VERSION_NUM < 100000
#define RandomDouble() pg_prng_double(&pg_global_prng_state) #define ItemPointerGetBlockNumberNoCheck ItemPointerGetBlockNumber
#define RandomInt() pg_prng_uint32(&pg_global_prng_state) #define ItemPointerGetOffsetNumberNoCheck ItemPointerGetOffsetNumber
#else
#define RandomDouble() (((double) random()) / MAX_RANDOM_VALUE)
#define RandomInt() random()
#endif #endif
/* Variables */ /* Variables */
extern int ivfflat_probes; extern int ivfflat_probes;
extern int ivfflat_bound;
/* Exported functions */
PGDLLEXPORT void _PG_init(void);
typedef struct VectorArrayData typedef struct VectorArrayData
{ {
int length; int length;
int maxlen; int maxlen;
int dim; int dim;
Vector *items; Vector items[FLEXIBLE_ARRAY_MEMBER];
} VectorArrayData; } VectorArrayData;
typedef VectorArrayData * VectorArray; typedef VectorArrayData * VectorArray;
@@ -133,8 +123,6 @@ typedef struct IvfflatBuildState
#ifdef IVFFLAT_KMEANS_DEBUG #ifdef IVFFLAT_KMEANS_DEBUG
double inertia; double inertia;
double *listSums;
int *listCounts;
#endif #endif
/* Sampling */ /* Sampling */
@@ -178,7 +166,6 @@ typedef IvfflatListData * IvfflatList;
typedef struct IvfflatScanList typedef struct IvfflatScanList
{ {
pairingheap_node ph_node;
BlockNumber startPage; BlockNumber startPage;
double distance; double distance;
} IvfflatScanList; } IvfflatScanList;
@@ -200,21 +187,19 @@ typedef struct IvfflatScanOpaqueData
FmgrInfo *normprocinfo; FmgrInfo *normprocinfo;
Oid collation; Oid collation;
/* Lists */
pairingheap *listQueue;
IvfflatScanList lists[FLEXIBLE_ARRAY_MEMBER]; /* must come last */ IvfflatScanList lists[FLEXIBLE_ARRAY_MEMBER]; /* must come last */
} IvfflatScanOpaqueData; } IvfflatScanOpaqueData;
typedef IvfflatScanOpaqueData * IvfflatScanOpaque; typedef IvfflatScanOpaqueData * IvfflatScanOpaque;
#define VECTOR_ARRAY_SIZE(_length, _dim) (sizeof(VectorArrayData) + (_length) * VECTOR_SIZE(_dim)) #define VECTOR_ARRAY_SIZE(_length, _dim) (offsetof(VectorArrayData, items) + _length * VECTOR_SIZE(_dim))
#define VECTOR_ARRAY_OFFSET(_arr, _offset) ((char*) (_arr)->items + (_offset) * VECTOR_SIZE((_arr)->dim)) #define VECTOR_ARRAY_OFFSET(_arr, _offset) ((char*) _arr + offsetof(VectorArrayData, items) + (_offset) * VECTOR_SIZE(_arr->dim))
#define VectorArrayGet(_arr, _offset) ((Vector *) VECTOR_ARRAY_OFFSET(_arr, _offset)) #define VectorArrayGet(_arr, _offset) ((Vector *) VECTOR_ARRAY_OFFSET(_arr, _offset))
#define VectorArraySet(_arr, _offset, _val) memcpy(VECTOR_ARRAY_OFFSET(_arr, _offset), _val, VECTOR_SIZE((_arr)->dim)) #define VectorArraySet(_arr, _offset, _val) (memcpy(VECTOR_ARRAY_OFFSET(_arr, _offset), _val, VECTOR_SIZE(_arr->dim)))
/* Methods */ /* Methods */
void _PG_init(void);
VectorArray VectorArrayInit(int maxlen, int dimensions); VectorArray VectorArrayInit(int maxlen, int dimensions);
void VectorArrayFree(VectorArray arr);
void PrintVectorArray(char *msg, VectorArray arr); void PrintVectorArray(char *msg, VectorArray arr);
void IvfflatKmeans(Relation index, VectorArray samples, VectorArray centers); void IvfflatKmeans(Relation index, VectorArray samples, VectorArray centers);
FmgrInfo *IvfflatOptionalProcInfo(Relation rel, uint16 procnum); FmgrInfo *IvfflatOptionalProcInfo(Relation rel, uint16 procnum);
@@ -224,8 +209,7 @@ void IvfflatUpdateList(Relation index, GenericXLogState *state, ListInfo listIn
void IvfflatCommitBuffer(Buffer buf, GenericXLogState *state); void IvfflatCommitBuffer(Buffer buf, GenericXLogState *state);
void IvfflatAppendPage(Relation index, Buffer *buf, Page *page, GenericXLogState **state, ForkNumber forkNum); void IvfflatAppendPage(Relation index, Buffer *buf, Page *page, GenericXLogState **state, ForkNumber forkNum);
Buffer IvfflatNewBuffer(Relation index, ForkNumber forkNum); Buffer IvfflatNewBuffer(Relation index, ForkNumber forkNum);
void IvfflatInitPage(Buffer buf, Page page); void IvfflatInitPage(Relation index, Buffer *buf, Page *page, GenericXLogState **state);
void IvfflatInitRegisterPage(Relation index, Buffer *buf, Page *page, GenericXLogState **state);
/* Index access methods */ /* Index access methods */
IndexBuildResult *ivfflatbuild(Relation heap, Relation index, IndexInfo *indexInfo); IndexBuildResult *ivfflatbuild(Relation heap, Relation index, IndexInfo *indexInfo);

View File

@@ -53,6 +53,18 @@ FindInsertPage(Relation rel, Datum *values, BlockNumber *insertPage, ListInfo *
} }
} }
/*
* Prepare to insert an index tuple
*/
static void
LoadInsertPage(Relation index, Buffer *buf, Page *page, GenericXLogState **state, BlockNumber insertPage)
{
*buf = ReadBuffer(index, insertPage);
LockBuffer(*buf, BUFFER_LOCK_EXCLUSIVE);
*state = GenericXLogStart(index);
*page = GenericXLogRegisterBuffer(*state, *buf, 0);
}
/* /*
* Insert a tuple into the index * Insert a tuple into the index
*/ */
@@ -75,18 +87,11 @@ InsertTuple(Relation rel, IndexTuple itup, Relation heapRel, Datum *values)
itemsz = MAXALIGN(IndexTupleSize(itup)); itemsz = MAXALIGN(IndexTupleSize(itup));
Assert(itemsz <= BLCKSZ - MAXALIGN(SizeOfPageHeaderData) - MAXALIGN(sizeof(IvfflatPageOpaqueData))); Assert(itemsz <= BLCKSZ - MAXALIGN(SizeOfPageHeaderData) - MAXALIGN(sizeof(IvfflatPageOpaqueData)));
LoadInsertPage(rel, &buf, &page, &state, insertPage);
/* Find a page to insert the item */ /* Find a page to insert the item */
for (;;) while (PageGetFreeSpace(page) < itemsz)
{ {
buf = ReadBuffer(rel, insertPage);
LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
state = GenericXLogStart(rel);
page = GenericXLogRegisterBuffer(state, buf, 0);
if (PageGetFreeSpace(page) >= itemsz)
break;
insertPage = IvfflatPageGetOpaque(page)->nextblkno; insertPage = IvfflatPageGetOpaque(page)->nextblkno;
if (BlockNumberIsValid(insertPage)) if (BlockNumberIsValid(insertPage))
@@ -94,45 +99,15 @@ InsertTuple(Relation rel, IndexTuple itup, Relation heapRel, Datum *values)
/* Move to next page */ /* Move to next page */
GenericXLogAbort(state); GenericXLogAbort(state);
UnlockReleaseBuffer(buf); UnlockReleaseBuffer(buf);
LoadInsertPage(rel, &buf, &page, &state, insertPage);
} }
else else
{ {
Buffer metabuf;
Buffer newbuf;
Page newpage;
/*
* From ReadBufferExtended: Caller is responsible for ensuring
* that only one backend tries to extend a relation at the same
* time!
*/
metabuf = ReadBuffer(rel, IVFFLAT_METAPAGE_BLKNO);
LockBuffer(metabuf, BUFFER_LOCK_EXCLUSIVE);
/* Add a new page */ /* Add a new page */
newbuf = IvfflatNewBuffer(rel, MAIN_FORKNUM); IvfflatAppendPage(rel, &buf, &page, &state, MAIN_FORKNUM);
newpage = GenericXLogRegisterBuffer(state, newbuf, GENERIC_XLOG_FULL_IMAGE);
/* Init new page */ insertPage = BufferGetBlockNumber(buf);
IvfflatInitPage(newbuf, newpage);
/* Update insert page */
insertPage = BufferGetBlockNumber(newbuf);
/* Update previous buffer */
IvfflatPageGetOpaque(page)->nextblkno = insertPage;
/* Commit */
MarkBufferDirty(newbuf);
MarkBufferDirty(buf);
GenericXLogFinish(state);
/* Unlock extend relation lock as early as possible */
UnlockReleaseBuffer(metabuf);
/* Unlock rest */
UnlockReleaseBuffer(newbuf);
UnlockReleaseBuffer(buf);
} }
} }

View File

@@ -16,7 +16,7 @@ InitCenters(Relation index, VectorArray samples, VectorArray centers, float *low
FmgrInfo *procinfo; FmgrInfo *procinfo;
Oid collation; Oid collation;
int i; int i;
int64 j; int j;
double distance; double distance;
double sum; double sum;
double choice; double choice;
@@ -29,7 +29,7 @@ InitCenters(Relation index, VectorArray samples, VectorArray centers, float *low
collation = index->rd_indcollation[0]; collation = index->rd_indcollation[0];
/* Choose an initial center uniformly at random */ /* Choose an initial center uniformly at random */
VectorArraySet(centers, 0, VectorArrayGet(samples, RandomInt() % samples->length)); VectorArraySet(centers, 0, VectorArrayGet(samples, random() % samples->length));
centers->length++; centers->length++;
for (j = 0; j < numSamples; j++) for (j = 0; j < numSamples; j++)
@@ -66,7 +66,7 @@ InitCenters(Relation index, VectorArray samples, VectorArray centers, float *low
break; break;
/* Choose new center using weighted probability distribution. */ /* Choose new center using weighted probability distribution. */
choice = sum * RandomDouble(); choice = sum * (((double) random()) / MAX_RANDOM_VALUE);
for (j = 0; j < numSamples - 1; j++) for (j = 0; j < numSamples - 1; j++)
{ {
choice -= weight[j]; choice -= weight[j];
@@ -145,7 +145,7 @@ QuickCenters(Relation index, VectorArray samples, VectorArray centers)
vec->dim = dimensions; vec->dim = dimensions;
for (j = 0; j < dimensions; j++) for (j = 0; j < dimensions; j++)
vec->x[j] = RandomDouble(); vec->x[j] = ((double) random()) / MAX_RANDOM_VALUE;
/* Normalize if needed (only needed for random centers) */ /* Normalize if needed (only needed for random centers) */
if (normprocinfo != NULL) if (normprocinfo != NULL)
@@ -172,8 +172,8 @@ ElkanKmeans(Relation index, VectorArray samples, VectorArray centers)
Vector *vec; Vector *vec;
Vector *newCenter; Vector *newCenter;
int iteration; int iteration;
int64 j; int j;
int64 k; int k;
int dimensions = centers->dim; int dimensions = centers->dim;
int numCenters = centers->maxlen; int numCenters = centers->maxlen;
int numSamples = samples->length; int numSamples = samples->length;
@@ -217,10 +217,6 @@ ElkanKmeans(Relation index, VectorArray samples, VectorArray centers)
errmsg("memory required is %zu MB, maintenance_work_mem is %d MB", errmsg("memory required is %zu MB, maintenance_work_mem is %d MB",
totalSize / (1024 * 1024) + 1, maintenance_work_mem / 1024))); totalSize / (1024 * 1024) + 1, maintenance_work_mem / 1024)));
/* Ensure indexing does not overflow */
if (numCenters * numCenters > INT_MAX)
elog(ERROR, "Indexing overflow detected. Please report a bug.");
/* Set support functions */ /* Set support functions */
procinfo = index_getprocinfo(index, 1, IVFFLAT_KMEANS_DISTANCE_PROC); procinfo = index_getprocinfo(index, 1, IVFFLAT_KMEANS_DISTANCE_PROC);
normprocinfo = IvfflatOptionalProcInfo(index, IVFFLAT_KMEANS_NORM_PROC); normprocinfo = IvfflatOptionalProcInfo(index, IVFFLAT_KMEANS_NORM_PROC);
@@ -233,7 +229,7 @@ ElkanKmeans(Relation index, VectorArray samples, VectorArray centers)
lowerBound = palloc_extended(lowerBoundSize, MCXT_ALLOC_HUGE); lowerBound = palloc_extended(lowerBoundSize, MCXT_ALLOC_HUGE);
upperBound = palloc(upperBoundSize); upperBound = palloc(upperBoundSize);
s = palloc(sSize); s = palloc(sSize);
halfcdist = palloc_extended(halfcdistSize, MCXT_ALLOC_HUGE); halfcdist = palloc(halfcdistSize);
newcdist = palloc(newcdistSize); newcdist = palloc(newcdistSize);
newCenters = VectorArrayInit(numCenters, dimensions); newCenters = VectorArrayInit(numCenters, dimensions);
@@ -253,6 +249,8 @@ ElkanKmeans(Relation index, VectorArray samples, VectorArray centers)
minDistance = DBL_MAX; minDistance = DBL_MAX;
closestCenter = -1; closestCenter = -1;
vec = VectorArrayGet(samples, j);
/* Find closest center */ /* Find closest center */
for (k = 0; k < numCenters; k++) for (k = 0; k < numCenters; k++)
{ {
@@ -405,7 +403,7 @@ ElkanKmeans(Relation index, VectorArray samples, VectorArray centers)
{ {
/* TODO Handle empty centers properly */ /* TODO Handle empty centers properly */
for (k = 0; k < dimensions; k++) for (k = 0; k < dimensions; k++)
vec->x[k] = RandomDouble(); vec->x[k] = ((double) random()) / MAX_RANDOM_VALUE;
} }
/* Normalize if needed */ /* Normalize if needed */
@@ -443,7 +441,7 @@ ElkanKmeans(Relation index, VectorArray samples, VectorArray centers)
break; break;
} }
VectorArrayFree(newCenters); pfree(newCenters);
pfree(centerCounts); pfree(centerCounts);
pfree(closestCenters); pfree(closestCenters);
pfree(lowerBound); pfree(lowerBound);

View File

@@ -1,7 +1,5 @@
#include "postgres.h" #include "postgres.h"
#include <float.h>
#include "access/relscan.h" #include "access/relscan.h"
#include "ivfflat.h" #include "ivfflat.h"
#include "miscadmin.h" #include "miscadmin.h"
@@ -19,12 +17,14 @@
* Compare list distances * Compare list distances
*/ */
static int static int
CompareLists(const pairingheap_node *a, const pairingheap_node *b, void *arg) CompareLists(const void *a, const void *b)
{ {
if (((const IvfflatScanList *) a)->distance > ((const IvfflatScanList *) b)->distance) double diff = (((IvfflatScanList *) a)->distance - ((IvfflatScanList *) b)->distance);
if (diff > 0)
return 1; return 1;
if (((const IvfflatScanList *) a)->distance < ((const IvfflatScanList *) b)->distance) if (diff < 0)
return -1; return -1;
return 0; return 0;
@@ -45,8 +45,6 @@ GetScanLists(IndexScanDesc scan, Datum value)
int listCount = 0; int listCount = 0;
IvfflatScanOpaque so = (IvfflatScanOpaque) scan->opaque; IvfflatScanOpaque so = (IvfflatScanOpaque) scan->opaque;
double distance; double distance;
IvfflatScanList *scanlist;
double maxDistance = DBL_MAX;
/* Search all list pages */ /* Search all list pages */
while (BlockNumberIsValid(nextblkno)) while (BlockNumberIsValid(nextblkno))
@@ -64,39 +62,22 @@ GetScanLists(IndexScanDesc scan, Datum value)
/* Use procinfo from the index instead of scan key for performance */ /* Use procinfo from the index instead of scan key for performance */
distance = DatumGetFloat8(FunctionCall2Coll(so->procinfo, so->collation, PointerGetDatum(&list->center), value)); distance = DatumGetFloat8(FunctionCall2Coll(so->procinfo, so->collation, PointerGetDatum(&list->center), value));
if (listCount < so->probes) so->lists[listCount].startPage = list->startPage;
{ so->lists[listCount].distance = distance;
scanlist = &so->lists[listCount]; listCount++;
scanlist->startPage = list->startPage;
scanlist->distance = distance;
listCount++;
/* Add to heap */
pairingheap_add(so->listQueue, &scanlist->ph_node);
/* Calculate max distance */
if (listCount == so->probes)
maxDistance = ((IvfflatScanList *) pairingheap_first(so->listQueue))->distance;
}
else if (distance < maxDistance)
{
/* Remove */
scanlist = (IvfflatScanList *) pairingheap_remove_first(so->listQueue);
/* Reuse */
scanlist->startPage = list->startPage;
scanlist->distance = distance;
pairingheap_add(so->listQueue, &scanlist->ph_node);
/* Update max distance */
maxDistance = ((IvfflatScanList *) pairingheap_first(so->listQueue))->distance;
}
} }
nextblkno = IvfflatPageGetOpaque(cpage)->nextblkno; nextblkno = IvfflatPageGetOpaque(cpage)->nextblkno;
UnlockReleaseBuffer(cbuf); UnlockReleaseBuffer(cbuf);
} }
/* Sort by distance */
/* TODO Use heap for performance */
qsort(so->lists, listCount, sizeof(IvfflatScanList), CompareLists);
if (so->probes > listCount)
so->probes = listCount;
} }
/* /*
@@ -114,6 +95,7 @@ GetScanItems(IndexScanDesc scan, Datum value)
OffsetNumber maxoffno; OffsetNumber maxoffno;
Datum datum; Datum datum;
bool isnull; bool isnull;
int i;
TupleDesc tupdesc = RelationGetDescr(scan->indexRelation); TupleDesc tupdesc = RelationGetDescr(scan->indexRelation);
#if PG_VERSION_NUM >= 120000 #if PG_VERSION_NUM >= 120000
@@ -129,10 +111,14 @@ GetScanItems(IndexScanDesc scan, Datum value)
*/ */
BufferAccessStrategy bas = GetAccessStrategy(BAS_BULKREAD); BufferAccessStrategy bas = GetAccessStrategy(BAS_BULKREAD);
/* Set the max number of results */
if (ivfflat_bound > 0)
tuplesort_set_bound(so->sortstate, ivfflat_bound);
/* Search closest probes lists */ /* Search closest probes lists */
while (!pairingheap_is_empty(so->listQueue)) for (i = 0; i < so->probes; i++)
{ {
searchPage = ((IvfflatScanList *) pairingheap_remove_first(so->listQueue))->startPage; searchPage = so->lists[i].startPage;
/* Search all entry pages for list */ /* Search all entry pages for list */
while (BlockNumberIsValid(searchPage)) while (BlockNumberIsValid(searchPage))
@@ -156,10 +142,12 @@ GetScanItems(IndexScanDesc scan, Datum value)
ExecClearTuple(slot); ExecClearTuple(slot);
slot->tts_values[0] = FunctionCall2Coll(so->procinfo, so->collation, datum, value); slot->tts_values[0] = FunctionCall2Coll(so->procinfo, so->collation, datum, value);
slot->tts_isnull[0] = false; slot->tts_isnull[0] = false;
slot->tts_values[1] = PointerGetDatum(&itup->t_tid); slot->tts_values[1] = Int32GetDatum((int) ItemPointerGetBlockNumberNoCheck(&itup->t_tid));
slot->tts_isnull[1] = false; slot->tts_isnull[1] = false;
slot->tts_values[2] = Int32GetDatum((int) searchPage); slot->tts_values[2] = Int32GetDatum((int) ItemPointerGetOffsetNumberNoCheck(&itup->t_tid));
slot->tts_isnull[2] = false; slot->tts_isnull[2] = false;
slot->tts_values[3] = Int32GetDatum((int) searchPage);
slot->tts_isnull[3] = false;
ExecStoreVirtualTuple(slot); ExecStoreVirtualTuple(slot);
tuplesort_puttupleslot(so->sortstate, slot); tuplesort_puttupleslot(so->sortstate, slot);
@@ -187,18 +175,13 @@ ivfflatbeginscan(Relation index, int nkeys, int norderbys)
Oid sortOperators[] = {Float8LessOperator}; Oid sortOperators[] = {Float8LessOperator};
Oid sortCollations[] = {InvalidOid}; Oid sortCollations[] = {InvalidOid};
bool nullsFirstFlags[] = {false}; bool nullsFirstFlags[] = {false};
int probes = ivfflat_probes;
scan = RelationGetIndexScan(index, nkeys, norderbys); scan = RelationGetIndexScan(index, nkeys, norderbys);
lists = IvfflatGetLists(scan->indexRelation); lists = IvfflatGetLists(scan->indexRelation);
if (probes > lists) so = (IvfflatScanOpaque) palloc(offsetof(IvfflatScanOpaqueData, lists) + lists * sizeof(IvfflatScanList));
probes = lists;
so = (IvfflatScanOpaque) palloc(offsetof(IvfflatScanOpaqueData, lists) + probes * sizeof(IvfflatScanList));
so->buf = InvalidBuffer; so->buf = InvalidBuffer;
so->first = true; so->first = true;
so->probes = probes;
/* Set support functions */ /* Set support functions */
so->procinfo = index_getprocinfo(index, 1, IVFFLAT_DISTANCE_PROC); so->procinfo = index_getprocinfo(index, 1, IVFFLAT_DISTANCE_PROC);
@@ -207,13 +190,14 @@ ivfflatbeginscan(Relation index, int nkeys, int norderbys)
/* Create tuple description for sorting */ /* Create tuple description for sorting */
#if PG_VERSION_NUM >= 120000 #if PG_VERSION_NUM >= 120000
so->tupdesc = CreateTemplateTupleDesc(3); so->tupdesc = CreateTemplateTupleDesc(4);
#else #else
so->tupdesc = CreateTemplateTupleDesc(3, false); so->tupdesc = CreateTemplateTupleDesc(4, false);
#endif #endif
TupleDescInitEntry(so->tupdesc, (AttrNumber) 1, "distance", FLOAT8OID, -1, 0); TupleDescInitEntry(so->tupdesc, (AttrNumber) 1, "distance", FLOAT8OID, -1, 0);
TupleDescInitEntry(so->tupdesc, (AttrNumber) 2, "tid", TIDOID, -1, 0); TupleDescInitEntry(so->tupdesc, (AttrNumber) 2, "blkno", INT4OID, -1, 0);
TupleDescInitEntry(so->tupdesc, (AttrNumber) 3, "indexblkno", INT4OID, -1, 0); TupleDescInitEntry(so->tupdesc, (AttrNumber) 3, "offset", INT4OID, -1, 0);
TupleDescInitEntry(so->tupdesc, (AttrNumber) 4, "indexblkno", INT4OID, -1, 0);
/* Prep sort */ /* Prep sort */
#if PG_VERSION_NUM >= 110000 #if PG_VERSION_NUM >= 110000
@@ -228,8 +212,6 @@ ivfflatbeginscan(Relation index, int nkeys, int norderbys)
so->slot = MakeSingleTupleTableSlot(so->tupdesc); so->slot = MakeSingleTupleTableSlot(so->tupdesc);
#endif #endif
so->listQueue = pairingheap_allocate(CompareLists, scan);
scan->opaque = so; scan->opaque = so;
return scan; return scan;
@@ -249,7 +231,7 @@ ivfflatrescan(IndexScanDesc scan, ScanKey keys, int nkeys, ScanKey orderbys, int
#endif #endif
so->first = true; so->first = true;
pairingheap_reset(so->listQueue); so->probes = ivfflat_probes;
if (keys && scan->numberOfKeys > 0) if (keys && scan->numberOfKeys > 0)
memmove(scan->keyData, keys, scan->numberOfKeys * sizeof(ScanKeyData)); memmove(scan->keyData, keys, scan->numberOfKeys * sizeof(ScanKeyData));
@@ -308,13 +290,14 @@ ivfflatgettuple(IndexScanDesc scan, ScanDirection dir)
if (tuplesort_gettupleslot(so->sortstate, true, so->slot, NULL)) if (tuplesort_gettupleslot(so->sortstate, true, so->slot, NULL))
#endif #endif
{ {
ItemPointer tid = (ItemPointer) DatumGetPointer(slot_getattr(so->slot, 2, &so->isnull)); BlockNumber blkno = DatumGetInt32(slot_getattr(so->slot, 2, &so->isnull));
BlockNumber indexblkno = DatumGetInt32(slot_getattr(so->slot, 3, &so->isnull)); OffsetNumber offset = DatumGetInt32(slot_getattr(so->slot, 3, &so->isnull));
BlockNumber indexblkno = DatumGetInt32(slot_getattr(so->slot, 4, &so->isnull));
#if PG_VERSION_NUM >= 120000 #if PG_VERSION_NUM >= 120000
scan->xs_heaptid = *tid; ItemPointerSet(&scan->xs_heaptid, blkno, offset);
#else #else
scan->xs_ctup.t_self = *tid; ItemPointerSet(&scan->xs_ctup.t_self, blkno, offset);
#endif #endif
if (BufferIsValid(so->buf)) if (BufferIsValid(so->buf))
@@ -347,7 +330,6 @@ ivfflatendscan(IndexScanDesc scan)
if (BufferIsValid(so->buf)) if (BufferIsValid(so->buf))
ReleaseBuffer(so->buf); ReleaseBuffer(so->buf);
pairingheap_free(so->listQueue);
tuplesort_end(so->sortstate); tuplesort_end(so->sortstate);
pfree(so); pfree(so);

View File

@@ -10,25 +10,14 @@
VectorArray VectorArray
VectorArrayInit(int maxlen, int dimensions) VectorArrayInit(int maxlen, int dimensions)
{ {
VectorArray res = palloc(sizeof(VectorArrayData)); VectorArray res = palloc0(VECTOR_ARRAY_SIZE(maxlen, dimensions));
res->length = 0; res->length = 0;
res->maxlen = maxlen; res->maxlen = maxlen;
res->dim = dimensions; res->dim = dimensions;
res->items = palloc_extended(maxlen * VECTOR_SIZE(dimensions), MCXT_ALLOC_ZERO | MCXT_ALLOC_HUGE);
return res; return res;
} }
/*
* Free a vector array
*/
void
VectorArrayFree(VectorArray arr)
{
pfree(arr->items);
pfree(arr);
}
/* /*
* Print vector array - useful for debugging * Print vector array - useful for debugging
*/ */
@@ -118,22 +107,13 @@ IvfflatNewBuffer(Relation index, ForkNumber forkNum)
* Init page * Init page
*/ */
void void
IvfflatInitPage(Buffer buf, Page page) IvfflatInitPage(Relation index, Buffer *buf, Page *page, GenericXLogState **state)
{
PageInit(page, BufferGetPageSize(buf), sizeof(IvfflatPageOpaqueData));
IvfflatPageGetOpaque(page)->nextblkno = InvalidBlockNumber;
IvfflatPageGetOpaque(page)->page_id = IVFFLAT_PAGE_ID;
}
/*
* Init and register page
*/
void
IvfflatInitRegisterPage(Relation index, Buffer *buf, Page *page, GenericXLogState **state)
{ {
*state = GenericXLogStart(index); *state = GenericXLogStart(index);
*page = GenericXLogRegisterBuffer(*state, *buf, GENERIC_XLOG_FULL_IMAGE); *page = GenericXLogRegisterBuffer(*state, *buf, GENERIC_XLOG_FULL_IMAGE);
IvfflatInitPage(*buf, *page); PageInit(*page, BufferGetPageSize(*buf), sizeof(IvfflatPageOpaqueData));
IvfflatPageGetOpaque(*page)->nextblkno = InvalidBlockNumber;
IvfflatPageGetOpaque(*page)->page_id = IVFFLAT_PAGE_ID;
} }
/* /*
@@ -155,27 +135,17 @@ IvfflatCommitBuffer(Buffer buf, GenericXLogState *state)
void void
IvfflatAppendPage(Relation index, Buffer *buf, Page *page, GenericXLogState **state, ForkNumber forkNum) IvfflatAppendPage(Relation index, Buffer *buf, Page *page, GenericXLogState **state, ForkNumber forkNum)
{ {
/* Get new buffer */ Buffer prevbuf = *buf;
Buffer newbuf = IvfflatNewBuffer(index, forkNum);
Page newpage = GenericXLogRegisterBuffer(*state, newbuf, GENERIC_XLOG_FULL_IMAGE);
/* Update the previous buffer */ /* Get new buffer */
IvfflatPageGetOpaque(*page)->nextblkno = BufferGetBlockNumber(newbuf); *buf = IvfflatNewBuffer(index, forkNum);
/* Update and commit previous buffer */
IvfflatPageGetOpaque(*page)->nextblkno = BufferGetBlockNumber(*buf);
IvfflatCommitBuffer(prevbuf, *state);
/* Init new page */ /* Init new page */
IvfflatInitPage(newbuf, newpage); IvfflatInitPage(index, buf, page, state);
/* Commit */
MarkBufferDirty(*buf);
MarkBufferDirty(newbuf);
GenericXLogFinish(*state);
/* Unlock */
UnlockReleaseBuffer(*buf);
*state = GenericXLogStart(index);
*page = GenericXLogRegisterBuffer(*state, newbuf, GENERIC_XLOG_FULL_IMAGE);
*buf = newbuf;
} }
/* /*

View File

@@ -13,10 +13,7 @@
#include "utils/numeric.h" #include "utils/numeric.h"
#if PG_VERSION_NUM >= 120000 #if PG_VERSION_NUM >= 120000
#include "common/shortest_dec.h"
#include "utils/float.h" #include "utils/float.h"
#else
#include <float.h>
#endif #endif
#if PG_VERSION_NUM < 130000 #if PG_VERSION_NUM < 130000
@@ -109,14 +106,14 @@ PrintVector(char *msg, Vector * vector)
/* /*
* Convert textual representation to internal representation * Convert textual representation to internal representation
*/ */
PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_in); PG_FUNCTION_INFO_V1(vector_in);
Datum Datum
vector_in(PG_FUNCTION_ARGS) vector_in(PG_FUNCTION_ARGS)
{ {
char *str = PG_GETARG_CSTRING(0); char *str = PG_GETARG_CSTRING(0);
int32 typmod = PG_GETARG_INT32(2); int32 typmod = PG_GETARG_INT32(2);
int i; int i;
float x[VECTOR_MAX_DIM]; double x[VECTOR_MAX_DIM];
int dim = 0; int dim = 0;
char *pt; char *pt;
char *stringEnd; char *stringEnd;
@@ -139,8 +136,7 @@ vector_in(PG_FUNCTION_ARGS)
(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
errmsg("vector cannot have more than %d dimensions", VECTOR_MAX_DIM))); errmsg("vector cannot have more than %d dimensions", VECTOR_MAX_DIM)));
/* Use strtof like float4in to avoid a double-rounding problem */ x[dim] = strtod(pt, &stringEnd);
x[dim] = strtof(pt, &stringEnd);
CheckElement(x[dim]); CheckElement(x[dim]);
dim++; dim++;
@@ -186,68 +182,35 @@ vector_in(PG_FUNCTION_ARGS)
/* /*
* Convert internal representation to textual representation * Convert internal representation to textual representation
*/ */
PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_out); PG_FUNCTION_INFO_V1(vector_out);
Datum Datum
vector_out(PG_FUNCTION_ARGS) vector_out(PG_FUNCTION_ARGS)
{ {
Vector *vector = PG_GETARG_VECTOR_P(0); Vector *vector = PG_GETARG_VECTOR_P(0);
StringInfoData buf;
int dim = vector->dim; int dim = vector->dim;
char *buf;
char *ptr;
int i; int i;
int n;
#if PG_VERSION_NUM < 120000 initStringInfo(&buf);
int ndig = FLT_DIG + extra_float_digits;
if (ndig < 1) appendStringInfoChar(&buf, '[');
ndig = 1;
#define FLOAT_SHORTEST_DECIMAL_LEN (ndig + 10)
#endif
/*
* Need:
*
* dim * (FLOAT_SHORTEST_DECIMAL_LEN - 1) bytes for
* float_to_shortest_decimal_bufn
*
* dim - 1 bytes for separator
*
* 3 bytes for [, ], and \0
*/
buf = (char *) palloc(FLOAT_SHORTEST_DECIMAL_LEN * dim + 2);
ptr = buf;
*ptr = '[';
ptr++;
for (i = 0; i < dim; i++) for (i = 0; i < dim; i++)
{ {
if (i > 0) if (i > 0)
{ appendStringInfoString(&buf, ",");
*ptr = ',';
ptr++;
}
#if PG_VERSION_NUM >= 120000 appendStringInfoString(&buf, float8out_internal(vector->x[i]));
n = float_to_shortest_decimal_bufn(vector->x[i], ptr);
#else
n = sprintf(ptr, "%.*g", ndig, vector->x[i]);
#endif
ptr += n;
} }
*ptr = ']'; appendStringInfoChar(&buf, ']');
ptr++;
*ptr = '\0';
PG_FREE_IF_COPY(vector, 0); PG_FREE_IF_COPY(vector, 0);
PG_RETURN_CSTRING(buf); PG_RETURN_CSTRING(buf.data);
} }
/* /*
* Convert type modifier * Convert type modifier
*/ */
PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_typmod_in); PG_FUNCTION_INFO_V1(vector_typmod_in);
Datum Datum
vector_typmod_in(PG_FUNCTION_ARGS) vector_typmod_in(PG_FUNCTION_ARGS)
{ {
@@ -278,7 +241,7 @@ vector_typmod_in(PG_FUNCTION_ARGS)
/* /*
* Convert external binary representation to internal representation * Convert external binary representation to internal representation
*/ */
PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_recv); PG_FUNCTION_INFO_V1(vector_recv);
Datum Datum
vector_recv(PG_FUNCTION_ARGS) vector_recv(PG_FUNCTION_ARGS)
{ {
@@ -310,7 +273,7 @@ vector_recv(PG_FUNCTION_ARGS)
/* /*
* Convert internal representation to the external binary representation * Convert internal representation to the external binary representation
*/ */
PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_send); PG_FUNCTION_INFO_V1(vector_send);
Datum Datum
vector_send(PG_FUNCTION_ARGS) vector_send(PG_FUNCTION_ARGS)
{ {
@@ -330,7 +293,7 @@ vector_send(PG_FUNCTION_ARGS)
/* /*
* Convert vector to vector * Convert vector to vector
*/ */
PGDLLEXPORT PG_FUNCTION_INFO_V1(vector); PG_FUNCTION_INFO_V1(vector);
Datum Datum
vector(PG_FUNCTION_ARGS) vector(PG_FUNCTION_ARGS)
{ {
@@ -345,7 +308,7 @@ vector(PG_FUNCTION_ARGS)
/* /*
* Convert array to vector * Convert array to vector
*/ */
PGDLLEXPORT PG_FUNCTION_INFO_V1(array_to_vector); PG_FUNCTION_INFO_V1(array_to_vector);
Datum Datum
array_to_vector(PG_FUNCTION_ARGS) array_to_vector(PG_FUNCTION_ARGS)
{ {
@@ -403,7 +366,7 @@ array_to_vector(PG_FUNCTION_ARGS)
/* /*
* Convert vector to float4[] * Convert vector to float4[]
*/ */
PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_to_float4); PG_FUNCTION_INFO_V1(vector_to_float4);
Datum Datum
vector_to_float4(PG_FUNCTION_ARGS) vector_to_float4(PG_FUNCTION_ARGS)
{ {
@@ -426,14 +389,12 @@ vector_to_float4(PG_FUNCTION_ARGS)
/* /*
* Get the L2 distance between vectors * Get the L2 distance between vectors
*/ */
PGDLLEXPORT PG_FUNCTION_INFO_V1(l2_distance); PG_FUNCTION_INFO_V1(l2_distance);
Datum Datum
l2_distance(PG_FUNCTION_ARGS) l2_distance(PG_FUNCTION_ARGS)
{ {
Vector *a = PG_GETARG_VECTOR_P(0); Vector *a = PG_GETARG_VECTOR_P(0);
Vector *b = PG_GETARG_VECTOR_P(1); Vector *b = PG_GETARG_VECTOR_P(1);
float *ax = a->x;
float *bx = b->x;
double distance = 0.0; double distance = 0.0;
double diff; double diff;
@@ -441,7 +402,7 @@ l2_distance(PG_FUNCTION_ARGS)
for (int i = 0; i < a->dim; i++) for (int i = 0; i < a->dim; i++)
{ {
diff = ax[i] - bx[i]; diff = a->x[i] - b->x[i];
distance += diff * diff; distance += diff * diff;
} }
@@ -452,14 +413,12 @@ l2_distance(PG_FUNCTION_ARGS)
* Get the L2 squared distance between vectors * Get the L2 squared distance between vectors
* This saves a sqrt calculation * This saves a sqrt calculation
*/ */
PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_l2_squared_distance); PG_FUNCTION_INFO_V1(vector_l2_squared_distance);
Datum Datum
vector_l2_squared_distance(PG_FUNCTION_ARGS) vector_l2_squared_distance(PG_FUNCTION_ARGS)
{ {
Vector *a = PG_GETARG_VECTOR_P(0); Vector *a = PG_GETARG_VECTOR_P(0);
Vector *b = PG_GETARG_VECTOR_P(1); Vector *b = PG_GETARG_VECTOR_P(1);
float *ax = a->x;
float *bx = b->x;
double distance = 0.0; double distance = 0.0;
double diff; double diff;
@@ -467,7 +426,7 @@ vector_l2_squared_distance(PG_FUNCTION_ARGS)
for (int i = 0; i < a->dim; i++) for (int i = 0; i < a->dim; i++)
{ {
diff = ax[i] - bx[i]; diff = a->x[i] - b->x[i];
distance += diff * diff; distance += diff * diff;
} }
@@ -477,20 +436,18 @@ vector_l2_squared_distance(PG_FUNCTION_ARGS)
/* /*
* Get the inner product of two vectors * Get the inner product of two vectors
*/ */
PGDLLEXPORT PG_FUNCTION_INFO_V1(inner_product); PG_FUNCTION_INFO_V1(inner_product);
Datum Datum
inner_product(PG_FUNCTION_ARGS) inner_product(PG_FUNCTION_ARGS)
{ {
Vector *a = PG_GETARG_VECTOR_P(0); Vector *a = PG_GETARG_VECTOR_P(0);
Vector *b = PG_GETARG_VECTOR_P(1); Vector *b = PG_GETARG_VECTOR_P(1);
float *ax = a->x;
float *bx = b->x;
double distance = 0.0; double distance = 0.0;
CheckDims(a, b); CheckDims(a, b);
for (int i = 0; i < a->dim; i++) for (int i = 0; i < a->dim; i++)
distance += ax[i] * bx[i]; distance += a->x[i] * b->x[i];
PG_RETURN_FLOAT8(distance); PG_RETURN_FLOAT8(distance);
} }
@@ -498,20 +455,18 @@ inner_product(PG_FUNCTION_ARGS)
/* /*
* Get the negative inner product of two vectors * Get the negative inner product of two vectors
*/ */
PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_negative_inner_product); PG_FUNCTION_INFO_V1(vector_negative_inner_product);
Datum Datum
vector_negative_inner_product(PG_FUNCTION_ARGS) vector_negative_inner_product(PG_FUNCTION_ARGS)
{ {
Vector *a = PG_GETARG_VECTOR_P(0); Vector *a = PG_GETARG_VECTOR_P(0);
Vector *b = PG_GETARG_VECTOR_P(1); Vector *b = PG_GETARG_VECTOR_P(1);
float *ax = a->x;
float *bx = b->x;
double distance = 0.0; double distance = 0.0;
CheckDims(a, b); CheckDims(a, b);
for (int i = 0; i < a->dim; i++) for (int i = 0; i < a->dim; i++)
distance += ax[i] * bx[i]; distance += a->x[i] * b->x[i];
PG_RETURN_FLOAT8(distance * -1); PG_RETURN_FLOAT8(distance * -1);
} }
@@ -519,14 +474,12 @@ vector_negative_inner_product(PG_FUNCTION_ARGS)
/* /*
* Get the cosine distance between two vectors * Get the cosine distance between two vectors
*/ */
PGDLLEXPORT PG_FUNCTION_INFO_V1(cosine_distance); PG_FUNCTION_INFO_V1(cosine_distance);
Datum Datum
cosine_distance(PG_FUNCTION_ARGS) cosine_distance(PG_FUNCTION_ARGS)
{ {
Vector *a = PG_GETARG_VECTOR_P(0); Vector *a = PG_GETARG_VECTOR_P(0);
Vector *b = PG_GETARG_VECTOR_P(1); Vector *b = PG_GETARG_VECTOR_P(1);
float *ax = a->x;
float *bx = b->x;
double distance = 0.0; double distance = 0.0;
double norma = 0.0; double norma = 0.0;
double normb = 0.0; double normb = 0.0;
@@ -535,9 +488,9 @@ cosine_distance(PG_FUNCTION_ARGS)
for (int i = 0; i < a->dim; i++) for (int i = 0; i < a->dim; i++)
{ {
distance += ax[i] * bx[i]; distance += a->x[i] * b->x[i];
norma += ax[i] * ax[i]; norma += a->x[i] * a->x[i];
normb += bx[i] * bx[i]; normb += b->x[i] * b->x[i];
} }
PG_RETURN_FLOAT8(1 - (distance / (sqrt(norma) * sqrt(normb)))); PG_RETURN_FLOAT8(1 - (distance / (sqrt(norma) * sqrt(normb))));
@@ -548,7 +501,7 @@ cosine_distance(PG_FUNCTION_ARGS)
* Currently uses angular distance since needs to satisfy triangle inequality * Currently uses angular distance since needs to satisfy triangle inequality
* Assumes inputs are unit vectors (skips norm) * Assumes inputs are unit vectors (skips norm)
*/ */
PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_spherical_distance); PG_FUNCTION_INFO_V1(vector_spherical_distance);
Datum Datum
vector_spherical_distance(PG_FUNCTION_ARGS) vector_spherical_distance(PG_FUNCTION_ARGS)
{ {
@@ -573,7 +526,7 @@ vector_spherical_distance(PG_FUNCTION_ARGS)
/* /*
* Get the dimensions of a vector * Get the dimensions of a vector
*/ */
PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_dims); PG_FUNCTION_INFO_V1(vector_dims);
Datum Datum
vector_dims(PG_FUNCTION_ARGS) vector_dims(PG_FUNCTION_ARGS)
{ {
@@ -585,16 +538,15 @@ vector_dims(PG_FUNCTION_ARGS)
/* /*
* Get the L2 norm of a vector * Get the L2 norm of a vector
*/ */
PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_norm); PG_FUNCTION_INFO_V1(vector_norm);
Datum Datum
vector_norm(PG_FUNCTION_ARGS) vector_norm(PG_FUNCTION_ARGS)
{ {
Vector *a = PG_GETARG_VECTOR_P(0); Vector *a = PG_GETARG_VECTOR_P(0);
float *ax = a->x;
double norm = 0.0; double norm = 0.0;
for (int i = 0; i < a->dim; i++) for (int i = 0; i < a->dim; i++)
norm += ax[i] * ax[i]; norm += a->x[i] * a->x[i];
PG_RETURN_FLOAT8(sqrt(norm)); PG_RETURN_FLOAT8(sqrt(norm));
} }
@@ -602,23 +554,20 @@ vector_norm(PG_FUNCTION_ARGS)
/* /*
* Add vectors * Add vectors
*/ */
PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_add); PG_FUNCTION_INFO_V1(vector_add);
Datum Datum
vector_add(PG_FUNCTION_ARGS) vector_add(PG_FUNCTION_ARGS)
{ {
Vector *a = PG_GETARG_VECTOR_P(0); Vector *a = PG_GETARG_VECTOR_P(0);
Vector *b = PG_GETARG_VECTOR_P(1); Vector *b = PG_GETARG_VECTOR_P(1);
float *ax = a->x;
float *bx = b->x;
Vector *result; Vector *result;
float *rx; int i;
CheckDims(a, b); CheckDims(a, b);
result = InitVector(a->dim); result = InitVector(a->dim);
rx = result->x; for (i = 0; i < a->dim; i++)
for (int i = 0, imax = a->dim; i < imax; i++) result->x[i] = a->x[i] + b->x[i];
rx[i] = ax[i] + bx[i];
PG_RETURN_POINTER(result); PG_RETURN_POINTER(result);
} }
@@ -626,23 +575,20 @@ vector_add(PG_FUNCTION_ARGS)
/* /*
* Subtract vectors * Subtract vectors
*/ */
PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_sub); PG_FUNCTION_INFO_V1(vector_sub);
Datum Datum
vector_sub(PG_FUNCTION_ARGS) vector_sub(PG_FUNCTION_ARGS)
{ {
Vector *a = PG_GETARG_VECTOR_P(0); Vector *a = PG_GETARG_VECTOR_P(0);
Vector *b = PG_GETARG_VECTOR_P(1); Vector *b = PG_GETARG_VECTOR_P(1);
float *ax = a->x;
float *bx = b->x;
Vector *result; Vector *result;
float *rx; int i;
CheckDims(a, b); CheckDims(a, b);
result = InitVector(a->dim); result = InitVector(a->dim);
rx = result->x; for (i = 0; i < a->dim; i++)
for (int i = 0, imax = a->dim; i < imax; i++) result->x[i] = a->x[i] - b->x[i];
rx[i] = ax[i] - bx[i];
PG_RETURN_POINTER(result); PG_RETURN_POINTER(result);
} }
@@ -671,7 +617,7 @@ vector_cmp_internal(Vector * a, Vector * b)
/* /*
* Less than * Less than
*/ */
PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_lt); PG_FUNCTION_INFO_V1(vector_lt);
Datum Datum
vector_lt(PG_FUNCTION_ARGS) vector_lt(PG_FUNCTION_ARGS)
{ {
@@ -684,7 +630,7 @@ vector_lt(PG_FUNCTION_ARGS)
/* /*
* Less than or equal * Less than or equal
*/ */
PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_le); PG_FUNCTION_INFO_V1(vector_le);
Datum Datum
vector_le(PG_FUNCTION_ARGS) vector_le(PG_FUNCTION_ARGS)
{ {
@@ -697,7 +643,7 @@ vector_le(PG_FUNCTION_ARGS)
/* /*
* Equal * Equal
*/ */
PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_eq); PG_FUNCTION_INFO_V1(vector_eq);
Datum Datum
vector_eq(PG_FUNCTION_ARGS) vector_eq(PG_FUNCTION_ARGS)
{ {
@@ -710,7 +656,7 @@ vector_eq(PG_FUNCTION_ARGS)
/* /*
* Not equal * Not equal
*/ */
PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_ne); PG_FUNCTION_INFO_V1(vector_ne);
Datum Datum
vector_ne(PG_FUNCTION_ARGS) vector_ne(PG_FUNCTION_ARGS)
{ {
@@ -723,7 +669,7 @@ vector_ne(PG_FUNCTION_ARGS)
/* /*
* Greater than or equal * Greater than or equal
*/ */
PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_ge); PG_FUNCTION_INFO_V1(vector_ge);
Datum Datum
vector_ge(PG_FUNCTION_ARGS) vector_ge(PG_FUNCTION_ARGS)
{ {
@@ -736,7 +682,7 @@ vector_ge(PG_FUNCTION_ARGS)
/* /*
* Greater than * Greater than
*/ */
PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_gt); PG_FUNCTION_INFO_V1(vector_gt);
Datum Datum
vector_gt(PG_FUNCTION_ARGS) vector_gt(PG_FUNCTION_ARGS)
{ {
@@ -749,7 +695,7 @@ vector_gt(PG_FUNCTION_ARGS)
/* /*
* Compare vectors * Compare vectors
*/ */
PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_cmp); PG_FUNCTION_INFO_V1(vector_cmp);
Datum Datum
vector_cmp(PG_FUNCTION_ARGS) vector_cmp(PG_FUNCTION_ARGS)
{ {

View File

@@ -1,3 +1,5 @@
SET client_min_messages = warning;
CREATE EXTENSION IF NOT EXISTS vector;
SET enable_seqscan = off; SET enable_seqscan = off;
CREATE TABLE t (val vector(3)); CREATE TABLE t (val vector(3));
INSERT INTO t (val) VALUES ('[0,0,0]'), ('[1,2,3]'), ('[1,1,1]'), (NULL); INSERT INTO t (val) VALUES ('[0,0,0]'), ('[1,2,3]'), ('[1,1,1]'), (NULL);

View File

@@ -1,3 +1,5 @@
SET client_min_messages = warning;
CREATE EXTENSION IF NOT EXISTS vector;
SELECT ARRAY[1,2,3]::vector; SELECT ARRAY[1,2,3]::vector;
array array
--------- ---------

View File

@@ -1,8 +1,10 @@
SET client_min_messages = warning;
CREATE EXTENSION IF NOT EXISTS vector;
CREATE TABLE t (val vector(3)); CREATE TABLE t (val vector(3));
INSERT INTO t (val) VALUES ('[0,0,0]'), ('[1,2,3]'), ('[1,1,1]'), (NULL); INSERT INTO t (val) VALUES ('[0,0,0]'), ('[1,2,3]'), ('[1,1,1]'), (NULL);
CREATE TABLE t2 (val vector(3)); CREATE TABLE t2 (val vector(3));
\copy t TO 'results/data.bin' WITH (FORMAT binary) \copy t TO '/tmp/data.bin' WITH (FORMAT binary)
\copy t2 FROM 'results/data.bin' WITH (FORMAT binary) \copy t2 FROM '/tmp/data.bin' WITH (FORMAT binary)
SELECT * FROM t2 ORDER BY val; SELECT * FROM t2 ORDER BY val;
val val
--------- ---------

View File

@@ -1,3 +1,5 @@
SET client_min_messages = warning;
CREATE EXTENSION IF NOT EXISTS vector;
SELECT '[1,2,3]'::vector + '[4,5,6]'; SELECT '[1,2,3]'::vector + '[4,5,6]';
?column? ?column?
---------- ----------

View File

@@ -1,3 +1,5 @@
SET client_min_messages = warning;
CREATE EXTENSION IF NOT EXISTS vector;
SELECT '[1,2,3]'::vector; SELECT '[1,2,3]'::vector;
vector vector
--------- ---------
@@ -10,12 +12,6 @@ SELECT '[-1,2,3]'::vector;
[-1,2,3] [-1,2,3]
(1 row) (1 row)
SELECT '[1.23456]'::vector;
vector
-----------
[1.23456]
(1 row)
SELECT '[hello,1]'::vector; SELECT '[hello,1]'::vector;
ERROR: invalid input syntax for type vector: "hello" ERROR: invalid input syntax for type vector: "hello"
LINE 1: SELECT '[hello,1]'::vector; LINE 1: SELECT '[hello,1]'::vector;

View File

@@ -1,3 +1,5 @@
SET client_min_messages = warning;
CREATE EXTENSION IF NOT EXISTS vector;
SET enable_seqscan = off; SET enable_seqscan = off;
CREATE TABLE t (val vector(3)); CREATE TABLE t (val vector(3));
INSERT INTO t (val) VALUES ('[0,0,0]'), ('[1,2,3]'), ('[1,1,1]'), (NULL); INSERT INTO t (val) VALUES ('[0,0,0]'), ('[1,2,3]'), ('[1,1,1]'), (NULL);

View File

@@ -1,3 +1,5 @@
SET client_min_messages = warning;
CREATE EXTENSION IF NOT EXISTS vector;
SET enable_seqscan = off; SET enable_seqscan = off;
CREATE TABLE t (val vector(3)); CREATE TABLE t (val vector(3));
INSERT INTO t (val) VALUES ('[0,0,0]'), ('[1,2,3]'), ('[1,1,1]'), (NULL); INSERT INTO t (val) VALUES ('[0,0,0]'), ('[1,2,3]'), ('[1,1,1]'), (NULL);

View File

@@ -1,3 +1,5 @@
SET client_min_messages = warning;
CREATE EXTENSION IF NOT EXISTS vector;
SET enable_seqscan = off; SET enable_seqscan = off;
CREATE TABLE t (val vector(3)); CREATE TABLE t (val vector(3));
INSERT INTO t (val) VALUES ('[0,0,0]'), ('[1,2,3]'), ('[1,1,1]'), (NULL); INSERT INTO t (val) VALUES ('[0,0,0]'), ('[1,2,3]'), ('[1,1,1]'), (NULL);

View File

@@ -1,3 +1,5 @@
SET client_min_messages = warning;
CREATE EXTENSION IF NOT EXISTS vector;
SET enable_seqscan = off; SET enable_seqscan = off;
CREATE TABLE t (val vector(3)); CREATE TABLE t (val vector(3));
CREATE INDEX ON t USING ivfflat (val) WITH (lists = 0); CREATE INDEX ON t USING ivfflat (val) WITH (lists = 0);

View File

@@ -1,3 +1,5 @@
SET client_min_messages = warning;
CREATE EXTENSION IF NOT EXISTS vector;
SET enable_seqscan = off; SET enable_seqscan = off;
CREATE UNLOGGED TABLE t (val vector(3)); CREATE UNLOGGED TABLE t (val vector(3));
INSERT INTO t (val) VALUES ('[0,0,0]'), ('[1,2,3]'), ('[1,1,1]'), (NULL); INSERT INTO t (val) VALUES ('[0,0,0]'), ('[1,2,3]'), ('[1,1,1]'), (NULL);

View File

@@ -1,8 +0,0 @@
use PostgreSQL::Test::Cluster;
sub get_new_node
{
return PostgreSQL::Test::Cluster->new(@_);
}
1;

View File

@@ -1,3 +0,0 @@
use PostgreSQL::Test::Utils;
1;

View File

@@ -1,3 +1,5 @@
SET client_min_messages = warning;
CREATE EXTENSION IF NOT EXISTS vector;
SET enable_seqscan = off; SET enable_seqscan = off;
CREATE TABLE t (val vector(3)); CREATE TABLE t (val vector(3));

View File

@@ -1,3 +1,6 @@
SET client_min_messages = warning;
CREATE EXTENSION IF NOT EXISTS vector;
SELECT ARRAY[1,2,3]::vector; SELECT ARRAY[1,2,3]::vector;
SELECT ARRAY[1.0,2.0,3.0]::vector; SELECT ARRAY[1.0,2.0,3.0]::vector;
SELECT ARRAY[1,2,3]::float4[]::vector; SELECT ARRAY[1,2,3]::float4[]::vector;

View File

@@ -1,10 +1,13 @@
SET client_min_messages = warning;
CREATE EXTENSION IF NOT EXISTS vector;
CREATE TABLE t (val vector(3)); CREATE TABLE t (val vector(3));
INSERT INTO t (val) VALUES ('[0,0,0]'), ('[1,2,3]'), ('[1,1,1]'), (NULL); INSERT INTO t (val) VALUES ('[0,0,0]'), ('[1,2,3]'), ('[1,1,1]'), (NULL);
CREATE TABLE t2 (val vector(3)); CREATE TABLE t2 (val vector(3));
\copy t TO 'results/data.bin' WITH (FORMAT binary) \copy t TO '/tmp/data.bin' WITH (FORMAT binary)
\copy t2 FROM 'results/data.bin' WITH (FORMAT binary) \copy t2 FROM '/tmp/data.bin' WITH (FORMAT binary)
SELECT * FROM t2 ORDER BY val; SELECT * FROM t2 ORDER BY val;

View File

@@ -1,3 +1,6 @@
SET client_min_messages = warning;
CREATE EXTENSION IF NOT EXISTS vector;
SELECT '[1,2,3]'::vector + '[4,5,6]'; SELECT '[1,2,3]'::vector + '[4,5,6]';
SELECT '[1,2,3]'::vector - '[4,5,6]'; SELECT '[1,2,3]'::vector - '[4,5,6]';

View File

@@ -1,6 +1,8 @@
SET client_min_messages = warning;
CREATE EXTENSION IF NOT EXISTS vector;
SELECT '[1,2,3]'::vector; SELECT '[1,2,3]'::vector;
SELECT '[-1,2,3]'::vector; SELECT '[-1,2,3]'::vector;
SELECT '[1.23456]'::vector;
SELECT '[hello,1]'::vector; SELECT '[hello,1]'::vector;
SELECT '[NaN,1]'::vector; SELECT '[NaN,1]'::vector;
SELECT '[Infinity,1]'::vector; SELECT '[Infinity,1]'::vector;

View File

@@ -1,3 +1,5 @@
SET client_min_messages = warning;
CREATE EXTENSION IF NOT EXISTS vector;
SET enable_seqscan = off; SET enable_seqscan = off;
CREATE TABLE t (val vector(3)); CREATE TABLE t (val vector(3));

View File

@@ -1,3 +1,5 @@
SET client_min_messages = warning;
CREATE EXTENSION IF NOT EXISTS vector;
SET enable_seqscan = off; SET enable_seqscan = off;
CREATE TABLE t (val vector(3)); CREATE TABLE t (val vector(3));

View File

@@ -1,3 +1,5 @@
SET client_min_messages = warning;
CREATE EXTENSION IF NOT EXISTS vector;
SET enable_seqscan = off; SET enable_seqscan = off;
CREATE TABLE t (val vector(3)); CREATE TABLE t (val vector(3));

View File

@@ -1,3 +1,5 @@
SET client_min_messages = warning;
CREATE EXTENSION IF NOT EXISTS vector;
SET enable_seqscan = off; SET enable_seqscan = off;
CREATE TABLE t (val vector(3)); CREATE TABLE t (val vector(3));

View File

@@ -1,3 +1,5 @@
SET client_min_messages = warning;
CREATE EXTENSION IF NOT EXISTS vector;
SET enable_seqscan = off; SET enable_seqscan = off;
CREATE UNLOGGED TABLE t (val vector(3)); CREATE UNLOGGED TABLE t (val vector(3));

View File

@@ -7,8 +7,6 @@ use PostgresNode;
use TestLib; use TestLib;
use Test::More tests => 31; use Test::More tests => 31;
my $dim = 32;
my $node_primary; my $node_primary;
my $node_replica; my $node_replica;
@@ -32,15 +30,13 @@ sub test_index_replay
$node_primary->poll_query_until('postgres', $caughtup_query) $node_primary->poll_query_until('postgres', $caughtup_query)
or die "Timed out while waiting for replica 1 to catch up"; or die "Timed out while waiting for replica 1 to catch up";
my @r = (); my $r1 = rand();
for (1 .. $dim) { my $r2 = rand();
push(@r, rand()); my $r3 = rand();
}
my $sql = join(",", @r);
my $queries = qq( my $queries = qq(
SET enable_seqscan = off; SET enable_seqscan = off;
SELECT * FROM tst ORDER BY v <-> '[$sql]' LIMIT 10; SELECT * FROM tst ORDER BY v <-> '[$r1,$r2,$r3]' LIMIT 10;
); );
# Run test queries and compare their result # Run test queries and compare their result
@@ -51,18 +47,9 @@ sub test_index_replay
return; return;
} }
# Use ARRAY[random(), random(), random(), ...] over
# SELECT array_agg(random()) FROM generate_series(1, $dim)
# to generate different values for each row
my $array_sql = join(",", ('random()') x $dim);
# Initialize primary node # Initialize primary node
$node_primary = get_new_node('primary'); $node_primary = get_new_node('primary');
$node_primary->init(allows_streaming => 1); $node_primary->init(allows_streaming => 1);
if ($dim > 32) {
# TODO use wal_keep_segments for Postgres < 13
$node_primary->append_conf('postgresql.conf', qq(wal_keep_size = 1GB));
}
$node_primary->start; $node_primary->start;
my $backup_name = 'my_backup'; my $backup_name = 'my_backup';
@@ -77,9 +64,9 @@ $node_replica->start;
# Create ivfflat index on primary # Create ivfflat index on primary
$node_primary->safe_psql("postgres", "CREATE EXTENSION vector;"); $node_primary->safe_psql("postgres", "CREATE EXTENSION vector;");
$node_primary->safe_psql("postgres", "CREATE TABLE tst (i int4, v vector($dim));"); $node_primary->safe_psql("postgres", "CREATE TABLE tst (i int4, v vector(3));");
$node_primary->safe_psql("postgres", $node_primary->safe_psql("postgres",
"INSERT INTO tst SELECT i % 10, ARRAY[$array_sql] FROM generate_series(1, 100000) i;" "INSERT INTO tst SELECT i % 10, ARRAY[random(), random(), random()] FROM generate_series(1, 100000) i;"
); );
$node_primary->safe_psql("postgres", "CREATE INDEX ON tst USING ivfflat (v);"); $node_primary->safe_psql("postgres", "CREATE INDEX ON tst USING ivfflat (v);");
@@ -95,7 +82,7 @@ for my $i (1 .. 10)
test_index_replay("vacuum $i"); test_index_replay("vacuum $i");
my ($start, $end) = (100001 + ($i - 1) * 10000, 100000 + $i * 10000); my ($start, $end) = (100001 + ($i - 1) * 10000, 100000 + $i * 10000);
$node_primary->safe_psql("postgres", $node_primary->safe_psql("postgres",
"INSERT INTO tst SELECT i % 10, ARRAY[$array_sql] FROM generate_series($start, $end) i;" "INSERT INTO tst SELECT i % 10, ARRAY[random(), random(), random()] FROM generate_series($start, $end) i;"
); );
test_index_replay("insert $i"); test_index_replay("insert $i");
} }

View File

@@ -2,16 +2,15 @@ use strict;
use warnings; use warnings;
use PostgresNode; use PostgresNode;
use TestLib; use TestLib;
use Test::More tests => 9; use Test::More tests => 2;
my $node; my $node;
my @queries = (); my @queries = ();
my @expected; my @expected = ();
my $limit = 20;
sub test_recall sub test_recall
{ {
my ($probes, $min, $operator) = @_; my ($probes, $min) = @_;
my $correct = 0; my $correct = 0;
my $total = 0; my $total = 0;
@@ -19,7 +18,7 @@ sub test_recall
my $actual = $node->safe_psql("postgres", qq( my $actual = $node->safe_psql("postgres", qq(
SET enable_seqscan = off; SET enable_seqscan = off;
SET ivfflat.probes = $probes; SET ivfflat.probes = $probes;
SELECT i FROM tst ORDER BY v $operator '$queries[$i]' LIMIT $limit; SELECT i FROM tst ORDER BY v <-> '$queries[$i]' LIMIT 10;
)); ));
my @actual_ids = split("\n", $actual); my @actual_ids = split("\n", $actual);
my %actual_set = map { $_ => 1 } @actual_ids; my %actual_set = map { $_ => 1 } @actual_ids;
@@ -34,7 +33,7 @@ sub test_recall
} }
} }
cmp_ok($correct / $total, ">=", $min, $operator); cmp_ok($correct / $total, ">=", $min);
} }
# Initialize node # Initialize node
@@ -57,32 +56,17 @@ for (1..20) {
push(@queries, "[$r1,$r2,$r3]"); push(@queries, "[$r1,$r2,$r3]");
} }
# Check each index type # Get exact results
my @operators = ("<->", "<#>", "<=>"); foreach (@queries) {
my $res = $node->safe_psql("postgres", "SELECT i FROM tst ORDER BY v <-> '$_' LIMIT 10;");
foreach (@operators) { push(@expected, $res);
my $operator = $_;
# Get exact results
@expected = ();
foreach (@queries) {
my $res = $node->safe_psql("postgres", "SELECT i FROM tst ORDER BY v $operator '$_' LIMIT $limit;");
push(@expected, $res);
}
# Add index
my $opclass;
if ($operator == "<->") {
$opclass = "vector_l2_ops";
} elsif ($operator == "<#>") {
$opclass = "vector_ip_ops";
} else {
$opclass = "vector_cosine_ops";
}
$node->safe_psql("postgres", "CREATE INDEX ON tst USING ivfflat (v $opclass);");
# Test approximate results
test_recall(1, 0.75, $operator);
test_recall(10, 0.95, $operator);
test_recall(100, 1.0, $operator);
} }
# Add index
$node->safe_psql("postgres", "CREATE INDEX ON tst USING ivfflat (v);");
# Test approximate results
test_recall(1, 0.8);
# Test probes
test_recall(100, 1.0);

View File

@@ -1,45 +0,0 @@
use strict;
use warnings;
use PostgresNode;
use TestLib;
use Test::More tests => 60;
# Initialize node
my $node = get_new_node('node');
$node->init;
$node->start;
# Create table
$node->safe_psql("postgres", "CREATE EXTENSION vector;");
$node->safe_psql("postgres", "CREATE TABLE tst (i int4 primary key, v vector(3));");
$node->safe_psql("postgres",
"INSERT INTO tst SELECT i, ARRAY[random(), random(), random()] FROM generate_series(1, 100000) i;"
);
# Check each index type
my @operators = ("<->", "<#>", "<=>");
foreach (@operators) {
my $operator = $_;
# Add index
my $opclass;
if ($operator == "<->") {
$opclass = "vector_l2_ops";
} elsif ($operator == "<#>") {
$opclass = "vector_ip_ops";
} else {
$opclass = "vector_cosine_ops";
}
$node->safe_psql("postgres", "CREATE INDEX ON tst USING ivfflat (v $opclass);");
# Test 100% recall
for (1..20) {
my $i = int(rand() * 100000);
my $query = $node->safe_psql("postgres", "SELECT v FROM tst WHERE i = $i;");
my $res = $node->safe_psql("postgres", qq(
SET enable_seqscan = off;
SELECT v FROM tst ORDER BY v <-> '$query' LIMIT 1;
));
is($res, $query);
}
}

View File

@@ -1,31 +0,0 @@
use strict;
use warnings;
use PostgresNode;
use TestLib;
use Test::More tests => 3;
# Initialize node
my $node = get_new_node('node');
$node->init;
$node->start;
# Create table
$node->safe_psql("postgres", "CREATE EXTENSION vector;");
$node->safe_psql("postgres", "CREATE TABLE tst (v vector(3));");
$node->safe_psql("postgres",
"INSERT INTO tst SELECT ARRAY[random(), random(), random()] FROM generate_series(1, 100000) i;"
);
$node->safe_psql("postgres", "CREATE INDEX lists50 ON tst USING ivfflat (v) WITH (lists = 50);");
$node->safe_psql("postgres", "CREATE INDEX lists100 ON tst USING ivfflat (v) WITH (lists = 100);");
# Test prefers more lists
my $res = $node->safe_psql("postgres", "EXPLAIN SELECT v FROM tst ORDER BY v <-> '[0.5,0.5,0.5]' LIMIT 10;");
like($res, qr/lists100/);
unlike($res, qr/lists50/);
# Test errors with too much memory
my ($ret, $stdout, $stderr) = $node->psql("postgres",
"CREATE INDEX lists10000 ON tst USING ivfflat (v) WITH (lists = 10000);"
);
like($stderr, qr/memory required is/);

View File

@@ -1,45 +0,0 @@
use strict;
use warnings;
use PostgresNode;
use TestLib;
use Test::More tests => 5;
my $dim = 768;
my $array_sql = join(",", ('random()') x $dim);
# Initialize node
my $node = get_new_node('node');
$node->init;
$node->start;
# Create table and index
$node->safe_psql("postgres", "CREATE EXTENSION vector;");
$node->safe_psql("postgres", "CREATE TABLE tst (v vector($dim));");
$node->safe_psql("postgres",
"INSERT INTO tst SELECT ARRAY[$array_sql] FROM generate_series(1, 10000) i;"
);
$node->safe_psql("postgres", "CREATE INDEX ON tst USING ivfflat (v);");
$node->pgbench(
"--no-vacuum --client=5 --transactions=100",
0,
[qr{actually processed}],
[qr{^$}],
"concurrent INSERTs",
{
"007_inserts" => "INSERT INTO tst SELECT ARRAY[$array_sql] FROM generate_series(1, 10) i;"
}
);
my $expected = 10000 + 5 * 100 * 10;
my $count = $node->safe_psql("postgres", "SELECT COUNT(*) FROM tst;");
is($count, $expected);
$count = $node->safe_psql("postgres", qq(
SET enable_seqscan = off;
SET ivfflat.probes = 100;
SELECT COUNT(*) FROM (SELECT v FROM tst ORDER BY v <-> (SELECT v FROM tst LIMIT 1)) t;
));
is($count, $expected);

View File

@@ -1,4 +1,4 @@
comment = 'vector data type and ivfflat access method' comment = 'vector data type and ivfflat access method'
default_version = '0.3.2' default_version = '0.2.5'
module_pathname = '$libdir/vector' module_pathname = '$libdir/vector'
relocatable = true relocatable = true