mirror of
https://github.com/pgvector/pgvector.git
synced 2026-07-22 20:15:46 +08:00
Compare commits
10 Commits
random_vec
...
minibatch
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9852351746 | ||
|
|
50349ed4f5 | ||
|
|
2ee510aa67 | ||
|
|
cad655b77f | ||
|
|
21ca5d3845 | ||
|
|
8374498e6c | ||
|
|
c1d6b9b41b | ||
|
|
a77340d40b | ||
|
|
81b68fbf5b | ||
|
|
8ee6d0e596 |
@@ -1,6 +1,6 @@
|
|||||||
root = true
|
root = true
|
||||||
|
|
||||||
[*.{c,h,pl,pm,sql}]
|
[*.{c,h,pl}]
|
||||||
indent_style = tab
|
indent_style = tab
|
||||||
indent_size = tab
|
indent_size = tab
|
||||||
tab_width = 4
|
tab_width = 4
|
||||||
|
|||||||
92
.github/workflows/build.yml
vendored
92
.github/workflows/build.yml
vendored
@@ -1,72 +1,38 @@
|
|||||||
name: build
|
name: build
|
||||||
on: [push, pull_request]
|
on: [push, pull_request]
|
||||||
jobs:
|
jobs:
|
||||||
ubuntu:
|
build:
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
if: ${{ !startsWith(github.ref_name, 'mac') && !startsWith(github.ref_name, 'windows') }}
|
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
|
os: [ubuntu-latest]
|
||||||
|
postgres: [14, 13, 12, 11, 10, 9.6]
|
||||||
include:
|
include:
|
||||||
- postgres: 15
|
- os: macos-latest
|
||||||
os: ubuntu-22.04
|
postgres: 14
|
||||||
- postgres: 14
|
|
||||||
os: ubuntu-22.04
|
|
||||||
- postgres: 13
|
|
||||||
os: ubuntu-20.04
|
|
||||||
- postgres: 12
|
|
||||||
os: ubuntu-20.04
|
|
||||||
- postgres: 11
|
|
||||||
os: ubuntu-18.04
|
|
||||||
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() }}
|
||||||
- run: 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 --notest IPC::Run
|
|
||||||
wget -q https://github.com/postgres/postgres/archive/refs/tags/REL_14_5.tar.gz
|
|
||||||
tar xf REL_14_5.tar.gz
|
|
||||||
- run: make prove_installcheck PROVE_FLAGS="-I ./postgres-REL_14_5/src/test/perl" PERL5LIB="/Users/runner/perl5/lib/perl5"
|
|
||||||
windows:
|
|
||||||
runs-on: windows-latest
|
|
||||||
if: ${{ !startsWith(github.ref_name, 'mac') }}
|
|
||||||
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 && ^
|
|
||||||
nmake /NOLOGO /F Makefile.win installcheck && ^
|
|
||||||
nmake /NOLOGO /F Makefile.win clean && ^
|
|
||||||
nmake /NOLOGO /F Makefile.win uninstall
|
|
||||||
shell: cmd
|
|
||||||
|
|||||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -5,8 +5,3 @@
|
|||||||
regression.*
|
regression.*
|
||||||
*.o
|
*.o
|
||||||
*.so
|
*.so
|
||||||
*.bc
|
|
||||||
*.dll
|
|
||||||
*.obj
|
|
||||||
*.lib
|
|
||||||
*.exp
|
|
||||||
|
|||||||
40
CHANGELOG.md
40
CHANGELOG.md
@@ -1,42 +1,6 @@
|
|||||||
## 0.4.1 (unreleased)
|
## 0.2.6 (unreleased)
|
||||||
|
|
||||||
- Added `random_vector` function
|
|
||||||
|
|
||||||
## 0.4.0 (2023-01-11)
|
|
||||||
|
|
||||||
If upgrading with Postgres < 13, see [this note](https://github.com/pgvector/pgvector#040).
|
|
||||||
|
|
||||||
- Changed text representation for vector elements to match `real`
|
|
||||||
- Changed storage for vector from `plain` to `extended`
|
|
||||||
- Increased max dimensions for vector from 1024 to 16000
|
|
||||||
- Increased max dimensions for index from 1024 to 2000
|
|
||||||
- Improved accuracy of text parsing for certain inputs
|
|
||||||
- Added `avg` aggregate for vector
|
|
||||||
- Added experimental support for Windows
|
|
||||||
- Dropped support for Postgres 10
|
|
||||||
|
|
||||||
## 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)
|
|
||||||
|
|
||||||
|
- Switched to mini-batch k-means
|
||||||
- Improved performance of index creation for Postgres < 12
|
- Improved performance of index creation for Postgres < 12
|
||||||
|
|
||||||
## 0.2.5 (2022-02-11)
|
## 0.2.5 (2022-02-11)
|
||||||
|
|||||||
@@ -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/*
|
||||||
|
|||||||
2
LICENSE
2
LICENSE
@@ -1,4 +1,4 @@
|
|||||||
Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
|
Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
|
||||||
|
|
||||||
Portions Copyright (c) 1994, The Regents of the University of California
|
Portions Copyright (c) 1994, The Regents of the University of California
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"name": "vector",
|
"name": "vector",
|
||||||
"abstract": "Open-source vector similarity search for Postgres",
|
"abstract": "Open-source vector similarity search for Postgres",
|
||||||
"description": "Supports L2 distance, inner product, and cosine distance",
|
"description": "Supports L2 distance, inner product, and cosine distance",
|
||||||
"version": "0.4.0",
|
"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": "11.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.4.0",
|
"version": "0.2.5",
|
||||||
"abstract": "Open-source vector similarity search for Postgres"
|
"abstract": "Open-source vector similarity search for Postgres"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
12
Makefile
12
Makefile
@@ -1,5 +1,5 @@
|
|||||||
EXTENSION = vector
|
EXTENSION = vector
|
||||||
EXTVERSION = 0.4.0
|
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,14 +40,6 @@ PG_CONFIG ?= pg_config
|
|||||||
PGXS := $(shell $(PG_CONFIG) --pgxs)
|
PGXS := $(shell $(PG_CONFIG) --pgxs)
|
||||||
include $(PGXS)
|
include $(PGXS)
|
||||||
|
|
||||||
# for Mac
|
|
||||||
ifeq ($(PROVE),)
|
|
||||||
PROVE = prove
|
|
||||||
endif
|
|
||||||
|
|
||||||
# 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)
|
||||||
|
|||||||
70
Makefile.win
70
Makefile.win
@@ -1,70 +0,0 @@
|
|||||||
EXTENSION = vector
|
|
||||||
EXTVERSION = 0.4.0
|
|
||||||
|
|
||||||
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
|
|
||||||
!ifndef PGROOT
|
|
||||||
!error PGROOT is not set
|
|
||||||
!endif
|
|
||||||
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 = $(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)
|
|
||||||
|
|
||||||
uninstall:
|
|
||||||
del /f "$(PKGLIBDIR)\$(SHLIB)"
|
|
||||||
del /f "$(SHAREDIR)\extension\$(EXTENSION).control"
|
|
||||||
del /f "$(SHAREDIR)\extension\vector--*.sql"
|
|
||||||
|
|
||||||
clean:
|
|
||||||
del /f $(SHLIB) $(EXTENSION).lib $(EXTENSION).exp
|
|
||||||
del /f $(OBJS)
|
|
||||||
del /f sql\$(EXTENSION)--$(EXTVERSION).sql
|
|
||||||
del /f /s /q results regression.diffs regression.out tmp_check tmp_check_iso log output_iso
|
|
||||||
134
README.md
134
README.md
@@ -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 11+)
|
Compile and install the extension (supports Postgres 9.6+)
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
git clone --branch v0.4.0 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
|
||||||
@@ -29,26 +29,26 @@ Then load it in databases where you want to use it
|
|||||||
CREATE EXTENSION vector;
|
CREATE EXTENSION vector;
|
||||||
```
|
```
|
||||||
|
|
||||||
You can also install it with [Docker](#docker), [Homebrew](#homebrew), [PGXN](#pgxn), or [conda-forge](#conda-forge)
|
You can also install it with [Docker](#docker), [Homebrew](#homebrew), or [PGXN](#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,29 +62,29 @@ 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. Vectors with up to 2,000 dimensions can be indexed.
|
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.
|
||||||
|
|
||||||
### Index Options
|
### Index Options
|
||||||
|
|
||||||
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)`
|
||||||
@@ -130,20 +130,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
|
||||||
|
|
||||||
@@ -156,14 +146,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 16,000 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
|
||||||
|
|
||||||
@@ -179,31 +169,23 @@ Operator | Description
|
|||||||
|
|
||||||
Function | Description
|
Function | Description
|
||||||
--- | ---
|
--- | ---
|
||||||
cosine_distance(vector, vector) → double precision | cosine distance
|
cosine_distance(vector, vector) | cosine distance
|
||||||
inner_product(vector, vector) → double precision | inner product
|
inner_product(vector, vector) | inner product
|
||||||
l2_distance(vector, vector) → double precision | Euclidean distance
|
l2_distance(vector, vector) | Euclidean distance
|
||||||
vector_dims(vector) → integer | number of dimensions
|
vector_dims(vector) | number of dimensions
|
||||||
vector_norm(vector) → double precision | Euclidean norm
|
vector_norm(vector) | Euclidean norm
|
||||||
random_vector(integer) → vector | random vector [unreleased]
|
|
||||||
|
|
||||||
### Aggregate Functions
|
|
||||||
|
|
||||||
Function | Description
|
|
||||||
--- | ---
|
|
||||||
avg(vector) → vector | arithmetic mean
|
|
||||||
|
|
||||||
## Libraries
|
## Libraries
|
||||||
|
|
||||||
Language | Libraries
|
Libraries that use pgvector:
|
||||||
--- | ---
|
|
||||||
Python | [pgvector-python](https://github.com/pgvector/pgvector-python)
|
- [pgvector-python](https://github.com/pgvector/pgvector-python) (Python)
|
||||||
Ruby | [Neighbor](https://github.com/ankane/neighbor), [pgvector-ruby](https://github.com/pgvector/pgvector-ruby)
|
- [Neighbor](https://github.com/ankane/neighbor) (Ruby)
|
||||||
Node | [pgvector-node](https://github.com/pgvector/pgvector-node)
|
- [pgvector-ruby](https://github.com/pgvector/pgvector-ruby) (Ruby)
|
||||||
Go | [pgvector-go](https://github.com/pgvector/pgvector-go)
|
- [pgvector-node](https://github.com/pgvector/pgvector-node) (Node.js)
|
||||||
PHP | [pgvector-php](https://github.com/pgvector/pgvector-php)
|
- [pgvector-go](https://github.com/pgvector/pgvector-go) (Go)
|
||||||
Rust | [pgvector-rust](https://github.com/pgvector/pgvector-rust)
|
- [pgvector-rust](https://github.com/pgvector/pgvector-rust) (Rust)
|
||||||
C++ | [pgvector-cpp](https://github.com/pgvector/pgvector-cpp)
|
- [pgvector-cpp](https://github.com/pgvector/pgvector-cpp) (C++)
|
||||||
Elixir | [pgvector-elixir](https://github.com/pgvector/pgvector-elixir)
|
|
||||||
|
|
||||||
## Frequently Asked Questions
|
## Frequently Asked Questions
|
||||||
|
|
||||||
@@ -215,12 +197,12 @@ A non-partitioned table has a limit of 32 TB by default in Postgres. A partition
|
|||||||
|
|
||||||
Yes, pgvector uses the write-ahead log (WAL), which allows for replication and point-in-time recovery.
|
Yes, pgvector uses the write-ahead log (WAL), which allows for replication and point-in-time recovery.
|
||||||
|
|
||||||
#### What if I want to index vectors with more than 2,000 dimensions?
|
#### What if my data has more than 1024 dimensions?
|
||||||
|
|
||||||
Two things you can try are:
|
Two things you can try are:
|
||||||
|
|
||||||
1. use dimensionality reduction
|
1. use dimensionality reduction
|
||||||
2. compile Postgres with a larger block size (`./configure --with-blocksize=32`) and edit the limit in `src/ivfflat.h`
|
2. compile Postgres with a larger block size (`./configure --with-blocksize=32`) and edit the limit in `src/vector.h`
|
||||||
|
|
||||||
## Additional Installation Methods
|
## Additional Installation Methods
|
||||||
|
|
||||||
@@ -237,14 +219,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.4.0 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
|
||||||
@@ -258,24 +240,14 @@ Install from the [PostgreSQL Extension Network](https://pgxn.org/dist/vector) wi
|
|||||||
pgxn install vector
|
pgxn install vector
|
||||||
```
|
```
|
||||||
|
|
||||||
### conda-forge
|
|
||||||
|
|
||||||
Install from [conda-forge](https://anaconda.org/conda-forge/pgvector) with:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
conda install -c conda-forge pgvector
|
|
||||||
```
|
|
||||||
|
|
||||||
This method is [community-maintained](https://github.com/conda-forge/pgvector-feedstock) by [@mmcauliffe](https://github.com/mmcauliffe)
|
|
||||||
|
|
||||||
## Hosted Postgres
|
## Hosted Postgres
|
||||||
|
|
||||||
Some Postgres providers only support specific extensions. To request a new extension:
|
Some Postgres providers only support specific extensions. To request a new extension:
|
||||||
|
|
||||||
- Amazon RDS - follow the instructions on [this page](https://aws.amazon.com/rds/postgresql/faqs/)
|
- Amazon RDS - follow the instructions on [this page](https://aws.amazon.com/rds/postgresql/faqs/)
|
||||||
- Google Cloud SQL - vote or comment on [this page](https://issuetracker.google.com/issues/265172065)
|
- Google Cloud SQL - follow the instructions on [this page](https://cloud.google.com/sql/docs/postgres/extensions#requesting-support-for-a-new-extension)
|
||||||
- DigitalOcean Managed Databases - vote or comment on [this page](https://ideas.digitalocean.com/app-framework-services/p/pgvector-extension-for-postgresql)
|
- DigitalOcean Managed Databases - vote or comment on [this page](https://ideas.digitalocean.com/app-framework-services/p/pgvector-extension-for-postgresql)
|
||||||
- Azure Database for PostgreSQL - vote or comment on [this page](https://feedback.azure.com/d365community/idea/7b423322-6189-ed11-a81b-000d3ae49307)
|
- Azure Database for PostgreSQL - follow the instructions on [this page](https://docs.microsoft.com/en-us/azure/postgresql/concepts-extensions#next-steps)
|
||||||
|
|
||||||
## Upgrading
|
## Upgrading
|
||||||
|
|
||||||
@@ -285,39 +257,13 @@ Install the latest version and run:
|
|||||||
ALTER EXTENSION vector UPDATE;
|
ALTER EXTENSION vector UPDATE;
|
||||||
```
|
```
|
||||||
|
|
||||||
## Upgrade Notes
|
|
||||||
|
|
||||||
### 0.4.0
|
|
||||||
|
|
||||||
If upgrading with Postgres < 13, remove this line from `sql/vector--0.3.2--0.4.0.sql`:
|
|
||||||
|
|
||||||
```sql
|
|
||||||
ALTER TYPE vector SET (STORAGE = extended);
|
|
||||||
```
|
|
||||||
|
|
||||||
Then run `make install` and `ALTER EXTENSION vector UPDATE;`.
|
|
||||||
|
|
||||||
### 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:
|
||||||
|
|
||||||
- [PASE: PostgreSQL Ultra-High-Dimensional Approximate Nearest Neighbor Search Extension](https://dl.acm.org/doi/pdf/10.1145/3318464.3386131)
|
- [PASE: PostgreSQL Ultra-High-Dimensional Approximate Nearest Neighbor Search Extension](https://dl.acm.org/doi/pdf/10.1145/3318464.3386131)
|
||||||
- [Faiss: A Library for Efficient Similarity Search and Clustering of Dense Vectors](https://github.com/facebookresearch/faiss)
|
- [Faiss: A Library for Efficient Similarity Search and Clustering of Dense Vectors](https://github.com/facebookresearch/faiss)
|
||||||
- [Using the Triangle Inequality to Accelerate k-means](https://www.aaai.org/Papers/ICML/2003/ICML03-022.pdf)
|
- [Web-Scale k-means Clustering](https://www.eecs.tufts.edu/~dsculley/papers/fastkmeans.pdf)
|
||||||
- [k-means++: The Advantage of Careful Seeding](https://theory.stanford.edu/~sergei/papers/kMeansPP-soda.pdf)
|
- [k-means++: The Advantage of Careful Seeding](https://theory.stanford.edu/~sergei/papers/kMeansPP-soda.pdf)
|
||||||
- [Concept Decompositions for Large Sparse Text Data using Clustering](https://www.cs.utexas.edu/users/inderjit/public_papers/concept_mlj.pdf)
|
- [Concept Decompositions for Large Sparse Text Data using Clustering](https://www.cs.utexas.edu/users/inderjit/public_papers/concept_mlj.pdf)
|
||||||
|
|
||||||
|
|||||||
@@ -7,13 +7,13 @@ DROP CAST (double precision[] AS vector);
|
|||||||
DROP CAST (numeric[] AS vector);
|
DROP CAST (numeric[] AS vector);
|
||||||
|
|
||||||
CREATE CAST (integer[] AS vector)
|
CREATE CAST (integer[] AS vector)
|
||||||
WITH FUNCTION array_to_vector(integer[], integer, boolean) AS ASSIGNMENT;
|
WITH FUNCTION array_to_vector(integer[], integer, boolean) AS ASSIGNMENT;
|
||||||
|
|
||||||
CREATE CAST (real[] AS vector)
|
CREATE CAST (real[] AS vector)
|
||||||
WITH FUNCTION array_to_vector(real[], integer, boolean) AS ASSIGNMENT;
|
WITH FUNCTION array_to_vector(real[], integer, boolean) AS ASSIGNMENT;
|
||||||
|
|
||||||
CREATE CAST (double precision[] AS vector)
|
CREATE CAST (double precision[] AS vector)
|
||||||
WITH FUNCTION array_to_vector(double precision[], integer, boolean) AS ASSIGNMENT;
|
WITH FUNCTION array_to_vector(double precision[], integer, boolean) AS ASSIGNMENT;
|
||||||
|
|
||||||
CREATE CAST (numeric[] AS vector)
|
CREATE CAST (numeric[] AS vector)
|
||||||
WITH FUNCTION array_to_vector(numeric[], integer, boolean) AS ASSIGNMENT;
|
WITH FUNCTION array_to_vector(numeric[], integer, boolean) AS ASSIGNMENT;
|
||||||
|
|||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
);
|
|
||||||
@@ -1,5 +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
|
|
||||||
|
|
||||||
CREATE FUNCTION random_vector(integer) RETURNS vector
|
|
||||||
AS 'MODULE_PATHNAME' LANGUAGE C VOLATILE STRICT PARALLEL SAFE;
|
|
||||||
@@ -25,8 +25,7 @@ CREATE TYPE vector (
|
|||||||
OUTPUT = vector_out,
|
OUTPUT = vector_out,
|
||||||
TYPMOD_IN = vector_typmod_in,
|
TYPMOD_IN = vector_typmod_in,
|
||||||
RECEIVE = vector_recv,
|
RECEIVE = vector_recv,
|
||||||
SEND = vector_send,
|
SEND = vector_send
|
||||||
STORAGE = extended
|
|
||||||
);
|
);
|
||||||
|
|
||||||
-- functions
|
-- functions
|
||||||
@@ -52,9 +51,6 @@ CREATE FUNCTION vector_add(vector, vector) RETURNS vector
|
|||||||
CREATE FUNCTION vector_sub(vector, vector) RETURNS vector
|
CREATE FUNCTION vector_sub(vector, vector) RETURNS vector
|
||||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||||
|
|
||||||
CREATE FUNCTION random_vector(integer) RETURNS vector
|
|
||||||
AS 'MODULE_PATHNAME' LANGUAGE C VOLATILE STRICT PARALLEL SAFE;
|
|
||||||
|
|
||||||
-- private functions
|
-- private functions
|
||||||
|
|
||||||
CREATE FUNCTION vector_lt(vector, vector) RETURNS bool
|
CREATE FUNCTION vector_lt(vector, vector) RETURNS bool
|
||||||
@@ -87,26 +83,6 @@ CREATE FUNCTION vector_negative_inner_product(vector, vector) RETURNS float8
|
|||||||
CREATE FUNCTION vector_spherical_distance(vector, vector) RETURNS float8
|
CREATE FUNCTION vector_spherical_distance(vector, vector) RETURNS float8
|
||||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
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
|
|
||||||
);
|
|
||||||
|
|
||||||
-- cast functions
|
-- cast functions
|
||||||
|
|
||||||
CREATE FUNCTION vector(vector, integer, boolean) RETURNS vector
|
CREATE FUNCTION vector(vector, integer, boolean) RETURNS vector
|
||||||
|
|||||||
224
src/ivfbuild.c
224
src/ivfbuild.c
@@ -6,7 +6,6 @@
|
|||||||
#include "ivfflat.h"
|
#include "ivfflat.h"
|
||||||
#include "miscadmin.h"
|
#include "miscadmin.h"
|
||||||
#include "storage/bufmgr.h"
|
#include "storage/bufmgr.h"
|
||||||
#include "utils/memutils.h"
|
|
||||||
|
|
||||||
#if PG_VERSION_NUM >= 140000
|
#if PG_VERSION_NUM >= 140000
|
||||||
#include "utils/backend_progress.h"
|
#include "utils/backend_progress.h"
|
||||||
@@ -23,8 +22,13 @@
|
|||||||
#define PROGRESS_CREATEIDX_TUPLES_DONE 0
|
#define PROGRESS_CREATEIDX_TUPLES_DONE 0
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if PG_VERSION_NUM >= 110000
|
||||||
#include "catalog/pg_operator_d.h"
|
#include "catalog/pg_operator_d.h"
|
||||||
#include "catalog/pg_type_d.h"
|
#include "catalog/pg_type_d.h"
|
||||||
|
#else
|
||||||
|
#include "catalog/pg_operator.h"
|
||||||
|
#include "catalog/pg_type.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
#if PG_VERSION_NUM >= 130000
|
#if PG_VERSION_NUM >= 130000
|
||||||
#define CALLBACK_ITEM_POINTER ItemPointer tid
|
#define CALLBACK_ITEM_POINTER ItemPointer tid
|
||||||
@@ -39,121 +43,27 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Add sample
|
* Callback for table_index_build_scan
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
AddSample(Datum *values, IvfflatBuildState * buildstate)
|
BuildCallback(Relation index, CALLBACK_ITEM_POINTER, Datum *values,
|
||||||
{
|
bool *isnull, bool tupleIsAlive, void *state)
|
||||||
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;
|
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 distance;
|
||||||
double minDistance = DBL_MAX;
|
double minDistance = DBL_MAX;
|
||||||
int closestCenter = -1;
|
int closestCenter = -1;
|
||||||
VectorArray centers = buildstate->centers;
|
VectorArray centers = buildstate->centers;
|
||||||
TupleTableSlot *slot = buildstate->slot;
|
TupleTableSlot *slot = buildstate->slot;
|
||||||
|
Datum value = values[0];
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
/* Detoast once for all calls */
|
#if PG_VERSION_NUM < 130000
|
||||||
Datum value = PointerGetDatum(PG_DETOAST_DATUM(values[0]));
|
ItemPointer tid = &hup->t_self;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (isnull[0])
|
||||||
|
return;
|
||||||
|
|
||||||
/* Normalize if needed */
|
/* Normalize if needed */
|
||||||
if (buildstate->normprocinfo != NULL)
|
if (buildstate->normprocinfo != NULL)
|
||||||
@@ -201,35 +111,6 @@ AddTupleToSort(Relation index, ItemPointer tid, Datum *values, IvfflatBuildState
|
|||||||
buildstate->indtuples++;
|
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
|
* Get index tuple from sort state
|
||||||
*/
|
*/
|
||||||
@@ -239,7 +120,11 @@ GetNextTuple(Tuplesortstate *sortstate, TupleDesc tupdesc, TupleTableSlot *slot,
|
|||||||
Datum value;
|
Datum value;
|
||||||
bool isnull;
|
bool isnull;
|
||||||
|
|
||||||
|
#if PG_VERSION_NUM >= 100000
|
||||||
if (tuplesort_gettupleslot(sortstate, true, false, slot, NULL))
|
if (tuplesort_gettupleslot(sortstate, true, false, slot, NULL))
|
||||||
|
#else
|
||||||
|
if (tuplesort_gettupleslot(sortstate, true, slot, NULL))
|
||||||
|
#endif
|
||||||
{
|
{
|
||||||
*list = DatumGetInt32(slot_getattr(slot, 1, &isnull));
|
*list = DatumGetInt32(slot_getattr(slot, 1, &isnull));
|
||||||
value = slot_getattr(slot, 3, &isnull);
|
value = slot_getattr(slot, 3, &isnull);
|
||||||
@@ -263,8 +148,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;
|
||||||
@@ -289,7 +174,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);
|
||||||
|
|
||||||
@@ -338,9 +223,6 @@ InitBuildState(IvfflatBuildState * buildstate, Relation heap, Relation index, In
|
|||||||
if (buildstate->dimensions < 0)
|
if (buildstate->dimensions < 0)
|
||||||
elog(ERROR, "column does not have dimensions");
|
elog(ERROR, "column does not have dimensions");
|
||||||
|
|
||||||
if (buildstate->dimensions > IVFFLAT_MAX_DIM)
|
|
||||||
elog(ERROR, "column cannot have more than %d dimensions for ivfflat index", IVFFLAT_MAX_DIM);
|
|
||||||
|
|
||||||
buildstate->reltuples = 0;
|
buildstate->reltuples = 0;
|
||||||
buildstate->indtuples = 0;
|
buildstate->indtuples = 0;
|
||||||
|
|
||||||
@@ -364,7 +246,11 @@ InitBuildState(IvfflatBuildState * buildstate, Relation heap, Relation index, In
|
|||||||
#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, "tid", TIDOID, -1, 0);
|
||||||
|
#if PG_VERSION_NUM >= 110000
|
||||||
TupleDescInitEntry(buildstate->tupdesc, (AttrNumber) 3, "vector", RelationGetDescr(index)->attrs[0].atttypid, -1, 0);
|
TupleDescInitEntry(buildstate->tupdesc, (AttrNumber) 3, "vector", RelationGetDescr(index)->attrs[0].atttypid, -1, 0);
|
||||||
|
#else
|
||||||
|
TupleDescInitEntry(buildstate->tupdesc, (AttrNumber) 3, "vector", RelationGetDescr(index)->attrs[0]->atttypid, -1, 0);
|
||||||
|
#endif
|
||||||
|
|
||||||
#if PG_VERSION_NUM >= 120000
|
#if PG_VERSION_NUM >= 120000
|
||||||
buildstate->slot = MakeSingleTupleTableSlot(buildstate->tupdesc, &TTSOpsVirtual);
|
buildstate->slot = MakeSingleTupleTableSlot(buildstate->tupdesc, &TTSOpsVirtual);
|
||||||
@@ -378,10 +264,6 @@ InitBuildState(IvfflatBuildState * buildstate, Relation heap, Relation index, In
|
|||||||
/* Reuse for each tuple */
|
/* Reuse for each tuple */
|
||||||
buildstate->normvec = InitVector(buildstate->dimensions);
|
buildstate->normvec = InitVector(buildstate->dimensions);
|
||||||
|
|
||||||
buildstate->tmpCtx = AllocSetContextCreate(CurrentMemoryContext,
|
|
||||||
"Ivfflat build temporary context",
|
|
||||||
ALLOCSET_DEFAULT_SIZES);
|
|
||||||
|
|
||||||
#ifdef IVFFLAT_KMEANS_DEBUG
|
#ifdef IVFFLAT_KMEANS_DEBUG
|
||||||
buildstate->inertia = 0;
|
buildstate->inertia = 0;
|
||||||
buildstate->listSums = palloc0(sizeof(double) * buildstate->lists);
|
buildstate->listSums = palloc0(sizeof(double) * buildstate->lists);
|
||||||
@@ -395,7 +277,7 @@ 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);
|
||||||
|
|
||||||
@@ -403,41 +285,6 @@ FreeBuildState(IvfflatBuildState * buildstate)
|
|||||||
pfree(buildstate->listSums);
|
pfree(buildstate->listSums);
|
||||||
pfree(buildstate->listCounts);
|
pfree(buildstate->listCounts);
|
||||||
#endif
|
#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);
|
|
||||||
|
|
||||||
/* Calculate centers */
|
|
||||||
IvfflatBench("k-means", IvfflatKmeans(buildstate->index, buildstate->samples, buildstate->centers));
|
|
||||||
|
|
||||||
/* Free samples before we allocate more memory */
|
|
||||||
VectorArrayFree(buildstate->samples);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -452,7 +299,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);
|
||||||
@@ -485,7 +332,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++)
|
||||||
{
|
{
|
||||||
@@ -571,7 +418,11 @@ CreateEntryPages(IvfflatBuildState * buildstate, ForkNumber forkNum)
|
|||||||
|
|
||||||
UpdateProgress(PROGRESS_CREATEIDX_SUBPHASE, PROGRESS_IVFFLAT_PHASE_SORT);
|
UpdateProgress(PROGRESS_CREATEIDX_SUBPHASE, PROGRESS_IVFFLAT_PHASE_SORT);
|
||||||
|
|
||||||
|
#if PG_VERSION_NUM >= 110000
|
||||||
buildstate->sortstate = tuplesort_begin_heap(buildstate->tupdesc, 1, attNums, sortOperators, sortCollations, nullsFirstFlags, maintenance_work_mem, NULL, false);
|
buildstate->sortstate = tuplesort_begin_heap(buildstate->tupdesc, 1, attNums, sortOperators, sortCollations, nullsFirstFlags, maintenance_work_mem, NULL, false);
|
||||||
|
#else
|
||||||
|
buildstate->sortstate = tuplesort_begin_heap(buildstate->tupdesc, 1, attNums, sortOperators, sortCollations, nullsFirstFlags, maintenance_work_mem, false);
|
||||||
|
#endif
|
||||||
|
|
||||||
/* Add tuples to sort */
|
/* Add tuples to sort */
|
||||||
if (buildstate->heap != NULL)
|
if (buildstate->heap != NULL)
|
||||||
@@ -579,9 +430,12 @@ CreateEntryPages(IvfflatBuildState * buildstate, ForkNumber forkNum)
|
|||||||
#if PG_VERSION_NUM >= 120000
|
#if PG_VERSION_NUM >= 120000
|
||||||
buildstate->reltuples = table_index_build_scan(buildstate->heap, buildstate->index, buildstate->indexInfo,
|
buildstate->reltuples = table_index_build_scan(buildstate->heap, buildstate->index, buildstate->indexInfo,
|
||||||
true, true, BuildCallback, (void *) buildstate, NULL);
|
true, true, BuildCallback, (void *) buildstate, NULL);
|
||||||
#else
|
#elif PG_VERSION_NUM >= 110000
|
||||||
buildstate->reltuples = IndexBuildHeapScan(buildstate->heap, buildstate->index, buildstate->indexInfo,
|
buildstate->reltuples = IndexBuildHeapScan(buildstate->heap, buildstate->index, buildstate->indexInfo,
|
||||||
true, BuildCallback, (void *) buildstate, NULL);
|
true, BuildCallback, (void *) buildstate, NULL);
|
||||||
|
#else
|
||||||
|
buildstate->reltuples = IndexBuildHeapScan(buildstate->heap, buildstate->index, buildstate->indexInfo,
|
||||||
|
true, BuildCallback, (void *) buildstate);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -606,7 +460,9 @@ BuildIndex(Relation heap, Relation index, IndexInfo *indexInfo,
|
|||||||
{
|
{
|
||||||
InitBuildState(buildstate, heap, index, indexInfo);
|
InitBuildState(buildstate, heap, index, indexInfo);
|
||||||
|
|
||||||
ComputeCenters(buildstate);
|
/* Perform k-means clustering */
|
||||||
|
UpdateProgress(PROGRESS_CREATEIDX_SUBPHASE, PROGRESS_IVFFLAT_PHASE_KMEANS);
|
||||||
|
IvfflatBench("k-means", IvfflatKmeans(buildstate));
|
||||||
|
|
||||||
/* Create pages */
|
/* Create pages */
|
||||||
CreateMetaPage(index, buildstate->dimensions, buildstate->lists, forkNum);
|
CreateMetaPage(index, buildstate->dimensions, buildstate->lists, forkNum);
|
||||||
|
|||||||
@@ -64,7 +64,9 @@ static void
|
|||||||
ivfflatcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
|
ivfflatcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
|
||||||
Cost *indexStartupCost, Cost *indexTotalCost,
|
Cost *indexStartupCost, Cost *indexTotalCost,
|
||||||
Selectivity *indexSelectivity, double *indexCorrelation
|
Selectivity *indexSelectivity, double *indexCorrelation
|
||||||
|
#if PG_VERSION_NUM >= 100000
|
||||||
,double *indexPages
|
,double *indexPages
|
||||||
|
#endif
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
GenericCosts costs;
|
GenericCosts costs;
|
||||||
@@ -82,7 +84,9 @@ ivfflatcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
|
|||||||
*indexTotalCost = DBL_MAX;
|
*indexTotalCost = DBL_MAX;
|
||||||
*indexSelectivity = 0;
|
*indexSelectivity = 0;
|
||||||
*indexCorrelation = 0;
|
*indexCorrelation = 0;
|
||||||
|
#if PG_VERSION_NUM >= 100000
|
||||||
*indexPages = 0;
|
*indexPages = 0;
|
||||||
|
#endif
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,7 +114,9 @@ ivfflatcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
|
|||||||
*indexTotalCost = costs.indexTotalCost;
|
*indexTotalCost = costs.indexTotalCost;
|
||||||
*indexSelectivity = costs.indexSelectivity;
|
*indexSelectivity = costs.indexSelectivity;
|
||||||
*indexCorrelation = costs.indexCorrelation;
|
*indexCorrelation = costs.indexCorrelation;
|
||||||
|
#if PG_VERSION_NUM >= 100000
|
||||||
*indexPages = costs.numIndexPages;
|
*indexPages = costs.numIndexPages;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -156,7 +162,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)
|
||||||
{
|
{
|
||||||
@@ -178,8 +184,12 @@ ivfflathandler(PG_FUNCTION_ARGS)
|
|||||||
amroutine->amstorage = false;
|
amroutine->amstorage = false;
|
||||||
amroutine->amclusterable = false;
|
amroutine->amclusterable = false;
|
||||||
amroutine->ampredlocks = false;
|
amroutine->ampredlocks = false;
|
||||||
|
#if PG_VERSION_NUM >= 100000
|
||||||
amroutine->amcanparallel = false;
|
amroutine->amcanparallel = false;
|
||||||
|
#endif
|
||||||
|
#if PG_VERSION_NUM >= 110000
|
||||||
amroutine->amcaninclude = false;
|
amroutine->amcaninclude = false;
|
||||||
|
#endif
|
||||||
#if PG_VERSION_NUM >= 130000
|
#if PG_VERSION_NUM >= 130000
|
||||||
amroutine->amusemaintenanceworkmem = false; /* not used during VACUUM */
|
amroutine->amusemaintenanceworkmem = false; /* not used during VACUUM */
|
||||||
amroutine->amparallelvacuumoptions = VACUUM_OPTION_PARALLEL_BULKDEL;
|
amroutine->amparallelvacuumoptions = VACUUM_OPTION_PARALLEL_BULKDEL;
|
||||||
@@ -212,9 +222,11 @@ ivfflathandler(PG_FUNCTION_ARGS)
|
|||||||
amroutine->amrestrpos = NULL;
|
amroutine->amrestrpos = NULL;
|
||||||
|
|
||||||
/* Interface functions to support parallel index scans */
|
/* Interface functions to support parallel index scans */
|
||||||
|
#if PG_VERSION_NUM >= 100000
|
||||||
amroutine->amestimateparallelscan = NULL;
|
amroutine->amestimateparallelscan = NULL;
|
||||||
amroutine->aminitparallelscan = NULL;
|
amroutine->aminitparallelscan = NULL;
|
||||||
amroutine->amparallelrescan = NULL;
|
amroutine->amparallelrescan = NULL;
|
||||||
|
#endif
|
||||||
|
|
||||||
PG_RETURN_POINTER(amroutine);
|
PG_RETURN_POINTER(amroutine);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,10 +3,6 @@
|
|||||||
|
|
||||||
#include "postgres.h"
|
#include "postgres.h"
|
||||||
|
|
||||||
#if PG_VERSION_NUM < 110000
|
|
||||||
#error "Requires PostgreSQL 11+"
|
|
||||||
#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"
|
||||||
@@ -18,7 +14,9 @@
|
|||||||
#include "portability/instr_time.h"
|
#include "portability/instr_time.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#define IVFFLAT_MAX_DIM 2000
|
#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
|
||||||
@@ -66,15 +64,12 @@
|
|||||||
/* Variables */
|
/* Variables */
|
||||||
extern int ivfflat_probes;
|
extern int ivfflat_probes;
|
||||||
|
|
||||||
/* 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;
|
||||||
@@ -134,9 +129,6 @@ typedef struct IvfflatBuildState
|
|||||||
Tuplesortstate *sortstate;
|
Tuplesortstate *sortstate;
|
||||||
TupleDesc tupdesc;
|
TupleDesc tupdesc;
|
||||||
TupleTableSlot *slot;
|
TupleTableSlot *slot;
|
||||||
|
|
||||||
/* Memory */
|
|
||||||
MemoryContext tmpCtx;
|
|
||||||
} IvfflatBuildState;
|
} IvfflatBuildState;
|
||||||
|
|
||||||
typedef struct IvfflatMetaPageData
|
typedef struct IvfflatMetaPageData
|
||||||
@@ -198,16 +190,16 @@ typedef struct 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(IvfflatBuildState * buildstate);
|
||||||
FmgrInfo *IvfflatOptionalProcInfo(Relation rel, uint16 procnum);
|
FmgrInfo *IvfflatOptionalProcInfo(Relation rel, uint16 procnum);
|
||||||
bool IvfflatNormValue(FmgrInfo *procinfo, Oid collation, Datum *value, Vector * result);
|
bool IvfflatNormValue(FmgrInfo *procinfo, Oid collation, Datum *value, Vector * result);
|
||||||
int IvfflatGetLists(Relation index);
|
int IvfflatGetLists(Relation index);
|
||||||
@@ -215,8 +207,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);
|
||||||
@@ -225,7 +216,9 @@ bool ivfflatinsert(Relation index, Datum *values, bool *isnull, ItemPointer hea
|
|||||||
#if PG_VERSION_NUM >= 140000
|
#if PG_VERSION_NUM >= 140000
|
||||||
,bool indexUnchanged
|
,bool indexUnchanged
|
||||||
#endif
|
#endif
|
||||||
|
#if PG_VERSION_NUM >= 100000
|
||||||
,IndexInfo *indexInfo
|
,IndexInfo *indexInfo
|
||||||
|
#endif
|
||||||
);
|
);
|
||||||
IndexBulkDeleteResult *ivfflatbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats, IndexBulkDeleteCallback callback, void *callback_state);
|
IndexBulkDeleteResult *ivfflatbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats, IndexBulkDeleteCallback callback, void *callback_state);
|
||||||
IndexBulkDeleteResult *ivfflatvacuumcleanup(IndexVacuumInfo *info, IndexBulkDeleteResult *stats);
|
IndexBulkDeleteResult *ivfflatvacuumcleanup(IndexVacuumInfo *info, IndexBulkDeleteResult *stats);
|
||||||
|
|||||||
127
src/ivfinsert.c
127
src/ivfinsert.c
@@ -4,7 +4,6 @@
|
|||||||
|
|
||||||
#include "ivfflat.h"
|
#include "ivfflat.h"
|
||||||
#include "storage/bufmgr.h"
|
#include "storage/bufmgr.h"
|
||||||
#include "utils/memutils.h"
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Find the list that minimizes the distance function
|
* Find the list that minimizes the distance function
|
||||||
@@ -54,15 +53,24 @@ 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
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
InsertTuple(Relation rel, Datum *values, bool *isnull, ItemPointer heap_tid, Relation heapRel)
|
InsertTuple(Relation rel, IndexTuple itup, Relation heapRel, Datum *values)
|
||||||
{
|
{
|
||||||
IndexTuple itup;
|
|
||||||
Datum value;
|
|
||||||
FmgrInfo *normprocinfo;
|
|
||||||
Buffer buf;
|
Buffer buf;
|
||||||
Page page;
|
Page page;
|
||||||
GenericXLogState *state;
|
GenericXLogState *state;
|
||||||
@@ -71,42 +79,19 @@ InsertTuple(Relation rel, Datum *values, bool *isnull, ItemPointer heap_tid, Rel
|
|||||||
ListInfo listInfo;
|
ListInfo listInfo;
|
||||||
BlockNumber originalInsertPage;
|
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 */
|
/* Find the insert page - sets the page and list info */
|
||||||
FindInsertPage(rel, values, &insertPage, &listInfo);
|
FindInsertPage(rel, values, &insertPage, &listInfo);
|
||||||
Assert(BlockNumberIsValid(insertPage));
|
Assert(BlockNumberIsValid(insertPage));
|
||||||
originalInsertPage = 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));
|
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))
|
||||||
@@ -114,50 +99,15 @@ InsertTuple(Relation rel, Datum *values, bool *isnull, ItemPointer heap_tid, Rel
|
|||||||
/* 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 previous buffer */
|
|
||||||
UnlockReleaseBuffer(buf);
|
|
||||||
|
|
||||||
/* Prepare new buffer */
|
|
||||||
state = GenericXLogStart(rel);
|
|
||||||
buf = newbuf;
|
|
||||||
page = GenericXLogRegisterBuffer(state, buf, 0);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -181,31 +131,36 @@ ivfflatinsert(Relation index, Datum *values, bool *isnull, ItemPointer heap_tid,
|
|||||||
#if PG_VERSION_NUM >= 140000
|
#if PG_VERSION_NUM >= 140000
|
||||||
,bool indexUnchanged
|
,bool indexUnchanged
|
||||||
#endif
|
#endif
|
||||||
|
#if PG_VERSION_NUM >= 100000
|
||||||
,IndexInfo *indexInfo
|
,IndexInfo *indexInfo
|
||||||
|
#endif
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
MemoryContext oldCtx;
|
IndexTuple itup;
|
||||||
MemoryContext insertCtx;
|
Datum value;
|
||||||
|
FmgrInfo *normprocinfo;
|
||||||
|
|
||||||
/* Skip nulls */
|
|
||||||
if (isnull[0])
|
if (isnull[0])
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
/*
|
value = values[0];
|
||||||
* 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 */
|
/* Normalize if needed */
|
||||||
InsertTuple(index, values, isnull, heap_tid, heap);
|
normprocinfo = IvfflatOptionalProcInfo(index, IVFFLAT_NORM_PROC);
|
||||||
|
if (normprocinfo != NULL)
|
||||||
|
{
|
||||||
|
if (!IvfflatNormValue(normprocinfo, index->rd_indcollation[0], &value, NULL))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/* Delete memory context */
|
itup = index_form_tuple(RelationGetDescr(index), &value, isnull);
|
||||||
MemoryContextSwitchTo(oldCtx);
|
itup->t_tid = *heap_tid;
|
||||||
MemoryContextDelete(insertCtx);
|
InsertTuple(index, itup, heap, &value);
|
||||||
|
pfree(itup);
|
||||||
|
|
||||||
|
/* Clean up if we allocated a new value */
|
||||||
|
if (value != values[0])
|
||||||
|
pfree(DatumGetPointer(value));
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
476
src/ivfkmeans.c
476
src/ivfkmeans.c
@@ -2,8 +2,20 @@
|
|||||||
|
|
||||||
#include <float.h>
|
#include <float.h>
|
||||||
|
|
||||||
|
#include "catalog/index.h"
|
||||||
#include "ivfflat.h"
|
#include "ivfflat.h"
|
||||||
#include "miscadmin.h"
|
#include "miscadmin.h"
|
||||||
|
#include "storage/bufmgr.h"
|
||||||
|
|
||||||
|
#if PG_VERSION_NUM >= 120000
|
||||||
|
#include "access/tableam.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if PG_VERSION_NUM >= 130000
|
||||||
|
#define CALLBACK_ITEM_POINTER ItemPointer tid
|
||||||
|
#else
|
||||||
|
#define CALLBACK_ITEM_POINTER HeapTuple hup
|
||||||
|
#endif
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Initialize with kmeans++
|
* Initialize with kmeans++
|
||||||
@@ -11,12 +23,12 @@
|
|||||||
* https://theory.stanford.edu/~sergei/papers/kMeansPP-soda.pdf
|
* https://theory.stanford.edu/~sergei/papers/kMeansPP-soda.pdf
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
InitCenters(Relation index, VectorArray samples, VectorArray centers, float *lowerBound)
|
InitCenters(Relation index, VectorArray samples, VectorArray centers)
|
||||||
{
|
{
|
||||||
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,13 +41,13 @@ 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++)
|
||||||
weight[j] = DBL_MAX;
|
weight[j] = DBL_MAX;
|
||||||
|
|
||||||
for (i = 0; i < numCenters; i++)
|
for (i = 0; i < numCenters - 1; i++)
|
||||||
{
|
{
|
||||||
CHECK_FOR_INTERRUPTS();
|
CHECK_FOR_INTERRUPTS();
|
||||||
|
|
||||||
@@ -49,9 +61,6 @@ InitCenters(Relation index, VectorArray samples, VectorArray centers, float *low
|
|||||||
/* TODO Use triangle inequality to reduce distance calculations */
|
/* TODO Use triangle inequality to reduce distance calculations */
|
||||||
distance = DatumGetFloat8(FunctionCall2Coll(procinfo, collation, PointerGetDatum(vec), PointerGetDatum(VectorArrayGet(centers, i))));
|
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 */
|
/* Use distance squared for weighted probability distribution */
|
||||||
distance *= distance;
|
distance *= distance;
|
||||||
|
|
||||||
@@ -61,12 +70,8 @@ InitCenters(Relation index, VectorArray samples, VectorArray centers, float *low
|
|||||||
sum += weight[j];
|
sum += weight[j];
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Only compute lower bound on last iteration */
|
|
||||||
if (i + 1 == numCenters)
|
|
||||||
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 +150,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)
|
||||||
@@ -156,301 +161,202 @@ QuickCenters(Relation index, VectorArray samples, VectorArray centers)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Use Elkan for performance. This requires distance function to satisfy triangle inequality.
|
* Callback for sampling
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
SampleCallback(Relation index, CALLBACK_ITEM_POINTER, Datum *values,
|
||||||
|
bool *isnull, bool tupleIsAlive, void *state)
|
||||||
|
{
|
||||||
|
IvfflatBuildState *buildstate = (IvfflatBuildState *) state;
|
||||||
|
VectorArray samples = buildstate->samples;
|
||||||
|
int targsamples = samples->maxlen;
|
||||||
|
Datum value = values[0];
|
||||||
|
|
||||||
|
/* Skip nulls */
|
||||||
|
if (isnull[0])
|
||||||
|
return;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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)
|
||||||
|
{
|
||||||
|
int k = (int) (targsamples * sampler_random_fract(buildstate->rstate.randstate));
|
||||||
|
|
||||||
|
Assert(k >= 0 && k < targsamples);
|
||||||
|
VectorArraySet(samples, k, DatumGetVector(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
buildstate->rowstoskip -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
buildstate->samples->length = 0;
|
||||||
|
|
||||||
|
BlockSampler_Init(&buildstate->bs, totalblocks, targsamples, random());
|
||||||
|
|
||||||
|
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);
|
||||||
|
#elif PG_VERSION_NUM >= 110000
|
||||||
|
IndexBuildHeapRangeScan(buildstate->heap, buildstate->index, buildstate->indexInfo,
|
||||||
|
false, true, targblock, 1, SampleCallback, (void *) buildstate, NULL);
|
||||||
|
#else
|
||||||
|
IndexBuildHeapRangeScan(buildstate->heap, buildstate->index, buildstate->indexInfo,
|
||||||
|
false, true, targblock, 1, SampleCallback, (void *) buildstate);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Use mini-batch k-means
|
||||||
*
|
*
|
||||||
* We use L2 distance for L2 (not L2 squared like index scan)
|
* We use L2 distance for L2 (not L2 squared like index scan)
|
||||||
* and angular distance for inner product and cosine distance
|
* and angular distance for inner product and cosine distance
|
||||||
*
|
*
|
||||||
* https://www.aaai.org/Papers/ICML/2003/ICML03-022.pdf
|
* https://www.eecs.tufts.edu/~dsculley/papers/fastkmeans.pdf
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
ElkanKmeans(Relation index, VectorArray samples, VectorArray centers)
|
MiniBatchKmeans(IvfflatBuildState * buildstate)
|
||||||
{
|
{
|
||||||
FmgrInfo *procinfo;
|
VectorArray centers = buildstate->centers;
|
||||||
FmgrInfo *normprocinfo;
|
VectorArray m = buildstate->samples;
|
||||||
Oid collation;
|
int b = m->maxlen;
|
||||||
Vector *vec;
|
int t = 20;
|
||||||
Vector *newCenter;
|
double distance;
|
||||||
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;
|
double minDistance;
|
||||||
int closestCenter;
|
int closestCenter;
|
||||||
double distance;
|
int i;
|
||||||
bool rj;
|
int j;
|
||||||
bool rjreset;
|
int k;
|
||||||
double dxcx;
|
Vector *c;
|
||||||
double dxc;
|
Vector *x;
|
||||||
|
int *v;
|
||||||
/* Calculate allocation sizes */
|
int *d;
|
||||||
Size samplesSize = VECTOR_ARRAY_SIZE(samples->maxlen, samples->dim);
|
double eta;
|
||||||
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 / 1024 > maintenance_work_mem)
|
|
||||||
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 */
|
/* Set support functions */
|
||||||
procinfo = index_getprocinfo(index, 1, IVFFLAT_KMEANS_DISTANCE_PROC);
|
FmgrInfo *procinfo = index_getprocinfo(buildstate->index, 1, IVFFLAT_KMEANS_DISTANCE_PROC);
|
||||||
normprocinfo = IvfflatOptionalProcInfo(index, IVFFLAT_KMEANS_NORM_PROC);
|
FmgrInfo *normprocinfo = buildstate->kmeansnormprocinfo;
|
||||||
collation = index->rd_indcollation[0];
|
Oid collation = buildstate->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 */
|
/* Pick initial centers */
|
||||||
InitCenters(index, samples, centers, lowerBound);
|
InitCenters(buildstate->index, buildstate->samples, buildstate->centers);
|
||||||
|
|
||||||
/* Assign each x to its closest initial center c(x) = argmin d(x,c) */
|
v = palloc(sizeof(int) * centers->maxlen);
|
||||||
for (j = 0; j < numSamples; j++)
|
d = palloc(sizeof(int) * b);
|
||||||
{
|
|
||||||
minDistance = DBL_MAX;
|
|
||||||
closestCenter = -1;
|
|
||||||
|
|
||||||
/* Find closest center */
|
for (int i = 0; i < centers->length; i++)
|
||||||
for (k = 0; k < numCenters; k++)
|
v[i] = 0;
|
||||||
{
|
|
||||||
/* TODO Use Lemma 1 in k-means++ initialization */
|
|
||||||
distance = lowerBound[j * numCenters + k];
|
|
||||||
|
|
||||||
if (distance < minDistance)
|
for (i = 0; i < t; i++)
|
||||||
{
|
|
||||||
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 */
|
/* Can take a while, so ensure we can interrupt */
|
||||||
CHECK_FOR_INTERRUPTS();
|
CHECK_FOR_INTERRUPTS();
|
||||||
|
|
||||||
changes = 0;
|
/* Get b examples picked randomly from X */
|
||||||
|
SampleRows(buildstate);
|
||||||
|
|
||||||
/* Step 1: For all centers, compute distance */
|
/* Cache nearest center to x */
|
||||||
for (j = 0; j < numCenters; j++)
|
for (j = 0; j < m->length; 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++)
|
|
||||||
{
|
{
|
||||||
|
/* compute closest */
|
||||||
minDistance = DBL_MAX;
|
minDistance = DBL_MAX;
|
||||||
|
closestCenter = -1;
|
||||||
|
|
||||||
for (k = 0; k < numCenters; k++)
|
x = VectorArrayGet(m, j);
|
||||||
|
|
||||||
|
/* Find closest center */
|
||||||
|
for (k = 0; k < centers->length; k++)
|
||||||
{
|
{
|
||||||
if (j == k)
|
distance = DatumGetFloat8(FunctionCall2Coll(procinfo, collation, PointerGetDatum(x), PointerGetDatum(VectorArrayGet(centers, k))));
|
||||||
continue;
|
|
||||||
|
|
||||||
distance = halfcdist[j * numCenters + k];
|
|
||||||
if (distance < minDistance)
|
if (distance < minDistance)
|
||||||
|
{
|
||||||
minDistance = distance;
|
minDistance = distance;
|
||||||
|
closestCenter = k;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
s[j] = minDistance;
|
d[j] = closestCenter;
|
||||||
}
|
}
|
||||||
|
|
||||||
rjreset = iteration != 0;
|
for (j = 0; j < m->length; j++)
|
||||||
|
|
||||||
for (j = 0; j < numSamples; j++)
|
|
||||||
{
|
{
|
||||||
/* Step 2: Identify all points x such that u(x) <= s(c(x)) */
|
x = VectorArrayGet(m, j);
|
||||||
if (upperBound[j] <= s[closestCenters[j]])
|
|
||||||
continue;
|
|
||||||
|
|
||||||
rj = rjreset;
|
/* Get cached center for this x */
|
||||||
|
c = VectorArrayGet(centers, d[j]);
|
||||||
|
|
||||||
for (k = 0; k < numCenters; k++)
|
/* Update per-center counts */
|
||||||
|
v[d[j]]++;
|
||||||
|
|
||||||
|
/* Get per-center learning rate */
|
||||||
|
eta = 1.0 / v[d[j]];
|
||||||
|
|
||||||
|
/* Take gradient step */
|
||||||
|
for (k = 0; k < c->dim; k++)
|
||||||
|
c->x[k] = (1 - eta) * c->x[k] + eta * x->x[k];
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check for empty centers (likely duplicates) */
|
||||||
|
if (i == 0)
|
||||||
|
{
|
||||||
|
for (j = 0; j < centers->length; j++)
|
||||||
{
|
{
|
||||||
/* Step 3: For all remaining points x and centers c */
|
if (v[j] == 0)
|
||||||
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]))));
|
c = VectorArrayGet(centers, 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++;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
/* TODO Handle empty centers properly */
|
||||||
|
for (k = 0; k < c->dim; k++)
|
||||||
|
c->x[k] = ((double) random()) / MAX_RANDOM_VALUE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Step 4: For each center c, let m(c) be mean of all points assigned */
|
/* Normalize if needed */
|
||||||
for (j = 0; j < numCenters; j++)
|
if (normprocinfo != NULL)
|
||||||
{
|
{
|
||||||
vec = VectorArrayGet(newCenters, j);
|
for (j = 0; j < centers->length; j++)
|
||||||
for (k = 0; k < dimensions; k++)
|
ApplyNorm(normprocinfo, collation, VectorArrayGet(centers, j));
|
||||||
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)
|
|
||||||
{
|
|
||||||
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(v);
|
||||||
pfree(centerCounts);
|
pfree(d);
|
||||||
pfree(closestCenters);
|
|
||||||
pfree(lowerBound);
|
|
||||||
pfree(upperBound);
|
|
||||||
pfree(s);
|
|
||||||
pfree(halfcdist);
|
|
||||||
pfree(newcdist);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -493,16 +399,48 @@ CheckCenters(Relation index, VectorArray centers)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Perform naive k-means centering
|
* Perform k-means clustering
|
||||||
* We use spherical k-means for inner product and cosine
|
* We use spherical k-means for inner product and cosine
|
||||||
*/
|
*/
|
||||||
void
|
void
|
||||||
IvfflatKmeans(Relation index, VectorArray samples, VectorArray centers)
|
IvfflatKmeans(IvfflatBuildState * buildstate)
|
||||||
{
|
{
|
||||||
if (samples->length <= centers->maxlen)
|
int numSamples;
|
||||||
QuickCenters(index, samples, centers);
|
Size totalSize;
|
||||||
else
|
|
||||||
ElkanKmeans(index, samples, centers);
|
|
||||||
|
|
||||||
CheckCenters(index, centers);
|
/* Target 10 samples per list, with at least 10000 samples */
|
||||||
|
/* The number of samples has a large effect on index build time */
|
||||||
|
numSamples = buildstate->lists * 10;
|
||||||
|
if (numSamples < 10000)
|
||||||
|
numSamples = 10000;
|
||||||
|
|
||||||
|
/* Skip samples for unlogged table */
|
||||||
|
if (buildstate->heap == NULL)
|
||||||
|
numSamples = 1;
|
||||||
|
|
||||||
|
/* Calculate total size */
|
||||||
|
totalSize = VECTOR_ARRAY_SIZE(numSamples, buildstate->dimensions);
|
||||||
|
|
||||||
|
/* Check memory requirements */
|
||||||
|
/* Add one to error message to ceil */
|
||||||
|
if (totalSize / 1024 > maintenance_work_mem)
|
||||||
|
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)));
|
||||||
|
|
||||||
|
/* Sample rows */
|
||||||
|
buildstate->samples = VectorArrayInit(numSamples, buildstate->dimensions);
|
||||||
|
if (buildstate->heap != NULL)
|
||||||
|
SampleRows(buildstate);
|
||||||
|
|
||||||
|
if (buildstate->samples->length <= buildstate->centers->maxlen)
|
||||||
|
QuickCenters(buildstate->index, buildstate->samples, buildstate->centers);
|
||||||
|
else
|
||||||
|
MiniBatchKmeans(buildstate);
|
||||||
|
|
||||||
|
CheckCenters(buildstate->index, buildstate->centers);
|
||||||
|
|
||||||
|
/* Free samples before we allocate more memory */
|
||||||
|
pfree(buildstate->samples);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,8 +7,13 @@
|
|||||||
#include "miscadmin.h"
|
#include "miscadmin.h"
|
||||||
#include "storage/bufmgr.h"
|
#include "storage/bufmgr.h"
|
||||||
|
|
||||||
|
#if PG_VERSION_NUM >= 110000
|
||||||
#include "catalog/pg_operator_d.h"
|
#include "catalog/pg_operator_d.h"
|
||||||
#include "catalog/pg_type_d.h"
|
#include "catalog/pg_type_d.h"
|
||||||
|
#else
|
||||||
|
#include "catalog/pg_operator.h"
|
||||||
|
#include "catalog/pg_type.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Compare list distances
|
* Compare list distances
|
||||||
@@ -211,7 +216,11 @@ ivfflatbeginscan(Relation index, int nkeys, int norderbys)
|
|||||||
TupleDescInitEntry(so->tupdesc, (AttrNumber) 3, "indexblkno", INT4OID, -1, 0);
|
TupleDescInitEntry(so->tupdesc, (AttrNumber) 3, "indexblkno", INT4OID, -1, 0);
|
||||||
|
|
||||||
/* Prep sort */
|
/* Prep sort */
|
||||||
|
#if PG_VERSION_NUM >= 110000
|
||||||
so->sortstate = tuplesort_begin_heap(so->tupdesc, 1, attNums, sortOperators, sortCollations, nullsFirstFlags, work_mem, NULL, false);
|
so->sortstate = tuplesort_begin_heap(so->tupdesc, 1, attNums, sortOperators, sortCollations, nullsFirstFlags, work_mem, NULL, false);
|
||||||
|
#else
|
||||||
|
so->sortstate = tuplesort_begin_heap(so->tupdesc, 1, attNums, sortOperators, sortCollations, nullsFirstFlags, work_mem, false);
|
||||||
|
#endif
|
||||||
|
|
||||||
#if PG_VERSION_NUM >= 120000
|
#if PG_VERSION_NUM >= 120000
|
||||||
so->slot = MakeSingleTupleTableSlot(so->tupdesc, &TTSOpsMinimalTuple);
|
so->slot = MakeSingleTupleTableSlot(so->tupdesc, &TTSOpsMinimalTuple);
|
||||||
@@ -277,10 +286,6 @@ ivfflatgettuple(IndexScanDesc scan, ScanDirection dir)
|
|||||||
|
|
||||||
value = scan->orderByData->sk_argument;
|
value = scan->orderByData->sk_argument;
|
||||||
|
|
||||||
/* Value should not be compressed or toasted */
|
|
||||||
Assert(!VARATT_IS_COMPRESSED(DatumGetPointer(value)));
|
|
||||||
Assert(!VARATT_IS_EXTENDED(DatumGetPointer(value)));
|
|
||||||
|
|
||||||
if (so->normprocinfo != NULL)
|
if (so->normprocinfo != NULL)
|
||||||
{
|
{
|
||||||
/* No items will match if normalization fails */
|
/* No items will match if normalization fails */
|
||||||
@@ -297,7 +302,11 @@ ivfflatgettuple(IndexScanDesc scan, ScanDirection dir)
|
|||||||
pfree(DatumGetPointer(value));
|
pfree(DatumGetPointer(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if PG_VERSION_NUM >= 100000
|
||||||
if (tuplesort_gettupleslot(so->sortstate, true, false, so->slot, NULL))
|
if (tuplesort_gettupleslot(so->sortstate, true, false, so->slot, NULL))
|
||||||
|
#else
|
||||||
|
if (tuplesort_gettupleslot(so->sortstate, true, so->slot, NULL))
|
||||||
|
#endif
|
||||||
{
|
{
|
||||||
ItemPointer tid = (ItemPointer) DatumGetPointer(slot_getattr(so->slot, 2, &so->isnull));
|
ItemPointer tid = (ItemPointer) DatumGetPointer(slot_getattr(so->slot, 2, &so->isnull));
|
||||||
BlockNumber indexblkno = DatumGetInt32(slot_getattr(so->slot, 3, &so->isnull));
|
BlockNumber indexblkno = DatumGetInt32(slot_getattr(so->slot, 3, &so->isnull));
|
||||||
|
|||||||
@@ -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
|
||||||
*/
|
*/
|
||||||
@@ -86,7 +75,7 @@ IvfflatNormValue(FmgrInfo *procinfo, Oid collation, Datum *value, Vector * resul
|
|||||||
|
|
||||||
if (norm > 0)
|
if (norm > 0)
|
||||||
{
|
{
|
||||||
v = DatumGetVector(*value);
|
v = (Vector *) DatumGetPointer(*value);
|
||||||
|
|
||||||
if (result == NULL)
|
if (result == NULL)
|
||||||
result = InitVector(v->dim);
|
result = InitVector(v->dim);
|
||||||
@@ -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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
361
src/vector.c
361
src/vector.c
@@ -13,20 +13,13 @@
|
|||||||
#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
|
||||||
#define TYPALIGN_DOUBLE 'd'
|
|
||||||
#define TYPALIGN_INT 'i'
|
#define TYPALIGN_INT 'i'
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#define STATE_DIMS(x) (ARR_DIMS(x)[0] - 1)
|
|
||||||
#define CreateStateDatums(dim) palloc(sizeof(Datum) * (dim + 1))
|
|
||||||
|
|
||||||
PG_MODULE_MAGIC;
|
PG_MODULE_MAGIC;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -86,30 +79,6 @@ CheckElement(float value)
|
|||||||
errmsg("infinite value not allowed in vector")));
|
errmsg("infinite value not allowed in vector")));
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* 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 < 120000
|
|
||||||
static pg_noinline void
|
|
||||||
float_overflow_error(void)
|
|
||||||
{
|
|
||||||
ereport(ERROR,
|
|
||||||
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
|
|
||||||
errmsg("value out of range: overflow")));
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Print vector - useful for debugging
|
* Print vector - useful for debugging
|
||||||
*/
|
*/
|
||||||
@@ -137,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;
|
||||||
@@ -167,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++;
|
||||||
|
|
||||||
@@ -214,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)
|
||||||
{
|
{
|
||||||
@@ -306,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)
|
||||||
{
|
{
|
||||||
@@ -338,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)
|
||||||
{
|
{
|
||||||
@@ -358,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)
|
||||||
{
|
{
|
||||||
@@ -373,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)
|
||||||
{
|
{
|
||||||
@@ -431,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)
|
||||||
{
|
{
|
||||||
@@ -454,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;
|
||||||
|
|
||||||
@@ -469,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -480,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;
|
||||||
|
|
||||||
@@ -495,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -505,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);
|
||||||
}
|
}
|
||||||
@@ -526,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);
|
||||||
}
|
}
|
||||||
@@ -547,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;
|
||||||
@@ -563,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))));
|
||||||
@@ -576,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)
|
||||||
{
|
{
|
||||||
@@ -601,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)
|
||||||
{
|
{
|
||||||
@@ -613,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));
|
||||||
}
|
}
|
||||||
@@ -630,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);
|
||||||
}
|
}
|
||||||
@@ -654,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);
|
||||||
}
|
}
|
||||||
@@ -699,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)
|
||||||
{
|
{
|
||||||
@@ -712,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)
|
||||||
{
|
{
|
||||||
@@ -725,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)
|
||||||
{
|
{
|
||||||
@@ -738,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)
|
||||||
{
|
{
|
||||||
@@ -751,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)
|
||||||
{
|
{
|
||||||
@@ -764,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)
|
||||||
{
|
{
|
||||||
@@ -777,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)
|
||||||
{
|
{
|
||||||
@@ -786,186 +704,3 @@ vector_cmp(PG_FUNCTION_ARGS)
|
|||||||
|
|
||||||
PG_RETURN_INT32(vector_cmp_internal(a, b));
|
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] = Float8GetDatumFast(n);
|
|
||||||
|
|
||||||
if (newarr)
|
|
||||||
{
|
|
||||||
for (int i = 0; i < dim; i++)
|
|
||||||
statedatums[i + 1] = Float8GetDatumFast(x[i]);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
for (int i = 0; i < dim; i++)
|
|
||||||
{
|
|
||||||
double v = statevalues[i + 1] + x[i];
|
|
||||||
|
|
||||||
if (isinf(v))
|
|
||||||
float_overflow_error();
|
|
||||||
|
|
||||||
statedatums[i + 1] = Float8GetDatumFast(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] = Float8GetDatumFast(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] = Float8GetDatumFast(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];
|
|
||||||
|
|
||||||
if (isinf(v))
|
|
||||||
float_overflow_error();
|
|
||||||
|
|
||||||
statedatums[i] = Float8GetDatumFast(v);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
statedatums[0] = Float8GetDatumFast(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;
|
|
||||||
float v;
|
|
||||||
|
|
||||||
/* 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);
|
|
||||||
result = InitVector(dim);
|
|
||||||
for (int i = 0; i < dim; i++)
|
|
||||||
{
|
|
||||||
v = statevalues[i + 1] / n;
|
|
||||||
CheckElement(v);
|
|
||||||
result->x[i] = v;
|
|
||||||
}
|
|
||||||
|
|
||||||
PG_RETURN_POINTER(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Generate a random vector
|
|
||||||
*/
|
|
||||||
PGDLLEXPORT PG_FUNCTION_INFO_V1(random_vector);
|
|
||||||
Datum
|
|
||||||
random_vector(PG_FUNCTION_ARGS)
|
|
||||||
{
|
|
||||||
int32 dim = PG_GETARG_INT32(0);
|
|
||||||
Vector *result;
|
|
||||||
|
|
||||||
CheckDim(dim);
|
|
||||||
|
|
||||||
result = InitVector(dim);
|
|
||||||
for (int i = 0; i < dim; i++)
|
|
||||||
result->x[i] = RandomDouble();
|
|
||||||
|
|
||||||
PG_RETURN_POINTER(result);
|
|
||||||
}
|
|
||||||
|
|||||||
16
src/vector.h
16
src/vector.h
@@ -3,27 +3,13 @@
|
|||||||
|
|
||||||
#include "postgres.h"
|
#include "postgres.h"
|
||||||
|
|
||||||
#include "port.h" /* for strtof() and random() */
|
#define VECTOR_MAX_DIM 1024
|
||||||
|
|
||||||
#if PG_VERSION_NUM >= 150000
|
|
||||||
#include "common/pg_prng.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define VECTOR_MAX_DIM 16000
|
|
||||||
|
|
||||||
#define VECTOR_SIZE(_dim) (offsetof(Vector, x) + sizeof(float)*(_dim))
|
#define VECTOR_SIZE(_dim) (offsetof(Vector, x) + sizeof(float)*(_dim))
|
||||||
#define DatumGetVector(x) ((Vector *) PG_DETOAST_DATUM(x))
|
#define DatumGetVector(x) ((Vector *) PG_DETOAST_DATUM(x))
|
||||||
#define PG_GETARG_VECTOR_P(x) DatumGetVector(PG_GETARG_DATUM(x))
|
#define PG_GETARG_VECTOR_P(x) DatumGetVector(PG_GETARG_DATUM(x))
|
||||||
#define PG_RETURN_VECTOR_P(x) PG_RETURN_POINTER(x)
|
#define PG_RETURN_VECTOR_P(x) PG_RETURN_POINTER(x)
|
||||||
|
|
||||||
#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
|
|
||||||
|
|
||||||
typedef struct Vector
|
typedef struct Vector
|
||||||
{
|
{
|
||||||
int32 vl_len_; /* varlena header (do not touch directly!) */
|
int32 vl_len_; /* varlena header (do not touch directly!) */
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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
|
||||||
---------
|
---------
|
||||||
@@ -38,8 +40,8 @@ SELECT '[1,2,3]'::vector::real[];
|
|||||||
{1,2,3}
|
{1,2,3}
|
||||||
(1 row)
|
(1 row)
|
||||||
|
|
||||||
SELECT array_agg(n)::vector FROM generate_series(1, 16001) n;
|
SELECT array_agg(n)::vector FROM generate_series(1, 1025) n;
|
||||||
ERROR: vector cannot have more than 16000 dimensions
|
ERROR: vector cannot have more than 1024 dimensions
|
||||||
-- ensure no error
|
-- ensure no error
|
||||||
SELECT ARRAY[1,2,3] = ARRAY[1,2,3];
|
SELECT ARRAY[1,2,3] = ARRAY[1,2,3];
|
||||||
?column?
|
?column?
|
||||||
|
|||||||
@@ -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
|
||||||
---------
|
---------
|
||||||
|
|||||||
@@ -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?
|
||||||
----------
|
----------
|
||||||
@@ -52,23 +54,3 @@ SELECT cosine_distance('[1,2]', '[0,0]');
|
|||||||
|
|
||||||
SELECT cosine_distance('[1,2]', '[3]');
|
SELECT cosine_distance('[1,2]', '[3]');
|
||||||
ERROR: different vector dimensions 2 and 1
|
ERROR: different vector dimensions 2 and 1
|
||||||
SELECT avg(v) FROM unnest(ARRAY['[1,2,3]'::vector, '[3,5,7]']) v;
|
|
||||||
avg
|
|
||||||
-----------
|
|
||||||
[2,3.5,5]
|
|
||||||
(1 row)
|
|
||||||
|
|
||||||
SELECT avg(v) FROM unnest(ARRAY['[1,2,3]'::vector, '[3,5,7]', NULL]) v;
|
|
||||||
avg
|
|
||||||
-----------
|
|
||||||
[2,3.5,5]
|
|
||||||
(1 row)
|
|
||||||
|
|
||||||
SELECT avg(v) FROM unnest(ARRAY[]::vector[]) v;
|
|
||||||
avg
|
|
||||||
-----
|
|
||||||
|
|
||||||
(1 row)
|
|
||||||
|
|
||||||
SELECT avg(v) FROM unnest(ARRAY['[1,2]'::vector, '[3]']) v;
|
|
||||||
ERROR: expected 2 dimensions, not 1
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
use PostgreSQL::Test::Cluster;
|
|
||||||
|
|
||||||
sub get_new_node
|
|
||||||
{
|
|
||||||
return PostgreSQL::Test::Cluster->new(@_);
|
|
||||||
}
|
|
||||||
|
|
||||||
1;
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
use PostgreSQL::Test::Utils;
|
|
||||||
|
|
||||||
1;
|
|
||||||
@@ -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));
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -8,7 +11,7 @@ SELECT '{Infinity}'::real[]::vector;
|
|||||||
SELECT '{-Infinity}'::real[]::vector;
|
SELECT '{-Infinity}'::real[]::vector;
|
||||||
SELECT '{}'::real[]::vector;
|
SELECT '{}'::real[]::vector;
|
||||||
SELECT '[1,2,3]'::vector::real[];
|
SELECT '[1,2,3]'::vector::real[];
|
||||||
SELECT array_agg(n)::vector FROM generate_series(1, 16001) n;
|
SELECT array_agg(n)::vector FROM generate_series(1, 1025) n;
|
||||||
|
|
||||||
-- ensure no error
|
-- ensure no error
|
||||||
SELECT ARRAY[1,2,3] = ARRAY[1,2,3];
|
SELECT ARRAY[1,2,3] = ARRAY[1,2,3];
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -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]';
|
||||||
|
|
||||||
@@ -13,8 +16,3 @@ SELECT inner_product('[1,2]', '[3]');
|
|||||||
SELECT round(cosine_distance('[1,2]', '[2,4]')::numeric, 5);
|
SELECT round(cosine_distance('[1,2]', '[2,4]')::numeric, 5);
|
||||||
SELECT cosine_distance('[1,2]', '[0,0]');
|
SELECT cosine_distance('[1,2]', '[0,0]');
|
||||||
SELECT cosine_distance('[1,2]', '[3]');
|
SELECT cosine_distance('[1,2]', '[3]');
|
||||||
|
|
||||||
SELECT avg(v) FROM unnest(ARRAY['[1,2,3]'::vector, '[3,5,7]']) v;
|
|
||||||
SELECT avg(v) FROM unnest(ARRAY['[1,2,3]'::vector, '[3,5,7]', NULL]) v;
|
|
||||||
SELECT avg(v) FROM unnest(ARRAY[]::vector[]) v;
|
|
||||||
SELECT avg(v) FROM unnest(ARRAY['[1,2]'::vector, '[3]']) v;
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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));
|
||||||
|
|||||||
@@ -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));
|
||||||
|
|||||||
@@ -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));
|
||||||
|
|||||||
@@ -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));
|
||||||
|
|||||||
@@ -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));
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
@@ -20,20 +18,25 @@ sub test_index_replay
|
|||||||
# Wait for replica to catch up
|
# Wait for replica to catch up
|
||||||
my $applname = $node_replica->name;
|
my $applname = $node_replica->name;
|
||||||
|
|
||||||
|
my $caughtup_query;
|
||||||
my $server_version_num = $node_primary->safe_psql("postgres", "SHOW server_version_num");
|
my $server_version_num = $node_primary->safe_psql("postgres", "SHOW server_version_num");
|
||||||
my $caughtup_query = "SELECT pg_current_wal_lsn() <= replay_lsn FROM pg_stat_replication WHERE application_name = '$applname';";
|
if ($server_version_num >= 100000) {
|
||||||
|
$caughtup_query = "SELECT pg_current_wal_lsn() <= replay_lsn FROM pg_stat_replication WHERE application_name = '$applname';";
|
||||||
|
} else {
|
||||||
|
# TODO figure out why replay location doesn't work
|
||||||
|
$caughtup_query = "SELECT pg_current_xlog_location() <= write_location FROM pg_stat_replication WHERE application_name = '$applname';";
|
||||||
|
}
|
||||||
|
|
||||||
$node_primary->poll_query_until('postgres', $caughtup_query)
|
$node_primary->poll_query_until('postgres', $caughtup_query)
|
||||||
or die "Timed out while waiting for replica 1 to catch up";
|
or die "Timed out while waiting for replica 1 to catch up";
|
||||||
|
|
||||||
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
|
||||||
@@ -47,13 +50,6 @@ sub test_index_replay
|
|||||||
# 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));
|
|
||||||
}
|
|
||||||
if ($dim > 1500) {
|
|
||||||
$node_primary->append_conf('postgresql.conf', qq(maintenance_work_mem = 128MB));
|
|
||||||
}
|
|
||||||
$node_primary->start;
|
$node_primary->start;
|
||||||
my $backup_name = 'my_backup';
|
my $backup_name = 'my_backup';
|
||||||
|
|
||||||
@@ -68,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, random_vector($dim) 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);");
|
||||||
|
|
||||||
@@ -86,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, random_vector($dim) 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");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,15 +4,6 @@ use PostgresNode;
|
|||||||
use TestLib;
|
use TestLib;
|
||||||
use Test::More tests => 1;
|
use Test::More tests => 1;
|
||||||
|
|
||||||
my $dim = 3;
|
|
||||||
|
|
||||||
my @r = ();
|
|
||||||
for (1 .. $dim) {
|
|
||||||
my $v = int(rand(1000)) + 1;
|
|
||||||
push(@r, "i % $v");
|
|
||||||
}
|
|
||||||
my $array_sql = join(", ", @r);
|
|
||||||
|
|
||||||
# Initialize node
|
# Initialize node
|
||||||
my $node = get_new_node('node');
|
my $node = get_new_node('node');
|
||||||
$node->init;
|
$node->init;
|
||||||
@@ -20,9 +11,9 @@ $node->start;
|
|||||||
|
|
||||||
# Create table and index
|
# Create table and index
|
||||||
$node->safe_psql("postgres", "CREATE EXTENSION vector;");
|
$node->safe_psql("postgres", "CREATE EXTENSION vector;");
|
||||||
$node->safe_psql("postgres", "CREATE TABLE tst (i int4, v vector($dim));");
|
$node->safe_psql("postgres", "CREATE TABLE tst (i int4, v vector(3));");
|
||||||
$node->safe_psql("postgres",
|
$node->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[i % 1000, i % 333, i % 55] FROM generate_series(1, 100000) i;"
|
||||||
);
|
);
|
||||||
$node->safe_psql("postgres", "CREATE INDEX ON tst USING ivfflat (v);");
|
$node->safe_psql("postgres", "CREATE INDEX ON tst USING ivfflat (v);");
|
||||||
|
|
||||||
@@ -33,7 +24,7 @@ my $size = $node->safe_psql("postgres", "SELECT pg_total_relation_size('tst_v_id
|
|||||||
$node->safe_psql("postgres", "DELETE FROM tst;");
|
$node->safe_psql("postgres", "DELETE FROM tst;");
|
||||||
$node->safe_psql("postgres", "VACUUM tst;");
|
$node->safe_psql("postgres", "VACUUM tst;");
|
||||||
$node->safe_psql("postgres",
|
$node->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[i % 1000, i % 333, i % 55] FROM generate_series(1, 100000) i;"
|
||||||
);
|
);
|
||||||
|
|
||||||
# Check size
|
# Check size
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ $node->start;
|
|||||||
$node->safe_psql("postgres", "CREATE EXTENSION vector;");
|
$node->safe_psql("postgres", "CREATE EXTENSION vector;");
|
||||||
$node->safe_psql("postgres", "CREATE TABLE tst (i int4, v vector(3));");
|
$node->safe_psql("postgres", "CREATE TABLE tst (i int4, v vector(3));");
|
||||||
$node->safe_psql("postgres",
|
$node->safe_psql("postgres",
|
||||||
"INSERT INTO tst SELECT i, random_vector(3) FROM generate_series(1, 100000) i;"
|
"INSERT INTO tst SELECT i, ARRAY[random(), random(), random()] FROM generate_series(1, 100000) i;"
|
||||||
);
|
);
|
||||||
|
|
||||||
# Generate queries
|
# Generate queries
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ $node->start;
|
|||||||
$node->safe_psql("postgres", "CREATE EXTENSION vector;");
|
$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", "CREATE TABLE tst (i int4 primary key, v vector(3));");
|
||||||
$node->safe_psql("postgres",
|
$node->safe_psql("postgres",
|
||||||
"INSERT INTO tst SELECT i, random_vector(3) FROM generate_series(1, 100000) i;"
|
"INSERT INTO tst SELECT i, ARRAY[random(), random(), random()] FROM generate_series(1, 100000) i;"
|
||||||
);
|
);
|
||||||
|
|
||||||
# Check each index type
|
# Check each index type
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ $node->start;
|
|||||||
$node->safe_psql("postgres", "CREATE EXTENSION vector;");
|
$node->safe_psql("postgres", "CREATE EXTENSION vector;");
|
||||||
$node->safe_psql("postgres", "CREATE TABLE tst (v vector(3));");
|
$node->safe_psql("postgres", "CREATE TABLE tst (v vector(3));");
|
||||||
$node->safe_psql("postgres",
|
$node->safe_psql("postgres",
|
||||||
"INSERT INTO tst SELECT random_vector(3) FROM generate_series(1, 100000) i;"
|
"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 lists50 ON tst USING ivfflat (v) WITH (lists = 50);");
|
||||||
|
|||||||
@@ -1,43 +0,0 @@
|
|||||||
use strict;
|
|
||||||
use warnings;
|
|
||||||
use PostgresNode;
|
|
||||||
use TestLib;
|
|
||||||
use Test::More tests => 5;
|
|
||||||
|
|
||||||
my $dim = 768;
|
|
||||||
|
|
||||||
# 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 random_vector($dim) 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 random_vector($dim) 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);
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
use strict;
|
|
||||||
use warnings;
|
|
||||||
use PostgresNode;
|
|
||||||
use TestLib;
|
|
||||||
use Test::More tests => 5;
|
|
||||||
|
|
||||||
# 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 (r1 real, r2 real, r3 real, v vector(3));");
|
|
||||||
$node->safe_psql("postgres", qq(
|
|
||||||
INSERT INTO tst SELECT r1, r2, r3, ARRAY[r1, r2, r3] FROM (
|
|
||||||
SELECT random() + 1.01 AS r1, random() + 2.01 AS r2, random() + 3.01 AS r3 FROM generate_series(1, 1000000) t
|
|
||||||
) i;
|
|
||||||
));
|
|
||||||
|
|
||||||
# Test avg
|
|
||||||
my $avg = $node->safe_psql("postgres", "SELECT AVG(v) FROM tst;");
|
|
||||||
like($avg, qr/\[1\.5/);
|
|
||||||
like($avg, qr/,2\.5/);
|
|
||||||
like($avg, qr/,3\.5/);
|
|
||||||
|
|
||||||
# Test matches real
|
|
||||||
my $r1 = $node->safe_psql("postgres", "SELECT AVG(r1)::float4 FROM tst;");
|
|
||||||
my $r2 = $node->safe_psql("postgres", "SELECT AVG(r2)::float4 FROM tst;");
|
|
||||||
my $r3 = $node->safe_psql("postgres", "SELECT AVG(r3)::float4 FROM tst;");
|
|
||||||
is($avg, "[$r1,$r2,$r3]");
|
|
||||||
|
|
||||||
# Test explain
|
|
||||||
my $explain = $node->safe_psql("postgres", "EXPLAIN SELECT AVG(v) FROM tst;");
|
|
||||||
like($explain, qr/Partial Aggregate/);
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
use strict;
|
|
||||||
use warnings;
|
|
||||||
use PostgresNode;
|
|
||||||
use TestLib;
|
|
||||||
use Test::More tests => 1;
|
|
||||||
|
|
||||||
my $dim = 1024;
|
|
||||||
|
|
||||||
# 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 (v1 vector(1024), v2 vector(1024), v3 vector(1024));");
|
|
||||||
|
|
||||||
# Test insert succeeds
|
|
||||||
$node->safe_psql("postgres",
|
|
||||||
"INSERT INTO tst SELECT random_vector($dim), random_vector($dim), random_vector($dim)"
|
|
||||||
);
|
|
||||||
|
|
||||||
# Change storage to PLAIN
|
|
||||||
$node->safe_psql("postgres", "ALTER TABLE tst ALTER COLUMN v1 SET STORAGE PLAIN");
|
|
||||||
$node->safe_psql("postgres", "ALTER TABLE tst ALTER COLUMN v2 SET STORAGE PLAIN");
|
|
||||||
$node->safe_psql("postgres", "ALTER TABLE tst ALTER COLUMN v3 SET STORAGE PLAIN");
|
|
||||||
|
|
||||||
# Test insert fails
|
|
||||||
my ($ret, $stdout, $stderr) = $node->psql("postgres",
|
|
||||||
"INSERT INTO tst SELECT random_vector($dim), random_vector($dim), random_vector($dim)"
|
|
||||||
);
|
|
||||||
like($stderr, qr/row is too big/);
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
comment = 'vector data type and ivfflat access method'
|
comment = 'vector data type and ivfflat access method'
|
||||||
default_version = '0.4.0'
|
default_version = '0.2.5'
|
||||||
module_pathname = '$libdir/vector'
|
module_pathname = '$libdir/vector'
|
||||||
relocatable = true
|
relocatable = true
|
||||||
|
|||||||
Reference in New Issue
Block a user