mirror of
https://github.com/pgvector/pgvector.git
synced 2026-07-22 20:15:46 +08:00
Compare commits
9 Commits
minivec-e2
...
minivec0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
edb806edb9 | ||
|
|
8f12b79024 | ||
|
|
81f0920515 | ||
|
|
9c7903fd46 | ||
|
|
244d338664 | ||
|
|
958af80e96 | ||
|
|
fd65bcfb10 | ||
|
|
274e6544d4 | ||
|
|
035a31ac91 |
@@ -266,9 +266,6 @@ COMMENT ON ACCESS METHOD hnsw IS 'hnsw index access method';
|
|||||||
CREATE FUNCTION ivfflat_halfvec_support(internal) RETURNS internal
|
CREATE FUNCTION ivfflat_halfvec_support(internal) RETURNS internal
|
||||||
AS 'MODULE_PATHNAME' LANGUAGE C;
|
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
|
CREATE FUNCTION ivfflat_bit_support(internal) RETURNS internal
|
||||||
AS 'MODULE_PATHNAME' LANGUAGE C;
|
AS 'MODULE_PATHNAME' LANGUAGE C;
|
||||||
|
|
||||||
@@ -751,9 +748,6 @@ CREATE FUNCTION minivec_l2_squared_distance(minivec, minivec) RETURNS float8
|
|||||||
CREATE FUNCTION minivec_negative_inner_product(minivec, minivec) RETURNS float8
|
CREATE FUNCTION minivec_negative_inner_product(minivec, minivec) RETURNS float8
|
||||||
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
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
|
-- minivec cast functions
|
||||||
|
|
||||||
CREATE FUNCTION minivec(minivec, integer, boolean) RETURNS minivec
|
CREATE FUNCTION minivec(minivec, integer, boolean) RETURNS minivec
|
||||||
@@ -893,30 +887,6 @@ CREATE OPERATOR CLASS minivec_ops
|
|||||||
OPERATOR 5 > ,
|
OPERATOR 5 > ,
|
||||||
FUNCTION 1 minivec_cmp(minivec, minivec);
|
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
|
CREATE OPERATOR CLASS minivec_l2_ops
|
||||||
FOR TYPE minivec USING hnsw AS
|
FOR TYPE minivec USING hnsw AS
|
||||||
OPERATOR 1 <-> (minivec, minivec) FOR ORDER BY float_ops,
|
OPERATOR 1 <-> (minivec, minivec) FOR ORDER BY float_ops,
|
||||||
@@ -932,7 +902,7 @@ CREATE OPERATOR CLASS minivec_ip_ops
|
|||||||
CREATE OPERATOR CLASS minivec_cosine_ops
|
CREATE OPERATOR CLASS minivec_cosine_ops
|
||||||
FOR TYPE minivec USING hnsw AS
|
FOR TYPE minivec USING hnsw AS
|
||||||
OPERATOR 1 <=> (minivec, minivec) FOR ORDER BY float_ops,
|
OPERATOR 1 <=> (minivec, minivec) FOR ORDER BY float_ops,
|
||||||
FUNCTION 1 cosine_distance(minivec, minivec),
|
FUNCTION 1 minivec_negative_inner_product(minivec, minivec),
|
||||||
FUNCTION 2 l2_norm(minivec),
|
FUNCTION 2 l2_norm(minivec),
|
||||||
FUNCTION 3 hnsw_minivec_support(internal);
|
FUNCTION 3 hnsw_minivec_support(internal);
|
||||||
|
|
||||||
|
|||||||
@@ -159,9 +159,6 @@ HnswOptionalProcInfo(Relation index, uint16 procnum)
|
|||||||
Datum
|
Datum
|
||||||
HnswNormValue(const HnswTypeInfo * typeInfo, Oid collation, Datum value)
|
HnswNormValue(const HnswTypeInfo * typeInfo, Oid collation, Datum value)
|
||||||
{
|
{
|
||||||
if (!typeInfo->normalize)
|
|
||||||
return value;
|
|
||||||
|
|
||||||
return DirectFunctionCall1Coll(typeInfo->normalize, collation, value);
|
return DirectFunctionCall1Coll(typeInfo->normalize, collation, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1385,8 +1382,7 @@ hnsw_minivec_support(PG_FUNCTION_ARGS)
|
|||||||
{
|
{
|
||||||
static const HnswTypeInfo typeInfo = {
|
static const HnswTypeInfo typeInfo = {
|
||||||
.maxDimensions = HNSW_MAX_DIM * 4,
|
.maxDimensions = HNSW_MAX_DIM * 4,
|
||||||
/* Do not normalize to maximize precision */
|
.normalize = minivec_l2_normalize,
|
||||||
.normalize = NULL,
|
|
||||||
.checkValue = NULL
|
.checkValue = NULL
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,6 @@
|
|||||||
#include "halfutils.h"
|
#include "halfutils.h"
|
||||||
#include "halfvec.h"
|
#include "halfvec.h"
|
||||||
#include "ivfflat.h"
|
#include "ivfflat.h"
|
||||||
#include "minivec.h"
|
|
||||||
#include "storage/bufmgr.h"
|
#include "storage/bufmgr.h"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -71,9 +70,6 @@ IvfflatOptionalProcInfo(Relation index, uint16 procnum)
|
|||||||
Datum
|
Datum
|
||||||
IvfflatNormValue(const IvfflatTypeInfo * typeInfo, Oid collation, Datum value)
|
IvfflatNormValue(const IvfflatTypeInfo * typeInfo, Oid collation, Datum value)
|
||||||
{
|
{
|
||||||
if (!typeInfo->normalize)
|
|
||||||
return value;
|
|
||||||
|
|
||||||
return DirectFunctionCall1Coll(typeInfo->normalize, collation, 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 l2_normalize(PG_FUNCTION_ARGS);
|
||||||
PGDLLEXPORT Datum halfvec_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);
|
PGDLLEXPORT Datum sparsevec_l2_normalize(PG_FUNCTION_ARGS);
|
||||||
|
|
||||||
static Size
|
static Size
|
||||||
@@ -250,12 +245,6 @@ HalfvecItemSize(int dimensions)
|
|||||||
return HALFVEC_SIZE(dimensions);
|
return HALFVEC_SIZE(dimensions);
|
||||||
}
|
}
|
||||||
|
|
||||||
static Size
|
|
||||||
MinivecItemSize(int dimensions)
|
|
||||||
{
|
|
||||||
return MINIVEC_SIZE(dimensions);
|
|
||||||
}
|
|
||||||
|
|
||||||
static Size
|
static Size
|
||||||
BitItemSize(int dimensions)
|
BitItemSize(int dimensions)
|
||||||
{
|
{
|
||||||
@@ -286,18 +275,6 @@ HalfvecUpdateCenter(Pointer v, int dimensions, float *x)
|
|||||||
vec->x[k] = Float4ToHalfUnchecked(x[k]);
|
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
|
static void
|
||||||
BitUpdateCenter(Pointer v, int dimensions, float *x)
|
BitUpdateCenter(Pointer v, int dimensions, float *x)
|
||||||
{
|
{
|
||||||
@@ -332,15 +309,6 @@ HalfvecSumCenter(Pointer v, float *x)
|
|||||||
x[k] += HalfToFloat4(vec->x[k]);
|
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
|
static void
|
||||||
BitSumCenter(Pointer v, float *x)
|
BitSumCenter(Pointer v, float *x)
|
||||||
{
|
{
|
||||||
@@ -389,22 +357,6 @@ ivfflat_halfvec_support(PG_FUNCTION_ARGS)
|
|||||||
PG_RETURN_POINTER(&typeInfo);
|
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);
|
FUNCTION_PREFIX PG_FUNCTION_INFO_V1(ivfflat_bit_support);
|
||||||
Datum
|
Datum
|
||||||
ivfflat_bit_support(PG_FUNCTION_ARGS)
|
ivfflat_bit_support(PG_FUNCTION_ARGS)
|
||||||
|
|||||||
@@ -69,11 +69,6 @@ CheckElement(fp8 value)
|
|||||||
ereport(ERROR,
|
ereport(ERROR,
|
||||||
(errcode(ERRCODE_DATA_EXCEPTION),
|
(errcode(ERRCODE_DATA_EXCEPTION),
|
||||||
errmsg("NaN not allowed in minivec")));
|
errmsg("NaN not allowed in minivec")));
|
||||||
|
|
||||||
if (Fp8IsInf(value))
|
|
||||||
ereport(ERROR,
|
|
||||||
(errcode(ERRCODE_DATA_EXCEPTION),
|
|
||||||
errmsg("infinite value not allowed in minivec")));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -174,7 +169,7 @@ minivec_in(PG_FUNCTION_ARGS)
|
|||||||
x[dim] = Float4ToFp8Unchecked(val);
|
x[dim] = Float4ToFp8Unchecked(val);
|
||||||
|
|
||||||
/* Check for range error like float4in */
|
/* Check for range error like float4in */
|
||||||
if ((errno == ERANGE && isinf(val)) || (Fp8IsInf(x[dim]) && !isinf(val)))
|
if ((errno == ERANGE && isinf(val)) || (Fp8IsNan(x[dim]) && !isnan(val)))
|
||||||
ereport(ERROR,
|
ereport(ERROR,
|
||||||
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
|
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
|
||||||
errmsg("\"%s\" is out of range for type minivec", pnstrdup(pt, stringEnd - pt))));
|
errmsg("\"%s\" is out of range for type minivec", pnstrdup(pt, stringEnd - pt))));
|
||||||
@@ -744,7 +739,7 @@ minivec_l2_normalize(PG_FUNCTION_ARGS)
|
|||||||
/* Check for overflow */
|
/* Check for overflow */
|
||||||
for (int i = 0; i < a->dim; i++)
|
for (int i = 0; i < a->dim; i++)
|
||||||
{
|
{
|
||||||
if (Fp8IsInf(rx[i]))
|
if (Fp8IsNan(rx[i]))
|
||||||
float_overflow_error();
|
float_overflow_error();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -773,12 +768,18 @@ minivec_add(PG_FUNCTION_ARGS)
|
|||||||
|
|
||||||
/* Auto-vectorized */
|
/* Auto-vectorized */
|
||||||
for (int i = 0, imax = a->dim; i < imax; i++)
|
for (int i = 0, imax = a->dim; i < imax; i++)
|
||||||
|
{
|
||||||
|
#ifdef FLT16_SUPPORT
|
||||||
|
rx[i] = ax[i] + bx[i];
|
||||||
|
#else
|
||||||
rx[i] = Float4ToFp8Unchecked(Fp8ToFloat4(ax[i]) + Fp8ToFloat4(bx[i]));
|
rx[i] = Float4ToFp8Unchecked(Fp8ToFloat4(ax[i]) + Fp8ToFloat4(bx[i]));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
/* Check for overflow */
|
/* Check for overflow */
|
||||||
for (int i = 0, imax = a->dim; i < imax; i++)
|
for (int i = 0, imax = a->dim; i < imax; i++)
|
||||||
{
|
{
|
||||||
if (Fp8IsInf(rx[i]))
|
if (Fp8IsNan(rx[i]))
|
||||||
float_overflow_error();
|
float_overflow_error();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -806,12 +807,18 @@ minivec_sub(PG_FUNCTION_ARGS)
|
|||||||
|
|
||||||
/* Auto-vectorized */
|
/* Auto-vectorized */
|
||||||
for (int i = 0, imax = a->dim; i < imax; i++)
|
for (int i = 0, imax = a->dim; i < imax; i++)
|
||||||
|
{
|
||||||
|
#ifdef FLT16_SUPPORT
|
||||||
rx[i] = ax[i] - bx[i];
|
rx[i] = ax[i] - bx[i];
|
||||||
|
#else
|
||||||
|
rx[i] = Float4ToFp8Unchecked(Fp8ToFloat4(ax[i]) - Fp8ToFloat4(bx[i]));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
/* Check for overflow */
|
/* Check for overflow */
|
||||||
for (int i = 0, imax = a->dim; i < imax; i++)
|
for (int i = 0, imax = a->dim; i < imax; i++)
|
||||||
{
|
{
|
||||||
if (Fp8IsInf(rx[i]))
|
if (Fp8IsNan(rx[i]))
|
||||||
float_overflow_error();
|
float_overflow_error();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -839,12 +846,18 @@ minivec_mul(PG_FUNCTION_ARGS)
|
|||||||
|
|
||||||
/* Auto-vectorized */
|
/* Auto-vectorized */
|
||||||
for (int i = 0, imax = a->dim; i < imax; i++)
|
for (int i = 0, imax = a->dim; i < imax; i++)
|
||||||
|
{
|
||||||
|
#ifdef FLT16_SUPPORT
|
||||||
|
rx[i] = ax[i] * bx[i];
|
||||||
|
#else
|
||||||
rx[i] = Float4ToFp8Unchecked(Fp8ToFloat4(ax[i]) * Fp8ToFloat4(bx[i]));
|
rx[i] = Float4ToFp8Unchecked(Fp8ToFloat4(ax[i]) * Fp8ToFloat4(bx[i]));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
/* Check for overflow and underflow */
|
/* Check for overflow and underflow */
|
||||||
for (int i = 0, imax = a->dim; i < imax; i++)
|
for (int i = 0, imax = a->dim; i < imax; i++)
|
||||||
{
|
{
|
||||||
if (Fp8IsInf(rx[i]))
|
if (Fp8IsNan(rx[i]))
|
||||||
float_overflow_error();
|
float_overflow_error();
|
||||||
|
|
||||||
if (Fp8IsZero(rx[i]) && !(Fp8IsZero(ax[i]) || Fp8IsZero(bx[i])))
|
if (Fp8IsZero(rx[i]) && !(Fp8IsZero(ax[i]) || Fp8IsZero(bx[i])))
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
#ifndef MINIVEC_H
|
#ifndef MINIVEC_H
|
||||||
#define MINIVEC_H
|
#define MINIVEC_H
|
||||||
|
|
||||||
|
#include <float.h>
|
||||||
|
|
||||||
#define MINIVEC_MAX_DIM 16000
|
#define MINIVEC_MAX_DIM 16000
|
||||||
|
|
||||||
#define fp8 uint8
|
#define fp8 uint8
|
||||||
@@ -26,16 +28,7 @@ MiniVector *InitMiniVector(int dim);
|
|||||||
static inline bool
|
static inline bool
|
||||||
Fp8IsNan(fp8 num)
|
Fp8IsNan(fp8 num)
|
||||||
{
|
{
|
||||||
return (num & 0x7C) == 0x7C && (num & 0x7F) != 0x7C;
|
return (num & 0x7F) == 0x7F;
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Check if fp8 is infinite
|
|
||||||
*/
|
|
||||||
static inline bool
|
|
||||||
Fp8IsInf(fp8 num)
|
|
||||||
{
|
|
||||||
return (num & 0x7F) == 0x7C;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -53,19 +46,10 @@ Fp8IsZero(fp8 num)
|
|||||||
static inline float
|
static inline float
|
||||||
Fp8ToFloat4(fp8 num)
|
Fp8ToFloat4(fp8 num)
|
||||||
{
|
{
|
||||||
/* Lookup table for non-sign bits */
|
float lookup[128] = {0, 0.00195312, 0.00390625, 0.00585938, 0.0078125, 0.00976562, 0.0117188, 0.0136719, 0.015625, 0.0175781, 0.0195312, 0.0214844, 0.0234375, 0.0253906, 0.0273438, 0.0292969, 0.03125, 0.0351562, 0.0390625, 0.0429688, 0.046875, 0.0507812, 0.0546875, 0.0585938, 0.0625, 0.0703125, 0.078125, 0.0859375, 0.09375, 0.101562, 0.109375, 0.117188, 0.125, 0.140625, 0.15625, 0.171875, 0.1875, 0.203125, 0.21875, 0.234375, 0.25, 0.28125, 0.3125, 0.34375, 0.375, 0.40625, 0.4375, 0.46875, 0.5, 0.5625, 0.625, 0.6875, 0.75, 0.8125, 0.875, 0.9375, 1, 1.125, 1.25, 1.375, 1.5, 1.625, 1.75, 1.875, 2, 2.25, 2.5, 2.75, 3, 3.25, 3.5, 3.75, 4, 4.5, 5, 5.5, 6, 6.5, 7, 7.5, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 20, 22, 24, 26, 28, 30, 32, 36, 40, 44, 48, 52, 56, 60, 64, 72, 80, 88, 96, 104, 112, 120, 128, 144, 160, 176, 192, 208, 224, 240, 256, 288, 320, 352, 384, 416, 448, NAN};
|
||||||
/* Uses uint32 for correctness */
|
float v = lookup[num & 0x7F];
|
||||||
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
|
return (num & 0x80) == 0x80 ? -v : v;
|
||||||
{
|
|
||||||
float f;
|
|
||||||
uint32 i;
|
|
||||||
} swap;
|
|
||||||
|
|
||||||
swap.i = lookup[num & 0x7F];
|
|
||||||
|
|
||||||
return (num & 0x80) == 0x80 ? -swap.f : swap.f;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -78,32 +62,27 @@ Float4ToFp8Unchecked(float num)
|
|||||||
{
|
{
|
||||||
float f;
|
float f;
|
||||||
uint32 i;
|
uint32 i;
|
||||||
} swap;
|
} swapfloat;
|
||||||
|
|
||||||
uint32 bin;
|
uint32 bin;
|
||||||
int exponent;
|
int exponent;
|
||||||
int mantissa;
|
int mantissa;
|
||||||
uint8 result;
|
uint8 result;
|
||||||
|
|
||||||
swap.f = num;
|
swapfloat.f = num;
|
||||||
bin = swap.i;
|
bin = swapfloat.i;
|
||||||
exponent = (bin & 0x7F800000) >> 23;
|
exponent = (bin & 0x7F800000) >> 23;
|
||||||
mantissa = bin & 0x007FFFFF;
|
mantissa = bin & 0x007FFFFF;
|
||||||
|
|
||||||
/* Sign */
|
/* Sign */
|
||||||
result = (bin & 0x80000000) >> 24;
|
result = (bin & 0x80000000) >> 24;
|
||||||
|
|
||||||
if (isinf(num))
|
if (isinf(num) || isnan(num))
|
||||||
{
|
|
||||||
/* Infinite */
|
|
||||||
result |= 0x7C;
|
|
||||||
}
|
|
||||||
else if (isnan(num))
|
|
||||||
{
|
{
|
||||||
/* NaN */
|
/* NaN */
|
||||||
result |= 0x7F;
|
result |= 0x7F;
|
||||||
}
|
}
|
||||||
else if (exponent > 98)
|
else if (exponent > 116)
|
||||||
{
|
{
|
||||||
int m;
|
int m;
|
||||||
int gr;
|
int gr;
|
||||||
@@ -113,37 +92,37 @@ Float4ToFp8Unchecked(float num)
|
|||||||
s = mantissa & 0x000FFFFF;
|
s = mantissa & 0x000FFFFF;
|
||||||
|
|
||||||
/* Subnormal */
|
/* Subnormal */
|
||||||
if (exponent < -14)
|
if (exponent < -6)
|
||||||
{
|
{
|
||||||
int diff = -exponent - 14;
|
int diff = -exponent - 6;
|
||||||
|
|
||||||
mantissa >>= diff;
|
mantissa >>= diff;
|
||||||
mantissa += 1 << (23 - diff);
|
mantissa += 1 << (23 - diff);
|
||||||
s |= mantissa & 0x000FFFFF;
|
s |= mantissa & 0x000FFFFF;
|
||||||
}
|
}
|
||||||
|
|
||||||
m = mantissa >> 21;
|
m = mantissa >> 20;
|
||||||
|
|
||||||
/* Round */
|
/* Round */
|
||||||
gr = (mantissa >> 20) % 4;
|
gr = (mantissa >> 19) % 4;
|
||||||
if (gr == 3 || (gr == 1 && s != 0))
|
if (gr == 3 || (gr == 1 && s != 0))
|
||||||
m += 1;
|
m += 1;
|
||||||
|
|
||||||
if (m == 4)
|
if (m == 8)
|
||||||
{
|
{
|
||||||
m = 0;
|
m = 0;
|
||||||
exponent += 1;
|
exponent += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (exponent > 15)
|
if (exponent > 8)
|
||||||
{
|
{
|
||||||
/* Infinite */
|
/* Infinite, which is NaN */
|
||||||
result |= 0x7C;
|
result |= 0x7F;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (exponent >= -14)
|
if (exponent >= -7)
|
||||||
result |= (exponent + 15) << 2;
|
result |= (exponent + 7) << 3;
|
||||||
|
|
||||||
result |= m;
|
result |= m;
|
||||||
}
|
}
|
||||||
@@ -160,7 +139,7 @@ Float4ToFp8(float num)
|
|||||||
{
|
{
|
||||||
fp8 result = Float4ToFp8Unchecked(num);
|
fp8 result = Float4ToFp8Unchecked(num);
|
||||||
|
|
||||||
if (unlikely(Fp8IsInf(result)) && !isinf(num))
|
if (unlikely(Fp8IsNan(result)) && !isnan(num))
|
||||||
{
|
{
|
||||||
char *buf = palloc(FLOAT_SHORTEST_DECIMAL_LEN);
|
char *buf = palloc(FLOAT_SHORTEST_DECIMAL_LEN);
|
||||||
|
|
||||||
|
|||||||
@@ -154,8 +154,8 @@ SELECT '[1,2,3]'::vector::minivec(3);
|
|||||||
|
|
||||||
SELECT '[1,2,3]'::vector::minivec(2);
|
SELECT '[1,2,3]'::vector::minivec(2);
|
||||||
ERROR: expected 2 dimensions, not 3
|
ERROR: expected 2 dimensions, not 3
|
||||||
SELECT '[61440]'::vector::minivec;
|
SELECT '[465]'::vector::minivec;
|
||||||
ERROR: "61440" is out of range for type minivec
|
ERROR: "465" is out of range for type minivec
|
||||||
SELECT '[1e-8]'::vector::minivec;
|
SELECT '[1e-8]'::vector::minivec;
|
||||||
minivec
|
minivec
|
||||||
---------
|
---------
|
||||||
@@ -190,8 +190,8 @@ SELECT '{1,2,3}'::real[]::minivec(3);
|
|||||||
|
|
||||||
SELECT '{1,2,3}'::real[]::minivec(2);
|
SELECT '{1,2,3}'::real[]::minivec(2);
|
||||||
ERROR: expected 2 dimensions, not 3
|
ERROR: expected 2 dimensions, not 3
|
||||||
SELECT '{61440,-61440}'::real[]::minivec;
|
SELECT '{465,-465}'::real[]::minivec;
|
||||||
ERROR: "61440" is out of range for type minivec
|
ERROR: "465" is out of range for type minivec
|
||||||
SELECT '{1e-8,-1e-8}'::real[]::minivec;
|
SELECT '{1e-8,-1e-8}'::real[]::minivec;
|
||||||
minivec
|
minivec
|
||||||
---------
|
---------
|
||||||
|
|||||||
@@ -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;
|
|
||||||
@@ -37,22 +37,20 @@ ERROR: NaN not allowed in minivec
|
|||||||
LINE 1: SELECT '[NaN,1]'::minivec;
|
LINE 1: SELECT '[NaN,1]'::minivec;
|
||||||
^
|
^
|
||||||
SELECT '[Infinity,1]'::minivec;
|
SELECT '[Infinity,1]'::minivec;
|
||||||
ERROR: infinite value not allowed in minivec
|
ERROR: "Infinity" is out of range for type minivec
|
||||||
LINE 1: SELECT '[Infinity,1]'::minivec;
|
LINE 1: SELECT '[Infinity,1]'::minivec;
|
||||||
^
|
^
|
||||||
SELECT '[-Infinity,1]'::minivec;
|
SELECT '[-Infinity,1]'::minivec;
|
||||||
ERROR: infinite value not allowed in minivec
|
ERROR: "-Infinity" is out of range for type minivec
|
||||||
LINE 1: SELECT '[-Infinity,1]'::minivec;
|
LINE 1: SELECT '[-Infinity,1]'::minivec;
|
||||||
^
|
^
|
||||||
SELECT '[61439,-61439]'::minivec;
|
SELECT '[65519,-65519]'::minivec;
|
||||||
minivec
|
ERROR: "65519" is out of range for type minivec
|
||||||
----------------
|
LINE 1: SELECT '[65519,-65519]'::minivec;
|
||||||
[57344,-57344]
|
^
|
||||||
(1 row)
|
SELECT '[65520,-65520]'::minivec;
|
||||||
|
ERROR: "65520" is out of range for type minivec
|
||||||
SELECT '[61440,-61440]'::minivec;
|
LINE 1: SELECT '[65520,-65520]'::minivec;
|
||||||
ERROR: "61440" is out of range for type minivec
|
|
||||||
LINE 1: SELECT '[61440,-61440]'::minivec;
|
|
||||||
^
|
^
|
||||||
SELECT '[1e-8,-1e-8]'::minivec;
|
SELECT '[1e-8,-1e-8]'::minivec;
|
||||||
minivec
|
minivec
|
||||||
@@ -165,30 +163,30 @@ ERROR: expected 2 dimensions, not 3
|
|||||||
SELECT '[1,2,3]'::minivec + '[4,5,6]';
|
SELECT '[1,2,3]'::minivec + '[4,5,6]';
|
||||||
?column?
|
?column?
|
||||||
----------
|
----------
|
||||||
[5,7,8]
|
[5,7,9]
|
||||||
(1 row)
|
(1 row)
|
||||||
|
|
||||||
SELECT '[61439]'::minivec + '[61439]';
|
SELECT '[448]'::minivec + '[448]';
|
||||||
ERROR: value out of range: overflow
|
ERROR: value out of range: overflow
|
||||||
SELECT '[1,2]'::minivec + '[3]';
|
SELECT '[1,2]'::minivec + '[3]';
|
||||||
ERROR: different minivec dimensions 2 and 1
|
ERROR: different minivec dimensions 2 and 1
|
||||||
SELECT '[1,2,3]'::minivec - '[4,5,6]';
|
SELECT '[1,2,3]'::minivec - '[4,5,6]';
|
||||||
ERROR: value out of range: overflow
|
|
||||||
SELECT '[-61439]'::minivec - '[61439]';
|
|
||||||
?column?
|
?column?
|
||||||
----------
|
------------
|
||||||
[-0]
|
[-3,-3,-3]
|
||||||
(1 row)
|
(1 row)
|
||||||
|
|
||||||
|
SELECT '[-448]'::minivec - '[448]';
|
||||||
|
ERROR: value out of range: overflow
|
||||||
SELECT '[1,2]'::minivec - '[3]';
|
SELECT '[1,2]'::minivec - '[3]';
|
||||||
ERROR: different minivec dimensions 2 and 1
|
ERROR: different minivec dimensions 2 and 1
|
||||||
SELECT '[1,2,3]'::minivec * '[4,5,6]';
|
SELECT '[1,2,3]'::minivec * '[4,5,6]';
|
||||||
?column?
|
?column?
|
||||||
-----------
|
-----------
|
||||||
[4,10,16]
|
[4,10,18]
|
||||||
(1 row)
|
(1 row)
|
||||||
|
|
||||||
SELECT '[61439]'::minivec * '[61439]';
|
SELECT '[448]'::minivec * '[448]';
|
||||||
ERROR: value out of range: overflow
|
ERROR: value out of range: overflow
|
||||||
SELECT '[1e-7]'::minivec * '[1e-7]';
|
SELECT '[1e-7]'::minivec * '[1e-7]';
|
||||||
?column?
|
?column?
|
||||||
@@ -399,7 +397,7 @@ 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 inner_product('[1,1,1,1,1,1,1,1,1]'::minivec, '[1,2,3,4,5,6,7,8,9]');
|
||||||
inner_product
|
inner_product
|
||||||
---------------
|
---------------
|
||||||
44
|
45
|
||||||
(1 row)
|
(1 row)
|
||||||
|
|
||||||
SELECT '[1,2]'::minivec <#> '[3,4]';
|
SELECT '[1,2]'::minivec <#> '[3,4]';
|
||||||
@@ -493,7 +491,7 @@ 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 l1_distance('[1,2,3,4,5,6,7,8,9]'::minivec, '[0,3,2,5,4,7,6,9,8]');
|
||||||
l1_distance
|
l1_distance
|
||||||
-------------
|
-------------
|
||||||
7
|
9
|
||||||
(1 row)
|
(1 row)
|
||||||
|
|
||||||
SELECT '[0,0]'::minivec <+> '[3,4]';
|
SELECT '[0,0]'::minivec <+> '[3,4]';
|
||||||
@@ -504,8 +502,8 @@ SELECT '[0,0]'::minivec <+> '[3,4]';
|
|||||||
|
|
||||||
SELECT l2_normalize('[3,4]'::minivec);
|
SELECT l2_normalize('[3,4]'::minivec);
|
||||||
l2_normalize
|
l2_normalize
|
||||||
--------------
|
----------------
|
||||||
[0.625,0.75]
|
[0.625,0.8125]
|
||||||
(1 row)
|
(1 row)
|
||||||
|
|
||||||
SELECT l2_normalize('[3,0]'::minivec);
|
SELECT l2_normalize('[3,0]'::minivec);
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ SELECT '{1e-8,-1e-8}'::real[]::halfvec;
|
|||||||
SELECT '[1,2,3]'::vector::minivec;
|
SELECT '[1,2,3]'::vector::minivec;
|
||||||
SELECT '[1,2,3]'::vector::minivec(3);
|
SELECT '[1,2,3]'::vector::minivec(3);
|
||||||
SELECT '[1,2,3]'::vector::minivec(2);
|
SELECT '[1,2,3]'::vector::minivec(2);
|
||||||
SELECT '[61440]'::vector::minivec;
|
SELECT '[465]'::vector::minivec;
|
||||||
SELECT '[1e-8]'::vector::minivec;
|
SELECT '[1e-8]'::vector::minivec;
|
||||||
|
|
||||||
SELECT '[1,2,3]'::minivec::vector;
|
SELECT '[1,2,3]'::minivec::vector;
|
||||||
@@ -51,7 +51,7 @@ SELECT '[1,2,3]'::minivec::vector(2);
|
|||||||
SELECT '{1,2,3}'::real[]::minivec;
|
SELECT '{1,2,3}'::real[]::minivec;
|
||||||
SELECT '{1,2,3}'::real[]::minivec(3);
|
SELECT '{1,2,3}'::real[]::minivec(3);
|
||||||
SELECT '{1,2,3}'::real[]::minivec(2);
|
SELECT '{1,2,3}'::real[]::minivec(2);
|
||||||
SELECT '{61440,-61440}'::real[]::minivec;
|
SELECT '{465,-465}'::real[]::minivec;
|
||||||
SELECT '{1e-8,-1e-8}'::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;
|
||||||
|
|||||||
@@ -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;
|
|
||||||
@@ -7,8 +7,8 @@ SELECT '[hello,1]'::minivec;
|
|||||||
SELECT '[NaN,1]'::minivec;
|
SELECT '[NaN,1]'::minivec;
|
||||||
SELECT '[Infinity,1]'::minivec;
|
SELECT '[Infinity,1]'::minivec;
|
||||||
SELECT '[-Infinity,1]'::minivec;
|
SELECT '[-Infinity,1]'::minivec;
|
||||||
SELECT '[61439,-61439]'::minivec;
|
SELECT '[65519,-65519]'::minivec;
|
||||||
SELECT '[61440,-61440]'::minivec;
|
SELECT '[65520,-65520]'::minivec;
|
||||||
SELECT '[1e-8,-1e-8]'::minivec;
|
SELECT '[1e-8,-1e-8]'::minivec;
|
||||||
SELECT '[4e38,1]'::minivec;
|
SELECT '[4e38,1]'::minivec;
|
||||||
SELECT '[1e-46,1]'::minivec;
|
SELECT '[1e-46,1]'::minivec;
|
||||||
@@ -38,15 +38,15 @@ SELECT unnest('{"[1,2,3]", "[4,5,6]"}'::minivec[]);
|
|||||||
SELECT '{"[1,2,3]"}'::minivec(2)[];
|
SELECT '{"[1,2,3]"}'::minivec(2)[];
|
||||||
|
|
||||||
SELECT '[1,2,3]'::minivec + '[4,5,6]';
|
SELECT '[1,2,3]'::minivec + '[4,5,6]';
|
||||||
SELECT '[61439]'::minivec + '[61439]';
|
SELECT '[448]'::minivec + '[448]';
|
||||||
SELECT '[1,2]'::minivec + '[3]';
|
SELECT '[1,2]'::minivec + '[3]';
|
||||||
|
|
||||||
SELECT '[1,2,3]'::minivec - '[4,5,6]';
|
SELECT '[1,2,3]'::minivec - '[4,5,6]';
|
||||||
SELECT '[-61439]'::minivec - '[61439]';
|
SELECT '[-448]'::minivec - '[448]';
|
||||||
SELECT '[1,2]'::minivec - '[3]';
|
SELECT '[1,2]'::minivec - '[3]';
|
||||||
|
|
||||||
SELECT '[1,2,3]'::minivec * '[4,5,6]';
|
SELECT '[1,2,3]'::minivec * '[4,5,6]';
|
||||||
SELECT '[61439]'::minivec * '[61439]';
|
SELECT '[448]'::minivec * '[448]';
|
||||||
SELECT '[1e-7]'::minivec * '[1e-7]';
|
SELECT '[1e-7]'::minivec * '[1e-7]';
|
||||||
SELECT '[1,2]'::minivec * '[3]';
|
SELECT '[1,2]'::minivec * '[3]';
|
||||||
|
|
||||||
|
|||||||
@@ -40,10 +40,6 @@ for (1 .. 50)
|
|||||||
$actual = $node->safe_psql("postgres", "SELECT halfvec_cmp(v::halfvec, '$query'::real[]::halfvec) FROM tst");
|
$actual = $node->safe_psql("postgres", "SELECT halfvec_cmp(v::halfvec, '$query'::real[]::halfvec) FROM tst");
|
||||||
is($expected, $actual);
|
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
|
# Test sparsevec
|
||||||
$actual = $node->safe_psql("postgres", "SELECT sparsevec_cmp(v::vector::sparsevec, '$query'::real[]::vector::sparsevec) FROM tst");
|
$actual = $node->safe_psql("postgres", "SELECT sparsevec_cmp(v::vector::sparsevec, '$query'::real[]::vector::sparsevec) FROM tst");
|
||||||
is($expected, $actual);
|
is($expected, $actual);
|
||||||
|
|||||||
@@ -45,10 +45,6 @@ for my $function (@functions)
|
|||||||
my $actual = $node->safe_psql("postgres", "SELECT $function(v::halfvec, '$query'::vector::halfvec) FROM tst");
|
my $actual = $node->safe_psql("postgres", "SELECT $function(v::halfvec, '$query'::vector::halfvec) FROM tst");
|
||||||
is($expected, $actual, "halfvec $function");
|
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
|
# Test sparsevec
|
||||||
$actual = $node->safe_psql("postgres", "SELECT $function(v::sparsevec, '$query'::vector::sparsevec) FROM tst");
|
$actual = $node->safe_psql("postgres", "SELECT $function(v::sparsevec, '$query'::vector::sparsevec) FROM tst");
|
||||||
is($expected, $actual, "sparsevec $function");
|
is($expected, $actual, "sparsevec $function");
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ $node->start;
|
|||||||
# Create extension
|
# Create extension
|
||||||
$node->safe_psql("postgres", "CREATE EXTENSION vector;");
|
$node->safe_psql("postgres", "CREATE EXTENSION vector;");
|
||||||
|
|
||||||
my @types = ("vector", "halfvec", "minivec", "sparsevec");
|
my @types = ("vector", "halfvec", "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 @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");
|
my @subs = (" ", " ", ",", ":", "-", "1", "9", "\0", "2147483648", "-2147483649");
|
||||||
|
|
||||||
for my $i (0 .. $#types)
|
for my $i (0 .. $#types)
|
||||||
|
|||||||
@@ -95,6 +95,10 @@ for my $i (0 .. $#operators)
|
|||||||
|
|
||||||
# Test approximate results
|
# Test approximate results
|
||||||
my $min = 0.98;
|
my $min = 0.98;
|
||||||
|
if ($operator eq '<=>')
|
||||||
|
{
|
||||||
|
$min = 0.65;
|
||||||
|
}
|
||||||
test_recall($min, $operator);
|
test_recall($min, $operator);
|
||||||
|
|
||||||
$node->safe_psql("postgres", "DROP INDEX idx;");
|
$node->safe_psql("postgres", "DROP INDEX idx;");
|
||||||
|
|||||||
@@ -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();
|
|
||||||
@@ -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();
|
|
||||||
@@ -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();
|
|
||||||
@@ -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();
|
|
||||||
Reference in New Issue
Block a user