Compare commits

..

2 Commits

Author SHA1 Message Date
Andrew Kane
5425dd2357 Fixed warning 2024-04-03 15:17:52 -07:00
Andrew Kane
7ca7a64dbb Added subvector function for sparsevec 2024-04-03 15:10:06 -07:00
35 changed files with 300 additions and 618 deletions

View File

@@ -123,6 +123,6 @@ jobs:
- uses: ankane/setup-postgres-valgrind@v1 - uses: ankane/setup-postgres-valgrind@v1
with: with:
postgres-version: 16 postgres-version: 16
- run: make OPTFLAGS="" - run: make
- run: sudo --preserve-env=PG_CONFIG make install - run: sudo --preserve-env=PG_CONFIG make install
- run: make installcheck - run: make installcheck

111
README.md
View File

@@ -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); 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 ## Hybrid Search
Use together with Postgres [full-text search](https://www.postgresql.org/docs/current/textsearch-intro.html) for 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; 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? #### Do indexes need to fit into memory?
No, but like other index types, youll likely see better performance if they do. You can get the size of an index with: No, but like other index types, youll 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 ### 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 ### Sparsevec Operators

View File

@@ -203,6 +203,9 @@ CREATE FUNCTION cosine_distance(sparsevec, sparsevec) RETURNS float8
CREATE FUNCTION sparsevec_norm(sparsevec) RETURNS float8 CREATE FUNCTION sparsevec_norm(sparsevec) RETURNS float8
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
CREATE FUNCTION subvector(sparsevec, int, int) RETURNS sparsevec
AS 'MODULE_PATHNAME', 'sparsevec_subvector' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
CREATE FUNCTION sparsevec_l2_squared_distance(sparsevec, sparsevec) RETURNS float8 CREATE FUNCTION sparsevec_l2_squared_distance(sparsevec, sparsevec) RETURNS float8
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;

View File

@@ -512,6 +512,9 @@ CREATE FUNCTION cosine_distance(sparsevec, sparsevec) RETURNS float8
CREATE FUNCTION sparsevec_norm(sparsevec) RETURNS float8 CREATE FUNCTION sparsevec_norm(sparsevec) RETURNS float8
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
CREATE FUNCTION subvector(sparsevec, int, int) RETURNS sparsevec
AS 'MODULE_PATHNAME', 'sparsevec_subvector' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
-- sparsevec private functions -- sparsevec private functions
CREATE FUNCTION sparsevec_l2_squared_distance(sparsevec, sparsevec) RETURNS float8 CREATE FUNCTION sparsevec_l2_squared_distance(sparsevec, sparsevec) RETURNS float8

View File

@@ -22,10 +22,6 @@
#define TYPALIGN_INT 'i' #define TYPALIGN_INT 'i'
#endif #endif
#ifdef F16C_SUPPORT
#include <immintrin.h>
#endif
/* /*
* Check if half is NaN * Check if half is NaN
*/ */
@@ -103,10 +99,7 @@ pq_sendhalf(StringInfo buf, half h)
float float
HalfToFloat4(half num) HalfToFloat4(half num)
{ {
#if defined(F16C_SUPPORT) && !defined(_MSC_VER) #ifdef FLT16_SUPPORT
/* TODO Use instrinsics for Windows */
return _cvtsh_ss(num);
#elif defined(FLT16_SUPPORT)
return (float) num; return (float) num;
#else #else
/* TODO Improve performance */ /* TODO Improve performance */
@@ -137,7 +130,7 @@ HalfToFloat4(half num)
/* Sign */ /* Sign */
result = (bin & 0x8000) << 16; result = (bin & 0x8000) << 16;
if (unlikely(exponent == 31)) if (exponent == 31)
{ {
if (mantissa == 0) if (mantissa == 0)
{ {
@@ -148,9 +141,10 @@ HalfToFloat4(half num)
{ {
/* NaN */ /* NaN */
result |= 0x7FC00000; result |= 0x7FC00000;
result |= mantissa << 13;
} }
} }
else if (unlikely(exponent == 0)) else if (exponent == 0)
{ {
/* Subnormal */ /* Subnormal */
if (mantissa != 0) if (mantissa != 0)
@@ -170,15 +164,15 @@ HalfToFloat4(half num)
} }
result |= (exponent + 127) << 23; result |= (exponent + 127) << 23;
result |= mantissa << 13;
} }
} }
else else
{ {
/* Normal */ /* Normal */
result |= (exponent - 15 + 127) << 23; result |= (exponent - 15 + 127) << 23;
}
result |= mantissa << 13; result |= mantissa << 13;
}
swapfloat.i = result; swapfloat.i = result;
return swapfloat.f; return swapfloat.f;
@@ -191,10 +185,7 @@ HalfToFloat4(half num)
half half
Float4ToHalfUnchecked(float num) Float4ToHalfUnchecked(float num)
{ {
#if defined(F16C_SUPPORT) && !defined(_MSC_VER) #ifdef FLT16_SUPPORT
/* TODO Use instrinsics for Windows */
return _cvtss_sh(num, 0);
#elif defined(FLT16_SUPPORT)
return (_Float16) num; return (_Float16) num;
#else #else
/* TODO Improve performance */ /* TODO Improve performance */
@@ -444,8 +435,6 @@ halfvec_in(PG_FUNCTION_ARGS)
while (pt != NULL && *stringEnd != ']') while (pt != NULL && *stringEnd != ']')
{ {
float val;
if (dim == HALFVEC_MAX_DIM) if (dim == HALFVEC_MAX_DIM)
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
@@ -461,24 +450,15 @@ halfvec_in(PG_FUNCTION_ARGS)
errmsg("invalid input syntax for type halfvec: \"%s\"", lit))); errmsg("invalid input syntax for type halfvec: \"%s\"", lit)));
/* Use strtof like float4in to avoid a double-rounding problem */ /* Use strtof like float4in to avoid a double-rounding problem */
errno = 0; x[dim] = Float4ToHalf(strtof(pt, &stringEnd));
val = strtof(pt, &stringEnd); CheckElement(x[dim]);
dim++;
if (stringEnd == pt) if (stringEnd == pt)
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("invalid input syntax for type halfvec: \"%s\"", lit))); 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)) while (halfvec_isspace(*stringEnd))
stringEnd++; stringEnd++;
@@ -802,56 +782,6 @@ vector_to_halfvec(PG_FUNCTION_ARGS)
PG_RETURN_POINTER(result); 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 * 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 *a = PG_GETARG_HALFVEC_P(0);
HalfVector *b = PG_GETARG_HALFVEC_P(1); HalfVector *b = PG_GETARG_HALFVEC_P(1);
half *ax = a->x;
half *bx = b->x;
float distance = 0.0;
CheckDims(a, b); 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 *a = PG_GETARG_HALFVEC_P(0);
HalfVector *b = PG_GETARG_HALFVEC_P(1); 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 *ax = a->x;
half *bx = b->x; half *bx = b->x;
float distance = 0.0; float distance = 0.0;
#if defined(F16C_SUPPORT) && defined(__FMA__) CheckDims(a, b);
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);
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 */ /* Auto-vectorized */
for (int i = 0; i < a->dim; i++) 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 *a = PG_GETARG_HALFVEC_P(0);
HalfVector *b = PG_GETARG_HALFVEC_P(1); HalfVector *b = PG_GETARG_HALFVEC_P(1);
half *ax = a->x;
half *bx = b->x;
float distance = 0.0;
CheckDims(a, b); 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 *a = PG_GETARG_HALFVEC_P(0);
HalfVector *b = PG_GETARG_HALFVEC_P(1); HalfVector *b = PG_GETARG_HALFVEC_P(1);
half *ax = a->x;
half *bx = b->x;
float distance = 0.0;
CheckDims(a, b); 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);
} }
/* /*

View File

@@ -7,10 +7,7 @@
#include "vector.h" #include "vector.h"
/* F16C has better performance than _Float16 (on x86-64) */ #ifdef __FLT16_MAX__
#if defined(__F16C__) || defined(_MSC_VER)
#define F16C_SUPPORT
#elif defined(__FLT16_MAX__)
#define FLT16_SUPPORT #define FLT16_SUPPORT
#endif #endif
@@ -18,6 +15,7 @@
#define half _Float16 #define half _Float16
#define HALF_MAX FLT16_MAX #define HALF_MAX FLT16_MAX
#else #else
/* TODO #pragma message("")? */
#define half uint16 #define half uint16
#define HALF_MAX 65504 #define HALF_MAX 65504
#endif #endif

