1
0
mirror of https://github.com/pgvector/pgvector.git synced 2026-10-23 10:48:54 +03:00

Improved amvalidate for indexes

This commit is contained in:
Andrew Kane
2026-10-21 00:11:26 -07:00
parent c707c56325
commit f0252a04b3
5 changed files with 297 additions and 2 deletions

View File

@@ -1,5 +1,6 @@
## 0.9.0 (unreleased)
- Improved `amvalidate` for indexes
- Dropped support for Postgres 13 and 14
## 0.8.7 (2026-10-01)

View File

@@ -5,8 +5,13 @@
#include <math.h>
#include "access/amapi.h"
#include "access/amvalidate.h"
#include "access/genam.h"
#include "access/htup_details.h"
#include "access/reloptions.h"
#include "catalog/pg_amop.h"
#include "catalog/pg_amproc.h"
#include "catalog/pg_opclass.h"
#include "commands/progress.h"
#include "commands/vacuum.h"
#include "fmgr.h"
@@ -14,13 +19,21 @@
#include "miscadmin.h"
#include "nodes/pg_list.h"
#include "storage/lwlock.h"
#include "utils/catcache.h"
#include "utils/float.h"
#include "utils/guc.h"
#include "utils/lsyscache.h"
#include "utils/regproc.h"
#include "utils/relcache.h"
#include "utils/selfuncs.h"
#include "utils/spccache.h"
#include "utils/syscache.h"
#include "vector.h"
#if PG_VERSION_NUM < 180000
#include "catalog/pg_opfamily.h"
#endif
static const struct config_enum_entry hnsw_iterative_scan_options[] = {
{"off", HNSW_ITERATIVE_SCAN_OFF, false},
{"relaxed_order", HNSW_ITERATIVE_SCAN_RELAXED, false},
@@ -251,7 +264,133 @@ hnswoptions(Datum reloptions, bool validate)
static bool
hnswvalidate(Oid opclassoid)
{
return true;
bool result = true;
HeapTuple classtup;
Form_pg_opclass classform;
Oid opcintype;
Oid opfamilyoid;
#if PG_VERSION_NUM < 180000
HeapTuple familytup;
Form_pg_opfamily familyform;
#endif
char *opfamilyname;
CatCList *proclist;
CatCList *oprlist;
/* Fetch opclass information */
classtup = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclassoid));
if (!HeapTupleIsValid(classtup))
elog(ERROR, "cache lookup failed for operator class %u", opclassoid);
classform = (Form_pg_opclass) GETSTRUCT(classtup);
opcintype = classform->opcintype;
/* Fetch opfamily information */
opfamilyoid = classform->opcfamily;
#if PG_VERSION_NUM >= 180000
opfamilyname = get_opfamily_name(opfamilyoid, false);
#else
familytup = SearchSysCache1(OPFAMILYOID, ObjectIdGetDatum(opfamilyoid));
if (!HeapTupleIsValid(familytup))
elog(ERROR, "cache lookup failed for operator family %u", opfamilyoid);
familyform = (Form_pg_opfamily) GETSTRUCT(familytup);
opfamilyname = NameStr(familyform->opfname);
#endif
/* Fetch all operators and support functions of the opfamily */
oprlist = SearchSysCacheList1(AMOPSTRATEGY, ObjectIdGetDatum(opfamilyoid));
proclist = SearchSysCacheList1(AMPROCNUM, ObjectIdGetDatum(opfamilyoid));
/* Check individual support functions */
for (int i = 0; i < proclist->n_members; i++)
{
HeapTuple proctup = &proclist->members[i]->tuple;
Form_pg_amproc procform = (Form_pg_amproc) GETSTRUCT(proctup);
bool ok;
/* Check procedure numbers and function signatures */
switch (procform->amprocnum)
{
case HNSW_DISTANCE_PROC:
ok = check_amproc_signature(procform->amproc, FLOAT8OID, true, 2, 2, opcintype, opcintype);
break;
case HNSW_NORM_PROC:
ok = check_amproc_signature(procform->amproc, FLOAT8OID, true, 1, 1, opcintype);
break;
case HNSW_TYPE_INFO_PROC:
ok = check_amproc_signature(procform->amproc, INTERNALOID, true, 1, 1, INTERNALOID);
break;
default:
ereport(INFO,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("operator family \"%s\" of access method %s contains function %s with invalid support number %d",
opfamilyname, "hnsw",
format_procedure(procform->amproc),
procform->amprocnum)));
result = false;
continue;
}
if (!ok)
{
ereport(INFO,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("operator family \"%s\" of access method %s contains function %s with wrong signature for support number %d",
opfamilyname, "hnsw",
format_procedure(procform->amproc),
procform->amprocnum)));
result = false;
}
}
/* Check individual operators */
for (int i = 0; i < oprlist->n_members; i++)
{
HeapTuple oprtup = &oprlist->members[i]->tuple;
Form_pg_amop oprform = (Form_pg_amop) GETSTRUCT(oprtup);
/* Check strategy number */
if (oprform->amopstrategy != 1)
{
ereport(INFO,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("operator family \"%s\" of access method %s contains operator %s with invalid strategy number %d",
opfamilyname, "hnsw",
format_operator(oprform->amopopr),
oprform->amopstrategy)));
result = false;
}
/* Check sort family */
if (oprform->amoppurpose != AMOP_ORDER || !opfamily_can_sort_type(oprform->amopsortfamily, FLOAT8OID))
{
ereport(INFO,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("operator family \"%s\" of access method %s contains invalid ORDER BY specification for operator %s",
opfamilyname, "hnsw",
format_operator(oprform->amopopr))));
result = false;
}
/* Check operator signature */
if (!check_amop_signature(oprform->amopopr, FLOAT8OID, opcintype, opcintype))
{
ereport(INFO,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("operator family \"%s\" of access method %s contains operator %s with wrong signature",
opfamilyname, "hnsw",
format_operator(oprform->amopopr))));
result = false;
}
}
ReleaseCatCacheList(proclist);
ReleaseCatCacheList(oprlist);
#if PG_VERSION_NUM < 180000
ReleaseSysCache(familytup);
#endif
ReleaseSysCache(classtup);
return result;
}
/*

View File

@@ -3,20 +3,33 @@
#include <float.h>
#include "access/amapi.h"
#include "access/amvalidate.h"
#include "access/genam.h"
#include "access/htup_details.h"
#include "access/reloptions.h"
#include "catalog/pg_amop.h"
#include "catalog/pg_amproc.h"
#include "catalog/pg_opclass.h"
#include "commands/progress.h"
#include "commands/vacuum.h"
#include "fmgr.h"
#include "ivfflat.h"
#include "nodes/pg_list.h"
#include "utils/catcache.h"
#include "utils/float.h"
#include "utils/guc.h"
#include "utils/lsyscache.h"
#include "utils/regproc.h"
#include "utils/relcache.h"
#include "utils/selfuncs.h"
#include "utils/spccache.h"
#include "utils/syscache.h"
#include "vector.h"
#if PG_VERSION_NUM < 180000
#include "catalog/pg_opfamily.h"
#endif
int ivfflat_probes;
int ivfflat_iterative_scan;
int ivfflat_max_probes;
@@ -168,7 +181,139 @@ ivfflatoptions(Datum reloptions, bool validate)
static bool
ivfflatvalidate(Oid opclassoid)
{
return true;
bool result = true;
HeapTuple classtup;
Form_pg_opclass classform;
Oid opcintype;
Oid opfamilyoid;
#if PG_VERSION_NUM < 180000
HeapTuple familytup;
Form_pg_opfamily familyform;
#endif
char *opfamilyname;
CatCList *proclist;
CatCList *oprlist;
/* Fetch opclass information */
classtup = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclassoid));
if (!HeapTupleIsValid(classtup))
elog(ERROR, "cache lookup failed for operator class %u", opclassoid);
classform = (Form_pg_opclass) GETSTRUCT(classtup);
opcintype = classform->opcintype;
/* Fetch opfamily information */
opfamilyoid = classform->opcfamily;
#if PG_VERSION_NUM >= 180000
opfamilyname = get_opfamily_name(opfamilyoid, false);
#else
familytup = SearchSysCache1(OPFAMILYOID, ObjectIdGetDatum(opfamilyoid));
if (!HeapTupleIsValid(familytup))
elog(ERROR, "cache lookup failed for operator family %u", opfamilyoid);
familyform = (Form_pg_opfamily) GETSTRUCT(familytup);
opfamilyname = NameStr(familyform->opfname);
#endif
/* Fetch all operators and support functions of the opfamily */
oprlist = SearchSysCacheList1(AMOPSTRATEGY, ObjectIdGetDatum(opfamilyoid));
proclist = SearchSysCacheList1(AMPROCNUM, ObjectIdGetDatum(opfamilyoid));
/* Check individual support functions */
for (int i = 0; i < proclist->n_members; i++)
{
HeapTuple proctup = &proclist->members[i]->tuple;
Form_pg_amproc procform = (Form_pg_amproc) GETSTRUCT(proctup);
bool ok;
/* Check procedure numbers and function signatures */
switch (procform->amprocnum)
{
case IVFFLAT_DISTANCE_PROC:
ok = check_amproc_signature(procform->amproc, FLOAT8OID, true, 2, 2, opcintype, opcintype);
break;
case IVFFLAT_NORM_PROC:
ok = check_amproc_signature(procform->amproc, FLOAT8OID, true, 1, 1, opcintype);
break;
case IVFFLAT_KMEANS_DISTANCE_PROC:
ok = check_amproc_signature(procform->amproc, FLOAT8OID, true, 2, 2, opcintype, opcintype);
break;
case IVFFLAT_KMEANS_NORM_PROC:
ok = check_amproc_signature(procform->amproc, FLOAT8OID, true, 1, 1, opcintype);
break;
case IVFFLAT_TYPE_INFO_PROC:
ok = check_amproc_signature(procform->amproc, INTERNALOID, true, 1, 1, INTERNALOID);
break;
default:
ereport(INFO,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("operator family \"%s\" of access method %s contains function %s with invalid support number %d",
opfamilyname, "ivfflat",
format_procedure(procform->amproc),
procform->amprocnum)));
result = false;
continue;
}
if (!ok)
{
ereport(INFO,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("operator family \"%s\" of access method %s contains function %s with wrong signature for support number %d",
opfamilyname, "ivfflat",
format_procedure(procform->amproc),
procform->amprocnum)));
result = false;
}
}
/* Check individual operators */
for (int i = 0; i < oprlist->n_members; i++)
{
HeapTuple oprtup = &oprlist->members[i]->tuple;
Form_pg_amop oprform = (Form_pg_amop) GETSTRUCT(oprtup);
/* Check strategy number */
if (oprform->amopstrategy != 1)
{
ereport(INFO,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("operator family \"%s\" of access method %s contains operator %s with invalid strategy number %d",
opfamilyname, "ivfflat",
format_operator(oprform->amopopr),
oprform->amopstrategy)));
result = false;
}
/* Check sort family */
if (oprform->amoppurpose != AMOP_ORDER || !opfamily_can_sort_type(oprform->amopsortfamily, FLOAT8OID))
{
ereport(INFO,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("operator family \"%s\" of access method %s contains invalid ORDER BY specification for operator %s",
opfamilyname, "ivfflat",
format_operator(oprform->amopopr))));
result = false;
}
/* Check operator signature */
if (!check_amop_signature(oprform->amopopr, FLOAT8OID, opcintype, opcintype))
{
ereport(INFO,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("operator family \"%s\" of access method %s contains operator %s with wrong signature",
opfamilyname, "ivfflat",
format_operator(oprform->amopopr))));
result = false;
}
}
ReleaseCatCacheList(proclist);
ReleaseCatCacheList(oprlist);
#if PG_VERSION_NUM < 180000
ReleaseSysCache(familytup);
#endif
ReleaseSysCache(classtup);
return result;
}
/*

View File

@@ -216,3 +216,9 @@ CREATE TABLE t (val vector(2001));
CREATE INDEX ON t USING hnsw (val vector_l2_ops);
ERROR: column cannot have more than 2000 dimensions for hnsw index
DROP TABLE t;
-- opclass validation
SELECT opcname FROM pg_opclass WHERE NOT amvalidate(oid);
opcname
---------
(0 rows)

View File

@@ -122,3 +122,7 @@ DROP TABLE t;
CREATE TABLE t (val vector(2001));
CREATE INDEX ON t USING hnsw (val vector_l2_ops);
DROP TABLE t;
-- opclass validation
SELECT opcname FROM pg_opclass WHERE NOT amvalidate(oid);