Compare commits

..

12 Commits

Author SHA1 Message Date
Andrew Kane
e4ac05f044 Limit max probes [skip ci] 2024-09-22 11:20:28 -07:00
Andrew Kane
25f97fd91d Updated cost estimation [skip ci] 2024-09-22 11:17:29 -07:00
Andrew Kane
cd3f9a38ae Added max_probes option [skip ci] 2024-09-21 20:19:26 -07:00
Andrew Kane
ff6267917e Improved approach [skip ci] 2024-09-21 19:55:55 -07:00
Andrew Kane
c950c5ffaa Merge branch 'master' into ivfflat-streaming 2024-09-21 19:26:12 -07:00
Andrew Kane
88f56dc234 Merge branch 'master' into ivfflat-streaming 2024-09-21 18:37:31 -07:00
Andrew Kane
e2ba6cf38f Updated comment [skip ci] 2024-09-20 21:33:46 -07:00
Andrew Kane
689f9c4659 Added cost estimation [skip ci] 2024-09-20 21:30:52 -07:00
Andrew Kane
88889f5a4c Fixed CI [skip ci] 2024-09-20 12:52:08 -07:00
Andrew Kane
79851729f1 Improved streaming test 2024-09-20 11:13:03 -07:00
Andrew Kane
3fd6a29c49 Added test for streaming recall [skip ci] 2024-09-20 10:50:53 -07:00
Andrew Kane
2c58804756 Added streaming option for IVFFlat [skip ci] 2024-09-20 10:20:40 -07:00
34 changed files with 303 additions and 3239 deletions

View File