View File

@@ -158,10 +158,6 @@ hnswgettuple(IndexScanDesc scan, ScanDirection dir)
UnlockPage(scan->indexRelation, HNSW_SCAN_LOCK, ShareLock); UnlockPage(scan->indexRelation, HNSW_SCAN_LOCK, ShareLock);
so->first = false; 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) while (list_length(so->w) > 0)

View File

@@ -163,9 +163,9 @@ HnswGetType(Relation index)
Oid typid = TupleDescAttr(index->rd_att, 0)->atttypid; Oid typid = TupleDescAttr(index->rd_att, 0)->atttypid;
HeapTuple tuple; HeapTuple tuple;
Form_pg_type type; Form_pg_type type;
HnswType result; int result;
if (typid == BITOID) if (typid == BITOID || typid == VARBITOID)
return HNSW_TYPE_BIT; return HNSW_TYPE_BIT;
tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(typid)); tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(typid));
@@ -180,10 +180,7 @@ HnswGetType(Relation index)
else if (strcmp(NameStr(type->typname), "sparsevec") == 0) else if (strcmp(NameStr(type->typname), "sparsevec") == 0)
result = HNSW_TYPE_SPARSEVEC; result = HNSW_TYPE_SPARSEVEC;
else else
{ elog(ERROR, "Unsupported type");
ReleaseSysCache(tuple);
elog(ERROR, "type not supported for hnsw index");
}
ReleaseSysCache(tuple); ReleaseSysCache(tuple);

View File

