/* * Is the datatype a legitimate input type for the btree opfamily? */ bool opfamily_can_sort_type(Oid opfamilyoid, Oid datatypeoid) { bool result = false; CatCList *opclist; int i; /* * We search through all btree opclasses to see if one matches. This is a * bit inefficient but there is no better index available. It also saves * making an explicit check that the opfamily belongs to btree. */ opclist = SearchSysCacheList1(CLAAMNAMENSP, ObjectIdGetDatum(BTREE_AM_OID)); for (i = 0; i < opclist->n_members; i++) { HeapTuple classtup = &opclist->members[i]->tuple; Form_pg_opclass classform = (Form_pg_opclass) GETSTRUCT(classtup); if (classform->opcfamily == opfamilyoid && classform->opcintype == datatypeoid) { result = true; break; } } ReleaseCatCacheList(opclist); return result; }
static ArrayType * enum_range_internal(Oid enumtypoid, Oid lower, Oid upper) { ArrayType *result; CatCList *list; int total, i, j; Datum *elems; list = SearchSysCacheList(ENUMTYPOIDNAME, 1, ObjectIdGetDatum(enumtypoid), 0, 0, 0); total = list->n_members; elems = (Datum *) palloc(total * sizeof(Datum)); j = 0; for (i = 0; i < total; i++) { Oid val = HeapTupleGetOid(&(list->members[i]->tuple)); if ((!OidIsValid(lower) || lower <= val) && (!OidIsValid(upper) || val <= upper)) elems[j++] = ObjectIdGetDatum(val); } /* shouldn't need the cache anymore */ ReleaseCatCacheList(list); /* sort results into OID order */ qsort(elems, j, sizeof(Datum), enum_elem_cmp); /* note this hardwires some details about the representation of Oid */ result = construct_array(elems, j, enumtypoid, sizeof(Oid), true, 'i'); pfree(elems); return result; }
Datum enum_last(PG_FUNCTION_ARGS) { Oid enumtypoid; Oid max = InvalidOid; CatCList *list; int num, i; /* * We rely on being able to get the specific enum type from the calling * expression tree. Notice that the actual value of the argument isn't * examined at all; in particular it might be NULL. */ enumtypoid = get_fn_expr_argtype(fcinfo->flinfo, 0); if (enumtypoid == InvalidOid) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("could not determine actual enum type"))); list = SearchSysCacheList(ENUMTYPOIDNAME, 1, ObjectIdGetDatum(enumtypoid), 0, 0, 0); num = list->n_members; for (i = 0; i < num; i++) { Oid valoid = HeapTupleHeaderGetOid(list->members[i]->tuple.t_data); if (!OidIsValid(max) || valoid > max) max = valoid; } ReleaseCatCacheList(list); if (!OidIsValid(max)) /* should not happen */ elog(ERROR, "no values found for enum %s", format_type_be(enumtypoid)); PG_RETURN_OID(max); }
static void ResourceOwnerReleaseInternal(ResourceOwner owner, ResourceReleasePhase phase, bool isCommit, bool isTopLevel) { ResourceOwner child; ResourceOwner save; ResourceReleaseCallbackItem *item; /* Recurse to handle descendants */ for (child = owner->firstchild; child != NULL; child = child->nextchild) ResourceOwnerReleaseInternal(child, phase, isCommit, isTopLevel); /* * Make CurrentResourceOwner point to me, so that ReleaseBuffer etc don't * get confused. We needn't PG_TRY here because the outermost level will * fix it on error abort. */ save = CurrentResourceOwner; CurrentResourceOwner = owner; if (phase == RESOURCE_RELEASE_BEFORE_LOCKS) { /* * Release buffer pins. Note that ReleaseBuffer will remove the * buffer entry from my list, so I just have to iterate till there are * none. * * During a commit, there shouldn't be any remaining pins --- that * would indicate failure to clean up the executor correctly --- so * issue warnings. In the abort case, just clean up quietly. * * We are careful to do the releasing back-to-front, so as to avoid * O(N^2) behavior in ResourceOwnerForgetBuffer(). */ while (owner->nbuffers > 0) { if (isCommit) PrintBufferLeakWarning(owner->buffers[owner->nbuffers - 1]); ReleaseBuffer(owner->buffers[owner->nbuffers - 1]); } /* * Release relcache references. Note that RelationClose will remove * the relref entry from my list, so I just have to iterate till there * are none. * * As with buffer pins, warn if any are left at commit time, and * release back-to-front for speed. */ while (owner->nrelrefs > 0) { if (isCommit) PrintRelCacheLeakWarning(owner->relrefs[owner->nrelrefs - 1]); RelationClose(owner->relrefs[owner->nrelrefs - 1]); } } else if (phase == RESOURCE_RELEASE_LOCKS) { if (isTopLevel) { /* * For a top-level xact we are going to release all locks (or at * least all non-session locks), so just do a single lmgr call at * the top of the recursion. */ if (owner == TopTransactionResourceOwner) { ProcReleaseLocks(isCommit); ReleasePredicateLocks(isCommit); } } else { /* * Release locks retail. Note that if we are committing a * subtransaction, we do NOT release its locks yet, but transfer * them to the parent. */ LOCALLOCK **locks; int nlocks; Assert(owner->parent != NULL); /* * Pass the list of locks owned by this resource owner to the lock * manager, unless it has overflowed. */ if (owner->nlocks > MAX_RESOWNER_LOCKS) { locks = NULL; nlocks = 0; } else { locks = owner->locks; nlocks = owner->nlocks; } if (isCommit) LockReassignCurrentOwner(locks, nlocks); else LockReleaseCurrentOwner(locks, nlocks); } } else if (phase == RESOURCE_RELEASE_AFTER_LOCKS) { /* * Release catcache references. Note that ReleaseCatCache will remove * the catref entry from my list, so I just have to iterate till there * are none. * * As with buffer pins, warn if any are left at commit time, and * release back-to-front for speed. */ while (owner->ncatrefs > 0) { if (isCommit) PrintCatCacheLeakWarning(owner->catrefs[owner->ncatrefs - 1]); ReleaseCatCache(owner->catrefs[owner->ncatrefs - 1]); } /* Ditto for catcache lists */ while (owner->ncatlistrefs > 0) { if (isCommit) PrintCatCacheListLeakWarning(owner->catlistrefs[owner->ncatlistrefs - 1]); ReleaseCatCacheList(owner->catlistrefs[owner->ncatlistrefs - 1]); } /* Ditto for plancache references */ while (owner->nplanrefs > 0) { if (isCommit) PrintPlanCacheLeakWarning(owner->planrefs[owner->nplanrefs - 1]); ReleaseCachedPlan(owner->planrefs[owner->nplanrefs - 1], true); } /* Ditto for tupdesc references */ while (owner->ntupdescs > 0) { if (isCommit) PrintTupleDescLeakWarning(owner->tupdescs[owner->ntupdescs - 1]); DecrTupleDescRefCount(owner->tupdescs[owner->ntupdescs - 1]); } /* Ditto for snapshot references */ while (owner->nsnapshots > 0) { if (isCommit) PrintSnapshotLeakWarning(owner->snapshots[owner->nsnapshots - 1]); UnregisterSnapshot(owner->snapshots[owner->nsnapshots - 1]); } /* Ditto for temporary files */ while (owner->nfiles > 0) { if (isCommit) PrintFileLeakWarning(owner->files[owner->nfiles - 1]); FileClose(owner->files[owner->nfiles - 1]); } /* Clean up index scans too */ ReleaseResources_hash(); } /* Let add-on modules get a chance too */ for (item = ResourceRelease_callbacks; item; item = item->next) (*item->callback) (phase, isCommit, isTopLevel, item->arg); CurrentResourceOwner = save; }
static void ResourceOwnerReleaseInternal(ResourceOwner owner, ResourceReleasePhase phase, bool isCommit, bool isTopLevel) { ResourceOwner child; ResourceOwner save; ResourceReleaseCallbackItem *item; Datum foundres; /* Recurse to handle descendants */ for (child = owner->firstchild; child != NULL; child = child->nextchild) ResourceOwnerReleaseInternal(child, phase, isCommit, isTopLevel); /* * Make CurrentResourceOwner point to me, so that ReleaseBuffer etc don't * get confused. */ save = CurrentResourceOwner; CurrentResourceOwner = owner; if (phase == RESOURCE_RELEASE_BEFORE_LOCKS) { /* * Release buffer pins. Note that ReleaseBuffer will remove the * buffer entry from our array, so we just have to iterate till there * are none. * * During a commit, there shouldn't be any remaining pins --- that * would indicate failure to clean up the executor correctly --- so * issue warnings. In the abort case, just clean up quietly. */ while (ResourceArrayGetAny(&(owner->bufferarr), &foundres)) { Buffer res = DatumGetBuffer(foundres); if (isCommit) PrintBufferLeakWarning(res); ReleaseBuffer(res); } /* Ditto for relcache references */ while (ResourceArrayGetAny(&(owner->relrefarr), &foundres)) { Relation res = (Relation) DatumGetPointer(foundres); if (isCommit) PrintRelCacheLeakWarning(res); RelationClose(res); } /* Ditto for dynamic shared memory segments */ while (ResourceArrayGetAny(&(owner->dsmarr), &foundres)) { dsm_segment *res = (dsm_segment *) DatumGetPointer(foundres); if (isCommit) PrintDSMLeakWarning(res); dsm_detach(res); } /* Ditto for JIT contexts */ while (ResourceArrayGetAny(&(owner->jitarr), &foundres)) { JitContext *context = (JitContext *) PointerGetDatum(foundres); jit_release_context(context); } } else if (phase == RESOURCE_RELEASE_LOCKS) { if (isTopLevel) { /* * For a top-level xact we are going to release all locks (or at * least all non-session locks), so just do a single lmgr call at * the top of the recursion. */ if (owner == TopTransactionResourceOwner) { ProcReleaseLocks(isCommit); ReleasePredicateLocks(isCommit); } } else { /* * Release locks retail. Note that if we are committing a * subtransaction, we do NOT release its locks yet, but transfer * them to the parent. */ LOCALLOCK **locks; int nlocks; Assert(owner->parent != NULL); /* * Pass the list of locks owned by this resource owner to the lock * manager, unless it has overflowed. */ if (owner->nlocks > MAX_RESOWNER_LOCKS) { locks = NULL; nlocks = 0; } else { locks = owner->locks; nlocks = owner->nlocks; } if (isCommit) LockReassignCurrentOwner(locks, nlocks); else LockReleaseCurrentOwner(locks, nlocks); } } else if (phase == RESOURCE_RELEASE_AFTER_LOCKS) { /* * Release catcache references. Note that ReleaseCatCache will remove * the catref entry from our array, so we just have to iterate till * there are none. * * As with buffer pins, warn if any are left at commit time. */ while (ResourceArrayGetAny(&(owner->catrefarr), &foundres)) { HeapTuple res = (HeapTuple) DatumGetPointer(foundres); if (isCommit) PrintCatCacheLeakWarning(res); ReleaseCatCache(res); } /* Ditto for catcache lists */ while (ResourceArrayGetAny(&(owner->catlistrefarr), &foundres)) { CatCList *res = (CatCList *) DatumGetPointer(foundres); if (isCommit) PrintCatCacheListLeakWarning(res); ReleaseCatCacheList(res); } /* Ditto for plancache references */ while (ResourceArrayGetAny(&(owner->planrefarr), &foundres)) { CachedPlan *res = (CachedPlan *) DatumGetPointer(foundres); if (isCommit) PrintPlanCacheLeakWarning(res); ReleaseCachedPlan(res, true); } /* Ditto for tupdesc references */ while (ResourceArrayGetAny(&(owner->tupdescarr), &foundres)) { TupleDesc res = (TupleDesc) DatumGetPointer(foundres); if (isCommit) PrintTupleDescLeakWarning(res); DecrTupleDescRefCount(res); } /* Ditto for snapshot references */ while (ResourceArrayGetAny(&(owner->snapshotarr), &foundres)) { Snapshot res = (Snapshot) DatumGetPointer(foundres); if (isCommit) PrintSnapshotLeakWarning(res); UnregisterSnapshot(res); } /* Ditto for temporary files */ while (ResourceArrayGetAny(&(owner->filearr), &foundres)) { File res = DatumGetFile(foundres); if (isCommit) PrintFileLeakWarning(res); FileClose(res); } } /* Let add-on modules get a chance too */ for (item = ResourceRelease_callbacks; item; item = item->next) item->callback(phase, isCommit, isTopLevel, item->arg); CurrentResourceOwner = save; }
/* * sepgsql_relation_drop * * It checks privileges to drop the supplied relation. */ void sepgsql_relation_drop(Oid relOid) { ObjectAddress object; char *audit_name; uint16_t tclass; char relkind; relkind = get_rel_relkind(relOid); switch (relkind) { case RELKIND_RELATION: tclass = SEPG_CLASS_DB_TABLE; break; case RELKIND_SEQUENCE: tclass = SEPG_CLASS_DB_SEQUENCE; break; case RELKIND_VIEW: tclass = SEPG_CLASS_DB_VIEW; break; case RELKIND_INDEX: /* ignore indexes on toast tables */ if (get_rel_namespace(relOid) == PG_TOAST_NAMESPACE) return; /* other indexes are handled specially below; no need for tclass */ break; default: /* ignore other relkinds */ return; } /* * check db_schema:{remove_name} permission */ object.classId = NamespaceRelationId; object.objectId = get_rel_namespace(relOid); object.objectSubId = 0; audit_name = getObjectDescription(&object); sepgsql_avc_check_perms(&object, SEPG_CLASS_DB_SCHEMA, SEPG_DB_SCHEMA__REMOVE_NAME, audit_name, true); pfree(audit_name); /* deal with indexes specially */ if (relkind == RELKIND_INDEX) { sepgsql_index_modify(relOid); return; } /* * check db_table/sequence/view:{drop} permission */ object.classId = RelationRelationId; object.objectId = relOid; object.objectSubId = 0; audit_name = getObjectDescription(&object); sepgsql_avc_check_perms(&object, tclass, SEPG_DB_TABLE__DROP, audit_name, true); pfree(audit_name); /* * check db_column:{drop} permission */ if (relkind == RELKIND_RELATION) { Form_pg_attribute attForm; CatCList *attrList; HeapTuple atttup; int i; attrList = SearchSysCacheList1(ATTNUM, ObjectIdGetDatum(relOid)); for (i = 0; i < attrList->n_members; i++) { atttup = &attrList->members[i]->tuple; attForm = (Form_pg_attribute) GETSTRUCT(atttup); if (attForm->attisdropped) continue; object.classId = RelationRelationId; object.objectId = relOid; object.objectSubId = attForm->attnum; audit_name = getObjectDescription(&object); sepgsql_avc_check_perms(&object, SEPG_CLASS_DB_COLUMN, SEPG_DB_COLUMN__DROP, audit_name, true); pfree(audit_name); } ReleaseCatCacheList(attrList); } }
/* * Validator for a BRIN opclass. */ bool brinvalidate(Oid opclassoid) { HeapTuple classtup; Form_pg_opclass classform; Oid opfamilyoid; Oid opcintype; int numclassops; int32 classfuncbits; CatCList *proclist, *oprlist; int i, j; /* 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; ReleaseSysCache(classtup); /* Fetch all operators and support functions of the opfamily */ oprlist = SearchSysCacheList1(AMOPSTRATEGY, ObjectIdGetDatum(opfamilyoid)); proclist = SearchSysCacheList1(AMPROCNUM, ObjectIdGetDatum(opfamilyoid)); /* We'll track the ops and functions belonging to the named opclass */ numclassops = 0; classfuncbits = 0; /* Check 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); /* Check that only allowed procedure numbers exist */ if (procform->amprocnum < 1 || procform->amprocnum > BRIN_LAST_OPTIONAL_PROCNUM) ereport(ERROR, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), errmsg("brin opfamily %u contains invalid support number %d for procedure %u", opfamilyoid, procform->amprocnum, procform->amproc))); /* Remember functions that are specifically for the named opclass */ if (procform->amproclefttype == opcintype && procform->amprocrighttype == opcintype) classfuncbits |= (1 << procform->amprocnum); } /* Check operators */ for (i = 0; i < oprlist->n_members; i++) { HeapTuple oprtup = &oprlist->members[i]->tuple; Form_pg_amop oprform = (Form_pg_amop) GETSTRUCT(oprtup); bool found = false; /* TODO: Check that only allowed strategy numbers exist */ if (oprform->amopstrategy < 1) ereport(ERROR, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), errmsg("brin opfamily %u contains invalid strategy number %d for operator %u", opfamilyoid, oprform->amopstrategy, oprform->amopopr))); /* TODO: check more thoroughly for missing support functions */ for (j = 0; j < proclist->n_members; j++) { HeapTuple proctup = &proclist->members[j]->tuple; Form_pg_amproc procform = (Form_pg_amproc) GETSTRUCT(proctup); /* note only the operator's lefttype matters */ if (procform->amproclefttype == oprform->amoplefttype && procform->amprocrighttype == oprform->amoplefttype) { found = true; break; } } if (!found) ereport(ERROR, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), errmsg("brin opfamily %u lacks support function for operator %u", opfamilyoid, oprform->amopopr))); /* brin doesn't support ORDER BY operators */ if (oprform->amoppurpose != AMOP_SEARCH || OidIsValid(oprform->amopsortfamily)) ereport(ERROR, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), errmsg("brin opfamily %u contains invalid ORDER BY specification for operator %u", opfamilyoid, oprform->amopopr))); /* Count operators that are specifically for the named opclass */ if (oprform->amoplefttype == opcintype && oprform->amoprighttype == opcintype) numclassops++; } /* Check that the named opclass is complete */ if (numclassops == 0) ereport(ERROR, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), errmsg("brin opclass %u is missing operator(s)", opclassoid))); for (i = 1; i <= BRIN_MANDATORY_NPROCS; i++) { if ((classfuncbits & (1 << i)) != 0) continue; /* got it */ ereport(ERROR, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), errmsg("brin opclass %u is missing required support function %d", opclassoid, i))); } ReleaseCatCacheList(proclist); ReleaseCatCacheList(oprlist); return true; }
/* * sepgsql_relation_drop * * It checks privileges to drop the supplied relation. */ void sepgsql_relation_drop(Oid relOid) { ObjectAddress object; char *audit_name; uint16_t tclass = 0; char relkind; relkind = get_rel_relkind(relOid); if (relkind == RELKIND_RELATION) tclass = SEPG_CLASS_DB_TABLE; else if (relkind == RELKIND_SEQUENCE) tclass = SEPG_CLASS_DB_SEQUENCE; else if (relkind == RELKIND_VIEW) tclass = SEPG_CLASS_DB_VIEW; else return; /* * check db_schema:{remove_name} permission */ object.classId = NamespaceRelationId; object.objectId = get_rel_namespace(relOid); object.objectSubId = 0; audit_name = getObjectDescription(&object); sepgsql_avc_check_perms(&object, SEPG_CLASS_DB_SCHEMA, SEPG_DB_SCHEMA__REMOVE_NAME, audit_name, true); pfree(audit_name); /* * check db_table/sequence/view:{drop} permission */ object.classId = RelationRelationId; object.objectId = relOid; object.objectSubId = 0; audit_name = getObjectDescription(&object); sepgsql_avc_check_perms(&object, tclass, SEPG_DB_TABLE__DROP, audit_name, true); pfree(audit_name); /* * check db_column:{drop} permission */ if (relkind == RELKIND_RELATION) { Form_pg_attribute attForm; CatCList *attrList; HeapTuple atttup; int i; attrList = SearchSysCacheList1(ATTNUM, ObjectIdGetDatum(relOid)); for (i = 0; i < attrList->n_members; i++) { atttup = &attrList->members[i]->tuple; attForm = (Form_pg_attribute) GETSTRUCT(atttup); if (attForm->attisdropped) continue; object.classId = RelationRelationId; object.objectId = relOid; object.objectSubId = attForm->attnum; audit_name = getObjectDescription(&object); sepgsql_avc_check_perms(&object, SEPG_CLASS_DB_COLUMN, SEPG_DB_COLUMN__DROP, audit_name, true); pfree(audit_name); } ReleaseCatCacheList(attrList); } }
/* * Validator for a btree opclass. * * Some of the checks done here cover the whole opfamily, and therefore are * redundant when checking each opclass in a family. But they don't run long * enough to be much of a problem, so we accept the duplication rather than * complicate the amvalidate API. */ bool btvalidate(Oid opclassoid) { HeapTuple classtup; Form_pg_opclass classform; Oid opfamilyoid; Oid opcintype; int numclassops; int32 classfuncbits; CatCList *proclist, *oprlist; Oid lastlefttype, lastrighttype; int numOps; int i, j; /* 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; ReleaseSysCache(classtup); /* Fetch all operators and support functions of the opfamily */ oprlist = SearchSysCacheList1(AMOPSTRATEGY, ObjectIdGetDatum(opfamilyoid)); proclist = SearchSysCacheList1(AMPROCNUM, ObjectIdGetDatum(opfamilyoid)); /* We rely on the oprlist to be ordered */ if (!oprlist->ordered) elog(ERROR, "cannot validate btree opclass without ordered data"); /* We'll track the ops and functions belonging to the named opclass */ numclassops = 0; classfuncbits = 0; /* Check 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); /* Check that only allowed procedure numbers exist */ if (procform->amprocnum != BTORDER_PROC && procform->amprocnum != BTSORTSUPPORT_PROC) ereport(ERROR, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), errmsg("btree opfamily %u contains invalid support number %d for procedure %u", opfamilyoid, procform->amprocnum, procform->amproc))); /* Remember functions that are specifically for the named opclass */ if (procform->amproclefttype == opcintype && procform->amprocrighttype == opcintype) classfuncbits |= (1 << procform->amprocnum); } /* Check operators */ lastlefttype = lastrighttype = InvalidOid; numOps = 0; for (i = 0; i < oprlist->n_members; i++) { HeapTuple oprtup = &oprlist->members[i]->tuple; Form_pg_amop oprform = (Form_pg_amop) GETSTRUCT(oprtup); /* Check that only allowed strategy numbers exist */ if (oprform->amopstrategy < 1 || oprform->amopstrategy > BTMaxStrategyNumber) ereport(ERROR, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), errmsg("btree opfamily %u contains invalid strategy number %d for operator %u", opfamilyoid, oprform->amopstrategy, oprform->amopopr))); /* * Check that we have all strategies for each supported datatype * combination. This is easy since the list will be sorted in * datatype order and there can't be duplicate strategy numbers. */ if (oprform->amoplefttype == lastlefttype && oprform->amoprighttype == lastrighttype) numOps++; else { /* reached a group boundary, so check ... */ if (numOps > 0 && numOps != BTMaxStrategyNumber) ereport(ERROR, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), errmsg("btree opfamily %u has a partial set of operators for datatypes %s and %s", opfamilyoid, format_type_be(lastlefttype), format_type_be(lastrighttype)))); /* ... and reset for new group */ lastlefttype = oprform->amoplefttype; lastrighttype = oprform->amoprighttype; numOps = 1; } /* * There should be a relevant support function for each operator, but * we only need to check this once per pair of datatypes. */ if (numOps == 1) { bool found = false; for (j = 0; j < proclist->n_members; j++) { HeapTuple proctup = &proclist->members[j]->tuple; Form_pg_amproc procform = (Form_pg_amproc) GETSTRUCT(proctup); if (procform->amprocnum == BTORDER_PROC && procform->amproclefttype == oprform->amoplefttype && procform->amprocrighttype == oprform->amoprighttype) { found = true; break; } } if (!found) ereport(ERROR, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), errmsg("btree opfamily %u lacks support function for operator %u", opfamilyoid, oprform->amopopr))); } /* btree doesn't support ORDER BY operators */ if (oprform->amoppurpose != AMOP_SEARCH || OidIsValid(oprform->amopsortfamily)) ereport(ERROR, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), errmsg("btree opfamily %u contains invalid ORDER BY specification for operator %u", opfamilyoid, oprform->amopopr))); /* Count operators that are specifically for the named opclass */ if (oprform->amoplefttype == opcintype && oprform->amoprighttype == opcintype) numclassops++; } /* don't forget to check the last batch of operators for completeness */ if (numOps > 0 && numOps != BTMaxStrategyNumber) ereport(ERROR, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), errmsg("btree opfamily %u has a partial set of operators for datatypes %s and %s", opfamilyoid, format_type_be(lastlefttype), format_type_be(lastrighttype)))); /* Check that the named opclass is complete */ if (numclassops != BTMaxStrategyNumber) ereport(ERROR, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), errmsg("btree opclass %u is missing operator(s)", opclassoid))); if ((classfuncbits & (1 << BTORDER_PROC)) == 0) ereport(ERROR, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), errmsg("btree opclass %u is missing required support function", opclassoid))); ReleaseCatCacheList(proclist); ReleaseCatCacheList(oprlist); return true; }