| /*------------------------------------------------------------------------- |
| * |
| * ginvalidate.c |
| * Opclass validator for GIN. |
| * |
| * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group |
| * Portions Copyright (c) 1994, Regents of the University of California |
| * |
| * IDENTIFICATION |
| * src/backend/access/gin/ginvalidate.c |
| * |
| *------------------------------------------------------------------------- |
| */ |
| #include "postgres.h" |
| |
| #include "access/amvalidate.h" |
| #include "access/gin_private.h" |
| #include "access/htup_details.h" |
| #include "catalog/pg_amop.h" |
| #include "catalog/pg_amproc.h" |
| #include "catalog/pg_opclass.h" |
| #include "catalog/pg_opfamily.h" |
| #include "catalog/pg_type.h" |
| #include "utils/builtins.h" |
| #include "utils/lsyscache.h" |
| #include "utils/regproc.h" |
| #include "utils/syscache.h" |
| |
| /* |
| * Validator for a GIN opclass. |
| */ |
| bool |
| ginvalidate(Oid opclassoid) |
| { |
| bool result = true; |
| HeapTuple classtup; |
| Form_pg_opclass classform; |
| Oid opfamilyoid; |
| Oid opcintype; |
| Oid opckeytype; |
| char *opclassname; |
| HeapTuple familytup; |
| Form_pg_opfamily familyform; |
| char *opfamilyname; |
| CatCList *proclist, |
| *oprlist; |
| List *grouplist; |
| OpFamilyOpFuncGroup *opclassgroup; |
| int i; |
| ListCell *lc; |
| |
| /* 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); |
| |
| opfamilyoid = classform->opcfamily; |
| opcintype = classform->opcintype; |
| opckeytype = classform->opckeytype; |
| if (!OidIsValid(opckeytype)) |
| opckeytype = opcintype; |
| opclassname = NameStr(classform->opcname); |
| |
| /* Fetch opfamily information */ |
| 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); |
| |
| /* Fetch all operators and support functions of the opfamily */ |
| oprlist = SearchSysCacheList1(AMOPSTRATEGY, ObjectIdGetDatum(opfamilyoid)); |
| proclist = SearchSysCacheList1(AMPROCNUM, ObjectIdGetDatum(opfamilyoid)); |
| |
| /* Check individual support functions */ |
| for (i = 0; i < proclist->n_members; i++) |
| { |
| HeapTuple proctup = &proclist->members[i]->tuple; |
| Form_pg_amproc procform = (Form_pg_amproc) GETSTRUCT(proctup); |
| bool ok; |
| |
| /* |
| * All GIN support functions should be registered with matching |
| * left/right types |
| */ |
| if (procform->amproclefttype != procform->amprocrighttype) |
| { |
| ereport(INFO, |
| (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), |
| errmsg("operator family \"%s\" of access method %s contains support function %s with different left and right input types", |
| opfamilyname, "gin", |
| format_procedure(procform->amproc)))); |
| result = false; |
| } |
| |
| /* |
| * We can't check signatures except within the specific opclass, since |
| * we need to know the associated opckeytype in many cases. |
| */ |
| if (procform->amproclefttype != opcintype) |
| continue; |
| |
| /* Check procedure numbers and function signatures */ |
| switch (procform->amprocnum) |
| { |
| case GIN_COMPARE_PROC: |
| ok = check_amproc_signature(procform->amproc, INT4OID, false, |
| 2, 2, opckeytype, opckeytype); |
| break; |
| case GIN_EXTRACTVALUE_PROC: |
| /* Some opclasses omit nullFlags */ |
| ok = check_amproc_signature(procform->amproc, INTERNALOID, false, |
| 2, 3, opcintype, INTERNALOID, |
| INTERNALOID); |
| break; |
| case GIN_EXTRACTQUERY_PROC: |
| /* Some opclasses omit nullFlags and searchMode */ |
| ok = check_amproc_signature(procform->amproc, INTERNALOID, false, |
| 5, 7, opcintype, INTERNALOID, |
| INT2OID, INTERNALOID, INTERNALOID, |
| INTERNALOID, INTERNALOID); |
| break; |
| case GIN_CONSISTENT_PROC: |
| /* Some opclasses omit queryKeys and nullFlags */ |
| ok = check_amproc_signature(procform->amproc, BOOLOID, false, |
| 6, 8, INTERNALOID, INT2OID, |
| opcintype, INT4OID, |
| INTERNALOID, INTERNALOID, |
| INTERNALOID, INTERNALOID); |
| break; |
| case GIN_COMPARE_PARTIAL_PROC: |
| ok = check_amproc_signature(procform->amproc, INT4OID, false, |
| 4, 4, opckeytype, opckeytype, |
| INT2OID, INTERNALOID); |
| break; |
| case GIN_TRICONSISTENT_PROC: |
| ok = check_amproc_signature(procform->amproc, CHAROID, false, |
| 7, 7, INTERNALOID, INT2OID, |
| opcintype, INT4OID, |
| INTERNALOID, INTERNALOID, |
| INTERNALOID); |
| break; |
| case GIN_OPTIONS_PROC: |
| ok = check_amoptsproc_signature(procform->amproc); |
| 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, "gin", |
| format_procedure(procform->amproc), |
| procform->amprocnum))); |
| result = false; |
| continue; /* don't want additional message */ |
| } |
| |
| 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, "gin", |
| format_procedure(procform->amproc), |
| procform->amprocnum))); |
| result = false; |
| } |
| } |
| |
| /* Check individual operators */ |
| for (i = 0; i < oprlist->n_members; i++) |
| { |
| HeapTuple oprtup = &oprlist->members[i]->tuple; |
| Form_pg_amop oprform = (Form_pg_amop) GETSTRUCT(oprtup); |
| |
| /* TODO: Check that only allowed strategy numbers exist */ |
| if (oprform->amopstrategy < 1 || oprform->amopstrategy > 63) |
| { |
| ereport(INFO, |
| (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), |
| errmsg("operator family \"%s\" of access method %s contains operator %s with invalid strategy number %d", |
| opfamilyname, "gin", |
| format_operator(oprform->amopopr), |
| oprform->amopstrategy))); |
| result = false; |
| } |
| |
| /* gin doesn't support ORDER BY operators */ |
| if (oprform->amoppurpose != AMOP_SEARCH || |
| OidIsValid(oprform->amopsortfamily)) |
| { |
| ereport(INFO, |
| (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), |
| errmsg("operator family \"%s\" of access method %s contains invalid ORDER BY specification for operator %s", |
| opfamilyname, "gin", |
| format_operator(oprform->amopopr)))); |
| result = false; |
| } |
| |
| /* Check operator signature --- same for all gin strategies */ |
| if (!check_amop_signature(oprform->amopopr, BOOLOID, |
| oprform->amoplefttype, |
| oprform->amoprighttype)) |
| { |
| ereport(INFO, |
| (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), |
| errmsg("operator family \"%s\" of access method %s contains operator %s with wrong signature", |
| opfamilyname, "gin", |
| format_operator(oprform->amopopr)))); |
| result = false; |
| } |
| } |
| |
| /* Now check for inconsistent groups of operators/functions */ |
| grouplist = identify_opfamily_groups(oprlist, proclist); |
| opclassgroup = NULL; |
| foreach(lc, grouplist) |
| { |
| OpFamilyOpFuncGroup *thisgroup = (OpFamilyOpFuncGroup *) lfirst(lc); |
| |
| /* Remember the group exactly matching the test opclass */ |
| if (thisgroup->lefttype == opcintype && |
| thisgroup->righttype == opcintype) |
| opclassgroup = thisgroup; |
| |
| /* |
| * There is not a lot we can do to check the operator sets, since each |
| * GIN opclass is more or less a law unto itself, and some contain |
| * only operators that are binary-compatible with the opclass datatype |
| * (meaning that empty operator sets can be OK). That case also means |
| * that we shouldn't insist on nonempty function sets except for the |
| * opclass's own group. |
| */ |
| } |
| |
| /* Check that the originally-named opclass is complete */ |
| for (i = 1; i <= GINNProcs; i++) |
| { |
| if (opclassgroup && |
| (opclassgroup->functionset & (((uint64) 1) << i)) != 0) |
| continue; /* got it */ |
| if (i == GIN_COMPARE_PROC || i == GIN_COMPARE_PARTIAL_PROC || |
| i == GIN_OPTIONS_PROC) |
| continue; /* optional method */ |
| if (i == GIN_CONSISTENT_PROC || i == GIN_TRICONSISTENT_PROC) |
| continue; /* don't need both, see check below loop */ |
| ereport(INFO, |
| (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), |
| errmsg("operator class \"%s\" of access method %s is missing support function %d", |
| opclassname, "gin", i))); |
| result = false; |
| } |
| if (!opclassgroup || |
| ((opclassgroup->functionset & (1 << GIN_CONSISTENT_PROC)) == 0 && |
| (opclassgroup->functionset & (1 << GIN_TRICONSISTENT_PROC)) == 0)) |
| { |
| ereport(INFO, |
| (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), |
| errmsg("operator class \"%s\" of access method %s is missing support function %d or %d", |
| opclassname, "gin", |
| GIN_CONSISTENT_PROC, GIN_TRICONSISTENT_PROC))); |
| result = false; |
| } |
| |
| |
| ReleaseCatCacheList(proclist); |
| ReleaseCatCacheList(oprlist); |
| ReleaseSysCache(familytup); |
| ReleaseSysCache(classtup); |
| |
| return result; |
| } |
| |
| /* |
| * Prechecking function for adding operators/functions to a GIN opfamily. |
| */ |
| void |
| ginadjustmembers(Oid opfamilyoid, |
| Oid opclassoid, |
| List *operators, |
| List *functions) |
| { |
| ListCell *lc; |
| |
| /* |
| * Operator members of a GIN opfamily should never have hard dependencies, |
| * since their connection to the opfamily depends only on what the support |
| * functions think, and that can be altered. For consistency, we make all |
| * soft dependencies point to the opfamily, though a soft dependency on |
| * the opclass would work as well in the CREATE OPERATOR CLASS case. |
| */ |
| foreach(lc, operators) |
| { |
| OpFamilyMember *op = (OpFamilyMember *) lfirst(lc); |
| |
| op->ref_is_hard = false; |
| op->ref_is_family = true; |
| op->refobjid = opfamilyoid; |
| } |
| |
| /* |
| * Required support functions should have hard dependencies. Preferably |
| * those are just dependencies on the opclass, but if we're in ALTER |
| * OPERATOR FAMILY, we leave the dependency pointing at the whole |
| * opfamily. (Given that GIN opclasses generally don't share opfamilies, |
| * it seems unlikely to be worth working harder.) |
| */ |
| foreach(lc, functions) |
| { |
| OpFamilyMember *op = (OpFamilyMember *) lfirst(lc); |
| |
| switch (op->number) |
| { |
| case GIN_EXTRACTVALUE_PROC: |
| case GIN_EXTRACTQUERY_PROC: |
| /* Required support function */ |
| op->ref_is_hard = true; |
| break; |
| case GIN_COMPARE_PROC: |
| case GIN_CONSISTENT_PROC: |
| case GIN_COMPARE_PARTIAL_PROC: |
| case GIN_TRICONSISTENT_PROC: |
| case GIN_OPTIONS_PROC: |
| /* Optional, so force it to be a soft family dependency */ |
| op->ref_is_hard = false; |
| op->ref_is_family = true; |
| op->refobjid = opfamilyoid; |
| break; |
| default: |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), |
| errmsg("support function number %d is invalid for access method %s", |
| op->number, "gin"))); |
| break; |
| } |
| } |
| } |