@@ -57,7 +57,7 @@ AddSample(Datum *values, IvfflatBuildState * buildstate)
*/ */
if (buildstate->kmeansnormprocinfo != NULL) if (buildstate->kmeansnormprocinfo != NULL)
{ {
if (!IvfflatNormValue(buildstate->kmeansnormprocinfo, buildstate->collation, &value, buildstate->type)) if (!IvfflatNormValue(buildstate->kmeansnormprocinfo, buildstate->collation, &value))
return; return;
} }
@@ -153,7 +153,7 @@ AddTupleToSort(Relation index, ItemPointer tid, Datum *values, IvfflatBuildState
/* Normalize if needed */ /* Normalize if needed */
if (buildstate->normprocinfo != NULL) if (buildstate->normprocinfo != NULL)
{ {
if (!IvfflatNormValue(buildstate->normprocinfo, buildstate->collation, &value, buildstate->type)) if (!IvfflatNormValue(buildstate->normprocinfo, buildstate->collation, &value))
return; return;
} }
@@ -312,39 +312,25 @@ InsertTuples(Relation index, IvfflatBuildState * buildstate, ForkNumber forkNum)
} }
} }
/*
* Get max dimensions
*/
static int
GetMaxDimensions(IvfflatType type)
{
return IVFFLAT_MAX_DIM;
}
/* /*
* Initialize the build state * Initialize the build state
*/ */
static void static void
InitBuildState(IvfflatBuildState * buildstate, Relation heap, Relation index, IndexInfo *indexInfo) InitBuildState(IvfflatBuildState * buildstate, Relation heap, Relation index, IndexInfo *indexInfo)
{ {
int maxDimensions;
buildstate->heap = heap; buildstate->heap = heap;
buildstate->index = index; buildstate->index = index;
buildstate->indexInfo = indexInfo; buildstate->indexInfo = indexInfo;
buildstate->type = IvfflatGetType(index);
buildstate->lists = IvfflatGetLists(index); buildstate->lists = IvfflatGetLists(index);
buildstate->dimensions = TupleDescAttr(index->rd_att, 0)->atttypmod; buildstate->dimensions = TupleDescAttr(index->rd_att, 0)->atttypmod;
maxDimensions = GetMaxDimensions(buildstate->type);
/* Require column to have dimensions to be indexed */ /* Require column to have dimensions to be indexed */
if (buildstate->dimensions < 0) if (buildstate->dimensions < 0)
elog(ERROR, "column does not have dimensions"); elog(ERROR, "column does not have dimensions");
if (buildstate->dimensions > maxDimensions) if (buildstate->dimensions > IVFFLAT_MAX_DIM)
elog(ERROR, "column cannot have more than %d dimensions for ivfflat index", maxDimensions); elog(ERROR, "column cannot have more than %d dimensions for ivfflat index", IVFFLAT_MAX_DIM);
buildstate->reltuples = 0; buildstate->reltuples = 0;
buildstate->indtuples = 0; buildstate->indtuples = 0;

View File

@@ -43,11 +43,6 @@
#define IVFFLAT_MAX_LISTS 32768 #define IVFFLAT_MAX_LISTS 32768
#define IVFFLAT_DEFAULT_PROBES 1 #define IVFFLAT_DEFAULT_PROBES 1
typedef enum IvfflatType
{
IVFFLAT_TYPE_VECTOR
} IvfflatType;
/* Build phases */ /* Build phases */
/* PROGRESS_CREATEIDX_SUBPHASE_INITIALIZE is 1 */ /* PROGRESS_CREATEIDX_SUBPHASE_INITIALIZE is 1 */
#define PROGRESS_IVFFLAT_PHASE_KMEANS 2 #define PROGRESS_IVFFLAT_PHASE_KMEANS 2
@@ -158,7 +153,6 @@ typedef struct IvfflatBuildState
Relation heap; Relation heap;
Relation index; Relation index;
IndexInfo *indexInfo; IndexInfo *indexInfo;
IvfflatType type;
/* Settings */ /* Settings */
int dimensions; int dimensions;
@@ -272,8 +266,7 @@ void VectorArrayFree(VectorArray arr);
void PrintVectorArray(char *msg, VectorArray arr); void PrintVectorArray(char *msg, VectorArray arr);
void IvfflatKmeans(Relation index, VectorArray samples, VectorArray centers); void IvfflatKmeans(Relation index, VectorArray samples, VectorArray centers);
FmgrInfo *IvfflatOptionalProcInfo(Relation index, uint16 procnum); FmgrInfo *IvfflatOptionalProcInfo(Relation index, uint16 procnum);
IvfflatType IvfflatGetType(Relation index); bool IvfflatNormValue(FmgrInfo *procinfo, Oid collation, Datum *value);
bool IvfflatNormValue(FmgrInfo *procinfo, Oid collation, Datum *value, IvfflatType type);
int IvfflatGetLists(Relation index); int IvfflatGetLists(Relation index);
void IvfflatGetMetaPageInfo(Relation index, int *lists, int *dimensions); void IvfflatGetMetaPageInfo(Relation index, int *lists, int *dimensions);
void IvfflatUpdateList(Relation index, ListInfo listInfo, BlockNumber insertPage, BlockNumber originalInsertPage, BlockNumber startPage, ForkNumber forkNum); void IvfflatUpdateList(Relation index, ListInfo listInfo, BlockNumber insertPage, BlockNumber originalInsertPage, BlockNumber startPage, ForkNumber forkNum);

View File

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

View File

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

View File

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

View File

@@ -68,12 +68,7 @@ CheckNnz(int nnz, int dim)
if (nnz < 0) if (nnz < 0)
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_DATA_EXCEPTION), (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) if (nnz > dim)
ereport(ERROR, ereport(ERROR,
@@ -89,15 +84,15 @@ CheckIndex(int32 *indices, int i, int dim)
{ {
int32 index = indices[i]; int32 index = indices[i];
if (index < 1) if (index < 0)
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_DATA_EXCEPTION), (errcode(ERRCODE_DATA_EXCEPTION),
errmsg("index must be greater than zero"))); errmsg("index must not be negative")));
if (index > dim) if (index >= dim)
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_DATA_EXCEPTION), (errcode(ERRCODE_DATA_EXCEPTION),
errmsg("index must be less than or equal to dimensions"))); errmsg("index must be less than dimensions")));
if (i > 0) if (i > 0)
{ {
@@ -195,11 +190,6 @@ sparsevec_in(PG_FUNCTION_ARGS)
pt++; pt++;
} }
if (maxNnz > SPARSEVEC_MAX_NNZ)
ereport(ERROR,
(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
errmsg("sparsevec cannot have more than %d non-zero elements", SPARSEVEC_MAX_NNZ)));
indices = palloc(maxNnz * sizeof(int32)); indices = palloc(maxNnz * sizeof(int32));
values = palloc(maxNnz * sizeof(float)); values = palloc(maxNnz * sizeof(float));
@@ -245,7 +235,7 @@ sparsevec_in(PG_FUNCTION_ARGS)
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("invalid input syntax for type sparsevec: \"%s\"", lit))); 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, ereport(ERROR,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("index \"%ld\" is out of range for type sparsevec", index))); errmsg("index \"%ld\" is out of range for type sparsevec", index)));
@@ -570,7 +560,7 @@ vector_to_sparsevec(PG_FUNCTION_ARGS)
if (j == nnz) if (j == nnz)
elog(ERROR, "safety check failed"); elog(ERROR, "safety check failed");
result->indices[j] = i + 1; result->indices[j] = i;
values[j] = vec->x[i]; values[j] = vec->x[i];
j++; j++;
} }
@@ -786,3 +776,55 @@ sparsevec_norm(PG_FUNCTION_ARGS)
PG_RETURN_FLOAT8(sqrt(norm)); PG_RETURN_FLOAT8(sqrt(norm));
} }
/*
* Get a subvector
*/
PGDLLEXPORT PG_FUNCTION_INFO_V1(sparsevec_subvector);
Datum
sparsevec_subvector(PG_FUNCTION_ARGS)
{
SparseVector *a = PG_GETARG_SPARSEVEC_P(0);
int32 start = PG_GETARG_INT32(1);
int32 count = PG_GETARG_INT32(2);
int32 end = start + count;
float *ax = SPARSEVEC_VALUES(a);
SparseVector *result;
float *rx;
int dim;
int nnz = 0;
int startIndex;
/* Indexing starts at 1, like substring */
if (start < 1)
start = 1;
if (end > a->dim)
end = a->dim + 1;
dim = end - start;
CheckDim(dim);
for (startIndex = 0; startIndex < a->nnz; startIndex++)
{
if (a->indices[startIndex] >= start - 1)
break;
}
for (int i = startIndex; i < a->nnz; i++)
{
if (a->indices[i] < end - 1)
nnz++;
}
result = InitSparseVector(dim, nnz);
rx = SPARSEVEC_VALUES(result);
for (int i = 0; i < nnz; i++)
{
result->indices[i] = a->indices[startIndex + i];
rx[i] = ax[startIndex + i];
}
PG_RETURN_POINTER(result);
}