@@ -4,8 +4,8 @@ EXTVERSION = 0.7.4
MODULE_big = vector
DATA = $(wildcard sql/*--*--*.sql)
DATA_built = sql/$(EXTENSION)--$(EXTVERSION).sql
OBJS = src/bitutils.o src/bitvec.o src/halfutils.o src/halfvec.o src/hnsw.o src/hnswbuild.o src/hnswinsert.o src/hnswscan.o src/hnswutils.o src/hnswvacuum.o src/ivfbuild.o src/ivfflat.o src/ivfinsert.o src/ivfkmeans.o src/ivfscan.o src/ivfutils.o src/minivec.o src/ivfvacuum.o src/sparsevec.o src/vector.o
HEADERS = src/halfvec.h src/minivec.h src/sparsevec.h src/vector.h
OBJS = src/bitutils.o src/bitvec.o src/halfutils.o src/halfvec.o src/hnsw.o src/hnswbuild.o src/hnswinsert.o src/hnswscan.o src/hnswutils.o src/hnswvacuum.o src/ivfbuild.o src/ivfflat.o src/ivfinsert.o src/ivfkmeans.o src/ivfscan.o src/ivfutils.o src/ivfvacuum.o src/sparsevec.o src/vector.o
HEADERS = src/halfvec.h src/sparsevec.h src/vector.h
TESTS = $(wildcard test/sql/*.sql)
REGRESS = $(patsubst test/sql/%.sql,%,$(TESTS))

View File

@@ -2,10 +2,10 @@ EXTENSION = vector
EXTVERSION = 0.7.4
DATA_built = sql\$(EXTENSION)--$(EXTVERSION).sql
OBJS = src\bitutils.obj src\bitvec.obj src\halfutils.obj src\halfvec.obj src\hnsw.obj src\hnswbuild.obj src\hnswinsert.obj src\hnswscan.obj src\hnswutils.obj src\hnswvacuum.obj src\ivfbuild.obj src\ivfflat.obj src\ivfinsert.obj src\ivfkmeans.obj src\ivfscan.obj src\ivfutils.obj src\ivfvacuum.obj src\minivec.obj src\sparsevec.obj src\vector.obj
HEADERS = src\halfvec.h src\minivec.h src\sparsevec.h src\vector.h
OBJS = src\bitutils.obj src\bitvec.obj src\halfutils.obj src\halfvec.obj src\hnsw.obj src\hnswbuild.obj src\hnswinsert.obj src\hnswscan.obj src\hnswutils.obj src\hnswvacuum.obj src\ivfbuild.obj src\ivfflat.obj src\ivfinsert.obj src\ivfkmeans.obj src\ivfscan.obj src\ivfutils.obj src\ivfvacuum.obj src\sparsevec.obj src\vector.obj
HEADERS = src\halfvec.h src\sparsevec.h src\vector.h
REGRESS = bit btree cast copy halfvec hnsw_bit hnsw_halfvec hnsw_sparsevec hnsw_vector ivfflat_bit ivfflat_halfvec ivfflat_vector minivec sparsevec vector_type
REGRESS = bit btree cast copy halfvec hnsw_bit hnsw_halfvec hnsw_sparsevec hnsw_vector ivfflat_bit ivfflat_halfvec ivfflat_vector sparsevec vector_type
REGRESS_OPTS = --inputdir=test --load-extension=$(EXTENSION)
# For /arch flags

View File

@@ -934,37 +934,6 @@ Function | Description | Added
avg(halfvec) → halfvec | average | 0.7.0
sum(halfvec) → halfvec | sum | 0.7.0
### Minivec Type
Each mini vector takes `dimensions + 8` bytes of storage. Each element is a E4M3 8-bit floating-point number, and all elements must be finite (no `NaN`). Mini vectors can have up to 16,000 dimensions.
### Minivec Operators
Operator | Description | Added
--- | --- | ---
\+ | element-wise addition | 0.8.0
\- | element-wise subtraction | 0.8.0
\* | element-wise multiplication | 0.8.0
\|\| | concatenate | 0.8.0
<-> | Euclidean distance | 0.8.0
<#> | negative inner product | 0.8.0
<=> | cosine distance | 0.8.0
<+> | taxicab distance | 0.8.0
### Minivec Functions
Function | Description | Added
--- | --- | ---
binary_quantize(minivec) → bit | binary quantize | 0.8.0
cosine_distance(minivec, minivec) → double precision | cosine distance | 0.8.0
inner_product(minivec, minivec) → double precision | inner product | 0.8.0
l1_distance(minivec, minivec) → double precision | taxicab distance | 0.8.0
l2_distance(minivec, minivec) → double precision | Euclidean distance | 0.8.0
l2_norm(minivec) → double precision | Euclidean norm | 0.8.0
l2_normalize(minivec) → minivec | Normalize with Euclidean norm | 0.8.0
subvector(minivec, integer, integer) → minivec | subvector | 0.8.0
vector_dims(minivec) → integer | number of dimensions | 0.8.0
### Bit Type
Each bit vector takes `dimensions / 8 + 8` bytes of storage. See the [Postgres docs](https://www.postgresql.org/docs/current/datatype-bit.html) for more info.

View File

@@ -1,8 +1,6 @@
-- complain if script is sourced in psql, rather than via CREATE EXTENSION
\echo Use "ALTER EXTENSION vector UPDATE TO '0.8.0'" to load this file. \quit
-- TODO minivec functions
CREATE FUNCTION array_to_sparsevec(integer[], integer, boolean) RETURNS sparsevec
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;

View File

@@ -266,18 +266,12 @@ COMMENT ON ACCESS METHOD hnsw IS 'hnsw index access method';
CREATE FUNCTION ivfflat_halfvec_support(internal) RETURNS internal
AS 'MODULE_PATHNAME' LANGUAGE C;
CREATE FUNCTION ivfflat_minivec_support(internal) RETURNS internal
AS 'MODULE_PATHNAME' LANGUAGE C;
CREATE FUNCTION ivfflat_bit_support(internal) RETURNS internal
AS 'MODULE_PATHNAME' LANGUAGE C;
CREATE FUNCTION hnsw_halfvec_support(internal) RETURNS internal
AS 'MODULE_PATHNAME' LANGUAGE C;
CREATE FUNCTION hnsw_minivec_support(internal) RETURNS internal
AS 'MODULE_PATHNAME' LANGUAGE C;
CREATE FUNCTION hnsw_bit_support(internal) RETURNS internal
AS 'MODULE_PATHNAME' LANGUAGE C;
@@ -653,295 +647,6 @@ CREATE OPERATOR CLASS halfvec_l1_ops
FUNCTION 1 l1_distance(halfvec, halfvec),
FUNCTION 3 hnsw_halfvec_support(internal);
-- minivec type
CREATE TYPE minivec;
CREATE FUNCTION minivec_in(cstring, oid, integer) RETURNS minivec
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
CREATE FUNCTION minivec_out(minivec) RETURNS cstring
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
CREATE FUNCTION minivec_typmod_in(cstring[]) RETURNS integer
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
CREATE FUNCTION minivec_recv(internal, oid, integer) RETURNS minivec
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
CREATE FUNCTION minivec_send(minivec) RETURNS bytea
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
CREATE TYPE minivec (
INPUT = minivec_in,
OUTPUT = minivec_out,
TYPMOD_IN = minivec_typmod_in,
RECEIVE = minivec_recv,
SEND = minivec_send,
STORAGE = external
);
-- minivec functions
CREATE FUNCTION l2_distance(minivec, minivec) RETURNS float8
AS 'MODULE_PATHNAME', 'minivec_l2_distance' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
CREATE FUNCTION inner_product(minivec, minivec) RETURNS float8
AS 'MODULE_PATHNAME', 'minivec_inner_product' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
CREATE FUNCTION cosine_distance(minivec, minivec) RETURNS float8
AS 'MODULE_PATHNAME', 'minivec_cosine_distance' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
CREATE FUNCTION l1_distance(minivec, minivec) RETURNS float8
AS 'MODULE_PATHNAME', 'minivec_l1_distance' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
CREATE FUNCTION vector_dims(minivec) RETURNS integer
AS 'MODULE_PATHNAME', 'minivec_vector_dims' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
CREATE FUNCTION l2_norm(minivec) RETURNS float8
AS 'MODULE_PATHNAME', 'minivec_l2_norm' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
CREATE FUNCTION l2_normalize(minivec) RETURNS minivec
AS 'MODULE_PATHNAME', 'minivec_l2_normalize' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
CREATE FUNCTION binary_quantize(minivec) RETURNS bit
AS 'MODULE_PATHNAME', 'minivec_binary_quantize' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
CREATE FUNCTION subvector(minivec, int, int) RETURNS minivec
AS 'MODULE_PATHNAME', 'minivec_subvector' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
-- minivec private functions
CREATE FUNCTION minivec_add(minivec, minivec) RETURNS minivec
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
CREATE FUNCTION minivec_sub(minivec, minivec) RETURNS minivec
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
CREATE FUNCTION minivec_mul(minivec, minivec) RETURNS minivec
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
CREATE FUNCTION minivec_concat(minivec, minivec) RETURNS minivec
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
CREATE FUNCTION minivec_lt(minivec, minivec) RETURNS bool
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
CREATE FUNCTION minivec_le(minivec, minivec) RETURNS bool
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
CREATE FUNCTION minivec_eq(minivec, minivec) RETURNS bool
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
CREATE FUNCTION minivec_ne(minivec, minivec) RETURNS bool
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
CREATE FUNCTION minivec_ge(minivec, minivec) RETURNS bool
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
CREATE FUNCTION minivec_gt(minivec, minivec) RETURNS bool
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
CREATE FUNCTION minivec_cmp(minivec, minivec) RETURNS int4
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
CREATE FUNCTION minivec_l2_squared_distance(minivec, minivec) RETURNS float8
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
CREATE FUNCTION minivec_negative_inner_product(minivec, minivec) RETURNS float8
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
CREATE FUNCTION minivec_spherical_distance(minivec, minivec) RETURNS float8
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
-- minivec cast functions
CREATE FUNCTION minivec(minivec, integer, boolean) RETURNS minivec
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
CREATE FUNCTION minivec_to_vector(minivec, integer, boolean) RETURNS vector
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
CREATE FUNCTION vector_to_minivec(vector, integer, boolean) RETURNS minivec
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
CREATE FUNCTION array_to_minivec(integer[], integer, boolean) RETURNS minivec
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
CREATE FUNCTION array_to_minivec(real[], integer, boolean) RETURNS minivec
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
CREATE FUNCTION array_to_minivec(double precision[], integer, boolean) RETURNS minivec
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
CREATE FUNCTION array_to_minivec(numeric[], integer, boolean) RETURNS minivec
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
CREATE FUNCTION minivec_to_float4(minivec, integer, boolean) RETURNS real[]
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
-- minivec casts
CREATE CAST (minivec AS minivec)
WITH FUNCTION minivec(minivec, integer, boolean) AS IMPLICIT;
CREATE CAST (minivec AS vector)
WITH FUNCTION minivec_to_vector(minivec, integer, boolean) AS ASSIGNMENT;
CREATE CAST (vector AS minivec)
WITH FUNCTION vector_to_minivec(vector, integer, boolean) AS IMPLICIT;
CREATE CAST (minivec AS real[])
WITH FUNCTION minivec_to_float4(minivec, integer, boolean) AS ASSIGNMENT;
CREATE CAST (integer[] AS minivec)
WITH FUNCTION array_to_minivec(integer[], integer, boolean) AS ASSIGNMENT;
CREATE CAST (real[] AS minivec)
WITH FUNCTION array_to_minivec(real[], integer, boolean) AS ASSIGNMENT;
CREATE CAST (double precision[] AS minivec)
WITH FUNCTION array_to_minivec(double precision[], integer, boolean) AS ASSIGNMENT;
CREATE CAST (numeric[] AS minivec)
WITH FUNCTION array_to_minivec(numeric[], integer, boolean) AS ASSIGNMENT;
-- minivec operators
CREATE OPERATOR <-> (
LEFTARG = minivec, RIGHTARG = minivec, PROCEDURE = l2_distance,
COMMUTATOR = '<->'
);
CREATE OPERATOR <#> (
LEFTARG = minivec, RIGHTARG = minivec, PROCEDURE = minivec_negative_inner_product,
COMMUTATOR = '<#>'
);
CREATE OPERATOR <=> (
LEFTARG = minivec, RIGHTARG = minivec, PROCEDURE = cosine_distance,
COMMUTATOR = '<=>'
);
CREATE OPERATOR <+> (
LEFTARG = minivec, RIGHTARG = minivec, PROCEDURE = l1_distance,
COMMUTATOR = '<+>'
);
CREATE OPERATOR + (
LEFTARG = minivec, RIGHTARG = minivec, PROCEDURE = minivec_add,
COMMUTATOR = +
);
CREATE OPERATOR - (
LEFTARG = minivec, RIGHTARG = minivec, PROCEDURE = minivec_sub
);
CREATE OPERATOR * (
LEFTARG = minivec, RIGHTARG = minivec, PROCEDURE = minivec_mul,
COMMUTATOR = *
);
CREATE OPERATOR || (
LEFTARG = minivec, RIGHTARG = minivec, PROCEDURE = minivec_concat
);
CREATE OPERATOR < (
LEFTARG = minivec, RIGHTARG = minivec, PROCEDURE = minivec_lt,
COMMUTATOR = > , NEGATOR = >= ,
RESTRICT = scalarltsel, JOIN = scalarltjoinsel
);
CREATE OPERATOR <= (
LEFTARG = minivec, RIGHTARG = minivec, PROCEDURE = minivec_le,
COMMUTATOR = >= , NEGATOR = > ,
RESTRICT = scalarlesel, JOIN = scalarlejoinsel
);
CREATE OPERATOR = (
LEFTARG = minivec, RIGHTARG = minivec, PROCEDURE = minivec_eq,
COMMUTATOR = = , NEGATOR = <> ,
RESTRICT = eqsel, JOIN = eqjoinsel
);
CREATE OPERATOR <> (
LEFTARG = minivec, RIGHTARG = minivec, PROCEDURE = minivec_ne,
COMMUTATOR = <> , NEGATOR = = ,
RESTRICT = eqsel, JOIN = eqjoinsel
);
CREATE OPERATOR >= (
LEFTARG = minivec, RIGHTARG = minivec, PROCEDURE = minivec_ge,
COMMUTATOR = <= , NEGATOR = < ,
RESTRICT = scalargesel, JOIN = scalargejoinsel
);
CREATE OPERATOR > (
LEFTARG = minivec, RIGHTARG = minivec, PROCEDURE = minivec_gt,
COMMUTATOR = < , NEGATOR = <= ,
RESTRICT = scalargtsel, JOIN = scalargtjoinsel
);
-- minivec op classes
CREATE OPERATOR CLASS minivec_ops
DEFAULT FOR TYPE minivec USING btree AS
OPERATOR 1 < ,
OPERATOR 2 <= ,
OPERATOR 3 = ,
OPERATOR 4 >= ,
OPERATOR 5 > ,
FUNCTION 1 minivec_cmp(minivec, minivec);
CREATE OPERATOR CLASS minivec_l2_ops
FOR TYPE minivec USING ivfflat AS
OPERATOR 1 <-> (minivec, minivec) FOR ORDER BY float_ops,
FUNCTION 1 minivec_l2_squared_distance(minivec, minivec),
FUNCTION 3 l2_distance(minivec, minivec),
FUNCTION 5 ivfflat_minivec_support(internal);
CREATE OPERATOR CLASS minivec_ip_ops
FOR TYPE minivec USING ivfflat AS
OPERATOR 1 <#> (minivec, minivec) FOR ORDER BY float_ops,
FUNCTION 1 minivec_negative_inner_product(minivec, minivec),
FUNCTION 3 minivec_spherical_distance(minivec, minivec),
FUNCTION 4 l2_norm(minivec),
FUNCTION 5 ivfflat_minivec_support(internal);
CREATE OPERATOR CLASS minivec_cosine_ops
FOR TYPE minivec USING ivfflat AS
OPERATOR 1 <=> (minivec, minivec) FOR ORDER BY float_ops,
FUNCTION 1 cosine_distance(minivec, minivec),
FUNCTION 2 l2_norm(minivec),
FUNCTION 3 minivec_spherical_distance(minivec, minivec),
FUNCTION 4 l2_norm(minivec),
FUNCTION 5 ivfflat_minivec_support(internal);
CREATE OPERATOR CLASS minivec_l2_ops
FOR TYPE minivec USING hnsw AS
OPERATOR 1 <-> (minivec, minivec) FOR ORDER BY float_ops,
FUNCTION 1 minivec_l2_squared_distance(minivec, minivec),
FUNCTION 3 hnsw_minivec_support(internal);
CREATE OPERATOR CLASS minivec_ip_ops
FOR TYPE minivec USING hnsw AS
OPERATOR 1 <#> (minivec, minivec) FOR ORDER BY float_ops,
FUNCTION 1 minivec_negative_inner_product(minivec, minivec),
FUNCTION 3 hnsw_minivec_support(internal);
CREATE OPERATOR CLASS minivec_cosine_ops
FOR TYPE minivec USING hnsw AS
OPERATOR 1 <=> (minivec, minivec) FOR ORDER BY float_ops,
FUNCTION 1 cosine_distance(minivec, minivec),
FUNCTION 2 l2_norm(minivec),
FUNCTION 3 hnsw_minivec_support(internal);
CREATE OPERATOR CLASS minivec_l1_ops
FOR TYPE minivec USING hnsw AS
OPERATOR 1 <+> (minivec, minivec) FOR ORDER BY float_ops,
FUNCTION 1 l1_distance(minivec, minivec),
FUNCTION 3 hnsw_minivec_support(internal);
-- bit functions
CREATE FUNCTION hamming_distance(bit, bit) RETURNS float8

View File

@@ -159,6 +159,24 @@ CheckStateArray(ArrayType *statearray, const char *caller)
return (float8 *) ARR_DATA_PTR(statearray);
}
#if PG_VERSION_NUM < 120003
static pg_noinline void
float_overflow_error(void)
{
ereport(ERROR,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("value out of range: overflow")));
}
static pg_noinline void
float_underflow_error(void)
{
ereport(ERROR,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("value out of range: underflow")));
}
#endif
/*
* Convert textual representation to internal representation
*/

View File

@@ -159,9 +159,6 @@ HnswOptionalProcInfo(Relation index, uint16 procnum)
Datum
HnswNormValue(const HnswTypeInfo * typeInfo, Oid collation, Datum value)
{
if (!typeInfo->normalize)
return value;
return DirectFunctionCall1Coll(typeInfo->normalize, collation, value);
}
@@ -1330,7 +1327,6 @@ HnswFindElementNeighbors(char *base, HnswElement element, HnswElement entryPoint
PGDLLEXPORT Datum l2_normalize(PG_FUNCTION_ARGS);
PGDLLEXPORT Datum halfvec_l2_normalize(PG_FUNCTION_ARGS);
PGDLLEXPORT Datum minivec_l2_normalize(PG_FUNCTION_ARGS);
PGDLLEXPORT Datum sparsevec_l2_normalize(PG_FUNCTION_ARGS);
static void
@@ -1379,20 +1375,6 @@ hnsw_halfvec_support(PG_FUNCTION_ARGS)
PG_RETURN_POINTER(&typeInfo);
};
FUNCTION_PREFIX PG_FUNCTION_INFO_V1(hnsw_minivec_support);
Datum
hnsw_minivec_support(PG_FUNCTION_ARGS)
{
static const HnswTypeInfo typeInfo = {
.maxDimensions = HNSW_MAX_DIM * 4,
/* Do not normalize to maximize precision */
.normalize = NULL,
.checkValue = NULL
};
PG_RETURN_POINTER(&typeInfo);
};
FUNCTION_PREFIX PG_FUNCTION_INFO_V1(hnsw_bit_support);
Datum
hnsw_bit_support(PG_FUNCTION_ARGS)

View File

@@ -17,6 +17,8 @@
#endif
int ivfflat_probes;
int ivfflat_max_probes;
bool ivfflat_streaming;
static relopt_kind ivfflat_relopt_kind;
/*
@@ -33,6 +35,14 @@ IvfflatInit(void)
"Valid range is 1..lists.", &ivfflat_probes,
IVFFLAT_DEFAULT_PROBES, IVFFLAT_MIN_LISTS, IVFFLAT_MAX_LISTS, PGC_USERSET, 0, NULL, NULL, NULL);
DefineCustomIntVariable("ivfflat.max_probes", "Sets the max number of probes for iterative scans",
NULL, &ivfflat_max_probes,
-1, IVFFLAT_MIN_LISTS, IVFFLAT_MAX_LISTS, PGC_USERSET, 0, NULL, NULL, NULL);
DefineCustomBoolVariable("ivfflat.streaming", "Use streaming mode",
NULL, &ivfflat_streaming,
IVFFLAT_DEFAULT_STREAMING, PGC_USERSET, 0, NULL, NULL, NULL);
MarkGUCPrefixReserved("ivfflat");
}
@@ -57,6 +67,35 @@ ivfflatbuildphasename(int64 phasenum)
}
}
/*
* Estimate the number of probes for iterative scans
*/
static int
EstimateProbes(PlannerInfo *root, IndexPath *path, int lists)
{
double selectivity = 1;
ListCell *lc;
double tuplesPerList;
/* Cannot estimate without limit */
/* limit_tuples includes offset */
if (root->limit_tuples < 0)
return 0;
/* Get the selectivity of non-index conditions */
foreach(lc, path->indexinfo->indrestrictinfo)
{
RestrictInfo *rinfo = lfirst(lc);
/* Skip DEFAULT_INEQ_SEL since it may be a distance filter */
if (rinfo->norm_selec >= 0 && rinfo->norm_selec <= 1 && rinfo->norm_selec != (Selectivity) DEFAULT_INEQ_SEL)
selectivity *= rinfo->norm_selec;
}
tuplesPerList = path->indexinfo->tuples / (double) lists;
return root->limit_tuples / (tuplesPerList * selectivity);
}
/*
* Estimate the cost of an index scan
*/
@@ -68,6 +107,7 @@ ivfflatcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
{
GenericCosts costs;
int lists;
int probes;
double ratio;
double spc_seq_page_cost;
Relation index;
@@ -89,8 +129,17 @@ ivfflatcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
IvfflatGetMetaPageInfo(index, &lists, NULL);
index_close(index, NoLock);
probes = ivfflat_probes;
if (ivfflat_streaming)
{
probes = Max(probes, EstimateProbes(root, path, lists));
if (ivfflat_max_probes != -1)
probes = Min(probes, ivfflat_max_probes);
}
/* Get the ratio of lists that we need to visit */
ratio = ((double) ivfflat_probes) / lists;
ratio = ((double) probes) / lists;
if (ratio > 1.0)
ratio = 1.0;

View File

@@ -43,6 +43,7 @@
#define IVFFLAT_MIN_LISTS 1
#define IVFFLAT_MAX_LISTS 32768
#define IVFFLAT_DEFAULT_PROBES 1
#define IVFFLAT_DEFAULT_STREAMING false
/* Build phases */
/* PROGRESS_CREATEIDX_SUBPHASE_INITIALIZE is 1 */
@@ -80,6 +81,8 @@
/* Variables */
extern int ivfflat_probes;
extern int ivfflat_max_probes;
extern bool ivfflat_streaming;
typedef struct VectorArrayData
{
@@ -247,8 +250,10 @@ typedef struct IvfflatScanOpaqueData
{
const IvfflatTypeInfo *typeInfo;
int probes;
int maxProbes;
int dimensions;
bool first;
Datum value;
/* Sorting */
Tuplesortstate *sortstate;
@@ -265,6 +270,8 @@ typedef struct IvfflatScanOpaqueData
/* Lists */
pairingheap *listQueue;
BlockNumber *startPages;
int currentIndex;
IvfflatScanList lists[FLEXIBLE_ARRAY_MEMBER]; /* must come last */
} IvfflatScanOpaqueData;

View File

@@ -65,7 +65,7 @@ GetScanLists(IndexScanDesc scan, Datum value)
/* Use procinfo from the index instead of scan key for performance */
distance = DatumGetFloat8(so->distfunc(so->procinfo, so->collation, PointerGetDatum(&list->center), value));
if (listCount < so->probes)
if (listCount < so->maxProbes)
{
IvfflatScanList *scanlist;
@@ -102,6 +102,11 @@ GetScanLists(IndexScanDesc scan, Datum value)
UnlockReleaseBuffer(cbuf);
}
for (int i = listCount - 1; i >= 0; i--)
so->startPages[i] = GetScanList(pairingheap_remove_first(so->listQueue))->startPage;
Assert(pairingheap_is_empty(so->listQueue));
}
/*
@@ -114,11 +119,14 @@ GetScanItems(IndexScanDesc scan, Datum value)
TupleDesc tupdesc = RelationGetDescr(scan->indexRelation);
double tuples = 0;
TupleTableSlot *slot = so->vslot;
int batchProbes = 0;
tuplesort_reset(so->sortstate);
/* Search closest probes lists */
while (!pairingheap_is_empty(so->listQueue))
while (so->currentIndex < so->maxProbes && (++batchProbes) <= so->probes)
{
BlockNumber searchPage = GetScanList(pairingheap_remove_first(so->listQueue))->startPage;
BlockNumber searchPage = so->startPages[so->currentIndex++];
/* Search all entry pages for list */
while (BlockNumberIsValid(searchPage))
@@ -166,13 +174,17 @@ GetScanItems(IndexScanDesc scan, Datum value)
}
}
if (tuples < 100)
if (tuples < 100 && !ivfflat_streaming)
ereport(DEBUG1,
(errmsg("index scan found few tuples"),
errdetail("Index may have been created with little data."),
errhint("Recreate the index and possibly decrease lists.")));
tuplesort_performsort(so->sortstate);
#if defined(IVFFLAT_MEMORY)
elog(INFO, "memory: %zu MB", MemoryContextMemAllocated(CurrentMemoryContext, true) / (1024 * 1024));
#endif
}
/*
@@ -240,6 +252,7 @@ ivfflatbeginscan(Relation index, int nkeys, int norderbys)
int lists;
int dimensions;
int probes = ivfflat_probes;
int maxProbes;
scan = RelationGetIndexScan(index, nkeys, norderbys);
@@ -249,10 +262,21 @@ ivfflatbeginscan(Relation index, int nkeys, int norderbys)
if (probes > lists)
probes = lists;
so = (IvfflatScanOpaque) palloc(offsetof(IvfflatScanOpaqueData, lists) + probes * sizeof(IvfflatScanList));
if (ivfflat_streaming)
{
if (ivfflat_max_probes == -1)
maxProbes = lists;
else
maxProbes = Min(ivfflat_max_probes, lists);
}
else
maxProbes = probes;
so = (IvfflatScanOpaque) palloc(offsetof(IvfflatScanOpaqueData, lists) + maxProbes * sizeof(IvfflatScanList));
so->typeInfo = IvfflatGetTypeInfo(index);
so->first = true;
so->probes = probes;
so->maxProbes = maxProbes;
so->dimensions = dimensions;
/* Set support functions */
@@ -280,6 +304,8 @@ ivfflatbeginscan(Relation index, int nkeys, int norderbys)
so->bas = GetAccessStrategy(BAS_BULKREAD);
so->listQueue = pairingheap_allocate(CompareLists, scan);
so->startPages = palloc(maxProbes * sizeof(BlockNumber));
so->currentIndex = 0;
scan->opaque = so;
@@ -294,11 +320,9 @@ ivfflatrescan(IndexScanDesc scan, ScanKey keys, int nkeys, ScanKey orderbys, int
{
IvfflatScanOpaque so = (IvfflatScanOpaque) scan->opaque;
if (!so->first)
tuplesort_reset(so->sortstate);
so->first = true;
pairingheap_reset(so->listQueue);
so->currentIndex = 0;
if (keys && scan->numberOfKeys > 0)
memmove(scan->keyData, keys, scan->numberOfKeys * sizeof(ScanKeyData));
@@ -314,6 +338,8 @@ bool
ivfflatgettuple(IndexScanDesc scan, ScanDirection dir)
{
IvfflatScanOpaque so = (IvfflatScanOpaque) scan->opaque;
ItemPointer heaptid;
bool isnull;
/*
* Index can be used to scan backward, but Postgres doesn't support
@@ -341,28 +367,25 @@ ivfflatgettuple(IndexScanDesc scan, ScanDirection dir)
IvfflatBench("GetScanLists", GetScanLists(scan, value));
IvfflatBench("GetScanItems", GetScanItems(scan, value));
so->first = false;
so->value = value;
#if defined(IVFFLAT_MEMORY)
elog(INFO, "memory: %zu MB", MemoryContextMemAllocated(CurrentMemoryContext, true) / (1024 * 1024));
#endif
/* Clean up if we allocated a new value */
if (value != scan->orderByData->sk_argument)
pfree(DatumGetPointer(value));
/* TODO clean up if we allocated a new value */
}
if (tuplesort_gettupleslot(so->sortstate, true, false, so->mslot, NULL))
while (!tuplesort_gettupleslot(so->sortstate, true, false, so->mslot, NULL))
{
bool isnull;
ItemPointer heaptid = (ItemPointer) DatumGetPointer(slot_getattr(so->mslot, 2, &isnull));
if (so->currentIndex == so->maxProbes)
return false;
scan->xs_heaptid = *heaptid;
scan->xs_recheck = false;
scan->xs_recheckorderby = false;
return true;
IvfflatBench("GetScanItems", GetScanItems(scan, so->value));
}
return false;
heaptid = (ItemPointer) DatumGetPointer(slot_getattr(so->mslot, 2, &isnull));
scan->xs_heaptid = *heaptid;
scan->xs_recheck = false;
scan->xs_recheckorderby = false;
return true;
}
/*
@@ -374,6 +397,7 @@ ivfflatendscan(IndexScanDesc scan)
IvfflatScanOpaque so = (IvfflatScanOpaque) scan->opaque;
pairingheap_free(so->listQueue);
pfree(so->startPages);
tuplesort_end(so->sortstate);
FreeAccessStrategy(so->bas);
FreeTupleDesc(so->tupdesc);

View File

@@ -7,7 +7,6 @@
#include "halfutils.h"
#include "halfvec.h"
#include "ivfflat.h"
#include "minivec.h"
#include "storage/bufmgr.h"
/*
@@ -71,9 +70,6 @@ IvfflatOptionalProcInfo(Relation index, uint16 procnum)
Datum
IvfflatNormValue(const IvfflatTypeInfo * typeInfo, Oid collation, Datum value)
{
if (!typeInfo->normalize)
return value;
return DirectFunctionCall1Coll(typeInfo->normalize, collation, value);
}
@@ -235,7 +231,6 @@ IvfflatUpdateList(Relation index, ListInfo listInfo,
PGDLLEXPORT Datum l2_normalize(PG_FUNCTION_ARGS);
PGDLLEXPORT Datum halfvec_l2_normalize(PG_FUNCTION_ARGS);
PGDLLEXPORT Datum minivec_l2_normalize(PG_FUNCTION_ARGS);
PGDLLEXPORT Datum sparsevec_l2_normalize(PG_FUNCTION_ARGS);
static Size
@@ -250,12 +245,6 @@ HalfvecItemSize(int dimensions)
return HALFVEC_SIZE(dimensions);
}
static Size
MinivecItemSize(int dimensions)
{
return MINIVEC_SIZE(dimensions);
}
static Size
BitItemSize(int dimensions)
{
@@ -286,18 +275,6 @@ HalfvecUpdateCenter(Pointer v, int dimensions, float *x)
vec->x[k] = Float4ToHalfUnchecked(x[k]);
}
static void
MinivecUpdateCenter(Pointer v, int dimensions, float *x)
{
MiniVector *vec = (MiniVector *) v;
SET_VARSIZE(vec, MINIVEC_SIZE(dimensions));
vec->dim = dimensions;
for (int k = 0; k < dimensions; k++)
vec->x[k] = Float4ToFp8Unchecked(x[k]);
}
static void
BitUpdateCenter(Pointer v, int dimensions, float *x)
{
@@ -332,15 +309,6 @@ HalfvecSumCenter(Pointer v, float *x)
x[k] += HalfToFloat4(vec->x[k]);
}
static void
MinivecSumCenter(Pointer v, float *x)
{
MiniVector *vec = (MiniVector *) v;
for (int k = 0; k < vec->dim; k++)
x[k] += Fp8ToFloat4(vec->x[k]);
}
static void
BitSumCenter(Pointer v, float *x)
{
@@ -389,22 +357,6 @@ ivfflat_halfvec_support(PG_FUNCTION_ARGS)
PG_RETURN_POINTER(&typeInfo);
};
FUNCTION_PREFIX PG_FUNCTION_INFO_V1(ivfflat_minivec_support);
Datum
ivfflat_minivec_support(PG_FUNCTION_ARGS)
{
static const IvfflatTypeInfo typeInfo = {
.maxDimensions = IVFFLAT_MAX_DIM * 4,
/* Do not normalize to maximize precision */
.normalize = NULL,
.itemSize = MinivecItemSize,
.updateCenter = MinivecUpdateCenter,
.sumCenter = MinivecSumCenter
};
PG_RETURN_POINTER(&typeInfo);
};
FUNCTION_PREFIX PG_FUNCTION_INFO_V1(ivfflat_bit_support);
Datum
ivfflat_bit_support(PG_FUNCTION_ARGS)

