mirror of
https://github.com/pgvector/pgvector.git
synced 2026-07-23 04:20:56 +08:00
Compare commits
1 Commits
windows-si
...
sparsevec-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dde3a2aacd |
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@@ -123,6 +123,6 @@ jobs:
|
||||
- uses: ankane/setup-postgres-valgrind@v1
|
||||
with:
|
||||
postgres-version: 16
|
||||
- run: make OPTFLAGS=""
|
||||
- run: make
|
||||
- run: sudo --preserve-env=PG_CONFIG make install
|
||||
- run: make installcheck
|
||||
|
||||
111
README.md
111
README.md
@@ -419,103 +419,6 @@ Use [partitioning](https://www.postgresql.org/docs/current/ddl-partitioning.html
|
||||
CREATE TABLE items (embedding vector(3), category_id int) PARTITION BY LIST(category_id);
|
||||
```
|
||||
|
||||
## Half Vectors
|
||||
|
||||
*Unreleased*
|
||||
|
||||
Use the `halfvec` type to store half-precision vectors
|
||||
|
||||
```sql
|
||||
CREATE TABLE items (id bigserial PRIMARY KEY, embedding halfvec(3));
|
||||
```
|
||||
|
||||
## Half Indexing
|
||||
|
||||
*Unreleased*
|
||||
|
||||
Index vectors at half precision for smaller indexes and faster build times
|
||||
|
||||
```sql
|
||||
CREATE INDEX ON items USING hnsw ((embedding::halfvec(3)) halfvec_l2_ops);
|
||||
```
|
||||
|
||||
Get the nearest neighbors
|
||||
|
||||
```sql
|
||||
SELECT * FROM items ORDER BY embedding::halfvec(3) <-> '[1,2,3]' LIMIT 5;
|
||||
```
|
||||
|
||||
## Binary Vectors
|
||||
|
||||
Use the `bit` type to store binary vectors ([example](https://github.com/pgvector/pgvector-python/blob/master/examples/hash_image_search.py))
|
||||
|
||||
```sql
|
||||
CREATE TABLE items (id bigserial PRIMARY KEY, embedding bit(3));
|
||||
INSERT INTO items (embedding) VALUES ('000'), ('111');
|
||||
```
|
||||
|
||||
Get the nearest neighbors by Hamming distance
|
||||
|
||||
```sql
|
||||
SELECT * FROM items ORDER BY bit_count(embedding # '101') LIMIT 5;
|
||||
```
|
||||
|
||||
Or (unreleased)
|
||||
|
||||
```sql
|
||||
SELECT * FROM items ORDER BY embedding <~> '101' LIMIT 5;
|
||||
```
|
||||
|
||||
Also supports Jaccard distance (`<%>`)
|
||||
|
||||
## Binary Quantization
|
||||
|
||||
*Unreleased*
|
||||
|
||||
Use expression indexing for binary quantization
|
||||
|
||||
```sql
|
||||
CREATE INDEX ON items USING hnsw ((quantize_binary(embedding)::bit(3)) bit_hamming_ops);
|
||||
```
|
||||
|
||||
Get the nearest neighbors by Hamming distance
|
||||
|
||||
```sql
|
||||
SELECT * FROM items ORDER BY quantize_binary(embedding)::bit(3) <~> quantize_binary('[1,-2,3]') LIMIT 5;
|
||||
```
|
||||
|
||||
Re-rank by the original vectors for better recall
|
||||
|
||||
```sql
|
||||
SELECT * FROM (
|
||||
SELECT * FROM items ORDER BY quantize_binary(embedding)::bit(3) <~> quantize_binary('[1,-2,3]') LIMIT 20
|
||||
) ORDER BY embedding <=> '[1,-2,3]' LIMIT 5;
|
||||
```
|
||||
|
||||
## Sparse Vectors
|
||||
|
||||
*Unreleased*
|
||||
|
||||
Use the `sparsevec` type to store sparse vectors
|
||||
|
||||
```sql
|
||||
CREATE TABLE items (id bigserial PRIMARY KEY, embedding sparsevec(5));
|
||||
```
|
||||
|
||||
Insert vectors
|
||||
|
||||
```sql
|
||||
INSERT INTO items (embedding) VALUES ('{1:1,3:2,5:3}/5'), ('{1:4,3:5,5:6}/5');
|
||||
```
|
||||
|
||||
Note: The format is `{index1:value1,index2:value2,...}/dimensions` and indices start at 1 like SQL arrays
|
||||
|
||||
Get the nearest neighbors by L2 distance
|
||||
|
||||
```sql
|
||||
SELECT * FROM items ORDER BY embedding <-> '{1:3,3:1,5:2}/5' LIMIT 5;
|
||||
```
|
||||
|
||||
## Hybrid Search
|
||||
|
||||
Use together with Postgres [full-text search](https://www.postgresql.org/docs/current/textsearch-intro.html) for hybrid search.
|
||||
@@ -732,6 +635,18 @@ and query with:
|
||||
SELECT * FROM items ORDER BY embedding::vector(3) <-> '[3,1,2]' LIMIT 5;
|
||||
```
|
||||
|
||||
#### Are binary vectors supported?
|
||||
|
||||
You can store binary vectors and perform exact nearest neighbor search by Hamming distance in Postgres without an extension ([example](https://github.com/pgvector/pgvector-python/blob/master/examples/hash_image_search.py)).
|
||||
|
||||
```tsql
|
||||
CREATE TABLE items (id bigserial PRIMARY KEY, embedding bit(3));
|
||||
INSERT INTO items (embedding) VALUES (B'000'), (B'111');
|
||||
SELECT * FROM items ORDER BY bit_count(embedding # B'101') LIMIT 5;
|
||||
```
|
||||
|
||||
Indexing is not currently supported.
|
||||
|
||||
#### Do indexes need to fit into memory?
|
||||
|
||||
No, but like other index types, you’ll likely see better performance if they do. You can get the size of an index with:
|
||||
@@ -886,7 +801,7 @@ jaccard_distance(bit, bit) → double precision | Jaccard distance | unreleased
|
||||
|
||||
### Sparsevec Type
|
||||
|
||||
Each sparse vector takes `8 * non-zero elements + 16` bytes of storage. Each element is a single-precision floating-point number, and all elements must be finite (no `NaN`, `Infinity` or `-Infinity`). Sparse vectors can have up to 16,000 non-zero elements.
|
||||
Each sparse vector takes `8 * non-zero elements + 16` bytes of storage. Each element is a single-precision floating-point number, and all elements must be finite (no `NaN`, `Infinity` or `-Infinity`). Sparse vectors can have up to 100,000 dimensions.
|
||||
|
||||
### Sparsevec Operators
|
||||
|
||||
|
||||
@@ -173,9 +173,6 @@ CREATE FUNCTION sparsevec_in(cstring, oid, integer) RETURNS sparsevec
|
||||
CREATE FUNCTION sparsevec_out(sparsevec) RETURNS cstring
|
||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
CREATE FUNCTION sparsevec_typmod_in(cstring[]) RETURNS integer
|
||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
CREATE FUNCTION sparsevec_recv(internal, oid, integer) RETURNS sparsevec
|
||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
@@ -185,7 +182,6 @@ CREATE FUNCTION sparsevec_send(sparsevec) RETURNS bytea
|
||||
CREATE TYPE sparsevec (
|
||||
INPUT = sparsevec_in,
|
||||
OUTPUT = sparsevec_out,
|
||||
TYPMOD_IN = sparsevec_typmod_in,
|
||||
RECEIVE = sparsevec_recv,
|
||||
SEND = sparsevec_send,
|
||||
STORAGE = external
|
||||
|
||||
@@ -480,9 +480,6 @@ CREATE FUNCTION sparsevec_in(cstring, oid, integer) RETURNS sparsevec
|
||||
CREATE FUNCTION sparsevec_out(sparsevec) RETURNS cstring
|
||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
CREATE FUNCTION sparsevec_typmod_in(cstring[]) RETURNS integer
|
||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
CREATE FUNCTION sparsevec_recv(internal, oid, integer) RETURNS sparsevec
|
||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
@@ -492,7 +489,6 @@ CREATE FUNCTION sparsevec_send(sparsevec) RETURNS bytea
|
||||
CREATE TYPE sparsevec (
|
||||
INPUT = sparsevec_in,
|
||||
OUTPUT = sparsevec_out,
|
||||
TYPMOD_IN = sparsevec_typmod_in,
|
||||
RECEIVE = sparsevec_recv,
|
||||
SEND = sparsevec_send,
|
||||
STORAGE = external
|
||||
|
||||
165
src/halfvec.c
165
src/halfvec.c
@@ -22,10 +22,6 @@
|
||||
#define TYPALIGN_INT 'i'
|
||||
#endif
|
||||
|
||||
#ifdef F16C_SUPPORT
|
||||
#include <immintrin.h>
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Check if half is NaN
|
||||
*/
|
||||
@@ -103,10 +99,7 @@ pq_sendhalf(StringInfo buf, half h)
|
||||
float
|
||||
HalfToFloat4(half num)
|
||||
{
|
||||
#if defined(F16C_SUPPORT) && !defined(_MSC_VER)
|
||||
/* TODO Use instrinsics for Windows */
|
||||
return _cvtsh_ss(num);
|
||||
#elif defined(FLT16_SUPPORT)
|
||||
#ifdef FLT16_SUPPORT
|
||||
return (float) num;
|
||||
#else
|
||||
/* TODO Improve performance */
|
||||
@@ -137,7 +130,7 @@ HalfToFloat4(half num)
|
||||
/* Sign */
|
||||
result = (bin & 0x8000) << 16;
|
||||
|
||||
if (unlikely(exponent == 31))
|
||||
if (exponent == 31)
|
||||
{
|
||||
if (mantissa == 0)
|
||||
{
|
||||
@@ -148,9 +141,10 @@ HalfToFloat4(half num)
|
||||
{
|
||||
/* NaN */
|
||||
result |= 0x7FC00000;
|
||||
result |= mantissa << 13;
|
||||
}
|
||||
}
|
||||
else if (unlikely(exponent == 0))
|
||||
else if (exponent == 0)
|
||||
{
|
||||
/* Subnormal */
|
||||
if (mantissa != 0)
|
||||
@@ -170,16 +164,16 @@ HalfToFloat4(half num)
|
||||
}
|
||||
|
||||
result |= (exponent + 127) << 23;
|
||||
result |= mantissa << 13;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Normal */
|
||||
result |= (exponent - 15 + 127) << 23;
|
||||
result |= mantissa << 13;
|
||||
}
|
||||
|
||||
result |= mantissa << 13;
|
||||
|
||||
swapfloat.i = result;
|
||||
return swapfloat.f;
|
||||
#endif
|
||||
@@ -191,10 +185,7 @@ HalfToFloat4(half num)
|
||||
half
|
||||
Float4ToHalfUnchecked(float num)
|
||||
{
|
||||
#if defined(F16C_SUPPORT) && !defined(_MSC_VER)
|
||||
/* TODO Use instrinsics for Windows */
|
||||
return _cvtss_sh(num, 0);
|
||||
#elif defined(FLT16_SUPPORT)
|
||||
#ifdef FLT16_SUPPORT
|
||||
return (_Float16) num;
|
||||
#else
|
||||
/* TODO Improve performance */
|
||||
@@ -444,8 +435,6 @@ halfvec_in(PG_FUNCTION_ARGS)
|
||||
|
||||
while (pt != NULL && *stringEnd != ']')
|
||||
{
|
||||
float val;
|
||||
|
||||
if (dim == HALFVEC_MAX_DIM)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
|
||||
@@ -461,24 +450,15 @@ halfvec_in(PG_FUNCTION_ARGS)
|
||||
errmsg("invalid input syntax for type halfvec: \"%s\"", lit)));
|
||||
|
||||
/* Use strtof like float4in to avoid a double-rounding problem */
|
||||
errno = 0;
|
||||
val = strtof(pt, &stringEnd);
|
||||
x[dim] = Float4ToHalf(strtof(pt, &stringEnd));
|
||||
CheckElement(x[dim]);
|
||||
dim++;
|
||||
|
||||
if (stringEnd == pt)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
|
||||
errmsg("invalid input syntax for type halfvec: \"%s\"", lit)));
|
||||
|
||||
x[dim] = Float4ToHalfUnchecked(val);
|
||||
|
||||
if ((errno == ERANGE && (isinf(val) || val == 0)) || (HalfIsInf(x[dim]) && !isinf(val)) || (HalfIsZero(x[dim]) && val != 0))
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
|
||||
errmsg("\"%s\" is out of range for type halfvec", pt)));
|
||||
|
||||
CheckElement(x[dim]);
|
||||
dim++;
|
||||
|
||||
while (halfvec_isspace(*stringEnd))
|
||||
stringEnd++;
|
||||
|
||||
@@ -802,56 +782,6 @@ vector_to_halfvec(PG_FUNCTION_ARGS)
|
||||
PG_RETURN_POINTER(result);
|
||||
}
|
||||
|
||||
/*
|
||||
* Get the L2 squared distance between half vectors
|
||||
*/
|
||||
static double
|
||||
l2_distance_squared_internal(HalfVector * a, HalfVector * b)
|
||||
{
|
||||
half *ax = a->x;
|
||||
half *bx = b->x;
|
||||
float distance = 0.0;
|
||||
|
||||
#if defined(F16C_SUPPORT) && defined(__FMA__)
|
||||
int i;
|
||||
float s[8];
|
||||
int count = (a->dim / 8) * 8;
|
||||
__m256 dist = _mm256_setzero_ps();
|
||||
|
||||
for (i = 0; i < count; i += 8)
|
||||
{
|
||||
__m128i axi = _mm_loadu_si128((__m128i *) (ax + i));
|
||||
__m128i bxi = _mm_loadu_si128((__m128i *) (bx + i));
|
||||
__m256 axs = _mm256_cvtph_ps(axi);
|
||||
__m256 bxs = _mm256_cvtph_ps(bxi);
|
||||
__m256 diff = _mm256_sub_ps(axs, bxs);
|
||||
|
||||
dist = _mm256_fmadd_ps(diff, diff, dist);
|
||||
}
|
||||
|
||||
_mm256_storeu_ps(s, dist);
|
||||
|
||||
distance = s[0] + s[1] + s[2] + s[3] + s[4] + s[5] + s[6] + s[7];
|
||||
|
||||
for (; i < a->dim; i++)
|
||||
{
|
||||
float diff = HalfToFloat4(ax[i]) - HalfToFloat4(bx[i]);
|
||||
|
||||
distance += diff * diff;
|
||||
}
|
||||
#else
|
||||
/* Auto-vectorized */
|
||||
for (int i = 0; i < a->dim; i++)
|
||||
{
|
||||
float diff = HalfToFloat4(ax[i]) - HalfToFloat4(bx[i]);
|
||||
|
||||
distance += diff * diff;
|
||||
}
|
||||
#endif
|
||||
|
||||
return (double) distance;
|
||||
}
|
||||
|
||||
/*
|
||||
* Get the L2 distance between half vectors
|
||||
*/
|
||||
@@ -861,10 +791,21 @@ halfvec_l2_distance(PG_FUNCTION_ARGS)
|
||||
{
|
||||
HalfVector *a = PG_GETARG_HALFVEC_P(0);
|
||||
HalfVector *b = PG_GETARG_HALFVEC_P(1);
|
||||
half *ax = a->x;
|
||||
half *bx = b->x;
|
||||
float distance = 0.0;
|
||||
|
||||
CheckDims(a, b);
|
||||
|
||||
PG_RETURN_FLOAT8(sqrt(l2_distance_squared_internal(a, b)));
|
||||
/* Auto-vectorized */
|
||||
for (int i = 0; i < a->dim; i++)
|
||||
{
|
||||
float diff = HalfToFloat4(ax[i]) - HalfToFloat4(bx[i]);
|
||||
|
||||
distance += diff * diff;
|
||||
}
|
||||
|
||||
PG_RETURN_FLOAT8(sqrt((double) distance));
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -876,51 +817,21 @@ halfvec_l2_squared_distance(PG_FUNCTION_ARGS)
|
||||
{
|
||||
HalfVector *a = PG_GETARG_HALFVEC_P(0);
|
||||
HalfVector *b = PG_GETARG_HALFVEC_P(1);
|
||||
|
||||
CheckDims(a, b);
|
||||
|
||||
PG_RETURN_FLOAT8(l2_distance_squared_internal(a, b));
|
||||
}
|
||||
|
||||
/*
|
||||
* Get the inner product of two half vectors
|
||||
*/
|
||||
static double
|
||||
inner_product_internal(HalfVector * a, HalfVector * b)
|
||||
{
|
||||
half *ax = a->x;
|
||||
half *bx = b->x;
|
||||
float distance = 0.0;
|
||||
|
||||
#if defined(F16C_SUPPORT) && defined(__FMA__)
|
||||
int i;
|
||||
float s[8];
|
||||
int count = (a->dim / 8) * 8;
|
||||
__m256 dist = _mm256_setzero_ps();
|
||||
CheckDims(a, b);
|
||||
|
||||
for (i = 0; i < count; i += 8)
|
||||
{
|
||||
__m128i axi = _mm_loadu_si128((__m128i *) (ax + i));
|
||||
__m128i bxi = _mm_loadu_si128((__m128i *) (bx + i));
|
||||
__m256 axs = _mm256_cvtph_ps(axi);
|
||||
__m256 bxs = _mm256_cvtph_ps(bxi);
|
||||
|
||||
dist = _mm256_fmadd_ps(axs, bxs, dist);
|
||||
}
|
||||
|
||||
_mm256_storeu_ps(s, dist);
|
||||
|
||||
distance = s[0] + s[1] + s[2] + s[3] + s[4] + s[5] + s[6] + s[7];
|
||||
|
||||
for (; i < a->dim; i++)
|
||||
distance += HalfToFloat4(ax[i]) * HalfToFloat4(bx[i]);
|
||||
#else
|
||||
/* Auto-vectorized */
|
||||
for (int i = 0; i < a->dim; i++)
|
||||
distance += HalfToFloat4(ax[i]) * HalfToFloat4(bx[i]);
|
||||
#endif
|
||||
{
|
||||
float diff = HalfToFloat4(ax[i]) - HalfToFloat4(bx[i]);
|
||||
|
||||
return (double) distance;
|
||||
distance += diff * diff;
|
||||
}
|
||||
|
||||
PG_RETURN_FLOAT8((double) distance);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -932,10 +843,17 @@ halfvec_inner_product(PG_FUNCTION_ARGS)
|
||||
{
|
||||
HalfVector *a = PG_GETARG_HALFVEC_P(0);
|
||||
HalfVector *b = PG_GETARG_HALFVEC_P(1);
|
||||
half *ax = a->x;
|
||||
half *bx = b->x;
|
||||
float distance = 0.0;
|
||||
|
||||
CheckDims(a, b);
|
||||
|
||||
PG_RETURN_FLOAT8(inner_product_internal(a, b));
|
||||
/* Auto-vectorized */
|
||||
for (int i = 0; i < a->dim; i++)
|
||||
distance += HalfToFloat4(ax[i]) * HalfToFloat4(bx[i]);
|
||||
|
||||
PG_RETURN_FLOAT8((double) distance);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -947,10 +865,17 @@ halfvec_negative_inner_product(PG_FUNCTION_ARGS)
|
||||
{
|
||||
HalfVector *a = PG_GETARG_HALFVEC_P(0);
|
||||
HalfVector *b = PG_GETARG_HALFVEC_P(1);
|
||||
half *ax = a->x;
|
||||
half *bx = b->x;
|
||||
float distance = 0.0;
|
||||
|
||||
CheckDims(a, b);
|
||||
|
||||
PG_RETURN_FLOAT8(-inner_product_internal(a, b));
|
||||
/* Auto-vectorized */
|
||||
for (int i = 0; i < a->dim; i++)
|
||||
distance += HalfToFloat4(ax[i]) * HalfToFloat4(bx[i]);
|
||||
|
||||
PG_RETURN_FLOAT8((double) distance * -1);
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -7,10 +7,7 @@
|
||||
|
||||
#include "vector.h"
|
||||
|
||||
/* F16C has better performance than _Float16 (on x86-64) */
|
||||
#if defined(__F16C__) || defined(_MSC_VER)
|
||||
#define F16C_SUPPORT
|
||||
#elif defined(__FLT16_MAX__)
|
||||
#ifdef __FLT16_MAX__
|
||||
#define FLT16_SUPPORT
|
||||
#endif
|
||||
|
||||
@@ -18,6 +15,7 @@
|
||||
#define half _Float16
|
||||
#define HALF_MAX FLT16_MAX
|
||||
#else
|
||||
/* TODO #pragma message("")? */
|
||||
#define half uint16
|
||||
#define HALF_MAX 65504
|
||||
#endif
|
||||
|
||||
@@ -681,8 +681,6 @@ GetMaxDimensions(HnswType type)
|
||||
maxDimensions *= 2;
|
||||
else if (type == HNSW_TYPE_BIT)
|
||||
maxDimensions *= 32;
|
||||
else if (type == HNSW_TYPE_SPARSEVEC)
|
||||
maxDimensions = INT_MAX;
|
||||
|
||||
return maxDimensions;
|
||||
}
|
||||
@@ -693,8 +691,6 @@ GetMaxDimensions(HnswType type)
|
||||
static void
|
||||
InitBuildState(HnswBuildState * buildstate, Relation heap, Relation index, IndexInfo *indexInfo, ForkNumber forkNum)
|
||||
{
|
||||
int maxDimensions;
|
||||
|
||||
buildstate->heap = heap;
|
||||
buildstate->index = index;
|
||||
buildstate->indexInfo = indexInfo;
|
||||
@@ -705,14 +701,17 @@ InitBuildState(HnswBuildState * buildstate, Relation heap, Relation index, Index
|
||||
buildstate->efConstruction = HnswGetEfConstruction(index);
|
||||
buildstate->dimensions = TupleDescAttr(index->rd_att, 0)->atttypmod;
|
||||
|
||||
maxDimensions = GetMaxDimensions(buildstate->type);
|
||||
|
||||
/* Require column to have dimensions to be indexed */
|
||||
if (buildstate->dimensions < 0)
|
||||
elog(ERROR, "column does not have dimensions");
|
||||
if (buildstate->type != HNSW_TYPE_SPARSEVEC)
|
||||
{
|
||||
int maxDimensions = GetMaxDimensions(buildstate->type);
|
||||
|
||||
if (buildstate->dimensions > maxDimensions)
|
||||
elog(ERROR, "column cannot have more than %d dimensions for hnsw index", maxDimensions);
|
||||
if (buildstate->dimensions < 0)
|
||||
elog(ERROR, "column does not have dimensions");
|
||||
|
||||
if (buildstate->dimensions > maxDimensions)
|
||||
elog(ERROR, "column cannot have more than %d dimensions for hnsw index", maxDimensions);
|
||||
}
|
||||
|
||||
if (buildstate->efConstruction < 2 * buildstate->m)
|
||||
elog(ERROR, "ef_construction must be greater than or equal to 2 * m");
|
||||
|
||||
@@ -158,10 +158,6 @@ hnswgettuple(IndexScanDesc scan, ScanDirection dir)
|
||||
UnlockPage(scan->indexRelation, HNSW_SCAN_LOCK, ShareLock);
|
||||
|
||||
so->first = false;
|
||||
|
||||
#if defined(HNSW_MEMORY) && PG_VERSION_NUM >= 130000
|
||||
elog(INFO, "memory: %zu MB", MemoryContextMemAllocated(so->tmpCtx, false) / (1024 * 1024));
|
||||
#endif
|
||||
}
|
||||
|
||||
while (list_length(so->w) > 0)
|
||||
|
||||
@@ -163,9 +163,9 @@ HnswGetType(Relation index)
|
||||
Oid typid = TupleDescAttr(index->rd_att, 0)->atttypid;
|
||||
HeapTuple tuple;
|
||||
Form_pg_type type;
|
||||
HnswType result;
|
||||
int result;
|
||||
|
||||
if (typid == BITOID)
|
||||
if (typid == BITOID || typid == VARBITOID)
|
||||
return HNSW_TYPE_BIT;
|
||||
|
||||
tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(typid));
|
||||
@@ -180,10 +180,7 @@ HnswGetType(Relation index)
|
||||
else if (strcmp(NameStr(type->typname), "sparsevec") == 0)
|
||||
result = HNSW_TYPE_SPARSEVEC;
|
||||
else
|
||||
{
|
||||
ReleaseSysCache(tuple);
|
||||
elog(ERROR, "type not supported for hnsw index");
|
||||
}
|
||||
elog(ERROR, "Unsupported type");
|
||||
|
||||
ReleaseSysCache(tuple);
|
||||
|
||||
@@ -232,7 +229,7 @@ HnswNormValue(FmgrInfo *procinfo, Oid collation, Datum *value, HnswType type)
|
||||
else if (type == HNSW_TYPE_SPARSEVEC)
|
||||
{
|
||||
SparseVector *v = DatumGetSparseVector(*value);
|
||||
SparseVector *result = InitSparseVector(v->dim, v->nnz);
|
||||
SparseVector *result = InitSparseVector(v->nnz);
|
||||
float *vx = SPARSEVEC_VALUES(v);
|
||||
float *rx = SPARSEVEC_VALUES(result);
|
||||
|
||||
|
||||
178
src/sparsevec.c
178
src/sparsevec.c
@@ -18,86 +18,35 @@
|
||||
#include "utils/builtins.h"
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Ensure same dimensions
|
||||
*/
|
||||
static inline void
|
||||
CheckDims(SparseVector * a, SparseVector * b)
|
||||
{
|
||||
if (a->dim != b->dim)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_DATA_EXCEPTION),
|
||||
errmsg("different sparsevec dimensions %d and %d", a->dim, b->dim)));
|
||||
}
|
||||
|
||||
/*
|
||||
* Ensure expected dimensions
|
||||
*/
|
||||
static inline void
|
||||
CheckExpectedDim(int32 typmod, int dim)
|
||||
{
|
||||
if (typmod != -1 && typmod != dim)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_DATA_EXCEPTION),
|
||||
errmsg("expected %d dimensions, not %d", typmod, dim)));
|
||||
}
|
||||
|
||||
/*
|
||||
* Ensure valid dimensions
|
||||
*/
|
||||
static inline void
|
||||
CheckDim(int dim)
|
||||
{
|
||||
if (dim < 1)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_DATA_EXCEPTION),
|
||||
errmsg("sparsevec must have at least 1 dimension")));
|
||||
|
||||
if (dim > SPARSEVEC_MAX_DIM)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
|
||||
errmsg("sparsevec cannot have more than %d dimensions", SPARSEVEC_MAX_DIM)));
|
||||
}
|
||||
|
||||
/*
|
||||
* Ensure valid nnz
|
||||
*/
|
||||
static inline void
|
||||
CheckNnz(int nnz, int dim)
|
||||
CheckNnz(int nnz)
|
||||
{
|
||||
if (nnz < 0)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_DATA_EXCEPTION),
|
||||
errmsg("sparsevec cannot have negative number of elements")));
|
||||
errmsg("sparsevec must have at least one element")));
|
||||
|
||||
if (nnz > SPARSEVEC_MAX_NNZ)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
|
||||
errmsg("sparsevec cannot have more than %d non-zero elements", SPARSEVEC_MAX_NNZ)));
|
||||
|
||||
if (nnz > dim)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
|
||||
errmsg("sparsevec cannot have more elements than dimensions")));
|
||||
errmsg("sparsevec cannot have more elements than non-zero elements")));
|
||||
}
|
||||
|
||||
/*
|
||||
* Ensure valid index
|
||||
*/
|
||||
static inline void
|
||||
CheckIndex(int32 *indices, int i, int dim)
|
||||
CheckIndex(int32 *indices, int i)
|
||||
{
|
||||
int32 index = indices[i];
|
||||
|
||||
if (index < 1)
|
||||
if (index < 0)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_DATA_EXCEPTION),
|
||||
errmsg("index must be greater than zero")));
|
||||
|
||||
if (index > dim)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_DATA_EXCEPTION),
|
||||
errmsg("index must be less than or equal to dimensions")));
|
||||
errmsg("index must not be negative")));
|
||||
|
||||
if (i > 0)
|
||||
{
|
||||
@@ -134,7 +83,7 @@ CheckElement(float value)
|
||||
* Allocate and initialize a new sparse vector
|
||||
*/
|
||||
SparseVector *
|
||||
InitSparseVector(int dim, int nnz)
|
||||
InitSparseVector(int nnz)
|
||||
{
|
||||
SparseVector *result;
|
||||
int size;
|
||||
@@ -142,7 +91,6 @@ InitSparseVector(int dim, int nnz)
|
||||
size = SPARSEVEC_SIZE(nnz);
|
||||
result = (SparseVector *) palloc0(size);
|
||||
SET_VARSIZE(result, size);
|
||||
result->dim = dim;
|
||||
result->nnz = nnz;
|
||||
|
||||
return result;
|
||||
@@ -172,8 +120,6 @@ Datum
|
||||
sparsevec_in(PG_FUNCTION_ARGS)
|
||||
{
|
||||
char *lit = PG_GETARG_CSTRING(0);
|
||||
int32 typmod = PG_GETARG_INT32(2);
|
||||
int dim;
|
||||
char *pt;
|
||||
char *stringEnd;
|
||||
SparseVector *result;
|
||||
@@ -245,7 +191,7 @@ sparsevec_in(PG_FUNCTION_ARGS)
|
||||
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
|
||||
errmsg("invalid input syntax for type sparsevec: \"%s\"", lit)));
|
||||
|
||||
if (errno == ERANGE || index < 1 || index > INT_MAX)
|
||||
if (errno == ERANGE || index < 0 || index > INT_MAX)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
|
||||
errmsg("index \"%ld\" is out of range for type sparsevec", index)));
|
||||
@@ -307,24 +253,6 @@ sparsevec_in(PG_FUNCTION_ARGS)
|
||||
|
||||
stringEnd++;
|
||||
|
||||
if (*stringEnd != '/')
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
|
||||
errmsg("malformed sparsevec literal: \"%s\"", lit),
|
||||
errdetail("Unexpected end of input.")));
|
||||
|
||||
stringEnd++;
|
||||
|
||||
/* Use similar logic as int2vectorin */
|
||||
errno = 0;
|
||||
pt = stringEnd;
|
||||
dim = strtol(pt, &stringEnd, 10);
|
||||
|
||||
if (stringEnd == pt)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
|
||||
errmsg("invalid input syntax for type sparsevec: \"%s\"", lit)));
|
||||
|
||||
/* Only whitespace is allowed after the closing brace */
|
||||
while (sparsevec_isspace(*stringEnd))
|
||||
stringEnd++;
|
||||
@@ -333,21 +261,18 @@ sparsevec_in(PG_FUNCTION_ARGS)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
|
||||
errmsg("malformed sparsevec literal: \"%s\"", lit),
|
||||
errdetail("Junk after closing.")));
|
||||
errdetail("Junk after closing right brace.")));
|
||||
|
||||
pfree(litcopy);
|
||||
|
||||
CheckDim(dim);
|
||||
CheckExpectedDim(typmod, dim);
|
||||
|
||||
result = InitSparseVector(dim, nnz);
|
||||
result = InitSparseVector(nnz);
|
||||
rvalues = SPARSEVEC_VALUES(result);
|
||||
for (int i = 0; i < nnz; i++)
|
||||
{
|
||||
result->indices[i] = indices[i];
|
||||
rvalues[i] = values[i];
|
||||
|
||||
CheckIndex(result->indices, i, dim);
|
||||
CheckIndex(result->indices, i);
|
||||
CheckElement(rvalues[i]);
|
||||
}
|
||||
|
||||
@@ -392,11 +317,9 @@ sparsevec_out(PG_FUNCTION_ARGS)
|
||||
*
|
||||
* nnz - 1 bytes for ,
|
||||
*
|
||||
* 10 bytes for dimensions
|
||||
*
|
||||
* 4 bytes for {, }, /, and \0
|
||||
* 3 bytes for {, }, and \0
|
||||
*/
|
||||
buf = (char *) palloc((11 + FLOAT_SHORTEST_DECIMAL_LEN) * sparsevec->nnz + 13);
|
||||
buf = (char *) palloc((11 + FLOAT_SHORTEST_DECIMAL_LEN) * sparsevec->nnz + 2);
|
||||
ptr = buf;
|
||||
|
||||
AppendChar(ptr, '{');
|
||||
@@ -412,45 +335,12 @@ sparsevec_out(PG_FUNCTION_ARGS)
|
||||
}
|
||||
|
||||
AppendChar(ptr, '}');
|
||||
AppendChar(ptr, '/');
|
||||
AppendInt(ptr, sparsevec->dim);
|
||||
*ptr = '\0';
|
||||
|
||||
PG_FREE_IF_COPY(sparsevec, 0);
|
||||
PG_RETURN_CSTRING(buf);
|
||||
}
|
||||
|
||||
/*
|
||||
* Convert type modifier
|
||||
*/
|
||||
PGDLLEXPORT PG_FUNCTION_INFO_V1(sparsevec_typmod_in);
|
||||
Datum
|
||||
sparsevec_typmod_in(PG_FUNCTION_ARGS)
|
||||
{
|
||||
ArrayType *ta = PG_GETARG_ARRAYTYPE_P(0);
|
||||
int32 *tl;
|
||||
int n;
|
||||
|
||||
tl = ArrayGetIntegerTypmods(ta, &n);
|
||||
|
||||
if (n != 1)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
||||
errmsg("invalid type modifier")));
|
||||
|
||||
if (*tl < 1)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
||||
errmsg("dimensions for type sparsevec must be at least 1")));
|
||||
|
||||
if (*tl > SPARSEVEC_MAX_DIM)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
||||
errmsg("dimensions for type sparsevec cannot exceed %d", SPARSEVEC_MAX_DIM)));
|
||||
|
||||
PG_RETURN_INT32(*tl);
|
||||
}
|
||||
|
||||
/*
|
||||
* Convert external binary representation to internal representation
|
||||
*/
|
||||
@@ -459,33 +349,30 @@ Datum
|
||||
sparsevec_recv(PG_FUNCTION_ARGS)
|
||||
{
|
||||
StringInfo buf = (StringInfo) PG_GETARG_POINTER(0);
|
||||
int32 typmod = PG_GETARG_INT32(2);
|
||||
SparseVector *result;
|
||||
int32 dim;
|
||||
int32 nnz;
|
||||
int32 unused;
|
||||
int32 unused2;
|
||||
float *values;
|
||||
|
||||
dim = pq_getmsgint(buf, sizeof(int32));
|
||||
nnz = pq_getmsgint(buf, sizeof(int32));
|
||||
unused = pq_getmsgint(buf, sizeof(int32));
|
||||
unused2 = pq_getmsgint(buf, sizeof(int32));
|
||||
|
||||
CheckDim(dim);
|
||||
CheckNnz(nnz, dim);
|
||||
CheckExpectedDim(typmod, dim);
|
||||
CheckNnz(nnz);
|
||||
|
||||
if (unused != 0)
|
||||
if (unused != 0 || unused2 != 0)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_DATA_EXCEPTION),
|
||||
errmsg("expected unused to be 0, not %d", unused)));
|
||||
|
||||
result = InitSparseVector(dim, nnz);
|
||||
result = InitSparseVector(nnz);
|
||||
values = SPARSEVEC_VALUES(result);
|
||||
|
||||
for (int i = 0; i < nnz; i++)
|
||||
{
|
||||
result->indices[i] = pq_getmsgint(buf, sizeof(int32));
|
||||
CheckIndex(result->indices, i, dim);
|
||||
CheckIndex(result->indices, i);
|
||||
}
|
||||
|
||||
for (int i = 0; i < nnz; i++)
|
||||
@@ -509,9 +396,9 @@ sparsevec_send(PG_FUNCTION_ARGS)
|
||||
StringInfoData buf;
|
||||
|
||||
pq_begintypsend(&buf);
|
||||
pq_sendint(&buf, svec->dim, sizeof(int32));
|
||||
pq_sendint(&buf, svec->nnz, sizeof(int32));
|
||||
pq_sendint(&buf, svec->unused, sizeof(int32));
|
||||
pq_sendint(&buf, svec->unused2, sizeof(int32));
|
||||
for (int i = 0; i < svec->nnz; i++)
|
||||
pq_sendint(&buf, svec->indices[i], sizeof(int32));
|
||||
for (int i = 0; i < svec->nnz; i++)
|
||||
@@ -522,16 +409,12 @@ sparsevec_send(PG_FUNCTION_ARGS)
|
||||
|
||||
/*
|
||||
* Convert sparse vector to sparse vector
|
||||
* This is needed to check the type modifier
|
||||
*/
|
||||
PGDLLEXPORT PG_FUNCTION_INFO_V1(sparsevec);
|
||||
Datum
|
||||
sparsevec(PG_FUNCTION_ARGS)
|
||||
{
|
||||
SparseVector *svec = PG_GETARG_SPARSEVEC_P(0);
|
||||
int32 typmod = PG_GETARG_INT32(1);
|
||||
|
||||
CheckExpectedDim(typmod, svec->dim);
|
||||
|
||||
PG_RETURN_POINTER(svec);
|
||||
}
|
||||
@@ -544,23 +427,20 @@ Datum
|
||||
vector_to_sparsevec(PG_FUNCTION_ARGS)
|
||||
{
|
||||
Vector *vec = PG_GETARG_VECTOR_P(0);
|
||||
int32 typmod = PG_GETARG_INT32(1);
|
||||
SparseVector *result;
|
||||
int dim = vec->dim;
|
||||
int nnz = 0;
|
||||
float *values;
|
||||
int j = 0;
|
||||
|
||||
CheckDim(dim);
|
||||
CheckExpectedDim(typmod, dim);
|
||||
|
||||
for (int i = 0; i < dim; i++)
|
||||
{
|
||||
if (vec->x[i] != 0)
|
||||
nnz++;
|
||||
}
|
||||
|
||||
result = InitSparseVector(dim, nnz);
|
||||
CheckNnz(nnz);
|
||||
result = InitSparseVector(nnz);
|
||||
values = SPARSEVEC_VALUES(result);
|
||||
for (int i = 0; i < dim; i++)
|
||||
{
|
||||
@@ -570,7 +450,7 @@ vector_to_sparsevec(PG_FUNCTION_ARGS)
|
||||
if (j == nnz)
|
||||
elog(ERROR, "safety check failed");
|
||||
|
||||
result->indices[j] = i + 1;
|
||||
result->indices[j] = i;
|
||||
values[j] = vec->x[i];
|
||||
j++;
|
||||
}
|
||||
@@ -580,7 +460,7 @@ vector_to_sparsevec(PG_FUNCTION_ARGS)
|
||||
}
|
||||
|
||||
/*
|
||||
* Get the L2 squared distance between sparse vectors
|
||||
* Get the L2 squared distance between sparse vectors
|
||||
*/
|
||||
static double
|
||||
l2_distance_squared_internal(SparseVector * a, SparseVector * b)
|
||||
@@ -637,8 +517,6 @@ sparsevec_l2_distance(PG_FUNCTION_ARGS)
|
||||
SparseVector *a = PG_GETARG_SPARSEVEC_P(0);
|
||||
SparseVector *b = PG_GETARG_SPARSEVEC_P(1);
|
||||
|
||||
CheckDims(a, b);
|
||||
|
||||
PG_RETURN_FLOAT8(sqrt(l2_distance_squared_internal(a, b)));
|
||||
}
|
||||
|
||||
@@ -653,8 +531,6 @@ sparsevec_l2_squared_distance(PG_FUNCTION_ARGS)
|
||||
SparseVector *a = PG_GETARG_SPARSEVEC_P(0);
|
||||
SparseVector *b = PG_GETARG_SPARSEVEC_P(1);
|
||||
|
||||
CheckDims(a, b);
|
||||
|
||||
PG_RETURN_FLOAT8(l2_distance_squared_internal(a, b));
|
||||
}
|
||||
|
||||
@@ -704,8 +580,6 @@ sparsevec_inner_product(PG_FUNCTION_ARGS)
|
||||
SparseVector *a = PG_GETARG_SPARSEVEC_P(0);
|
||||
SparseVector *b = PG_GETARG_SPARSEVEC_P(1);
|
||||
|
||||
CheckDims(a, b);
|
||||
|
||||
PG_RETURN_FLOAT8(inner_product_internal(a, b));
|
||||
}
|
||||
|
||||
@@ -719,8 +593,6 @@ sparsevec_negative_inner_product(PG_FUNCTION_ARGS)
|
||||
SparseVector *a = PG_GETARG_SPARSEVEC_P(0);
|
||||
SparseVector *b = PG_GETARG_SPARSEVEC_P(1);
|
||||
|
||||
CheckDims(a, b);
|
||||
|
||||
PG_RETURN_FLOAT8(-inner_product_internal(a, b));
|
||||
}
|
||||
|
||||
@@ -739,8 +611,6 @@ sparsevec_cosine_distance(PG_FUNCTION_ARGS)
|
||||
float normb = 0.0;
|
||||
double similarity;
|
||||
|
||||
CheckDims(a, b);
|
||||
|
||||
similarity = inner_product_internal(a, b);
|
||||
|
||||
/* Auto-vectorized */
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
#ifndef SPARSEVEC_H
|
||||
#define SPARSEVEC_H
|
||||
|
||||
#define SPARSEVEC_MAX_DIM 100000
|
||||
#define SPARSEVEC_MAX_NNZ 16000
|
||||
#define SPARSEVEC_MAX_NNZ 100000
|
||||
|
||||
/* Ensure values are aligned */
|
||||
#define SPARSEVEC_SIZE(_nnz) (offsetof(SparseVector, indices) + MAXALIGN((_nnz) * sizeof(int32)) + (_nnz * sizeof(float)))
|
||||
@@ -14,12 +13,12 @@
|
||||
typedef struct SparseVector
|
||||
{
|
||||
int32 vl_len_; /* varlena header (do not touch directly!) */
|
||||
int32 dim; /* number of dimensions */
|
||||
int32 nnz;
|
||||
int32 unused;
|
||||
int32 unused2;
|
||||
int32 indices[FLEXIBLE_ARRAY_MEMBER];
|
||||
} SparseVector;
|
||||
|
||||
SparseVector *InitSparseVector(int dim, int nnz);
|
||||
SparseVector *InitSparseVector(int nnz);
|
||||
|
||||
#endif
|
||||
|
||||
14
src/vector.c
14
src/vector.c
@@ -1236,15 +1236,23 @@ sparsevec_to_vector(PG_FUNCTION_ARGS)
|
||||
SparseVector *svec = PG_GETARG_SPARSEVEC_P(0);
|
||||
int32 typmod = PG_GETARG_INT32(1);
|
||||
Vector *result;
|
||||
int dim = svec->dim;
|
||||
int dim;
|
||||
float *values = SPARSEVEC_VALUES(svec);
|
||||
int maxIndex = svec->nnz == 0 ? -1 : svec->indices[svec->nnz - 1];
|
||||
|
||||
if (typmod == -1)
|
||||
dim = maxIndex + 1;
|
||||
else
|
||||
dim = typmod;
|
||||
|
||||
CheckDim(dim);
|
||||
CheckExpectedDim(typmod, dim);
|
||||
|
||||
if (dim < maxIndex + 1)
|
||||
elog(ERROR, "Vector must have at least %d dimensions", maxIndex + 1);
|
||||
|
||||
result = InitVector(dim);
|
||||
for (int i = 0; i < svec->nnz; i++)
|
||||
result->x[svec->indices[i] - 1] = values[i];
|
||||
result->x[svec->indices[i]] = values[i];
|
||||
|
||||
PG_RETURN_POINTER(result);
|
||||
}
|
||||
|
||||
@@ -1,104 +1,64 @@
|
||||
SELECT hamming_distance('111', '111');
|
||||
SELECT hamming_distance(B'111', B'111');
|
||||
hamming_distance
|
||||
------------------
|
||||
0
|
||||
(1 row)
|
||||
|
||||
SELECT hamming_distance('111', '110');
|
||||
SELECT hamming_distance(B'111', B'110');
|
||||
hamming_distance
|
||||
------------------
|
||||
1
|
||||
(1 row)
|
||||
|
||||
SELECT hamming_distance('111', '100');
|
||||
SELECT hamming_distance(B'111', B'100');
|
||||
hamming_distance
|
||||
------------------
|
||||
2
|
||||
(1 row)
|
||||
|
||||
SELECT hamming_distance('111', '000');
|
||||
SELECT hamming_distance(B'111', B'000');
|
||||
hamming_distance
|
||||
------------------
|
||||
3
|
||||
(1 row)
|
||||
|
||||
SELECT hamming_distance('10101010101010101010', '01010101010101010101');
|
||||
hamming_distance
|
||||
------------------
|
||||
20
|
||||
(1 row)
|
||||
|
||||
SELECT hamming_distance('', '');
|
||||
hamming_distance
|
||||
------------------
|
||||
0
|
||||
(1 row)
|
||||
|
||||
SELECT hamming_distance('111', '00');
|
||||
SELECT hamming_distance(B'111', B'00');
|
||||
ERROR: different bit lengths 3 and 2
|
||||
SELECT hamming_distance('111', '000'::varbit(4));
|
||||
hamming_distance
|
||||
------------------
|
||||
3
|
||||
(1 row)
|
||||
|
||||
SELECT hamming_distance('111', '0000'::varbit(4));
|
||||
ERROR: different bit lengths 3 and 4
|
||||
SELECT jaccard_distance('1111', '1111');
|
||||
SELECT jaccard_distance(B'1111', B'1111');
|
||||
jaccard_distance
|
||||
------------------
|
||||
0
|
||||
(1 row)
|
||||
|
||||
SELECT jaccard_distance('1111', '1110');
|
||||
SELECT jaccard_distance(B'1111', B'1110');
|
||||
jaccard_distance
|
||||
------------------
|
||||
0.25
|
||||
(1 row)
|
||||
|
||||
SELECT jaccard_distance('1111', '1100');
|
||||
SELECT jaccard_distance(B'1111', B'1100');
|
||||
jaccard_distance
|
||||
------------------
|
||||
0.5
|
||||
(1 row)
|
||||
|
||||
SELECT jaccard_distance('1111', '1000');
|
||||
SELECT jaccard_distance(B'1111', B'1000');
|
||||
jaccard_distance
|
||||
------------------
|
||||
0.75
|
||||
(1 row)
|
||||
|
||||
SELECT jaccard_distance('1111', '0000');
|
||||
SELECT jaccard_distance(B'1111', B'0000');
|
||||
jaccard_distance
|
||||
------------------
|
||||
1
|
||||
(1 row)
|
||||
|
||||
SELECT jaccard_distance('1100', '1000');
|
||||
SELECT jaccard_distance(B'1100', B'1000');
|
||||
jaccard_distance
|
||||
------------------
|
||||
0.5
|
||||
(1 row)
|
||||
|
||||
SELECT jaccard_distance('10101010101010101010', '01010101010101010101');
|
||||
jaccard_distance
|
||||
------------------
|
||||
1
|
||||
(1 row)
|
||||
|
||||
SELECT jaccard_distance('', '');
|
||||
jaccard_distance
|
||||
------------------
|
||||
1
|
||||
(1 row)
|
||||
|
||||
SELECT jaccard_distance('1111', '000');
|
||||
SELECT jaccard_distance(B'1111', B'000');
|
||||
ERROR: different bit lengths 4 and 3
|
||||
SELECT jaccard_distance('1111', '0000'::varbit(5));
|
||||
jaccard_distance
|
||||
------------------
|
||||
1
|
||||
(1 row)
|
||||
|
||||
SELECT jaccard_distance('1111', '00000'::varbit(5));
|
||||
ERROR: different bit lengths 4 and 5
|
||||
|
||||
@@ -51,21 +51,17 @@ SELECT '[65519,-65519]'::halfvec;
|
||||
(1 row)
|
||||
|
||||
SELECT '[65520,-65520]'::halfvec;
|
||||
ERROR: "65520" is out of range for type halfvec
|
||||
ERROR: value out of range: overflow
|
||||
LINE 1: SELECT '[65520,-65520]'::halfvec;
|
||||
^
|
||||
SELECT '[1e-8,-1e-8]'::halfvec;
|
||||
ERROR: "1e-8" is out of range for type halfvec
|
||||
ERROR: value out of range: underflow
|
||||
LINE 1: SELECT '[1e-8,-1e-8]'::halfvec;
|
||||
^
|
||||
SELECT '[4e38,1]'::halfvec;
|
||||
ERROR: "4e38" is out of range for type halfvec
|
||||
ERROR: infinite value not allowed in halfvec
|
||||
LINE 1: SELECT '[4e38,1]'::halfvec;
|
||||
^
|
||||
SELECT '[1e-46,1]'::halfvec;
|
||||
ERROR: "1e-46" is out of range for type halfvec
|
||||
LINE 1: SELECT '[1e-46,1]'::halfvec;
|
||||
^
|
||||
SELECT '[1,2,3'::halfvec;
|
||||
ERROR: malformed halfvec literal: "[1,2,3"
|
||||
LINE 1: SELECT '[1,2,3'::halfvec;
|
||||
|
||||
@@ -19,9 +19,3 @@ SELECT COUNT(*) FROM (SELECT * FROM t ORDER BY val <~> (SELECT NULL::bit)) t2;
|
||||
(1 row)
|
||||
|
||||
DROP TABLE t;
|
||||
-- TODO move
|
||||
CREATE TABLE t (val varbit(3));
|
||||
CREATE INDEX ON t USING hnsw (val bit_hamming_ops);
|
||||
ERROR: type not supported for hnsw index
|
||||
CREATE INDEX ON t USING hnsw ((val::bit(3)) bit_hamming_ops);
|
||||
DROP TABLE t;
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
SET enable_seqscan = off;
|
||||
CREATE TABLE t (val sparsevec(3));
|
||||
INSERT INTO t (val) VALUES ('{}/3'), ('{1:1,2:2,3:3}/3'), ('{1:1,2:1,3:1}/3'), (NULL);
|
||||
CREATE TABLE t (val sparsevec);
|
||||
INSERT INTO t (val) VALUES ('{}'), ('{0:1,1:2,2:3}'), ('{0:1,1:1,2:1}'), (NULL);
|
||||
CREATE INDEX ON t USING hnsw (val sparsevec_cosine_ops);
|
||||
INSERT INTO t (val) VALUES ('{1:1,2:2,3:4}/3');
|
||||
SELECT * FROM t ORDER BY val <=> '{1:3,2:3,3:3}/3';
|
||||
val
|
||||
-----------------
|
||||
{1:1,2:1,3:1}/3
|
||||
{1:1,2:2,3:3}/3
|
||||
{1:1,2:2,3:4}/3
|
||||
INSERT INTO t (val) VALUES ('{0:1,1:2,2:4}');
|
||||
SELECT * FROM t ORDER BY val <=> '{0:3,1:3,2:3}';
|
||||
val
|
||||
---------------
|
||||
{0:1,1:1,2:1}
|
||||
{0:1,1:2,2:3}
|
||||
{0:1,1:2,2:4}
|
||||
(3 rows)
|
||||
|
||||
SELECT COUNT(*) FROM (SELECT * FROM t ORDER BY val <=> '{}/3') t2;
|
||||
SELECT COUNT(*) FROM (SELECT * FROM t ORDER BY val <=> '{}') t2;
|
||||
count
|
||||
-------
|
||||
3
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
SET enable_seqscan = off;
|
||||
CREATE TABLE t (val sparsevec(3));
|
||||
INSERT INTO t (val) VALUES ('{}/3'), ('{1:1,2:2,3:3}/3'), ('{1:1,2:1,3:1}/3'), (NULL);
|
||||
CREATE TABLE t (val sparsevec);
|
||||
INSERT INTO t (val) VALUES ('{}'), ('{0:1,1:2,2:3}'), ('{0:1,1:1,2:1}'), (NULL);
|
||||
CREATE INDEX ON t USING hnsw (val sparsevec_ip_ops);
|
||||
INSERT INTO t (val) VALUES ('{1:1,2:2,3:4}/3');
|
||||
SELECT * FROM t ORDER BY val <#> '{1:3,2:3,3:3}/3';
|
||||
val
|
||||
-----------------
|
||||
{1:1,2:2,3:4}/3
|
||||
{1:1,2:2,3:3}/3
|
||||
{1:1,2:1,3:1}/3
|
||||
{}/3
|
||||
INSERT INTO t (val) VALUES ('{0:1,1:2,2:4}');
|
||||
SELECT * FROM t ORDER BY val <#> '{0:3,1:3,2:3}';
|
||||
val
|
||||
---------------
|
||||
{0:1,1:2,2:4}
|
||||
{0:1,1:2,2:3}
|
||||
{0:1,1:1,2:1}
|
||||
{}
|
||||
(4 rows)
|
||||
|
||||
SELECT COUNT(*) FROM (SELECT * FROM t ORDER BY val <#> (SELECT NULL::sparsevec)) t2;
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
SET enable_seqscan = off;
|
||||
CREATE TABLE t (val sparsevec(3));
|
||||
INSERT INTO t (val) VALUES ('{}/3'), ('{1:1,2:2,3:3}/3'), ('{1:1,2:1,3:1}/3'), (NULL);
|
||||
CREATE TABLE t (val sparsevec);
|
||||
INSERT INTO t (val) VALUES ('{}'), ('{0:1,1:2,2:3}'), ('{0:1,1:1,2:1}'), (NULL);
|
||||
CREATE INDEX ON t USING hnsw (val sparsevec_l2_ops);
|
||||
INSERT INTO t (val) VALUES ('{1:1,2:2,3:4}/3');
|
||||
SELECT * FROM t ORDER BY val <-> '{1:3,2:3,3:3}/3';
|
||||
val
|
||||
-----------------
|
||||
{1:1,2:2,3:3}/3
|
||||
{1:1,2:2,3:4}/3
|
||||
{1:1,2:1,3:1}/3
|
||||
{}/3
|
||||
INSERT INTO t (val) VALUES ('{0:1,1:2,2:4}');
|
||||
SELECT * FROM t ORDER BY val <-> '{0:3,1:3,2:3}';
|
||||
val
|
||||
---------------
|
||||
{0:1,1:2,2:3}
|
||||
{0:1,1:2,2:4}
|
||||
{0:1,1:1,2:1}
|
||||
{}
|
||||
(4 rows)
|
||||
|
||||
SELECT COUNT(*) FROM (SELECT * FROM t ORDER BY val <-> (SELECT NULL::sparsevec)) t2;
|
||||
@@ -25,14 +25,14 @@ SELECT COUNT(*) FROM t;
|
||||
(1 row)
|
||||
|
||||
TRUNCATE t;
|
||||
SELECT * FROM t ORDER BY val <-> '{1:3,2:3,3:3}/3';
|
||||
SELECT * FROM t ORDER BY val <-> '{0:3,1:3,2:3}';
|
||||
val
|
||||
-----
|
||||
(0 rows)
|
||||
|
||||
DROP TABLE t;
|
||||
-- TODO move
|
||||
CREATE TABLE t (val sparsevec(1001));
|
||||
CREATE TABLE t (val sparsevec);
|
||||
INSERT INTO t (val) VALUES (array_fill(1, ARRAY[1001])::vector::sparsevec);
|
||||
CREATE INDEX ON t USING hnsw (val sparsevec_l2_ops);
|
||||
ERROR: sparsevec cannot have more than 1000 non-zero elements for hnsw index
|
||||
|
||||
@@ -1,62 +1,60 @@
|
||||
SELECT l2_distance('{}/2'::sparsevec, '{1:3,2:4}/2');
|
||||
SELECT l2_distance('{}'::sparsevec, '{0:3,1:4}');
|
||||
l2_distance
|
||||
-------------
|
||||
5
|
||||
(1 row)
|
||||
|
||||
SELECT l2_distance('{}/2'::sparsevec, '{2:1}/2');
|
||||
SELECT l2_distance('{}'::sparsevec, '{1:1}');
|
||||
l2_distance
|
||||
-------------
|
||||
1
|
||||
(1 row)
|
||||
|
||||
SELECT '{}/2'::sparsevec <-> '{1:3,2:4}/2';
|
||||
SELECT '{}'::sparsevec <-> '{0:3,1:4}';
|
||||
?column?
|
||||
----------
|
||||
5
|
||||
(1 row)
|
||||
|
||||
SELECT inner_product('{1:1,2:2}/2'::sparsevec, '{1:2,2:4}/2');
|
||||
SELECT inner_product('{0:1,1:2}'::sparsevec, '{0:2,1:4}');
|
||||
inner_product
|
||||
---------------
|
||||
10
|
||||
(1 row)
|
||||
|
||||
SELECT sparsevec_negative_inner_product('{1:1,2:2}/2', '{1:2,2:4}/2');
|
||||
SELECT sparsevec_negative_inner_product('{0:1,1:2}', '{0:2,1:4}');
|
||||
sparsevec_negative_inner_product
|
||||
----------------------------------
|
||||
-10
|
||||
(1 row)
|
||||
|
||||
SELECT cosine_distance('{1:1,2:2}/2'::sparsevec, '{1:2,2:4}/2');
|
||||
SELECT cosine_distance('{0:1,1:2}'::sparsevec, '{0:2,1:4}');
|
||||
cosine_distance
|
||||
-----------------
|
||||
0
|
||||
(1 row)
|
||||
|
||||
SELECT cosine_distance('{1:1,2:2}/2'::sparsevec, '{}/2');
|
||||
SELECT cosine_distance('{0:1,1:2}'::sparsevec, '{}');
|
||||
cosine_distance
|
||||
-----------------
|
||||
NaN
|
||||
(1 row)
|
||||
|
||||
SELECT cosine_distance('{1:1,2:1}/2'::sparsevec, '{1:-1,2:-1}/2');
|
||||
SELECT cosine_distance('{0:1,1:1}'::sparsevec, '{0:-1,1:-1}');
|
||||
cosine_distance
|
||||
-----------------
|
||||
2
|
||||
(1 row)
|
||||
|
||||
SELECT cosine_distance('{1:2}/2'::sparsevec, '{2:2}/2');
|
||||
SELECT cosine_distance('{0:1}'::sparsevec, '{1:2}');
|
||||
cosine_distance
|
||||
-----------------
|
||||
1
|
||||
(1 row)
|
||||
|
||||
SELECT cosine_distance('{}/1'::sparsevec, '{}/1');
|
||||
SELECT cosine_distance('{}'::sparsevec, '{}');
|
||||
cosine_distance
|
||||
-----------------
|
||||
NaN
|
||||
(1 row)
|
||||
|
||||
SELECT cosine_distance('{1:2}/2'::sparsevec, '{1:1}/3');
|
||||
ERROR: different sparsevec dimensions 2 and 3
|
||||
|
||||
@@ -1,62 +1,64 @@
|
||||
SELECT '{1:1.5,3:3.5}/5'::sparsevec;
|
||||
sparsevec
|
||||
-----------------
|
||||
{1:1.5,3:3.5}/5
|
||||
SELECT '{0:1.5,2:3.5}'::sparsevec;
|
||||
sparsevec
|
||||
---------------
|
||||
{0:1.5,2:3.5}
|
||||
(1 row)
|
||||
|
||||
SELECT '{1:1.5,3:3.5}/5'::sparsevec::vector;
|
||||
SELECT '{0:1.5,2:3.5}'::sparsevec::vector;
|
||||
vector
|
||||
-------------
|
||||
[1.5,0,3.5]
|
||||
(1 row)
|
||||
|
||||
SELECT '{0:1.5,2:3.5}'::sparsevec::vector(5);
|
||||
vector
|
||||
-----------------
|
||||
[1.5,0,3.5,0,0]
|
||||
(1 row)
|
||||
|
||||
SELECT '{1:1.5,3:3.5}/5'::sparsevec::vector(5);
|
||||
vector
|
||||
-----------------
|
||||
[1.5,0,3.5,0,0]
|
||||
SELECT '{0:1.5,2:3.5}'::sparsevec::vector(4);
|
||||
vector
|
||||
---------------
|
||||
[1.5,0,3.5,0]
|
||||
(1 row)
|
||||
|
||||
SELECT '{1:1.5,3:3.5}/5'::sparsevec::vector(4);
|
||||
ERROR: expected 4 dimensions, not 5
|
||||
SELECT '{0:1.5,2:3.5}'::sparsevec::vector(2);
|
||||
ERROR: Vector must have at least 3 dimensions
|
||||
SELECT '[0,1.5,0,3.5,0]'::vector::sparsevec;
|
||||
sparsevec
|
||||
-----------------
|
||||
{2:1.5,4:3.5}/5
|
||||
sparsevec
|
||||
---------------
|
||||
{1:1.5,3:3.5}
|
||||
(1 row)
|
||||
|
||||
SELECT '{1:0,2:1,3:0}/3'::sparsevec;
|
||||
SELECT '{0:0,1:1,2:0}'::sparsevec;
|
||||
sparsevec
|
||||
-----------
|
||||
{2:1}/3
|
||||
{1:1}
|
||||
(1 row)
|
||||
|
||||
SELECT '{2:1,1:1}/2'::sparsevec;
|
||||
SELECT '{1:1,0:1}'::sparsevec;
|
||||
ERROR: indexes must be in ascending order
|
||||
LINE 1: SELECT '{2:1,1:1}/2'::sparsevec;
|
||||
LINE 1: SELECT '{1:1,0:1}'::sparsevec;
|
||||
^
|
||||
SELECT '{}/5'::sparsevec;
|
||||
SELECT '{}'::sparsevec;
|
||||
sparsevec
|
||||
-----------
|
||||
{}/5
|
||||
{}
|
||||
(1 row)
|
||||
|
||||
SELECT '{}/-1'::sparsevec;
|
||||
ERROR: sparsevec must have at least 1 dimension
|
||||
LINE 1: SELECT '{}/-1'::sparsevec;
|
||||
SELECT '{}'::sparsevec::vector;
|
||||
ERROR: vector must have at least 1 dimension
|
||||
SELECT '{-1:1}'::sparsevec;
|
||||
ERROR: index "-1" is out of range for type sparsevec
|
||||
LINE 1: SELECT '{-1:1}'::sparsevec;
|
||||
^
|
||||
SELECT '{}/100001'::sparsevec;
|
||||
ERROR: sparsevec cannot have more than 100000 dimensions
|
||||
LINE 1: SELECT '{}/100001'::sparsevec;
|
||||
^
|
||||
SELECT '{}/16001'::sparsevec::vector;
|
||||
ERROR: vector cannot have more than 16000 dimensions
|
||||
SELECT '{0:1}/1'::sparsevec;
|
||||
ERROR: index "0" is out of range for type sparsevec
|
||||
LINE 1: SELECT '{0:1}/1'::sparsevec;
|
||||
^
|
||||
SELECT '{2:1}/1'::sparsevec;
|
||||
ERROR: index must be less than or equal to dimensions
|
||||
LINE 1: SELECT '{2:1}/1'::sparsevec;
|
||||
^
|
||||
SELECT '{}/1'::sparsevec(2);
|
||||
ERROR: expected 2 dimensions, not 1
|
||||
SELECT '{1:1}'::sparsevec;
|
||||
sparsevec
|
||||
-----------
|
||||
{1:1}
|
||||
(1 row)
|
||||
|
||||
SELECT '{}'::sparsevec(2);
|
||||
ERROR: type modifier is not allowed for type "sparsevec"
|
||||
LINE 1: SELECT '{}'::sparsevec(2);
|
||||
^
|
||||
|
||||
@@ -1,21 +1,13 @@
|
||||
SELECT hamming_distance('111', '111');
|
||||
SELECT hamming_distance('111', '110');
|
||||
SELECT hamming_distance('111', '100');
|
||||
SELECT hamming_distance('111', '000');
|
||||
SELECT hamming_distance('10101010101010101010', '01010101010101010101');
|
||||
SELECT hamming_distance('', '');
|
||||
SELECT hamming_distance('111', '00');
|
||||
SELECT hamming_distance('111', '000'::varbit(4));
|
||||
SELECT hamming_distance('111', '0000'::varbit(4));
|
||||
SELECT hamming_distance(B'111', B'111');
|
||||
SELECT hamming_distance(B'111', B'110');
|
||||
SELECT hamming_distance(B'111', B'100');
|
||||
SELECT hamming_distance(B'111', B'000');
|
||||
SELECT hamming_distance(B'111', B'00');
|
||||
|
||||
SELECT jaccard_distance('1111', '1111');
|
||||
SELECT jaccard_distance('1111', '1110');
|
||||
SELECT jaccard_distance('1111', '1100');
|
||||
SELECT jaccard_distance('1111', '1000');
|
||||
SELECT jaccard_distance('1111', '0000');
|
||||
SELECT jaccard_distance('1100', '1000');
|
||||
SELECT jaccard_distance('10101010101010101010', '01010101010101010101');
|
||||
SELECT jaccard_distance('', '');
|
||||
SELECT jaccard_distance('1111', '000');
|
||||
SELECT jaccard_distance('1111', '0000'::varbit(5));
|
||||
SELECT jaccard_distance('1111', '00000'::varbit(5));
|
||||
SELECT jaccard_distance(B'1111', B'1111');
|
||||
SELECT jaccard_distance(B'1111', B'1110');
|
||||
SELECT jaccard_distance(B'1111', B'1100');
|
||||
SELECT jaccard_distance(B'1111', B'1000');
|
||||
SELECT jaccard_distance(B'1111', B'0000');
|
||||
SELECT jaccard_distance(B'1100', B'1000');
|
||||
SELECT jaccard_distance(B'1111', B'000');
|
||||
|
||||
@@ -11,7 +11,6 @@ SELECT '[65519,-65519]'::halfvec;
|
||||
SELECT '[65520,-65520]'::halfvec;
|
||||
SELECT '[1e-8,-1e-8]'::halfvec;
|
||||
SELECT '[4e38,1]'::halfvec;
|
||||
SELECT '[1e-46,1]'::halfvec;
|
||||
SELECT '[1,2,3'::halfvec;
|
||||
SELECT '[1,2,3]9'::halfvec;
|
||||
SELECT '1,2,3'::halfvec;
|
||||
|
||||
@@ -10,9 +10,3 @@ SELECT * FROM t ORDER BY val <~> B'111';
|
||||
SELECT COUNT(*) FROM (SELECT * FROM t ORDER BY val <~> (SELECT NULL::bit)) t2;
|
||||
|
||||
DROP TABLE t;
|
||||
|
||||
-- TODO move
|
||||
CREATE TABLE t (val varbit(3));
|
||||
CREATE INDEX ON t USING hnsw (val bit_hamming_ops);
|
||||
CREATE INDEX ON t USING hnsw ((val::bit(3)) bit_hamming_ops);
|
||||
DROP TABLE t;
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
SET enable_seqscan = off;
|
||||
|
||||
CREATE TABLE t (val sparsevec(3));
|
||||
INSERT INTO t (val) VALUES ('{}/3'), ('{1:1,2:2,3:3}/3'), ('{1:1,2:1,3:1}/3'), (NULL);
|
||||
CREATE TABLE t (val sparsevec);
|
||||
INSERT INTO t (val) VALUES ('{}'), ('{0:1,1:2,2:3}'), ('{0:1,1:1,2:1}'), (NULL);
|
||||
CREATE INDEX ON t USING hnsw (val sparsevec_cosine_ops);
|
||||
|
||||
INSERT INTO t (val) VALUES ('{1:1,2:2,3:4}/3');
|
||||
INSERT INTO t (val) VALUES ('{0:1,1:2,2:4}');
|
||||
|
||||
SELECT * FROM t ORDER BY val <=> '{1:3,2:3,3:3}/3';
|
||||
SELECT COUNT(*) FROM (SELECT * FROM t ORDER BY val <=> '{}/3') t2;
|
||||
SELECT * FROM t ORDER BY val <=> '{0:3,1:3,2:3}';
|
||||
SELECT COUNT(*) FROM (SELECT * FROM t ORDER BY val <=> '{}') t2;
|
||||
SELECT COUNT(*) FROM (SELECT * FROM t ORDER BY val <=> (SELECT NULL::sparsevec)) t2;
|
||||
|
||||
DROP TABLE t;
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
SET enable_seqscan = off;
|
||||
|
||||
CREATE TABLE t (val sparsevec(3));
|
||||
INSERT INTO t (val) VALUES ('{}/3'), ('{1:1,2:2,3:3}/3'), ('{1:1,2:1,3:1}/3'), (NULL);
|
||||
CREATE TABLE t (val sparsevec);
|
||||
INSERT INTO t (val) VALUES ('{}'), ('{0:1,1:2,2:3}'), ('{0:1,1:1,2:1}'), (NULL);
|
||||
CREATE INDEX ON t USING hnsw (val sparsevec_ip_ops);
|
||||
|
||||
INSERT INTO t (val) VALUES ('{1:1,2:2,3:4}/3');
|
||||
INSERT INTO t (val) VALUES ('{0:1,1:2,2:4}');
|
||||
|
||||
SELECT * FROM t ORDER BY val <#> '{1:3,2:3,3:3}/3';
|
||||
SELECT * FROM t ORDER BY val <#> '{0:3,1:3,2:3}';
|
||||
SELECT COUNT(*) FROM (SELECT * FROM t ORDER BY val <#> (SELECT NULL::sparsevec)) t2;
|
||||
|
||||
DROP TABLE t;
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
SET enable_seqscan = off;
|
||||
|
||||
CREATE TABLE t (val sparsevec(3));
|
||||
INSERT INTO t (val) VALUES ('{}/3'), ('{1:1,2:2,3:3}/3'), ('{1:1,2:1,3:1}/3'), (NULL);
|
||||
CREATE TABLE t (val sparsevec);
|
||||
INSERT INTO t (val) VALUES ('{}'), ('{0:1,1:2,2:3}'), ('{0:1,1:1,2:1}'), (NULL);
|
||||
CREATE INDEX ON t USING hnsw (val sparsevec_l2_ops);
|
||||
|
||||
INSERT INTO t (val) VALUES ('{1:1,2:2,3:4}/3');
|
||||
INSERT INTO t (val) VALUES ('{0:1,1:2,2:4}');
|
||||
|
||||
SELECT * FROM t ORDER BY val <-> '{1:3,2:3,3:3}/3';
|
||||
SELECT * FROM t ORDER BY val <-> '{0:3,1:3,2:3}';
|
||||
SELECT COUNT(*) FROM (SELECT * FROM t ORDER BY val <-> (SELECT NULL::sparsevec)) t2;
|
||||
SELECT COUNT(*) FROM t;
|
||||
|
||||
TRUNCATE t;
|
||||
SELECT * FROM t ORDER BY val <-> '{1:3,2:3,3:3}/3';
|
||||
SELECT * FROM t ORDER BY val <-> '{0:3,1:3,2:3}';
|
||||
|
||||
DROP TABLE t;
|
||||
|
||||
-- TODO move
|
||||
CREATE TABLE t (val sparsevec(1001));
|
||||
CREATE TABLE t (val sparsevec);
|
||||
INSERT INTO t (val) VALUES (array_fill(1, ARRAY[1001])::vector::sparsevec);
|
||||
CREATE INDEX ON t USING hnsw (val sparsevec_l2_ops);
|
||||
TRUNCATE t;
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
SELECT l2_distance('{}/2'::sparsevec, '{1:3,2:4}/2');
|
||||
SELECT l2_distance('{}/2'::sparsevec, '{2:1}/2');
|
||||
SELECT '{}/2'::sparsevec <-> '{1:3,2:4}/2';
|
||||
SELECT l2_distance('{}'::sparsevec, '{0:3,1:4}');
|
||||
SELECT l2_distance('{}'::sparsevec, '{1:1}');
|
||||
SELECT '{}'::sparsevec <-> '{0:3,1:4}';
|
||||
|
||||
SELECT inner_product('{1:1,2:2}/2'::sparsevec, '{1:2,2:4}/2');
|
||||
SELECT sparsevec_negative_inner_product('{1:1,2:2}/2', '{1:2,2:4}/2');
|
||||
SELECT inner_product('{0:1,1:2}'::sparsevec, '{0:2,1:4}');
|
||||
SELECT sparsevec_negative_inner_product('{0:1,1:2}', '{0:2,1:4}');
|
||||
|
||||
SELECT cosine_distance('{1:1,2:2}/2'::sparsevec, '{1:2,2:4}/2');
|
||||
SELECT cosine_distance('{1:1,2:2}/2'::sparsevec, '{}/2');
|
||||
SELECT cosine_distance('{1:1,2:1}/2'::sparsevec, '{1:-1,2:-1}/2');
|
||||
SELECT cosine_distance('{1:2}/2'::sparsevec, '{2:2}/2');
|
||||
SELECT cosine_distance('{}/1'::sparsevec, '{}/1');
|
||||
SELECT cosine_distance('{1:2}/2'::sparsevec, '{1:1}/3');
|
||||
SELECT cosine_distance('{0:1,1:2}'::sparsevec, '{0:2,1:4}');
|
||||
SELECT cosine_distance('{0:1,1:2}'::sparsevec, '{}');
|
||||
SELECT cosine_distance('{0:1,1:1}'::sparsevec, '{0:-1,1:-1}');
|
||||
SELECT cosine_distance('{0:1}'::sparsevec, '{1:2}');
|
||||
SELECT cosine_distance('{}'::sparsevec, '{}');
|
||||
|
||||
@@ -1,19 +1,18 @@
|
||||
SELECT '{1:1.5,3:3.5}/5'::sparsevec;
|
||||
SELECT '{1:1.5,3:3.5}/5'::sparsevec::vector;
|
||||
SELECT '{1:1.5,3:3.5}/5'::sparsevec::vector(5);
|
||||
SELECT '{1:1.5,3:3.5}/5'::sparsevec::vector(4);
|
||||
SELECT '{0:1.5,2:3.5}'::sparsevec;
|
||||
SELECT '{0:1.5,2:3.5}'::sparsevec::vector;
|
||||
SELECT '{0:1.5,2:3.5}'::sparsevec::vector(5);
|
||||
SELECT '{0:1.5,2:3.5}'::sparsevec::vector(4);
|
||||
SELECT '{0:1.5,2:3.5}'::sparsevec::vector(2);
|
||||
SELECT '[0,1.5,0,3.5,0]'::vector::sparsevec;
|
||||
|
||||
SELECT '{1:0,2:1,3:0}/3'::sparsevec;
|
||||
SELECT '{0:0,1:1,2:0}'::sparsevec;
|
||||
|
||||
SELECT '{2:1,1:1}/2'::sparsevec;
|
||||
SELECT '{1:1,0:1}'::sparsevec;
|
||||
|
||||
SELECT '{}/5'::sparsevec;
|
||||
SELECT '{}/-1'::sparsevec;
|
||||
SELECT '{}/100001'::sparsevec;
|
||||
SELECT '{}/16001'::sparsevec::vector;
|
||||
SELECT '{}'::sparsevec;
|
||||
SELECT '{}'::sparsevec::vector;
|
||||
|
||||
SELECT '{0:1}/1'::sparsevec;
|
||||
SELECT '{2:1}/1'::sparsevec;
|
||||
SELECT '{-1:1}'::sparsevec;
|
||||
SELECT '{1:1}'::sparsevec;
|
||||
|
||||
SELECT '{}/1'::sparsevec(2);
|
||||
SELECT '{}'::sparsevec(2);
|
||||
|
||||
@@ -86,7 +86,7 @@ foreach (@queries)
|
||||
push(@expected, $res);
|
||||
}
|
||||
|
||||
test_recall(0.18, $limit, "before vacuum");
|
||||
test_recall(0.19, $limit, "before vacuum");
|
||||
test_recall(0.95, 100, "before vacuum");
|
||||
|
||||
# TODO Test concurrent inserts with vacuum
|
||||
|
||||
@@ -99,7 +99,7 @@ for my $i (0 .. $#operators)
|
||||
));
|
||||
|
||||
# Test approximate results
|
||||
my $min = $operator eq "<\%>" ? 0.95 : 0.98;
|
||||
my $min = $operator eq "<\%>" ? 0.96 : 0.98;
|
||||
test_recall($min, $operator);
|
||||
|
||||
$node->safe_psql("postgres", "DROP INDEX idx;");
|
||||
|
||||
@@ -1,128 +0,0 @@
|
||||
use strict;
|
||||
use warnings;
|
||||
use PostgresNode;
|
||||
use TestLib;
|
||||
use Test::More;
|
||||
|
||||
my $node;
|
||||
my @queries = ();
|
||||
my @expected;
|
||||
my $limit = 20;
|
||||
|
||||
sub test_recall
|
||||
{
|
||||
my ($min, $operator) = @_;
|
||||
my $correct = 0;
|
||||
my $total = 0;
|
||||
|
||||
my $explain = $node->safe_psql("postgres", qq(
|
||||
SET enable_seqscan = off;
|
||||
EXPLAIN ANALYZE SELECT i FROM tst ORDER BY v $operator '$queries[0]' LIMIT $limit;
|
||||
));
|
||||
like($explain, qr/Index Scan/);
|
||||
|
||||
for my $i (0 .. $#queries)
|
||||
{
|
||||
my $actual = $node->safe_psql("postgres", qq(
|
||||
SET enable_seqscan = off;
|
||||
SELECT i FROM tst ORDER BY v $operator '$queries[$i]' LIMIT $limit;
|
||||
));
|
||||
my @actual_ids = split("\n", $actual);
|
||||
my %actual_set = map { $_ => 1 } @actual_ids;
|
||||
|
||||
my @expected_ids = split("\n", $expected[$i]);
|
||||
|
||||
foreach (@expected_ids)
|
||||
{
|
||||
if (exists($actual_set{$_}))
|
||||
{
|
||||
$correct++;
|
||||
}
|
||||
$total++;
|
||||
}
|
||||
}
|
||||
|
||||
cmp_ok($correct / $total, ">=", $min, $operator);
|
||||
}
|
||||
|
||||
# Initialize node
|
||||
$node = get_new_node('node');
|
||||
$node->init;
|
||||
$node->start;
|
||||
|
||||
# Create table
|
||||
$node->safe_psql("postgres", "CREATE EXTENSION vector;");
|
||||
$node->safe_psql("postgres", "CREATE TABLE tst (i int4, v sparsevec(3));");
|
||||
$node->safe_psql("postgres",
|
||||
"INSERT INTO tst SELECT i, ARRAY[random(), random(), random()]::vector::sparsevec FROM generate_series(1, 10000) i;"
|
||||
);
|
||||
|
||||
# Generate queries
|
||||
for (1 .. 20)
|
||||
{
|
||||
my $r1 = rand();
|
||||
my $r2 = rand();
|
||||
my $r3 = rand();
|
||||
push(@queries, "{1:$r1,2:$r2,3:$r3}/3");
|
||||
}
|
||||
|
||||
# Check each index type
|
||||
my @operators = ("<->", "<#>", "<=>");
|
||||
my @opclasses = ("sparsevec_l2_ops", "sparsevec_ip_ops", "sparsevec_cosine_ops");
|
||||
|
||||
for my $i (0 .. $#operators)
|
||||
{
|
||||
my $operator = $operators[$i];
|
||||
my $opclass = $opclasses[$i];
|
||||
|
||||
# Get exact results
|
||||
@expected = ();
|
||||
foreach (@queries)
|
||||
{
|
||||
my $res = $node->safe_psql("postgres", "SELECT i FROM tst ORDER BY v $operator '$_' LIMIT $limit;");
|
||||
push(@expected, $res);
|
||||
}
|
||||
|
||||
# Build index serially
|
||||
$node->safe_psql("postgres", qq(
|
||||
SET max_parallel_maintenance_workers = 0;
|
||||
CREATE INDEX idx ON tst USING hnsw (v $opclass);
|
||||
));
|
||||
|
||||
# Test approximate results
|
||||
my $min = $operator eq "<#>" ? 0.80 : 0.99;
|
||||
test_recall($min, $operator);
|
||||
|
||||
$node->safe_psql("postgres", "DROP INDEX idx;");
|
||||
|
||||
# Build index in parallel in memory
|
||||
my ($ret, $stdout, $stderr) = $node->psql("postgres", qq(
|
||||
SET client_min_messages = DEBUG;
|
||||
SET min_parallel_table_scan_size = 1;
|
||||
CREATE INDEX idx ON tst USING hnsw (v $opclass);
|
||||
));
|
||||
is($ret, 0, $stderr);
|
||||
like($stderr, qr/using \d+ parallel workers/);
|
||||
|
||||
# Test approximate results
|
||||
test_recall($min, $operator);
|
||||
|
||||
$node->safe_psql("postgres", "DROP INDEX idx;");
|
||||
|
||||
# Build index in parallel on disk
|
||||
# Set parallel_workers on table to use workers with low maintenance_work_mem
|
||||
($ret, $stdout, $stderr) = $node->psql("postgres", qq(
|
||||
ALTER TABLE tst SET (parallel_workers = 2);
|
||||
SET client_min_messages = DEBUG;
|
||||
SET maintenance_work_mem = '4MB';
|
||||
CREATE INDEX idx ON tst USING hnsw (v $opclass);
|
||||
ALTER TABLE tst RESET (parallel_workers);
|
||||
));
|
||||
is($ret, 0, $stderr);
|
||||
like($stderr, qr/using \d+ parallel workers/);
|
||||
like($stderr, qr/hnsw graph no longer fits into maintenance_work_mem/);
|
||||
|
||||
$node->safe_psql("postgres", "DROP INDEX idx;");
|
||||
}
|
||||
|
||||
done_testing();
|
||||
Reference in New Issue
Block a user