View File

@@ -2,7 +2,6 @@
#define SPARSEVEC_H #define SPARSEVEC_H
#define SPARSEVEC_MAX_DIM 100000 #define SPARSEVEC_MAX_DIM 100000
#define SPARSEVEC_MAX_NNZ 16000
/* Ensure values are aligned */ /* Ensure values are aligned */
#define SPARSEVEC_SIZE(_nnz) (offsetof(SparseVector, indices) + MAXALIGN((_nnz) * sizeof(int32)) + (_nnz * sizeof(float))) #define SPARSEVEC_SIZE(_nnz) (offsetof(SparseVector, indices) + MAXALIGN((_nnz) * sizeof(int32)) + (_nnz * sizeof(float)))

View File

@@ -1244,7 +1244,7 @@ sparsevec_to_vector(PG_FUNCTION_ARGS)
result = InitVector(dim); result = InitVector(dim);
for (int i = 0; i < svec->nnz; i++) 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); PG_RETURN_POINTER(result);
} }

View File

@@ -1,104 +1,64 @@
SELECT hamming_distance('111', '111'); SELECT hamming_distance(B'111', B'111');
hamming_distance hamming_distance
------------------ ------------------
0 0
(1 row) (1 row)
SELECT hamming_distance('111', '110'); SELECT hamming_distance(B'111', B'110');
hamming_distance hamming_distance
------------------ ------------------
1 1
(1 row) (1 row)
SELECT hamming_distance('111', '100'); SELECT hamming_distance(B'111', B'100');
hamming_distance hamming_distance
------------------ ------------------
2 2
(1 row) (1 row)
SELECT hamming_distance('111', '000'); SELECT hamming_distance(B'111', B'000');
hamming_distance hamming_distance
------------------ ------------------
3 3
(1 row) (1 row)
SELECT hamming_distance('10101010101010101010', '01010101010101010101'); SELECT hamming_distance(B'111', B'00');
hamming_distance
------------------
20
(1 row)
SELECT hamming_distance('', '');
hamming_distance
------------------
0
(1 row)
SELECT hamming_distance('111', '00');
ERROR: different bit lengths 3 and 2 ERROR: different bit lengths 3 and 2
SELECT hamming_distance('111', '000'::varbit(4)); SELECT jaccard_distance(B'1111', B'1111');
hamming_distance
------------------
3
(1 row)
SELECT hamming_distance('111', '0000'::varbit(4));
ERROR: different bit lengths 3 and 4
SELECT jaccard_distance('1111', '1111');
jaccard_distance jaccard_distance
------------------ ------------------
0 0
(1 row) (1 row)
SELECT jaccard_distance('1111', '1110'); SELECT jaccard_distance(B'1111', B'1110');
jaccard_distance jaccard_distance
------------------ ------------------
0.25 0.25
(1 row) (1 row)
SELECT jaccard_distance('1111', '1100'); SELECT jaccard_distance(B'1111', B'1100');
jaccard_distance jaccard_distance
------------------ ------------------
0.5 0.5
(1 row) (1 row)
SELECT jaccard_distance('1111', '1000'); SELECT jaccard_distance(B'1111', B'1000');
jaccard_distance jaccard_distance
------------------ ------------------
0.75 0.75
(1 row) (1 row)
SELECT jaccard_distance('1111', '0000'); SELECT jaccard_distance(B'1111', B'0000');
jaccard_distance jaccard_distance
------------------ ------------------
1 1
(1 row) (1 row)
SELECT jaccard_distance('1100', '1000'); SELECT jaccard_distance(B'1100', B'1000');
jaccard_distance jaccard_distance
------------------ ------------------
0.5 0.5
(1 row) (1 row)
SELECT jaccard_distance('10101010101010101010', '01010101010101010101'); SELECT jaccard_distance(B'1111', B'000');
jaccard_distance
------------------
1
(1 row)
SELECT jaccard_distance('', '');
jaccard_distance
------------------
1
(1 row)
SELECT jaccard_distance('1111', '000');
ERROR: different bit lengths 4 and 3 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