File diff suppressed because it is too large Load Diff

View File

@@ -1,177 +0,0 @@
#ifndef MINIVEC_H
#define MINIVEC_H
#define MINIVEC_MAX_DIM 16000
#define fp8 uint8
#define MINIVEC_SIZE(_dim) (offsetof(MiniVector, x) + sizeof(fp8)*(_dim))
#define DatumGetMiniVector(x) ((MiniVector *) PG_DETOAST_DATUM(x))
#define PG_GETARG_MINIVEC_P(x) DatumGetMiniVector(PG_GETARG_DATUM(x))
#define PG_RETURN_MINIVEC_P(x) PG_RETURN_POINTER(x)
typedef struct MiniVector
{
int32 vl_len_; /* varlena header (do not touch directly!) */
int16 dim; /* number of dimensions */
int16 unused; /* reserved for future use, always zero */
fp8 x[FLEXIBLE_ARRAY_MEMBER];
} MiniVector;
MiniVector *InitMiniVector(int dim);
/*
* Check if fp8 is NaN
*/
static inline bool
Fp8IsNan(fp8 num)
{
return (num & 0x7C) == 0x7C && (num & 0x7F) != 0x7C;
}
/*
* Check if fp8 is infinite
*/
static inline bool
Fp8IsInf(fp8 num)
{
return (num & 0x7F) == 0x7C;
}
/*
* Check if fp8 is zero
*/
static inline bool
Fp8IsZero(fp8 num)
{
return num == 0;
}
/*
* Convert a fp8 to a float4
*/
static inline float
Fp8ToFloat4(fp8 num)
{
/* Lookup table for non-sign bits */
/* Uses uint32 for correctness */
uint32 lookup[128] = {0, 931135488, 939524096, 943718400, 947912704, 950009856, 952107008, 954204160, 956301312, 958398464, 960495616, 962592768, 964689920, 966787072, 968884224, 970981376, 973078528, 975175680, 977272832, 979369984, 981467136, 983564288, 985661440, 987758592, 989855744, 991952896, 994050048, 996147200, 998244352, 1000341504, 1002438656, 1004535808, 1006632960, 1008730112, 1010827264, 1012924416, 1015021568, 1017118720, 1019215872, 1021313024, 1023410176, 1025507328, 1027604480, 1029701632, 1031798784, 1033895936, 1035993088, 1038090240, 1040187392, 1042284544, 1044381696, 1046478848, 1048576000, 1050673152, 1052770304, 1054867456, 1056964608, 1059061760, 1061158912, 1063256064, 1065353216, 1067450368, 1069547520, 1071644672, 1073741824, 1075838976, 1077936128, 1080033280, 1082130432, 1084227584, 1086324736, 1088421888, 1090519040, 1092616192, 1094713344, 1096810496, 1098907648, 1101004800, 1103101952, 1105199104, 1107296256, 1109393408, 1111490560, 1113587712, 1115684864, 1117782016, 1119879168, 1121976320, 1124073472, 1126170624, 1128267776, 1130364928, 1132462080, 1134559232, 1136656384, 1138753536, 1140850688, 1142947840, 1145044992, 1147142144, 1149239296, 1151336448, 1153433600, 1155530752, 1157627904, 1159725056, 1161822208, 1163919360, 1166016512, 1168113664, 1170210816, 1172307968, 1174405120, 1176502272, 1178599424, 1180696576, 1182793728, 1184890880, 1186988032, 1189085184, 1191182336, 1193279488, 1195376640, 1197473792, 2139095040, 2145386496, 2143289344, 2145386496};
union
{
float f;
uint32 i;
} swap;
swap.i = lookup[num & 0x7F];
return (num & 0x80) == 0x80 ? -swap.f : swap.f;
}
/*
* Convert a float4 to a fp8
*/
static inline fp8
Float4ToFp8Unchecked(float num)
{
union
{
float f;
uint32 i;
} swap;
uint32 bin;
int exponent;
int mantissa;
uint8 result;
swap.f = num;
bin = swap.i;
exponent = (bin & 0x7F800000) >> 23;
mantissa = bin & 0x007FFFFF;
/* Sign */
result = (bin & 0x80000000) >> 24;
if (isinf(num))
{
/* Infinite */
result |= 0x7C;
}
else if (isnan(num))
{
/* NaN */
result |= 0x7F;
}
else if (exponent > 98)
{
int m;
int gr;
int s;
exponent -= 127;
s = mantissa & 0x000FFFFF;
/* Subnormal */
if (exponent < -14)
{
int diff = -exponent - 14;
mantissa >>= diff;
mantissa += 1 << (23 - diff);
s |= mantissa & 0x000FFFFF;
}
m = mantissa >> 21;
/* Round */
gr = (mantissa >> 20) % 4;
if (gr == 3 || (gr == 1 && s != 0))
m += 1;
if (m == 4)
{
m = 0;
exponent += 1;
}
if (exponent > 15)
{
/* Infinite */
result |= 0x7C;
}
else
{
if (exponent >= -14)
result |= (exponent + 15) << 2;
result |= m;
}
}
return result;
}
/*
* Convert a float4 to a fp8
*/
static inline fp8
Float4ToFp8(float num)
{
fp8 result = Float4ToFp8Unchecked(num);
if (unlikely(Fp8IsInf(result)) && !isinf(num))
{
char *buf = palloc(FLOAT_SHORTEST_DECIMAL_LEN);
float_to_shortest_decimal_buf(num, buf);
ereport(ERROR,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("\"%s\" is out of range for type minivec", buf)));
}
return result;
}
#endif