View File

@@ -51,21 +51,17 @@ SELECT '[65519,-65519]'::halfvec;
(1 row) (1 row)
SELECT '[65520,-65520]'::halfvec; 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; LINE 1: SELECT '[65520,-65520]'::halfvec;
^ ^
SELECT '[1e-8,-1e-8]'::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; LINE 1: SELECT '[1e-8,-1e-8]'::halfvec;
^ ^
SELECT '[4e38,1]'::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; 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; SELECT '[1,2,3'::halfvec;
ERROR: malformed halfvec literal: "[1,2,3" ERROR: malformed halfvec literal: "[1,2,3"
LINE 1: SELECT '[1,2,3'::halfvec; LINE 1: SELECT '[1,2,3'::halfvec;

View File

@@ -19,9 +19,3 @@ SELECT COUNT(*) FROM (SELECT * FROM t ORDER BY val <~> (SELECT NULL::bit)) t2;
(1 row) (1 row)
DROP TABLE t; 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;

View File

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

View File

@@ -1,14 +1,14 @@
SET enable_seqscan = off; SET enable_seqscan = off;
CREATE TABLE t (val sparsevec(3)); 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); INSERT INTO t (val) VALUES ('{}/3'), ('{0:1,1:2,2:3}/3'), ('{0:1,1:1,2:1}/3'), (NULL);
CREATE INDEX ON t USING hnsw (val sparsevec_ip_ops); 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}/3');
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}/3';
val val
----------------- -----------------
{1:1,2:2,3:4}/3 {0:1,1:2,2:4}/3
{1:1,2:2,3:3}/3 {0:1,1:2,2:3}/3
{1:1,2:1,3:1}/3 {0:1,1:1,2:1}/3
{}/3 {}/3
(4 rows) (4 rows)

View File

@@ -1,14 +1,14 @@
SET enable_seqscan = off; SET enable_seqscan = off;
CREATE TABLE t (val sparsevec(3)); 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); INSERT INTO t (val) VALUES ('{}/3'), ('{0:1,1:2,2:3}/3'), ('{0:1,1:1,2:1}/3'), (NULL);
CREATE INDEX ON t USING hnsw (val sparsevec_l2_ops); 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}/3');
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}/3';
val val
----------------- -----------------
{1:1,2:2,3:3}/3 {0:1,1:2,2:3}/3
{1:1,2:2,3:4}/3 {0:1,1:2,2:4}/3
{1:1,2:1,3:1}/3 {0:1,1:1,2:1}/3
{}/3 {}/3
(4 rows) (4 rows)
@@ -25,7 +25,7 @@ SELECT COUNT(*) FROM t;
(1 row) (1 row)
TRUNCATE 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}/3';
val val
----- -----
(0 rows) (0 rows)

View File

@@ -1,52 +1,52 @@
SELECT l2_distance('{}/2'::sparsevec, '{1:3,2:4}/2'); SELECT l2_distance('{}/2'::sparsevec, '{0:3,1:4}/2');
l2_distance l2_distance
------------- -------------
5 5
(1 row) (1 row)
SELECT l2_distance('{}/2'::sparsevec, '{2:1}/2'); SELECT l2_distance('{}/2'::sparsevec, '{1:1}/2');
l2_distance l2_distance
------------- -------------
1 1
(1 row) (1 row)
SELECT '{}/2'::sparsevec <-> '{1:3,2:4}/2'; SELECT '{}/2'::sparsevec <-> '{0:3,1:4}/2';
?column? ?column?
---------- ----------
5 5
(1 row) (1 row)
SELECT inner_product('{1:1,2:2}/2'::sparsevec, '{1:2,2:4}/2'); SELECT inner_product('{0:1,1:2}/2'::sparsevec, '{0:2,1:4}/2');
inner_product inner_product
--------------- ---------------
10 10
(1 row) (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}/2', '{0:2,1:4}/2');
sparsevec_negative_inner_product sparsevec_negative_inner_product
---------------------------------- ----------------------------------
-10 -10
(1 row) (1 row)
SELECT cosine_distance('{1:1,2:2}/2'::sparsevec, '{1:2,2:4}/2'); SELECT cosine_distance('{0:1,1:2}/2'::sparsevec, '{0:2,1:4}/2');
cosine_distance cosine_distance
----------------- -----------------
0 0
(1 row) (1 row)
SELECT cosine_distance('{1:1,2:2}/2'::sparsevec, '{}/2'); SELECT cosine_distance('{0:1,1:2}/2'::sparsevec, '{}/2');
cosine_distance cosine_distance
----------------- -----------------
NaN NaN
(1 row) (1 row)
SELECT cosine_distance('{1:1,2:1}/2'::sparsevec, '{1:-1,2:-1}/2'); SELECT cosine_distance('{0:1,1:1}/2'::sparsevec, '{0:-1,1:-1}/2');
cosine_distance cosine_distance
----------------- -----------------
2 2
(1 row) (1 row)
SELECT cosine_distance('{1:2}/2'::sparsevec, '{2:2}/2'); SELECT cosine_distance('{0:1}/2'::sparsevec, '{1:2}/2');
cosine_distance cosine_distance
----------------- -----------------
1 1
@@ -58,5 +58,35 @@ SELECT cosine_distance('{}/1'::sparsevec, '{}/1');
NaN NaN
(1 row) (1 row)
SELECT cosine_distance('{1:2}/2'::sparsevec, '{1:1}/3'); SELECT cosine_distance('{0:1}/2'::sparsevec, '{0:1}/3');
ERROR: different sparsevec dimensions 2 and 3 ERROR: different sparsevec dimensions 2 and 3
SELECT subvector('{0:1,1:2,2:3,3:4,4:5}/5'::sparsevec, 1, 3);
subvector
-----------------
{0:1,1:2,2:3}/3
(1 row)
SELECT subvector('{0:1,1:2,2:3,3:4,4:5}/5'::sparsevec, 3, 2);
subvector
-------------
{2:3,3:4}/2
(1 row)
SELECT subvector('{0:1,1:2,2:3,3:4,4:5}/5'::sparsevec, -1, 3);
subvector
-----------
{0:1}/1
(1 row)
SELECT subvector('{0:1,1:2,2:3,3:4,4:5}/5'::sparsevec, 3, 9);
subvector
-----------------
{2:3,3:4,4:5}/3
(1 row)
SELECT subvector('{0:1,1:2,2:3,3:4,4:5}/5'::sparsevec, 1, 0);
ERROR: sparsevec must have at least 1 dimension
SELECT subvector('{0:1,1:2,2:3,3:4,4:5}/5'::sparsevec, 3, -1);
ERROR: sparsevec must have at least 1 dimension
SELECT subvector('{0:1,1:2,2:3,3:4,4:5}/5'::sparsevec, -1, 2);
ERROR: sparsevec must have at least 1 dimension

View File

@@ -1,38 +1,38 @@
SELECT '{1:1.5,3:3.5}/5'::sparsevec; SELECT '{0:1.5,2:3.5}/5'::sparsevec;
sparsevec
-----------------
{0:1.5,2:3.5}/5
(1 row)
SELECT '{0:1.5,2:3.5}/5'::sparsevec::vector;
vector
-----------------
[1.5,0,3.5,0,0]
(1 row)
SELECT '{0:1.5,2:3.5}/5'::sparsevec::vector(5);
vector
-----------------
[1.5,0,3.5,0,0]
(1 row)
SELECT '{0:1.5,2:3.5}/5'::sparsevec::vector(4);
ERROR: expected 4 dimensions, not 5
SELECT '[0,1.5,0,3.5,0]'::vector::sparsevec;
sparsevec sparsevec
----------------- -----------------
{1:1.5,3:3.5}/5 {1:1.5,3:3.5}/5
(1 row) (1 row)
SELECT '{1:1.5,3:3.5}/5'::sparsevec::vector; SELECT '{0:0,1:1,2:0}/3'::sparsevec;
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]
(1 row)
SELECT '{1:1.5,3:3.5}/5'::sparsevec::vector(4);
ERROR: expected 4 dimensions, not 5
SELECT '[0,1.5,0,3.5,0]'::vector::sparsevec;
sparsevec
-----------------
{2:1.5,4:3.5}/5
(1 row)
SELECT '{1:0,2:1,3:0}/3'::sparsevec;
sparsevec sparsevec
----------- -----------
{2:1}/3 {1:1}/3
(1 row) (1 row)
SELECT '{2:1,1:1}/2'::sparsevec; SELECT '{1:1,0:1}/2'::sparsevec;
ERROR: indexes must be in ascending order ERROR: indexes must be in ascending order
LINE 1: SELECT '{2:1,1:1}/2'::sparsevec; LINE 1: SELECT '{1:1,0:1}/2'::sparsevec;
^ ^
SELECT '{}/5'::sparsevec; SELECT '{}/5'::sparsevec;
sparsevec sparsevec
@@ -50,13 +50,13 @@ LINE 1: SELECT '{}/100001'::sparsevec;
^ ^
SELECT '{}/16001'::sparsevec::vector; SELECT '{}/16001'::sparsevec::vector;
ERROR: vector cannot have more than 16000 dimensions ERROR: vector cannot have more than 16000 dimensions
SELECT '{0:1}/1'::sparsevec; SELECT '{-1:1}/1'::sparsevec;
ERROR: index "0" is out of range for type sparsevec ERROR: index "-1" is out of range for type sparsevec
LINE 1: SELECT '{0:1}/1'::sparsevec; LINE 1: SELECT '{-1:1}/1'::sparsevec;
^ ^
SELECT '{2:1}/1'::sparsevec; SELECT '{1:1}/1'::sparsevec;
ERROR: index must be less than or equal to dimensions ERROR: index must be less than dimensions
LINE 1: SELECT '{2:1}/1'::sparsevec; LINE 1: SELECT '{1:1}/1'::sparsevec;
^ ^
SELECT '{}/1'::sparsevec(2); SELECT '{}/1'::sparsevec(2);
ERROR: expected 2 dimensions, not 1 ERROR: expected 2 dimensions, not 1