View File

@@ -13,7 +13,6 @@
#include "ivfflat.h"
#include "lib/stringinfo.h"
#include "libpq/pqformat.h"
#include "minivec.h"
#include "port.h" /* for strtof() */
#include "sparsevec.h"
#include "utils/array.h"
@@ -156,6 +155,24 @@ CheckStateArray(ArrayType *statearray, const char *caller)
return (float8 *) ARR_DATA_PTR(statearray);
}
#if PG_VERSION_NUM < 120003
static pg_noinline void
float_overflow_error(void)
{
ereport(ERROR,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("value out of range: overflow")));
}
static pg_noinline void
float_underflow_error(void)
{
ereport(ERROR,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("value out of range: underflow")));
}
#endif
/*
* Convert textual representation to internal representation
*/
@@ -543,28 +560,6 @@ halfvec_to_vector(PG_FUNCTION_ARGS)
PG_RETURN_POINTER(result);
}
/*
* Convert fp8 vector to vector
*/
FUNCTION_PREFIX PG_FUNCTION_INFO_V1(minivec_to_vector);
Datum
minivec_to_vector(PG_FUNCTION_ARGS)
{
MiniVector *vec = PG_GETARG_MINIVEC_P(0);
int32 typmod = PG_GETARG_INT32(1);
Vector *result;
CheckDim(vec->dim);
CheckExpectedDim(typmod, vec->dim);
result = InitVector(vec->dim);
for (int i = 0; i < vec->dim; i++)
result->x[i] = Fp8ToFloat4(vec->x[i]);
PG_RETURN_POINTER(result);
}
VECTOR_TARGET_CLONES static float
VectorL2SquaredDistance(int dim, float *ax, float *bx)
{

View File

@@ -38,26 +38,6 @@ SELECT * FROM t ORDER BY val;
(4 rows)
DROP TABLE t;
-- minivec
CREATE TABLE t (val minivec(3));
INSERT INTO t (val) VALUES ('[0,0,0]'), ('[1,2,3]'), ('[1,1,1]'), (NULL);
CREATE INDEX ON t (val);
SELECT * FROM t WHERE val = '[1,2,3]';
val
---------
[1,2,3]
(1 row)
SELECT * FROM t ORDER BY val;
val
---------
[0,0,0]
[1,1,1]
[1,2,3]
(4 rows)
DROP TABLE t;
-- sparsevec
CREATE TABLE t (val sparsevec(3));

View File

@@ -140,64 +140,6 @@ SELECT '{1e-8,-1e-8}'::real[]::halfvec;
[0,-0]
(1 row)
SELECT '[1,2,3]'::vector::minivec;
minivec
---------
[1,2,3]
(1 row)
SELECT '[1,2,3]'::vector::minivec(3);
minivec
---------
[1,2,3]
(1 row)
SELECT '[1,2,3]'::vector::minivec(2);
ERROR: expected 2 dimensions, not 3
SELECT '[61440]'::vector::minivec;
ERROR: "61440" is out of range for type minivec
SELECT '[1e-8]'::vector::minivec;
minivec
---------
[0]
(1 row)
SELECT '[1,2,3]'::minivec::vector;
vector
---------
[1,2,3]
(1 row)
SELECT '[1,2,3]'::minivec::vector(3);
vector
---------
[1,2,3]
(1 row)
SELECT '[1,2,3]'::minivec::vector(2);
ERROR: expected 2 dimensions, not 3
SELECT '{1,2,3}'::real[]::minivec;
minivec
---------
[1,2,3]
(1 row)
SELECT '{1,2,3}'::real[]::minivec(3);
minivec
---------
[1,2,3]
(1 row)
SELECT '{1,2,3}'::real[]::minivec(2);
ERROR: expected 2 dimensions, not 3
SELECT '{61440,-61440}'::real[]::minivec;
ERROR: "61440" is out of range for type minivec
SELECT '{1e-8,-1e-8}'::real[]::minivec;
minivec
---------
[0,-0]
(1 row)
SELECT '[0,1.5,0,3.5,0]'::vector::sparsevec;
sparsevec
-----------------

View File

@@ -30,23 +30,6 @@ SELECT * FROM t2 ORDER BY val;
(4 rows)
DROP TABLE t;
DROP TABLE t2;
-- minivec
CREATE TABLE t (val minivec(3));
INSERT INTO t (val) VALUES ('[0,0,0]'), ('[1,2,3]'), ('[1,1,1]'), (NULL);
CREATE TABLE t2 (val minivec(3));
\copy t TO 'results/minivec.bin' WITH (FORMAT binary)
\copy t2 FROM 'results/minivec.bin' WITH (FORMAT binary)
SELECT * FROM t2 ORDER BY val;
val
---------
[0,0,0]
[1,1,1]
[1,2,3]
(4 rows)
DROP TABLE t;
DROP TABLE t2;
-- sparsevec

View File

@@ -1,84 +0,0 @@
SET enable_seqscan = off;
-- L2
CREATE TABLE t (val minivec(3));
INSERT INTO t (val) VALUES ('[0,0,0]'), ('[1,2,3]'), ('[1,1,1]'), (NULL);
CREATE INDEX ON t USING ivfflat (val minivec_l2_ops) WITH (lists = 1);
INSERT INTO t (val) VALUES ('[1,2,4]');
SELECT * FROM t ORDER BY val <-> '[3,3,3]';
val
---------
[1,2,3]
[1,2,4]
[1,1,1]
[0,0,0]
(4 rows)
SELECT COUNT(*) FROM (SELECT * FROM t ORDER BY val <-> (SELECT NULL::minivec)) t2;
count
-------
4
(1 row)
SELECT COUNT(*) FROM t;
count
-------
5
(1 row)
TRUNCATE t;
NOTICE: ivfflat index created with little data
DETAIL: This will cause low recall.
HINT: Drop the index until the table has more data.
SELECT * FROM t ORDER BY val <-> '[3,3,3]';
val
-----
(0 rows)
DROP TABLE t;
-- inner product
CREATE TABLE t (val minivec(3));
INSERT INTO t (val) VALUES ('[0,0,0]'), ('[1,2,3]'), ('[1,1,1]'), (NULL);
CREATE INDEX ON t USING ivfflat (val minivec_ip_ops) WITH (lists = 1);
INSERT INTO t (val) VALUES ('[1,2,4]');
SELECT * FROM t ORDER BY val <#> '[3,3,3]';
val
---------
[1,2,4]
[1,2,3]
[1,1,1]
[0,0,0]
(4 rows)
SELECT COUNT(*) FROM (SELECT * FROM t ORDER BY val <#> (SELECT NULL::minivec)) t2;
count
-------
4
(1 row)
DROP TABLE t;
-- cosine
CREATE TABLE t (val minivec(3));
INSERT INTO t (val) VALUES ('[0,0,0]'), ('[1,2,3]'), ('[1,1,1]'), (NULL);
CREATE INDEX ON t USING ivfflat (val minivec_cosine_ops) WITH (lists = 1);
INSERT INTO t (val) VALUES ('[1,2,4]');
SELECT * FROM t ORDER BY val <=> '[3,3,3]';
val
---------
[1,1,1]
[1,2,3]
[1,2,4]
(3 rows)
SELECT COUNT(*) FROM (SELECT * FROM t ORDER BY val <=> '[0,0,0]') t2;
count
-------
3
(1 row)
SELECT COUNT(*) FROM (SELECT * FROM t ORDER BY val <=> (SELECT NULL::minivec)) t2;
count
-------
3
(1 row)
DROP TABLE t;

View File

@@ -1,590 +0,0 @@
SELECT '[1,2,3]'::minivec;
minivec
---------
[1,2,3]
(1 row)
SELECT '[-1,-2,-3]'::minivec;
minivec
------------
[-1,-2,-3]
(1 row)
SELECT '[1.,2.,3.]'::minivec;
minivec
---------
[1,2,3]
(1 row)
SELECT ' [ 1, 2 , 3 ] '::minivec;
minivec
---------
[1,2,3]
(1 row)
SELECT '[1.23456]'::minivec;
minivec
---------
[1.25]
(1 row)
SELECT '[hello,1]'::minivec;
ERROR: invalid input syntax for type minivec: "[hello,1]"
LINE 1: SELECT '[hello,1]'::minivec;
^
SELECT '[NaN,1]'::minivec;
ERROR: NaN not allowed in minivec
LINE 1: SELECT '[NaN,1]'::minivec;
^
SELECT '[Infinity,1]'::minivec;
ERROR: infinite value not allowed in minivec
LINE 1: SELECT '[Infinity,1]'::minivec;
^
SELECT '[-Infinity,1]'::minivec;
ERROR: infinite value not allowed in minivec
LINE 1: SELECT '[-Infinity,1]'::minivec;
^
SELECT '[61439,-61439]'::minivec;
minivec
----------------
[57344,-57344]
(1 row)
SELECT '[61440,-61440]'::minivec;
ERROR: "61440" is out of range for type minivec
LINE 1: SELECT '[61440,-61440]'::minivec;
^
SELECT '[1e-8,-1e-8]'::minivec;
minivec
---------
[0,-0]
(1 row)
SELECT '[4e38,1]'::minivec;
ERROR: "4e38" is out of range for type minivec
LINE 1: SELECT '[4e38,1]'::minivec;
^
SELECT '[1e-46,1]'::minivec;
minivec
---------
[0,1]
(1 row)
SELECT '[1,2,3'::minivec;
ERROR: invalid input syntax for type minivec: "[1,2,3"
LINE 1: SELECT '[1,2,3'::minivec;
^
SELECT '[1,2,3]9'::minivec;
ERROR: invalid input syntax for type minivec: "[1,2,3]9"
LINE 1: SELECT '[1,2,3]9'::minivec;
^
DETAIL: Junk after closing right brace.
SELECT '1,2,3'::minivec;
ERROR: invalid input syntax for type minivec: "1,2,3"
LINE 1: SELECT '1,2,3'::minivec;
^
DETAIL: Vector contents must start with "[".
SELECT ''::minivec;
ERROR: invalid input syntax for type minivec: ""
LINE 1: SELECT ''::minivec;
^
DETAIL: Vector contents must start with "[".
SELECT '['::minivec;
ERROR: invalid input syntax for type minivec: "["
LINE 1: SELECT '['::minivec;
^
SELECT '[ '::minivec;
ERROR: invalid input syntax for type minivec: "[ "
LINE 1: SELECT '[ '::minivec;
^
SELECT '[,'::minivec;
ERROR: invalid input syntax for type minivec: "[,"
LINE 1: SELECT '[,'::minivec;
^
SELECT '[]'::minivec;
ERROR: minivec must have at least 1 dimension
LINE 1: SELECT '[]'::minivec;
^
SELECT '[ ]'::minivec;
ERROR: minivec must have at least 1 dimension
LINE 1: SELECT '[ ]'::minivec;
^
SELECT '[,]'::minivec;
ERROR: invalid input syntax for type minivec: "[,]"
LINE 1: SELECT '[,]'::minivec;
^
SELECT '[1,]'::minivec;
ERROR: invalid input syntax for type minivec: "[1,]"
LINE 1: SELECT '[1,]'::minivec;
^
SELECT '[1a]'::minivec;
ERROR: invalid input syntax for type minivec: "[1a]"
LINE 1: SELECT '[1a]'::minivec;
^
SELECT '[1,,3]'::minivec;
ERROR: invalid input syntax for type minivec: "[1,,3]"
LINE 1: SELECT '[1,,3]'::minivec;
^
SELECT '[1, ,3]'::minivec;
ERROR: invalid input syntax for type minivec: "[1, ,3]"
LINE 1: SELECT '[1, ,3]'::minivec;
^
SELECT '[1,2,3]'::minivec(3);
minivec
---------
[1,2,3]
(1 row)
SELECT '[1,2,3]'::minivec(2);
ERROR: expected 2 dimensions, not 3
SELECT '[1,2,3]'::minivec(3, 2);
ERROR: invalid type modifier
LINE 1: SELECT '[1,2,3]'::minivec(3, 2);
^
SELECT '[1,2,3]'::minivec('a');
ERROR: invalid input syntax for type integer: "a"
LINE 1: SELECT '[1,2,3]'::minivec('a');
^
SELECT '[1,2,3]'::minivec(0);
ERROR: dimensions for type minivec must be at least 1
LINE 1: SELECT '[1,2,3]'::minivec(0);
^
SELECT '[1,2,3]'::minivec(16001);
ERROR: dimensions for type minivec cannot exceed 16000
LINE 1: SELECT '[1,2,3]'::minivec(16001);
^
SELECT unnest('{"[1,2,3]", "[4,5,6]"}'::minivec[]);
unnest
---------
[1,2,3]
[4,5,6]
(2 rows)
SELECT '{"[1,2,3]"}'::minivec(2)[];
ERROR: expected 2 dimensions, not 3
SELECT '[1,2,3]'::minivec + '[4,5,6]';
?column?
----------
[5,7,8]
(1 row)
SELECT '[61439]'::minivec + '[61439]';
ERROR: value out of range: overflow
SELECT '[1,2]'::minivec + '[3]';
ERROR: different minivec dimensions 2 and 1
SELECT '[1,2,3]'::minivec - '[4,5,6]';
ERROR: value out of range: overflow
SELECT '[-61439]'::minivec - '[61439]';
?column?
----------
[-0]
(1 row)
SELECT '[1,2]'::minivec - '[3]';
ERROR: different minivec dimensions 2 and 1
SELECT '[1,2,3]'::minivec * '[4,5,6]';
?column?
-----------
[4,10,16]
(1 row)
SELECT '[61439]'::minivec * '[61439]';
ERROR: value out of range: overflow
SELECT '[1e-7]'::minivec * '[1e-7]';
?column?
----------
[0]
(1 row)
SELECT '[1,2]'::minivec * '[3]';
ERROR: different minivec dimensions 2 and 1
SELECT '[1,2,3]'::minivec || '[4,5]';
?column?
-------------
[1,2,3,4,5]
(1 row)
SELECT array_fill(0, ARRAY[16000])::minivec || '[1]';
ERROR: minivec cannot have more than 16000 dimensions
SELECT '[1,2,3]'::minivec < '[1,2,3]';
?column?
----------
f
(1 row)
SELECT '[1,2,3]'::minivec < '[1,2]';
?column?
----------
f
(1 row)
SELECT '[1,2,3]'::minivec <= '[1,2,3]';
?column?
----------
t
(1 row)
SELECT '[1,2,3]'::minivec <= '[1,2]';
?column?
----------
f
(1 row)
SELECT '[1,2,3]'::minivec = '[1,2,3]';
?column?
----------
t
(1 row)
SELECT '[1,2,3]'::minivec = '[1,2]';
?column?
----------
f
(1 row)
SELECT '[1,2,3]'::minivec != '[1,2,3]';
?column?
----------
f
(1 row)
SELECT '[1,2,3]'::minivec != '[1,2]';
?column?
----------
t
(1 row)
SELECT '[1,2,3]'::minivec >= '[1,2,3]';
?column?
----------
t
(1 row)
SELECT '[1,2,3]'::minivec >= '[1,2]';
?column?
----------
t
(1 row)
SELECT '[1,2,3]'::minivec > '[1,2,3]';
?column?
----------
f
(1 row)
SELECT '[1,2,3]'::minivec > '[1,2]';
?column?
----------
t
(1 row)
SELECT minivec_cmp('[1,2,3]', '[1,2,3]');
minivec_cmp
-------------
0
(1 row)
SELECT minivec_cmp('[1,2,3]', '[0,0,0]');
minivec_cmp
-------------
1
(1 row)
SELECT minivec_cmp('[0,0,0]', '[1,2,3]');
minivec_cmp
-------------
-1
(1 row)
SELECT minivec_cmp('[1,2]', '[1,2,3]');
minivec_cmp
-------------
-1
(1 row)
SELECT minivec_cmp('[1,2,3]', '[1,2]');
minivec_cmp
-------------
1
(1 row)
SELECT minivec_cmp('[1,2]', '[2,3,4]');
minivec_cmp
-------------
-1
(1 row)
SELECT minivec_cmp('[2,3]', '[1,2,3]');
minivec_cmp
-------------
1
(1 row)
SELECT vector_dims('[1,2,3]'::minivec);
vector_dims
-------------
3
(1 row)
SELECT round(l2_norm('[1,1]'::minivec)::numeric, 5);
round
---------
1.41421
(1 row)
SELECT l2_norm('[3,4]'::minivec);
l2_norm
---------
5
(1 row)
SELECT l2_norm('[0,1]'::minivec);
l2_norm
---------
1
(1 row)
SELECT l2_norm('[0,0]'::minivec);
l2_norm
---------
0
(1 row)
SELECT l2_norm('[2]'::minivec);
l2_norm
---------
2
(1 row)
SELECT l2_distance('[0,0]'::minivec, '[3,4]');
l2_distance
-------------
5
(1 row)
SELECT l2_distance('[0,0]'::minivec, '[0,1]');
l2_distance
-------------
1
(1 row)
SELECT l2_distance('[1,2]'::minivec, '[3]');
ERROR: different minivec dimensions 2 and 1
SELECT l2_distance('[1,1,1,1,1,1,1,1,1]'::minivec, '[1,1,1,1,1,1,1,4,5]');
l2_distance
-------------
5
(1 row)
SELECT '[0,0]'::minivec <-> '[3,4]';
?column?
----------
5
(1 row)
SELECT inner_product('[1,2]'::minivec, '[3,4]');
inner_product
---------------
11
(1 row)
SELECT inner_product('[1,2]'::minivec, '[3]');
ERROR: different minivec dimensions 2 and 1
SELECT inner_product('[448]'::minivec, '[448]');
inner_product
---------------
200704
(1 row)
SELECT inner_product('[1,1,1,1,1,1,1,1,1]'::minivec, '[1,2,3,4,5,6,7,8,9]');
inner_product
---------------
44
(1 row)
SELECT '[1,2]'::minivec <#> '[3,4]';
?column?
----------
-11
(1 row)
SELECT cosine_distance('[1,2]'::minivec, '[2,4]');
cosine_distance
-----------------
0
(1 row)
SELECT cosine_distance('[1,2]'::minivec, '[0,0]');
cosine_distance
-----------------
NaN
(1 row)
SELECT cosine_distance('[1,1]'::minivec, '[1,1]');
cosine_distance
-----------------
0
(1 row)
SELECT cosine_distance('[1,0]'::minivec, '[0,2]');
cosine_distance
-----------------
1
(1 row)
SELECT cosine_distance('[1,1]'::minivec, '[-1,-1]');
cosine_distance
-----------------
2
(1 row)
SELECT cosine_distance('[1,2]'::minivec, '[3]');
ERROR: different minivec dimensions 2 and 1
SELECT cosine_distance('[1,1]'::minivec, '[1.1,1.1]');
cosine_distance
-----------------
0
(1 row)
SELECT cosine_distance('[1,1]'::minivec, '[-1.1,-1.1]');
cosine_distance
-----------------
2
(1 row)
SELECT cosine_distance('[1,2,3,4,5,6,7,8,9]'::minivec, '[1,2,3,4,5,6,7,8,9]');
cosine_distance
-----------------
0
(1 row)
SELECT cosine_distance('[1,2,3,4,5,6,7,8,9]'::minivec, '[-1,-2,-3,-4,-5,-6,-7,-8,-9]');
cosine_distance
-----------------
2
(1 row)
SELECT '[1,2]'::minivec <=> '[2,4]';
?column?
----------
0
(1 row)
SELECT l1_distance('[0,0]'::minivec, '[3,4]');
l1_distance
-------------
7
(1 row)
SELECT l1_distance('[0,0]'::minivec, '[0,1]');
l1_distance
-------------
1
(1 row)
SELECT l1_distance('[1,2]'::minivec, '[3]');
ERROR: different minivec dimensions 2 and 1
SELECT l1_distance('[1,2,3,4,5,6,7,8,9]'::minivec, '[1,2,3,4,5,6,7,8,9]');
l1_distance
-------------
0
(1 row)
SELECT l1_distance('[1,2,3,4,5,6,7,8,9]'::minivec, '[0,3,2,5,4,7,6,9,8]');
l1_distance
-------------
7
(1 row)
SELECT '[0,0]'::minivec <+> '[3,4]';
?column?
----------
7
(1 row)
SELECT l2_normalize('[3,4]'::minivec);
l2_normalize
--------------
[0.625,0.75]
(1 row)
SELECT l2_normalize('[3,0]'::minivec);
l2_normalize
--------------
[1,0]
(1 row)
SELECT l2_normalize('[0,0.1]'::minivec);
l2_normalize
--------------
[0,1]
(1 row)
SELECT l2_normalize('[0,0]'::minivec);
l2_normalize
--------------
[0,0]
(1 row)
SELECT l2_normalize('[448]'::minivec);
l2_normalize
--------------
[1]
(1 row)
SELECT binary_quantize('[1,0,-1]'::minivec);
binary_quantize
-----------------
100
(1 row)
SELECT binary_quantize('[0,0.1,-0.2,-0.3,0.4,0.5,0.6,-0.7,0.8,-0.9,1]'::minivec);
binary_quantize
-----------------
01001110101
(1 row)
SELECT subvector('[1,2,3,4,5]'::minivec, 1, 3);
subvector
-----------
[1,2,3]
(1 row)
SELECT subvector('[1,2,3,4,5]'::minivec, 3, 2);
subvector
-----------
[3,4]
(1 row)
SELECT subvector('[1,2,3,4,5]'::minivec, -1, 3);
subvector
-----------
[1]
(1 row)
SELECT subvector('[1,2,3,4,5]'::minivec, 3, 9);
subvector
-----------
[3,4,5]
(1 row)
SELECT subvector('[1,2,3,4,5]'::minivec, 1, 0);
ERROR: minivec must have at least 1 dimension
SELECT subvector('[1,2,3,4,5]'::minivec, 3, -1);
ERROR: minivec must have at least 1 dimension
SELECT subvector('[1,2,3,4,5]'::minivec, -1, 2);
ERROR: minivec must have at least 1 dimension
SELECT subvector('[1,2,3,4,5]'::minivec, 2147483647, 10);
ERROR: minivec must have at least 1 dimension
SELECT subvector('[1,2,3,4,5]'::minivec, 3, 2147483647);
subvector
-----------
[3,4,5]
(1 row)
SELECT subvector('[1,2,3,4,5]'::minivec, -2147483644, 2147483647);
subvector
-----------
[1,2]
(1 row)

View File

@@ -22,17 +22,6 @@ SELECT * FROM t ORDER BY val;
DROP TABLE t;
-- minivec
CREATE TABLE t (val minivec(3));
INSERT INTO t (val) VALUES ('[0,0,0]'), ('[1,2,3]'), ('[1,1,1]'), (NULL);
CREATE INDEX ON t (val);
SELECT * FROM t WHERE val = '[1,2,3]';
SELECT * FROM t ORDER BY val;
DROP TABLE t;
-- sparsevec
CREATE TABLE t (val sparsevec(3));

View File

@@ -38,22 +38,6 @@ SELECT '{1,2,3}'::real[]::halfvec(2);
SELECT '{65520,-65520}'::real[]::halfvec;
SELECT '{1e-8,-1e-8}'::real[]::halfvec;
SELECT '[1,2,3]'::vector::minivec;
SELECT '[1,2,3]'::vector::minivec(3);
SELECT '[1,2,3]'::vector::minivec(2);
SELECT '[61440]'::vector::minivec;
SELECT '[1e-8]'::vector::minivec;
SELECT '[1,2,3]'::minivec::vector;
SELECT '[1,2,3]'::minivec::vector(3);
SELECT '[1,2,3]'::minivec::vector(2);
SELECT '{1,2,3}'::real[]::minivec;
SELECT '{1,2,3}'::real[]::minivec(3);
SELECT '{1,2,3}'::real[]::minivec(2);
SELECT '{61440,-61440}'::real[]::minivec;
SELECT '{1e-8,-1e-8}'::real[]::minivec;
SELECT '[0,1.5,0,3.5,0]'::vector::sparsevec;
SELECT '[0,1.5,0,3.5,0]'::vector::sparsevec(5);
SELECT '[0,1.5,0,3.5,0]'::vector::sparsevec(4);

View File

@@ -28,21 +28,6 @@ SELECT * FROM t2 ORDER BY val;
DROP TABLE t;
DROP TABLE t2;
-- minivec
CREATE TABLE t (val minivec(3));
INSERT INTO t (val) VALUES ('[0,0,0]'), ('[1,2,3]'), ('[1,1,1]'), (NULL);
CREATE TABLE t2 (val minivec(3));
\copy t TO 'results/minivec.bin' WITH (FORMAT binary)
\copy t2 FROM 'results/minivec.bin' WITH (FORMAT binary)
SELECT * FROM t2 ORDER BY val;
DROP TABLE t;
DROP TABLE t2;
-- sparsevec
CREATE TABLE t (val sparsevec(3));

View File

@@ -1,45 +0,0 @@
SET enable_seqscan = off;
-- L2
CREATE TABLE t (val minivec(3));
INSERT INTO t (val) VALUES ('[0,0,0]'), ('[1,2,3]'), ('[1,1,1]'), (NULL);
CREATE INDEX ON t USING ivfflat (val minivec_l2_ops) WITH (lists = 1);
INSERT INTO t (val) VALUES ('[1,2,4]');
SELECT * FROM t ORDER BY val <-> '[3,3,3]';
SELECT COUNT(*) FROM (SELECT * FROM t ORDER BY val <-> (SELECT NULL::minivec)) t2;
SELECT COUNT(*) FROM t;
TRUNCATE t;
SELECT * FROM t ORDER BY val <-> '[3,3,3]';
DROP TABLE t;
-- inner product
CREATE TABLE t (val minivec(3));
INSERT INTO t (val) VALUES ('[0,0,0]'), ('[1,2,3]'), ('[1,1,1]'), (NULL);
CREATE INDEX ON t USING ivfflat (val minivec_ip_ops) WITH (lists = 1);
INSERT INTO t (val) VALUES ('[1,2,4]');
SELECT * FROM t ORDER BY val <#> '[3,3,3]';
SELECT COUNT(*) FROM (SELECT * FROM t ORDER BY val <#> (SELECT NULL::minivec)) t2;
DROP TABLE t;
-- cosine
CREATE TABLE t (val minivec(3));
INSERT INTO t (val) VALUES ('[0,0,0]'), ('[1,2,3]'), ('[1,1,1]'), (NULL);
CREATE INDEX ON t USING ivfflat (val minivec_cosine_ops) WITH (lists = 1);
INSERT INTO t (val) VALUES ('[1,2,4]');
SELECT * FROM t ORDER BY val <=> '[3,3,3]';
SELECT COUNT(*) FROM (SELECT * FROM t ORDER BY val <=> '[0,0,0]') t2;
SELECT COUNT(*) FROM (SELECT * FROM t ORDER BY val <=> (SELECT NULL::minivec)) t2;
DROP TABLE t;

View File

@@ -1,134 +0,0 @@
SELECT '[1,2,3]'::minivec;
SELECT '[-1,-2,-3]'::minivec;
SELECT '[1.,2.,3.]'::minivec;
SELECT ' [ 1, 2 , 3 ] '::minivec;
SELECT '[1.23456]'::minivec;
SELECT '[hello,1]'::minivec;
SELECT '[NaN,1]'::minivec;
SELECT '[Infinity,1]'::minivec;
SELECT '[-Infinity,1]'::minivec;
SELECT '[61439,-61439]'::minivec;
SELECT '[61440,-61440]'::minivec;
SELECT '[1e-8,-1e-8]'::minivec;
SELECT '[4e38,1]'::minivec;
SELECT '[1e-46,1]'::minivec;
SELECT '[1,2,3'::minivec;
SELECT '[1,2,3]9'::minivec;
SELECT '1,2,3'::minivec;
SELECT ''::minivec;
SELECT '['::minivec;
SELECT '[ '::minivec;
SELECT '[,'::minivec;
SELECT '[]'::minivec;
SELECT '[ ]'::minivec;
SELECT '[,]'::minivec;
SELECT '[1,]'::minivec;
SELECT '[1a]'::minivec;
SELECT '[1,,3]'::minivec;
SELECT '[1, ,3]'::minivec;
SELECT '[1,2,3]'::minivec(3);
SELECT '[1,2,3]'::minivec(2);
SELECT '[1,2,3]'::minivec(3, 2);
SELECT '[1,2,3]'::minivec('a');
SELECT '[1,2,3]'::minivec(0);
SELECT '[1,2,3]'::minivec(16001);
SELECT unnest('{"[1,2,3]", "[4,5,6]"}'::minivec[]);
SELECT '{"[1,2,3]"}'::minivec(2)[];
SELECT '[1,2,3]'::minivec + '[4,5,6]';
SELECT '[61439]'::minivec + '[61439]';
SELECT '[1,2]'::minivec + '[3]';
SELECT '[1,2,3]'::minivec - '[4,5,6]';
SELECT '[-61439]'::minivec - '[61439]';
SELECT '[1,2]'::minivec - '[3]';
SELECT '[1,2,3]'::minivec * '[4,5,6]';
SELECT '[61439]'::minivec * '[61439]';
SELECT '[1e-7]'::minivec * '[1e-7]';
SELECT '[1,2]'::minivec * '[3]';
SELECT '[1,2,3]'::minivec || '[4,5]';
SELECT array_fill(0, ARRAY[16000])::minivec || '[1]';
SELECT '[1,2,3]'::minivec < '[1,2,3]';
SELECT '[1,2,3]'::minivec < '[1,2]';
SELECT '[1,2,3]'::minivec <= '[1,2,3]';
SELECT '[1,2,3]'::minivec <= '[1,2]';
SELECT '[1,2,3]'::minivec = '[1,2,3]';
SELECT '[1,2,3]'::minivec = '[1,2]';
SELECT '[1,2,3]'::minivec != '[1,2,3]';
SELECT '[1,2,3]'::minivec != '[1,2]';
SELECT '[1,2,3]'::minivec >= '[1,2,3]';
SELECT '[1,2,3]'::minivec >= '[1,2]';
SELECT '[1,2,3]'::minivec > '[1,2,3]';
SELECT '[1,2,3]'::minivec > '[1,2]';
SELECT minivec_cmp('[1,2,3]', '[1,2,3]');
SELECT minivec_cmp('[1,2,3]', '[0,0,0]');
SELECT minivec_cmp('[0,0,0]', '[1,2,3]');
SELECT minivec_cmp('[1,2]', '[1,2,3]');
SELECT minivec_cmp('[1,2,3]', '[1,2]');
SELECT minivec_cmp('[1,2]', '[2,3,4]');
SELECT minivec_cmp('[2,3]', '[1,2,3]');
SELECT vector_dims('[1,2,3]'::minivec);
SELECT round(l2_norm('[1,1]'::minivec)::numeric, 5);
SELECT l2_norm('[3,4]'::minivec);
SELECT l2_norm('[0,1]'::minivec);
SELECT l2_norm('[0,0]'::minivec);
SELECT l2_norm('[2]'::minivec);
SELECT l2_distance('[0,0]'::minivec, '[3,4]');
SELECT l2_distance('[0,0]'::minivec, '[0,1]');
SELECT l2_distance('[1,2]'::minivec, '[3]');
SELECT l2_distance('[1,1,1,1,1,1,1,1,1]'::minivec, '[1,1,1,1,1,1,1,4,5]');
SELECT '[0,0]'::minivec <-> '[3,4]';
SELECT inner_product('[1,2]'::minivec, '[3,4]');
SELECT inner_product('[1,2]'::minivec, '[3]');
SELECT inner_product('[448]'::minivec, '[448]');
SELECT inner_product('[1,1,1,1,1,1,1,1,1]'::minivec, '[1,2,3,4,5,6,7,8,9]');
SELECT '[1,2]'::minivec <#> '[3,4]';
SELECT cosine_distance('[1,2]'::minivec, '[2,4]');
SELECT cosine_distance('[1,2]'::minivec, '[0,0]');
SELECT cosine_distance('[1,1]'::minivec, '[1,1]');
SELECT cosine_distance('[1,0]'::minivec, '[0,2]');
SELECT cosine_distance('[1,1]'::minivec, '[-1,-1]');
SELECT cosine_distance('[1,2]'::minivec, '[3]');
SELECT cosine_distance('[1,1]'::minivec, '[1.1,1.1]');
SELECT cosine_distance('[1,1]'::minivec, '[-1.1,-1.1]');
SELECT cosine_distance('[1,2,3,4,5,6,7,8,9]'::minivec, '[1,2,3,4,5,6,7,8,9]');
SELECT cosine_distance('[1,2,3,4,5,6,7,8,9]'::minivec, '[-1,-2,-3,-4,-5,-6,-7,-8,-9]');
SELECT '[1,2]'::minivec <=> '[2,4]';
SELECT l1_distance('[0,0]'::minivec, '[3,4]');
SELECT l1_distance('[0,0]'::minivec, '[0,1]');
SELECT l1_distance('[1,2]'::minivec, '[3]');
SELECT l1_distance('[1,2,3,4,5,6,7,8,9]'::minivec, '[1,2,3,4,5,6,7,8,9]');
SELECT l1_distance('[1,2,3,4,5,6,7,8,9]'::minivec, '[0,3,2,5,4,7,6,9,8]');
SELECT '[0,0]'::minivec <+> '[3,4]';
SELECT l2_normalize('[3,4]'::minivec);
SELECT l2_normalize('[3,0]'::minivec);
SELECT l2_normalize('[0,0.1]'::minivec);
SELECT l2_normalize('[0,0]'::minivec);
SELECT l2_normalize('[448]'::minivec);
SELECT binary_quantize('[1,0,-1]'::minivec);
SELECT binary_quantize('[0,0.1,-0.2,-0.3,0.4,0.5,0.6,-0.7,0.8,-0.9,1]'::minivec);
SELECT subvector('[1,2,3,4,5]'::minivec, 1, 3);
SELECT subvector('[1,2,3,4,5]'::minivec, 3, 2);
SELECT subvector('[1,2,3,4,5]'::minivec, -1, 3);
SELECT subvector('[1,2,3,4,5]'::minivec, 3, 9);
SELECT subvector('[1,2,3,4,5]'::minivec, 1, 0);
SELECT subvector('[1,2,3,4,5]'::minivec, 3, -1);
SELECT subvector('[1,2,3,4,5]'::minivec, -1, 2);
SELECT subvector('[1,2,3,4,5]'::minivec, 2147483647, 10);
SELECT subvector('[1,2,3,4,5]'::minivec, 3, 2147483647);
SELECT subvector('[1,2,3,4,5]'::minivec, -2147483644, 2147483647);

View File

@@ -40,10 +40,6 @@ for (1 .. 50)
$actual = $node->safe_psql("postgres", "SELECT halfvec_cmp(v::halfvec, '$query'::real[]::halfvec) FROM tst");
is($expected, $actual);
# Test minivec
$actual = $node->safe_psql("postgres", "SELECT minivec_cmp(v::minivec, '$query'::real[]::minivec) FROM tst");
is($expected, $actual);
# Test sparsevec
$actual = $node->safe_psql("postgres", "SELECT sparsevec_cmp(v::vector::sparsevec, '$query'::real[]::vector::sparsevec) FROM tst");
is($expected, $actual);

View File

@@ -45,10 +45,6 @@ for my $function (@functions)
my $actual = $node->safe_psql("postgres", "SELECT $function(v::halfvec, '$query'::vector::halfvec) FROM tst");
is($expected, $actual, "halfvec $function");
# Test minivec
$actual = $node->safe_psql("postgres", "SELECT $function(v::minivec, '$query'::vector::minivec) FROM tst");
is($expected, $actual, "minivec $function");
# Test sparsevec
$actual = $node->safe_psql("postgres", "SELECT $function(v::sparsevec, '$query'::vector::sparsevec) FROM tst");
is($expected, $actual, "sparsevec $function");

View File

@@ -12,8 +12,8 @@ $node->start;
# Create extension
$node->safe_psql("postgres", "CREATE EXTENSION vector;");
my @types = ("vector", "halfvec", "minivec", "sparsevec");
my @inputs = ("[1.23,4.56,7.89]", "[1.23,4.56,7.89]", "[1.23,4.56,7.89]", "{1:1.23,2:4.56,3:7.89}/3");
my @types = ("vector", "halfvec", "sparsevec");
my @inputs = ("[1.23,4.56,7.89]", "[1.23,4.56,7.89]", "{1:1.23,2:4.56,3:7.89}/3");
my @subs = (" ", " ", ",", ":", "-", "1", "9", "\0", "2147483648", "-2147483649");
for my $i (0 .. $#types)

View File

@@ -1,132 +0,0 @@
use strict;
use warnings FATAL => 'all';
use PostgreSQL::Test::Cluster;
use PostgreSQL::Test::Utils;
use Test::More;
my $node;
my @queries = ();
my @expected;
my $limit = 20;
my $dim = 10;
my $array_sql = join(",", ('2 * random() * random()') x $dim);
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 = PostgreSQL::Test::Cluster->new('node');
$node->init;
$node->start;
# Create table
$node->safe_psql("postgres", "CREATE EXTENSION vector;");
$node->safe_psql("postgres", "CREATE TABLE tst (i int4, v minivec($dim));");
$node->safe_psql("postgres",
"INSERT INTO tst SELECT i, ARRAY[$array_sql] FROM generate_series(1, 10000) i;"
);
# Generate queries
for (1 .. 20)
{
my @r = ();
for (1 .. $dim)
{
push(@r, rand());
}
push(@queries, "[" . join(",", @r) . "]");
}
# Check each index type
my @operators = ("<->", "<#>", "<=>", "<+>");
my @opclasses = ("minivec_l2_ops", "minivec_ip_ops", "minivec_cosine_ops", "minivec_l1_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 = 0.98;
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();

View File

@@ -0,0 +1,31 @@
use strict;
use warnings FATAL => 'all';
use PostgreSQL::Test::Cluster;
use PostgreSQL::Test::Utils;
use Test::More;
my $dim = 3;
my $array_sql = join(",", ('random()') x $dim);
# Initialize node
my $node = PostgreSQL::Test::Cluster->new('node');
$node->init;
$node->start;
# Create table
$node->safe_psql("postgres", "CREATE EXTENSION vector;");
$node->safe_psql("postgres", "CREATE TABLE tst (i int4, v vector($dim));");
$node->safe_psql("postgres",
"INSERT INTO tst SELECT i, ARRAY[$array_sql] FROM generate_series(1, 100000) i;"
);
$node->safe_psql("postgres", "CREATE INDEX ON tst USING ivfflat (v vector_l2_ops);");
my $count = $node->safe_psql("postgres", qq(
SET enable_seqscan = off;
SET ivfflat.probes = 10;
SET ivfflat.streaming = on;
SELECT COUNT(*) FROM (SELECT v FROM tst WHERE i % 10000 = 0 ORDER BY v <-> (SELECT v FROM tst LIMIT 1) LIMIT 11) t;
));
is($count, 10);
done_testing();

View File

@@ -1,113 +0,0 @@
use strict;
use warnings FATAL => 'all';
use PostgreSQL::Test::Cluster;
use PostgreSQL::Test::Utils;
use Test::More;
my $node;
my @queries = ();
my @expected;
my $limit = 20;
my $dim = 10;
my $array_sql = join(",", ('2 * random() * random()') x $dim);
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 = PostgreSQL::Test::Cluster->new('node');
$node->init;
$node->start;
# Create table
$node->safe_psql("postgres", "CREATE EXTENSION vector;");
$node->safe_psql("postgres", "CREATE TABLE tst (i serial, v minivec($dim));");
# Generate queries
for (1 .. 20)
{
my @r = ();
for (1 .. $dim)
{
push(@r, rand());
}
push(@queries, "[" . join(",", @r) . "]");
}
# Check each index type
my @operators = ("<->", "<#>", "<=>", "<+>");
my @opclasses = ("minivec_l2_ops", "minivec_ip_ops", "minivec_cosine_ops", "minivec_l1_ops");
for my $i (0 .. $#operators)
{
my $operator = $operators[$i];
my $opclass = $opclasses[$i];
# Add index
$node->safe_psql("postgres", "CREATE INDEX idx ON tst USING hnsw (v $opclass);");
# Use concurrent inserts
$node->pgbench(
"--no-vacuum --client=10 --transactions=1000",
0,
[qr{actually processed}],
[qr{^$}],
"concurrent INSERTs",
{
"040_hnsw_minivec_insert_recall_$opclass" => "INSERT INTO tst (v) VALUES (ARRAY[$array_sql]);"
}
);
# Get exact results
@expected = ();
foreach (@queries)
{
my $res = $node->safe_psql("postgres", qq(
SET enable_indexscan = off;
SELECT i FROM tst ORDER BY v $operator '$_' LIMIT $limit;
));
push(@expected, $res);
}
# Test approximate results
my $min = 0.98;
test_recall($min, $operator);
$node->safe_psql("postgres", "DROP INDEX idx;");
$node->safe_psql("postgres", "TRUNCATE tst;");
}
done_testing();

View File

@@ -0,0 +1,125 @@
use strict;
use warnings FATAL => 'all';
use PostgreSQL::Test::Cluster;
use PostgreSQL::Test::Utils;
use Test::More;
my $node;
my @queries = ();
my @expected;
my $limit = 20;
my @cs = (100, 1000);
sub test_recall
{
my ($c, $probes, $min, $operator) = @_;
my $correct = 0;
my $total = 0;
my $explain = $node->safe_psql("postgres", qq(
SET enable_seqscan = off;
SET ivfflat.probes = $probes;
SET ivfflat.streaming = on;
EXPLAIN ANALYZE SELECT i FROM tst WHERE i % $c = 0 ORDER BY v $operator '$queries[0]' LIMIT $limit;
));
like($explain, qr/Index Scan using idx on tst/);
for my $i (0 .. $#queries)
{
my $actual = $node->safe_psql("postgres", qq(
SET enable_seqscan = off;
SET ivfflat.probes = $probes;
SET ivfflat.streaming = on;
SELECT i FROM tst WHERE i % $c = 0 ORDER BY v $operator '$queries[$i]' LIMIT $limit;
));
my @actual_ids = split("\n", $actual);
my @expected_ids = split("\n", $expected[$i]);
my %expected_set = map { $_ => 1 } @expected_ids;
foreach (@actual_ids)
{
if (exists($expected_set{$_}))
{
$correct++;
}
}
$total += $limit;
}
cmp_ok($correct / $total, ">=", $min, $operator);
}
# Initialize node
$node = PostgreSQL::Test::Cluster->new('node');
$node->init;
$node->start;
# Create table
$node->safe_psql("postgres", "CREATE EXTENSION vector;");
$node->safe_psql("postgres", "CREATE TABLE tst (i int4, v vector(3));");
$node->safe_psql("postgres",
"INSERT INTO tst SELECT i, ARRAY[random(), random(), random()] FROM generate_series(1, 100000) i;"
);
# Generate queries
for (1 .. 20)
{
my $r1 = rand();
my $r2 = rand();
my $r3 = rand();
push(@queries, "[$r1,$r2,$r3]");
}
# Check each index type
my @operators = ("<->", "<=>");
my @opclasses = ("vector_l2_ops", "vector_cosine_ops");
for my $i (0 .. $#operators)
{
my $operator = $operators[$i];
my $opclass = $opclasses[$i];
$node->safe_psql("postgres", "CREATE INDEX idx ON tst USING ivfflat (v $opclass);");
foreach (@cs)
{
my $c = $_;
# Get exact results
@expected = ();
foreach (@queries)
{
my $res = $node->safe_psql("postgres", qq(
SET enable_indexscan = off;
WITH top AS (
SELECT v $operator '$_' AS distance FROM tst WHERE i % $c = 0 ORDER BY distance LIMIT $limit
)
SELECT i FROM tst WHERE (v $operator '$_') <= (SELECT MAX(distance) FROM top)
));
push(@expected, $res);
}
if ($c == 100)
{
test_recall($c, 1, 0.58, $operator);
test_recall($c, 10, 0.98, $operator);
}
else
{
if ($operator eq "<->")
{
test_recall($c, 1, 0.80, $operator);
}
else
{
test_recall($c, 1, 0.88, $operator);
}
}
}
$node->safe_psql("postgres", "DROP INDEX idx;");
}
done_testing();

View File

@@ -1,97 +0,0 @@
use strict;
use warnings FATAL => 'all';
use PostgreSQL::Test::Cluster;
use PostgreSQL::Test::Utils;
use Test::More;
my $node;
my @queries = ();
my @expected;
my $limit = 20;
sub test_recall
{
my ($min, $ef_search, $test_name) = @_;
my $correct = 0;
my $total = 0;
my $explain = $node->safe_psql("postgres", qq(
SET enable_seqscan = off;
SET hnsw.ef_search = $ef_search;
EXPLAIN ANALYZE SELECT i FROM tst ORDER BY v <-> '$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;
SET hnsw.ef_search = $ef_search;
SELECT i FROM tst ORDER BY v <-> '$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, $test_name);
}
# Initialize node
$node = PostgreSQL::Test::Cluster->new('node');
$node->init;
$node->start;
# Create table
$node->safe_psql("postgres", "CREATE EXTENSION vector;");
$node->safe_psql("postgres", "CREATE TABLE tst (i int4, v minivec(3));");
$node->safe_psql("postgres", "ALTER TABLE tst SET (autovacuum_enabled = false);");
$node->safe_psql("postgres",
"INSERT INTO tst SELECT i, ARRAY[random(), random(), random()] FROM generate_series(1, 10000) i;"
);
# Add index
$node->safe_psql("postgres", "CREATE INDEX ON tst USING hnsw (v minivec_l2_ops) WITH (m = 4, ef_construction = 8);");
# Delete data
$node->safe_psql("postgres", "DELETE FROM tst WHERE i > 2500;");
# Generate queries
for (1 .. 20)
{
my $r1 = rand();
my $r2 = rand();
my $r3 = rand();
push(@queries, "[$r1,$r2,$r3]");
}
# Get exact results
@expected = ();
foreach (@queries)
{
my $res = $node->safe_psql("postgres", qq(
SET enable_indexscan = off;
SELECT i FROM tst ORDER BY v <-> '$_' LIMIT $limit;
));
push(@expected, $res);
}
test_recall(0.18, $limit, "before vacuum");
test_recall(0.93, 100, "before vacuum");
# TODO Test concurrent inserts with vacuum
$node->safe_psql("postgres", "VACUUM tst;");
test_recall(0.95, $limit, "after vacuum");
done_testing();

View File

@@ -1,58 +0,0 @@
use strict;
use warnings FATAL => 'all';
use PostgreSQL::Test::Cluster;
use PostgreSQL::Test::Utils;
use Test::More;
# Initialize node
my $node = PostgreSQL::Test::Cluster->new('node');
$node->init;
$node->start;
# Create table
$node->safe_psql("postgres", "CREATE EXTENSION vector;");
$node->safe_psql("postgres", "CREATE TABLE tst (v minivec(3));");
sub insert_vectors
{
for my $i (1 .. 20)
{
$node->safe_psql("postgres", "INSERT INTO tst VALUES ('[1,1,1]');");
}
}
sub test_duplicates
{
my $res = $node->safe_psql("postgres", qq(
SET enable_seqscan = off;
SET hnsw.ef_search = 1;
SELECT COUNT(*) FROM (SELECT * FROM tst ORDER BY v <-> '[1,1,1]') t;
));
is($res, 10);
}
# Test duplicates with build
insert_vectors();
$node->safe_psql("postgres", "CREATE INDEX idx ON tst USING hnsw (v minivec_l2_ops);");
test_duplicates();
# Reset
$node->safe_psql("postgres", "TRUNCATE tst;");
# Test duplicates with inserts
insert_vectors();
test_duplicates();
# Test fallback path for inserts
$node->pgbench(
"--no-vacuum --client=5 --transactions=100",
0,
[qr{actually processed}],
[qr{^$}],
"concurrent INSERTs",
{
"042_hnsw_minivec_duplicates" => "INSERT INTO tst VALUES ('[1,1,1]');"
}
);
done_testing();

View File

@@ -1,154 +0,0 @@
use strict;
use warnings FATAL => 'all';
use PostgreSQL::Test::Cluster;
use PostgreSQL::Test::Utils;
use Test::More;
my $node;
my @queries = ();
my @expected;
my $limit = 20;
my $dim = 10;
my $array_sql = join(",", ('random()') x $dim);
sub test_recall
{
my ($probes, $min, $operator) = @_;
my $correct = 0;
my $total = 0;
my $explain = $node->safe_psql("postgres", qq(
SET enable_seqscan = off;
SET ivfflat.probes = $probes;
EXPLAIN ANALYZE SELECT i FROM tst ORDER BY v $operator '$queries[0]' LIMIT $limit;
));
like($explain, qr/Index Scan using idx on tst/);
for my $i (0 .. $#queries)
{
my $actual = $node->safe_psql("postgres", qq(
SET enable_seqscan = off;
SET ivfflat.probes = $probes;
SELECT i FROM tst ORDER BY v $operator '$queries[$i]' LIMIT $limit;
));
my @actual_ids = split("\n", $actual);
my @expected_ids = split("\n", $expected[$i]);
my %expected_set = map { $_ => 1 } @expected_ids;
foreach (@actual_ids)
{
if (exists($expected_set{$_}))
{
$correct++;
}
}
$total += $limit;
}
cmp_ok($correct / $total, ">=", $min, $operator);
}
# Initialize node
$node = PostgreSQL::Test::Cluster->new('node');
$node->init;
$node->start;
# Create table
$node->safe_psql("postgres", "CREATE EXTENSION vector;");
$node->safe_psql("postgres", "CREATE TABLE tst (i int4, v minivec($dim));");
$node->safe_psql("postgres",
"INSERT INTO tst SELECT i, ARRAY[$array_sql] FROM generate_series(1, 100000) i;"
);
# Generate queries
for (1 .. 20)
{
my @r = ();
for (1 .. $dim)
{
push(@r, rand());
}
push(@queries, "[" . join(",", @r) . "]");
}
# Check each index type
my @operators = ("<->", "<#>", "<=>");
my @opclasses = ("minivec_l2_ops", "minivec_ip_ops", "minivec_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", qq(
WITH top AS (
SELECT v $operator '$_' AS distance FROM tst ORDER BY distance LIMIT $limit
)
SELECT i FROM tst WHERE (v $operator '$_') <= (SELECT MAX(distance) FROM top)
));
push(@expected, $res);
}
# Build index serially
$node->safe_psql("postgres", qq(
SET max_parallel_maintenance_workers = 0;
CREATE INDEX idx ON tst USING ivfflat (v $opclass);
));
# Test approximate results
if ($operator ne "<#>")
{
# TODO Fix test (uniform random vectors all have similar inner product)
test_recall(1, 0.33, $operator);
test_recall(10, 0.93, $operator);
}
# Test probes equals lists
if ($operator eq "<=>")
{
test_recall(100, 0.98, $operator);
}
else
{
test_recall(100, 1.00, $operator);
}
$node->safe_psql("postgres", "DROP INDEX idx;");
# Build index in parallel
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 ivfflat (v $opclass);
));
is($ret, 0, $stderr);
like($stderr, qr/using \d+ parallel workers/);
# Test approximate results
if ($operator ne "<#>")
{
# TODO Fix test (uniform random vectors all have similar inner product)
test_recall(1, 0.33, $operator);
test_recall(10, 0.93, $operator);
}
# Test probes equals lists
if ($operator eq "<=>")
{
test_recall(100, 0.98, $operator);
}
else
{
test_recall(100, 1.00, $operator);
}
$node->safe_psql("postgres", "DROP INDEX idx;");
}
done_testing();