View File

@@ -1,21 +1,13 @@
SELECT hamming_distance('111', '111'); SELECT hamming_distance(B'111', B'111');
SELECT hamming_distance('111', '110'); SELECT hamming_distance(B'111', B'110');
SELECT hamming_distance('111', '100'); SELECT hamming_distance(B'111', B'100');
SELECT hamming_distance('111', '000'); SELECT hamming_distance(B'111', B'000');
SELECT hamming_distance('10101010101010101010', '01010101010101010101'); SELECT hamming_distance(B'111', B'00');
SELECT hamming_distance('', '');
SELECT hamming_distance('111', '00');
SELECT hamming_distance('111', '000'::varbit(4));
SELECT hamming_distance('111', '0000'::varbit(4));
SELECT jaccard_distance('1111', '1111'); SELECT jaccard_distance(B'1111', B'1111');
SELECT jaccard_distance('1111', '1110'); SELECT jaccard_distance(B'1111', B'1110');
SELECT jaccard_distance('1111', '1100'); SELECT jaccard_distance(B'1111', B'1100');
SELECT jaccard_distance('1111', '1000'); SELECT jaccard_distance(B'1111', B'1000');
SELECT jaccard_distance('1111', '0000'); SELECT jaccard_distance(B'1111', B'0000');
SELECT jaccard_distance('1100', '1000'); SELECT jaccard_distance(B'1100', B'1000');
SELECT jaccard_distance('10101010101010101010', '01010101010101010101'); SELECT jaccard_distance(B'1111', B'000');
SELECT jaccard_distance('', '');
SELECT jaccard_distance('1111', '000');
SELECT jaccard_distance('1111', '0000'::varbit(5));
SELECT jaccard_distance('1111', '00000'::varbit(5));

View File

@@ -11,7 +11,6 @@ SELECT '[65519,-65519]'::halfvec;
SELECT '[65520,-65520]'::halfvec; SELECT '[65520,-65520]'::halfvec;
SELECT '[1e-8,-1e-8]'::halfvec; SELECT '[1e-8,-1e-8]'::halfvec;
SELECT '[4e38,1]'::halfvec; SELECT '[4e38,1]'::halfvec;
SELECT '[1e-46,1]'::halfvec;
SELECT '[1,2,3'::halfvec; SELECT '[1,2,3'::halfvec;
SELECT '[1,2,3]9'::halfvec; SELECT '[1,2,3]9'::halfvec;
SELECT '1,2,3'::halfvec; SELECT '1,2,3'::halfvec;

View File

@@ -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; SELECT COUNT(*) FROM (SELECT * FROM t ORDER BY val <~> (SELECT NULL::bit)) t2;
DROP TABLE t; 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;

View File

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

View File

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

View File

@@ -1,17 +1,17 @@
SET enable_seqscan = off; SET enable_seqscan = off;
CREATE TABLE t (val sparsevec(3)); 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); INSERT INTO t (val) VALUES ('{}/3'), ('{0:1,1:2,2:3}/3'), ('{0:1,1:1,2:1}/3'), (NULL);
CREATE INDEX ON t USING hnsw (val sparsevec_l2_ops); 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}/3');
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}/3';
SELECT COUNT(*) FROM (SELECT * FROM t ORDER BY val <-> (SELECT NULL::sparsevec)) t2; SELECT COUNT(*) FROM (SELECT * FROM t ORDER BY val <-> (SELECT NULL::sparsevec)) t2;
SELECT COUNT(*) FROM t; SELECT COUNT(*) FROM t;
TRUNCATE 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}/3';
DROP TABLE t; DROP TABLE t;

View File

@@ -1,13 +1,21 @@
SELECT l2_distance('{}/2'::sparsevec, '{1:3,2:4}/2'); SELECT l2_distance('{}/2'::sparsevec, '{0:3,1:4}/2');
SELECT l2_distance('{}/2'::sparsevec, '{2:1}/2'); SELECT l2_distance('{}/2'::sparsevec, '{1:1}/2');
SELECT '{}/2'::sparsevec <-> '{1:3,2:4}/2'; SELECT '{}/2'::sparsevec <-> '{0:3,1:4}/2';
SELECT inner_product('{1:1,2:2}/2'::sparsevec, '{1:2,2:4}/2'); SELECT inner_product('{0:1,1:2}/2'::sparsevec, '{0:2,1:4}/2');
SELECT sparsevec_negative_inner_product('{1:1,2:2}/2', '{1:2,2:4}/2'); SELECT sparsevec_negative_inner_product('{0:1,1:2}/2', '{0:2,1:4}/2');
SELECT cosine_distance('{1:1,2:2}/2'::sparsevec, '{1:2,2:4}/2'); SELECT cosine_distance('{0:1,1:2}/2'::sparsevec, '{0:2,1:4}/2');
SELECT cosine_distance('{1:1,2:2}/2'::sparsevec, '{}/2'); SELECT cosine_distance('{0:1,1:2}/2'::sparsevec, '{}/2');
SELECT cosine_distance('{1:1,2:1}/2'::sparsevec, '{1:-1,2:-1}/2'); SELECT cosine_distance('{0:1,1:1}/2'::sparsevec, '{0:-1,1:-1}/2');
SELECT cosine_distance('{1:2}/2'::sparsevec, '{2:2}/2'); SELECT cosine_distance('{0:1}/2'::sparsevec, '{1:2}/2');
SELECT cosine_distance('{}/1'::sparsevec, '{}/1'); SELECT cosine_distance('{}/1'::sparsevec, '{}/1');
SELECT cosine_distance('{1:2}/2'::sparsevec, '{1:1}/3'); SELECT cosine_distance('{0:1}/2'::sparsevec, '{0:1}/3');
SELECT subvector('{0:1,1:2,2:3,3:4,4:5}/5'::sparsevec, 1, 3);
SELECT subvector('{0:1,1:2,2:3,3:4,4:5}/5'::sparsevec, 3, 2);
SELECT subvector('{0:1,1:2,2:3,3:4,4:5}/5'::sparsevec, -1, 3);
SELECT subvector('{0:1,1:2,2:3,3:4,4:5}/5'::sparsevec, 3, 9);
SELECT subvector('{0:1,1:2,2:3,3:4,4:5}/5'::sparsevec, 1, 0);
SELECT subvector('{0:1,1:2,2:3,3:4,4:5}/5'::sparsevec, 3, -1);
SELECT subvector('{0:1,1:2,2:3,3:4,4:5}/5'::sparsevec, -1, 2);

View File

@@ -1,19 +1,19 @@
SELECT '{1:1.5,3:3.5}/5'::sparsevec; SELECT '{0:1.5,2:3.5}/5'::sparsevec;
SELECT '{1:1.5,3:3.5}/5'::sparsevec::vector; SELECT '{0:1.5,2:3.5}/5'::sparsevec::vector;
SELECT '{1:1.5,3:3.5}/5'::sparsevec::vector(5); SELECT '{0:1.5,2:3.5}/5'::sparsevec::vector(5);
SELECT '{1:1.5,3:3.5}/5'::sparsevec::vector(4); SELECT '{0:1.5,2:3.5}/5'::sparsevec::vector(4);
SELECT '[0,1.5,0,3.5,0]'::vector::sparsevec; 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}/3'::sparsevec;
SELECT '{2:1,1:1}/2'::sparsevec; SELECT '{1:1,0:1}/2'::sparsevec;
SELECT '{}/5'::sparsevec; SELECT '{}/5'::sparsevec;
SELECT '{}/-1'::sparsevec; SELECT '{}/-1'::sparsevec;
SELECT '{}/100001'::sparsevec; SELECT '{}/100001'::sparsevec;
SELECT '{}/16001'::sparsevec::vector; SELECT '{}/16001'::sparsevec::vector;
SELECT '{0:1}/1'::sparsevec; SELECT '{-1:1}/1'::sparsevec;
SELECT '{2:1}/1'::sparsevec; SELECT '{1:1}/1'::sparsevec;
SELECT '{}/1'::sparsevec(2); SELECT '{}/1'::sparsevec(2);

View File

@@ -86,7 +86,7 @@ foreach (@queries)
push(@expected, $res); push(@expected, $res);
} }
test_recall(0.18, $limit, "before vacuum"); test_recall(0.19, $limit, "before vacuum");
test_recall(0.95, 100, "before vacuum"); test_recall(0.95, 100, "before vacuum");
# TODO Test concurrent inserts with vacuum # TODO Test concurrent inserts with vacuum

View File

@@ -99,7 +99,7 @@ for my $i (0 .. $#operators)
)); ));
# Test approximate results # Test approximate results
my $min = $operator eq "<\%>" ? 0.95 : 0.98; my $min = $operator eq "<\%>" ? 0.96 : 0.98;
test_recall($min, $operator); test_recall($min, $operator);
$node->safe_psql("postgres", "DROP INDEX idx;"); $node->safe_psql("postgres", "DROP INDEX idx;");

View File

@@ -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();