diff options
Diffstat (limited to 'src/backend/commands/opclasscmds.c')
-rw-r--r-- | src/backend/commands/opclasscmds.c | 1745 |
1 files changed, 1745 insertions, 0 deletions
diff --git a/src/backend/commands/opclasscmds.c b/src/backend/commands/opclasscmds.c new file mode 100644 index 0000000..11d152f --- /dev/null +++ b/src/backend/commands/opclasscmds.c @@ -0,0 +1,1745 @@ +/*------------------------------------------------------------------------- + * + * opclasscmds.c + * + * Routines for opclass (and opfamily) manipulation commands + * + * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/commands/opclasscmds.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include <limits.h> + +#include "access/genam.h" +#include "access/hash.h" +#include "access/htup_details.h" +#include "access/nbtree.h" +#include "access/sysattr.h" +#include "access/table.h" +#include "catalog/catalog.h" +#include "catalog/dependency.h" +#include "catalog/indexing.h" +#include "catalog/objectaccess.h" +#include "catalog/pg_am.h" +#include "catalog/pg_amop.h" +#include "catalog/pg_amproc.h" +#include "catalog/pg_namespace.h" +#include "catalog/pg_opclass.h" +#include "catalog/pg_operator.h" +#include "catalog/pg_opfamily.h" +#include "catalog/pg_proc.h" +#include "catalog/pg_type.h" +#include "commands/alter.h" +#include "commands/defrem.h" +#include "commands/event_trigger.h" +#include "miscadmin.h" +#include "parser/parse_func.h" +#include "parser/parse_oper.h" +#include "parser/parse_type.h" +#include "utils/builtins.h" +#include "utils/fmgroids.h" +#include "utils/lsyscache.h" +#include "utils/rel.h" +#include "utils/syscache.h" + +static void AlterOpFamilyAdd(AlterOpFamilyStmt *stmt, + Oid amoid, Oid opfamilyoid, + int maxOpNumber, int maxProcNumber, + int opclassOptsProcNumber, List *items); +static void AlterOpFamilyDrop(AlterOpFamilyStmt *stmt, + Oid amoid, Oid opfamilyoid, + int maxOpNumber, int maxProcNumber, + List *items); +static void processTypesSpec(List *args, Oid *lefttype, Oid *righttype); +static void assignOperTypes(OpFamilyMember *member, Oid amoid, Oid typeoid); +static void assignProcTypes(OpFamilyMember *member, Oid amoid, Oid typeoid, + int opclassOptsProcNum); +static void addFamilyMember(List **list, OpFamilyMember *member); +static void storeOperators(List *opfamilyname, Oid amoid, Oid opfamilyoid, + List *operators, bool isAdd); +static void storeProcedures(List *opfamilyname, Oid amoid, Oid opfamilyoid, + List *procedures, bool isAdd); +static void dropOperators(List *opfamilyname, Oid amoid, Oid opfamilyoid, + List *operators); +static void dropProcedures(List *opfamilyname, Oid amoid, Oid opfamilyoid, + List *procedures); + +/* + * OpFamilyCacheLookup + * Look up an existing opfamily by name. + * + * Returns a syscache tuple reference, or NULL if not found. + */ +static HeapTuple +OpFamilyCacheLookup(Oid amID, List *opfamilyname, bool missing_ok) +{ + char *schemaname; + char *opfname; + HeapTuple htup; + + /* deconstruct the name list */ + DeconstructQualifiedName(opfamilyname, &schemaname, &opfname); + + if (schemaname) + { + /* Look in specific schema only */ + Oid namespaceId; + + namespaceId = LookupExplicitNamespace(schemaname, missing_ok); + if (!OidIsValid(namespaceId)) + htup = NULL; + else + htup = SearchSysCache3(OPFAMILYAMNAMENSP, + ObjectIdGetDatum(amID), + PointerGetDatum(opfname), + ObjectIdGetDatum(namespaceId)); + } + else + { + /* Unqualified opfamily name, so search the search path */ + Oid opfID = OpfamilynameGetOpfid(amID, opfname); + + if (!OidIsValid(opfID)) + htup = NULL; + else + htup = SearchSysCache1(OPFAMILYOID, ObjectIdGetDatum(opfID)); + } + + if (!HeapTupleIsValid(htup) && !missing_ok) + { + HeapTuple amtup; + + amtup = SearchSysCache1(AMOID, ObjectIdGetDatum(amID)); + if (!HeapTupleIsValid(amtup)) + elog(ERROR, "cache lookup failed for access method %u", amID); + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("operator family \"%s\" does not exist for access method \"%s\"", + NameListToString(opfamilyname), + NameStr(((Form_pg_am) GETSTRUCT(amtup))->amname)))); + } + + return htup; +} + +/* + * get_opfamily_oid + * find an opfamily OID by possibly qualified name + * + * If not found, returns InvalidOid if missing_ok, else throws error. + */ +Oid +get_opfamily_oid(Oid amID, List *opfamilyname, bool missing_ok) +{ + HeapTuple htup; + Form_pg_opfamily opfamform; + Oid opfID; + + htup = OpFamilyCacheLookup(amID, opfamilyname, missing_ok); + if (!HeapTupleIsValid(htup)) + return InvalidOid; + opfamform = (Form_pg_opfamily) GETSTRUCT(htup); + opfID = opfamform->oid; + ReleaseSysCache(htup); + + return opfID; +} + +/* + * OpClassCacheLookup + * Look up an existing opclass by name. + * + * Returns a syscache tuple reference, or NULL if not found. + */ +static HeapTuple +OpClassCacheLookup(Oid amID, List *opclassname, bool missing_ok) +{ + char *schemaname; + char *opcname; + HeapTuple htup; + + /* deconstruct the name list */ + DeconstructQualifiedName(opclassname, &schemaname, &opcname); + + if (schemaname) + { + /* Look in specific schema only */ + Oid namespaceId; + + namespaceId = LookupExplicitNamespace(schemaname, missing_ok); + if (!OidIsValid(namespaceId)) + htup = NULL; + else + htup = SearchSysCache3(CLAAMNAMENSP, + ObjectIdGetDatum(amID), + PointerGetDatum(opcname), + ObjectIdGetDatum(namespaceId)); + } + else + { + /* Unqualified opclass name, so search the search path */ + Oid opcID = OpclassnameGetOpcid(amID, opcname); + + if (!OidIsValid(opcID)) + htup = NULL; + else + htup = SearchSysCache1(CLAOID, ObjectIdGetDatum(opcID)); + } + + if (!HeapTupleIsValid(htup) && !missing_ok) + { + HeapTuple amtup; + + amtup = SearchSysCache1(AMOID, ObjectIdGetDatum(amID)); + if (!HeapTupleIsValid(amtup)) + elog(ERROR, "cache lookup failed for access method %u", amID); + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("operator class \"%s\" does not exist for access method \"%s\"", + NameListToString(opclassname), + NameStr(((Form_pg_am) GETSTRUCT(amtup))->amname)))); + } + + return htup; +} + +/* + * get_opclass_oid + * find an opclass OID by possibly qualified name + * + * If not found, returns InvalidOid if missing_ok, else throws error. + */ +Oid +get_opclass_oid(Oid amID, List *opclassname, bool missing_ok) +{ + HeapTuple htup; + Form_pg_opclass opcform; + Oid opcID; + + htup = OpClassCacheLookup(amID, opclassname, missing_ok); + if (!HeapTupleIsValid(htup)) + return InvalidOid; + opcform = (Form_pg_opclass) GETSTRUCT(htup); + opcID = opcform->oid; + ReleaseSysCache(htup); + + return opcID; +} + +/* + * CreateOpFamily + * Internal routine to make the catalog entry for a new operator family. + * + * Caller must have done permissions checks etc. already. + */ +static ObjectAddress +CreateOpFamily(CreateOpFamilyStmt *stmt, const char *opfname, + Oid namespaceoid, Oid amoid) +{ + Oid opfamilyoid; + Relation rel; + HeapTuple tup; + Datum values[Natts_pg_opfamily]; + bool nulls[Natts_pg_opfamily]; + NameData opfName; + ObjectAddress myself, + referenced; + + rel = table_open(OperatorFamilyRelationId, RowExclusiveLock); + + /* + * Make sure there is no existing opfamily of this name (this is just to + * give a more friendly error message than "duplicate key"). + */ + if (SearchSysCacheExists3(OPFAMILYAMNAMENSP, + ObjectIdGetDatum(amoid), + CStringGetDatum(opfname), + ObjectIdGetDatum(namespaceoid))) + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_OBJECT), + errmsg("operator family \"%s\" for access method \"%s\" already exists", + opfname, stmt->amname))); + + /* + * Okay, let's create the pg_opfamily entry. + */ + memset(values, 0, sizeof(values)); + memset(nulls, false, sizeof(nulls)); + + opfamilyoid = GetNewOidWithIndex(rel, OpfamilyOidIndexId, + Anum_pg_opfamily_oid); + values[Anum_pg_opfamily_oid - 1] = ObjectIdGetDatum(opfamilyoid); + values[Anum_pg_opfamily_opfmethod - 1] = ObjectIdGetDatum(amoid); + namestrcpy(&opfName, opfname); + values[Anum_pg_opfamily_opfname - 1] = NameGetDatum(&opfName); + values[Anum_pg_opfamily_opfnamespace - 1] = ObjectIdGetDatum(namespaceoid); + values[Anum_pg_opfamily_opfowner - 1] = ObjectIdGetDatum(GetUserId()); + + tup = heap_form_tuple(rel->rd_att, values, nulls); + + CatalogTupleInsert(rel, tup); + + heap_freetuple(tup); + + /* + * Create dependencies for the opfamily proper. + */ + myself.classId = OperatorFamilyRelationId; + myself.objectId = opfamilyoid; + myself.objectSubId = 0; + + /* dependency on access method */ + referenced.classId = AccessMethodRelationId; + referenced.objectId = amoid; + referenced.objectSubId = 0; + recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO); + + /* dependency on namespace */ + referenced.classId = NamespaceRelationId; + referenced.objectId = namespaceoid; + referenced.objectSubId = 0; + recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + + /* dependency on owner */ + recordDependencyOnOwner(OperatorFamilyRelationId, opfamilyoid, GetUserId()); + + /* dependency on extension */ + recordDependencyOnCurrentExtension(&myself, false); + + /* Report the new operator family to possibly interested event triggers */ + EventTriggerCollectSimpleCommand(myself, InvalidObjectAddress, + (Node *) stmt); + + /* Post creation hook for new operator family */ + InvokeObjectPostCreateHook(OperatorFamilyRelationId, opfamilyoid, 0); + + table_close(rel, RowExclusiveLock); + + return myself; +} + +/* + * DefineOpClass + * Define a new index operator class. + */ +ObjectAddress +DefineOpClass(CreateOpClassStmt *stmt) +{ + char *opcname; /* name of opclass we're creating */ + Oid amoid, /* our AM's oid */ + typeoid, /* indexable datatype oid */ + storageoid, /* storage datatype oid, if any */ + namespaceoid, /* namespace to create opclass in */ + opfamilyoid, /* oid of containing opfamily */ + opclassoid; /* oid of opclass we create */ + int maxOpNumber, /* amstrategies value */ + optsProcNumber, /* amoptsprocnum value */ + maxProcNumber; /* amsupport value */ + bool amstorage; /* amstorage flag */ + List *operators; /* OpFamilyMember list for operators */ + List *procedures; /* OpFamilyMember list for support procs */ + ListCell *l; + Relation rel; + HeapTuple tup; + Form_pg_am amform; + IndexAmRoutine *amroutine; + Datum values[Natts_pg_opclass]; + bool nulls[Natts_pg_opclass]; + AclResult aclresult; + NameData opcName; + ObjectAddress myself, + referenced; + + /* Convert list of names to a name and namespace */ + namespaceoid = QualifiedNameGetCreationNamespace(stmt->opclassname, + &opcname); + + /* Check we have creation rights in target namespace */ + aclresult = pg_namespace_aclcheck(namespaceoid, GetUserId(), ACL_CREATE); + if (aclresult != ACLCHECK_OK) + aclcheck_error(aclresult, OBJECT_SCHEMA, + get_namespace_name(namespaceoid)); + + /* Get necessary info about access method */ + tup = SearchSysCache1(AMNAME, CStringGetDatum(stmt->amname)); + if (!HeapTupleIsValid(tup)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("access method \"%s\" does not exist", + stmt->amname))); + + amform = (Form_pg_am) GETSTRUCT(tup); + amoid = amform->oid; + amroutine = GetIndexAmRoutineByAmId(amoid, false); + ReleaseSysCache(tup); + + maxOpNumber = amroutine->amstrategies; + /* if amstrategies is zero, just enforce that op numbers fit in int16 */ + if (maxOpNumber <= 0) + maxOpNumber = SHRT_MAX; + maxProcNumber = amroutine->amsupport; + optsProcNumber = amroutine->amoptsprocnum; + amstorage = amroutine->amstorage; + + /* XXX Should we make any privilege check against the AM? */ + + /* + * The question of appropriate permissions for CREATE OPERATOR CLASS is + * interesting. Creating an opclass is tantamount to granting public + * execute access on the functions involved, since the index machinery + * generally does not check access permission before using the functions. + * A minimum expectation therefore is that the caller have execute + * privilege with grant option. Since we don't have a way to make the + * opclass go away if the grant option is revoked, we choose instead to + * require ownership of the functions. It's also not entirely clear what + * permissions should be required on the datatype, but ownership seems + * like a safe choice. + * + * Currently, we require superuser privileges to create an opclass. This + * seems necessary because we have no way to validate that the offered set + * of operators and functions are consistent with the AM's expectations. + * It would be nice to provide such a check someday, if it can be done + * without solving the halting problem :-( + * + * XXX re-enable NOT_USED code sections below if you remove this test. + */ + if (!superuser()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be superuser to create an operator class"))); + + /* Look up the datatype */ + typeoid = typenameTypeId(NULL, stmt->datatype); + +#ifdef NOT_USED + /* XXX this is unnecessary given the superuser check above */ + /* Check we have ownership of the datatype */ + if (!pg_type_ownercheck(typeoid, GetUserId())) + aclcheck_error_type(ACLCHECK_NOT_OWNER, typeoid); +#endif + + /* + * Look up the containing operator family, or create one if FAMILY option + * was omitted and there's not a match already. + */ + if (stmt->opfamilyname) + { + opfamilyoid = get_opfamily_oid(amoid, stmt->opfamilyname, false); + } + else + { + /* Lookup existing family of same name and namespace */ + tup = SearchSysCache3(OPFAMILYAMNAMENSP, + ObjectIdGetDatum(amoid), + PointerGetDatum(opcname), + ObjectIdGetDatum(namespaceoid)); + if (HeapTupleIsValid(tup)) + { + opfamilyoid = ((Form_pg_opfamily) GETSTRUCT(tup))->oid; + + /* + * XXX given the superuser check above, there's no need for an + * ownership check here + */ + ReleaseSysCache(tup); + } + else + { + CreateOpFamilyStmt *opfstmt; + ObjectAddress tmpAddr; + + opfstmt = makeNode(CreateOpFamilyStmt); + opfstmt->opfamilyname = stmt->opclassname; + opfstmt->amname = stmt->amname; + + /* + * Create it ... again no need for more permissions ... + */ + tmpAddr = CreateOpFamily(opfstmt, opcname, namespaceoid, amoid); + opfamilyoid = tmpAddr.objectId; + } + } + + operators = NIL; + procedures = NIL; + + /* Storage datatype is optional */ + storageoid = InvalidOid; + + /* + * Scan the "items" list to obtain additional info. + */ + foreach(l, stmt->items) + { + CreateOpClassItem *item = lfirst_node(CreateOpClassItem, l); + Oid operOid; + Oid funcOid; + Oid sortfamilyOid; + OpFamilyMember *member; + + switch (item->itemtype) + { + case OPCLASS_ITEM_OPERATOR: + if (item->number <= 0 || item->number > maxOpNumber) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("invalid operator number %d," + " must be between 1 and %d", + item->number, maxOpNumber))); + if (item->name->objargs != NIL) + operOid = LookupOperWithArgs(item->name, false); + else + { + /* Default to binary op on input datatype */ + operOid = LookupOperName(NULL, item->name->objname, + typeoid, typeoid, + false, -1); + } + + if (item->order_family) + sortfamilyOid = get_opfamily_oid(BTREE_AM_OID, + item->order_family, + false); + else + sortfamilyOid = InvalidOid; + +#ifdef NOT_USED + /* XXX this is unnecessary given the superuser check above */ + /* Caller must own operator and its underlying function */ + if (!pg_oper_ownercheck(operOid, GetUserId())) + aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_OPERATOR, + get_opname(operOid)); + funcOid = get_opcode(operOid); + if (!pg_proc_ownercheck(funcOid, GetUserId())) + aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_FUNCTION, + get_func_name(funcOid)); +#endif + + /* Save the info */ + member = (OpFamilyMember *) palloc0(sizeof(OpFamilyMember)); + member->is_func = false; + member->object = operOid; + member->number = item->number; + member->sortfamily = sortfamilyOid; + assignOperTypes(member, amoid, typeoid); + addFamilyMember(&operators, member); + break; + case OPCLASS_ITEM_FUNCTION: + if (item->number <= 0 || item->number > maxProcNumber) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("invalid function number %d," + " must be between 1 and %d", + item->number, maxProcNumber))); + funcOid = LookupFuncWithArgs(OBJECT_FUNCTION, item->name, false); +#ifdef NOT_USED + /* XXX this is unnecessary given the superuser check above */ + /* Caller must own function */ + if (!pg_proc_ownercheck(funcOid, GetUserId())) + aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_FUNCTION, + get_func_name(funcOid)); +#endif + /* Save the info */ + member = (OpFamilyMember *) palloc0(sizeof(OpFamilyMember)); + member->is_func = true; + member->object = funcOid; + member->number = item->number; + + /* allow overriding of the function's actual arg types */ + if (item->class_args) + processTypesSpec(item->class_args, + &member->lefttype, &member->righttype); + + assignProcTypes(member, amoid, typeoid, optsProcNumber); + addFamilyMember(&procedures, member); + break; + case OPCLASS_ITEM_STORAGETYPE: + if (OidIsValid(storageoid)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("storage type specified more than once"))); + storageoid = typenameTypeId(NULL, item->storedtype); + +#ifdef NOT_USED + /* XXX this is unnecessary given the superuser check above */ + /* Check we have ownership of the datatype */ + if (!pg_type_ownercheck(storageoid, GetUserId())) + aclcheck_error_type(ACLCHECK_NOT_OWNER, storageoid); +#endif + break; + default: + elog(ERROR, "unrecognized item type: %d", item->itemtype); + break; + } + } + + /* + * If storagetype is specified, make sure it's legal. + */ + if (OidIsValid(storageoid)) + { + /* Just drop the spec if same as column datatype */ + if (storageoid == typeoid) + storageoid = InvalidOid; + else if (!amstorage) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("storage type cannot be different from data type for access method \"%s\"", + stmt->amname))); + } + + rel = table_open(OperatorClassRelationId, RowExclusiveLock); + + /* + * Make sure there is no existing opclass of this name (this is just to + * give a more friendly error message than "duplicate key"). + */ + if (SearchSysCacheExists3(CLAAMNAMENSP, + ObjectIdGetDatum(amoid), + CStringGetDatum(opcname), + ObjectIdGetDatum(namespaceoid))) + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_OBJECT), + errmsg("operator class \"%s\" for access method \"%s\" already exists", + opcname, stmt->amname))); + + /* + * If we are creating a default opclass, check there isn't one already. + * (Note we do not restrict this test to visible opclasses; this ensures + * that typcache.c can find unique solutions to its questions.) + */ + if (stmt->isDefault) + { + ScanKeyData skey[1]; + SysScanDesc scan; + + ScanKeyInit(&skey[0], + Anum_pg_opclass_opcmethod, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(amoid)); + + scan = systable_beginscan(rel, OpclassAmNameNspIndexId, true, + NULL, 1, skey); + + while (HeapTupleIsValid(tup = systable_getnext(scan))) + { + Form_pg_opclass opclass = (Form_pg_opclass) GETSTRUCT(tup); + + if (opclass->opcintype == typeoid && opclass->opcdefault) + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_OBJECT), + errmsg("could not make operator class \"%s\" be default for type %s", + opcname, + TypeNameToString(stmt->datatype)), + errdetail("Operator class \"%s\" already is the default.", + NameStr(opclass->opcname)))); + } + + systable_endscan(scan); + } + + /* + * Okay, let's create the pg_opclass entry. + */ + memset(values, 0, sizeof(values)); + memset(nulls, false, sizeof(nulls)); + + opclassoid = GetNewOidWithIndex(rel, OpclassOidIndexId, + Anum_pg_opclass_oid); + values[Anum_pg_opclass_oid - 1] = ObjectIdGetDatum(opclassoid); + values[Anum_pg_opclass_opcmethod - 1] = ObjectIdGetDatum(amoid); + namestrcpy(&opcName, opcname); + values[Anum_pg_opclass_opcname - 1] = NameGetDatum(&opcName); + values[Anum_pg_opclass_opcnamespace - 1] = ObjectIdGetDatum(namespaceoid); + values[Anum_pg_opclass_opcowner - 1] = ObjectIdGetDatum(GetUserId()); + values[Anum_pg_opclass_opcfamily - 1] = ObjectIdGetDatum(opfamilyoid); + values[Anum_pg_opclass_opcintype - 1] = ObjectIdGetDatum(typeoid); + values[Anum_pg_opclass_opcdefault - 1] = BoolGetDatum(stmt->isDefault); + values[Anum_pg_opclass_opckeytype - 1] = ObjectIdGetDatum(storageoid); + + tup = heap_form_tuple(rel->rd_att, values, nulls); + + CatalogTupleInsert(rel, tup); + + heap_freetuple(tup); + + /* + * Now that we have the opclass OID, set up default dependency info for + * the pg_amop and pg_amproc entries. Historically, CREATE OPERATOR CLASS + * has created hard dependencies on the opclass, so that's what we use. + */ + foreach(l, operators) + { + OpFamilyMember *op = (OpFamilyMember *) lfirst(l); + + op->ref_is_hard = true; + op->ref_is_family = false; + op->refobjid = opclassoid; + } + foreach(l, procedures) + { + OpFamilyMember *proc = (OpFamilyMember *) lfirst(l); + + proc->ref_is_hard = true; + proc->ref_is_family = false; + proc->refobjid = opclassoid; + } + + /* + * Let the index AM editorialize on the dependency choices. It could also + * do further validation on the operators and functions, if it likes. + */ + if (amroutine->amadjustmembers) + amroutine->amadjustmembers(opfamilyoid, + opclassoid, + operators, + procedures); + + /* + * Now add tuples to pg_amop and pg_amproc tying in the operators and + * functions. Dependencies on them are inserted, too. + */ + storeOperators(stmt->opfamilyname, amoid, opfamilyoid, + operators, false); + storeProcedures(stmt->opfamilyname, amoid, opfamilyoid, + procedures, false); + + /* let event triggers know what happened */ + EventTriggerCollectCreateOpClass(stmt, opclassoid, operators, procedures); + + /* + * Create dependencies for the opclass proper. Note: we do not need a + * dependency link to the AM, because that exists through the opfamily. + */ + myself.classId = OperatorClassRelationId; + myself.objectId = opclassoid; + myself.objectSubId = 0; + + /* dependency on namespace */ + referenced.classId = NamespaceRelationId; + referenced.objectId = namespaceoid; + referenced.objectSubId = 0; + recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + + /* dependency on opfamily */ + referenced.classId = OperatorFamilyRelationId; + referenced.objectId = opfamilyoid; + referenced.objectSubId = 0; + recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO); + + /* dependency on indexed datatype */ + referenced.classId = TypeRelationId; + referenced.objectId = typeoid; + referenced.objectSubId = 0; + recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + + /* dependency on storage datatype */ + if (OidIsValid(storageoid)) + { + referenced.classId = TypeRelationId; + referenced.objectId = storageoid; + referenced.objectSubId = 0; + recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + } + + /* dependency on owner */ + recordDependencyOnOwner(OperatorClassRelationId, opclassoid, GetUserId()); + + /* dependency on extension */ + recordDependencyOnCurrentExtension(&myself, false); + + /* Post creation hook for new operator class */ + InvokeObjectPostCreateHook(OperatorClassRelationId, opclassoid, 0); + + table_close(rel, RowExclusiveLock); + + return myself; +} + + +/* + * DefineOpFamily + * Define a new index operator family. + */ +ObjectAddress +DefineOpFamily(CreateOpFamilyStmt *stmt) +{ + char *opfname; /* name of opfamily we're creating */ + Oid amoid, /* our AM's oid */ + namespaceoid; /* namespace to create opfamily in */ + AclResult aclresult; + + /* Convert list of names to a name and namespace */ + namespaceoid = QualifiedNameGetCreationNamespace(stmt->opfamilyname, + &opfname); + + /* Check we have creation rights in target namespace */ + aclresult = pg_namespace_aclcheck(namespaceoid, GetUserId(), ACL_CREATE); + if (aclresult != ACLCHECK_OK) + aclcheck_error(aclresult, OBJECT_SCHEMA, + get_namespace_name(namespaceoid)); + + /* Get access method OID, throwing an error if it doesn't exist. */ + amoid = get_index_am_oid(stmt->amname, false); + + /* XXX Should we make any privilege check against the AM? */ + + /* + * Currently, we require superuser privileges to create an opfamily. See + * comments in DefineOpClass. + */ + if (!superuser()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be superuser to create an operator family"))); + + /* Insert pg_opfamily catalog entry */ + return CreateOpFamily(stmt, opfname, namespaceoid, amoid); +} + + +/* + * AlterOpFamily + * Add or remove operators/procedures within an existing operator family. + * + * Note: this implements only ALTER OPERATOR FAMILY ... ADD/DROP. Some + * other commands called ALTER OPERATOR FAMILY exist, but go through + * different code paths. + */ +Oid +AlterOpFamily(AlterOpFamilyStmt *stmt) +{ + Oid amoid, /* our AM's oid */ + opfamilyoid; /* oid of opfamily */ + int maxOpNumber, /* amstrategies value */ + optsProcNumber, /* amopclassopts value */ + maxProcNumber; /* amsupport value */ + HeapTuple tup; + Form_pg_am amform; + IndexAmRoutine *amroutine; + + /* Get necessary info about access method */ + tup = SearchSysCache1(AMNAME, CStringGetDatum(stmt->amname)); + if (!HeapTupleIsValid(tup)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("access method \"%s\" does not exist", + stmt->amname))); + + amform = (Form_pg_am) GETSTRUCT(tup); + amoid = amform->oid; + amroutine = GetIndexAmRoutineByAmId(amoid, false); + ReleaseSysCache(tup); + + maxOpNumber = amroutine->amstrategies; + /* if amstrategies is zero, just enforce that op numbers fit in int16 */ + if (maxOpNumber <= 0) + maxOpNumber = SHRT_MAX; + maxProcNumber = amroutine->amsupport; + optsProcNumber = amroutine->amoptsprocnum; + + /* XXX Should we make any privilege check against the AM? */ + + /* Look up the opfamily */ + opfamilyoid = get_opfamily_oid(amoid, stmt->opfamilyname, false); + + /* + * Currently, we require superuser privileges to alter an opfamily. + * + * XXX re-enable NOT_USED code sections below if you remove this test. + */ + if (!superuser()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be superuser to alter an operator family"))); + + /* + * ADD and DROP cases need separate code from here on down. + */ + if (stmt->isDrop) + AlterOpFamilyDrop(stmt, amoid, opfamilyoid, + maxOpNumber, maxProcNumber, stmt->items); + else + AlterOpFamilyAdd(stmt, amoid, opfamilyoid, + maxOpNumber, maxProcNumber, optsProcNumber, + stmt->items); + + return opfamilyoid; +} + +/* + * ADD part of ALTER OP FAMILY + */ +static void +AlterOpFamilyAdd(AlterOpFamilyStmt *stmt, Oid amoid, Oid opfamilyoid, + int maxOpNumber, int maxProcNumber, int optsProcNumber, + List *items) +{ + IndexAmRoutine *amroutine = GetIndexAmRoutineByAmId(amoid, false); + List *operators; /* OpFamilyMember list for operators */ + List *procedures; /* OpFamilyMember list for support procs */ + ListCell *l; + + operators = NIL; + procedures = NIL; + + /* + * Scan the "items" list to obtain additional info. + */ + foreach(l, items) + { + CreateOpClassItem *item = lfirst_node(CreateOpClassItem, l); + Oid operOid; + Oid funcOid; + Oid sortfamilyOid; + OpFamilyMember *member; + + switch (item->itemtype) + { + case OPCLASS_ITEM_OPERATOR: + if (item->number <= 0 || item->number > maxOpNumber) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("invalid operator number %d," + " must be between 1 and %d", + item->number, maxOpNumber))); + if (item->name->objargs != NIL) + operOid = LookupOperWithArgs(item->name, false); + else + { + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("operator argument types must be specified in ALTER OPERATOR FAMILY"))); + operOid = InvalidOid; /* keep compiler quiet */ + } + + if (item->order_family) + sortfamilyOid = get_opfamily_oid(BTREE_AM_OID, + item->order_family, + false); + else + sortfamilyOid = InvalidOid; + +#ifdef NOT_USED + /* XXX this is unnecessary given the superuser check above */ + /* Caller must own operator and its underlying function */ + if (!pg_oper_ownercheck(operOid, GetUserId())) + aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_OPERATOR, + get_opname(operOid)); + funcOid = get_opcode(operOid); + if (!pg_proc_ownercheck(funcOid, GetUserId())) + aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_FUNCTION, + get_func_name(funcOid)); +#endif + + /* Save the info */ + member = (OpFamilyMember *) palloc0(sizeof(OpFamilyMember)); + member->is_func = false; + member->object = operOid; + member->number = item->number; + member->sortfamily = sortfamilyOid; + /* We can set up dependency fields immediately */ + /* Historically, ALTER ADD has created soft dependencies */ + member->ref_is_hard = false; + member->ref_is_family = true; + member->refobjid = opfamilyoid; + assignOperTypes(member, amoid, InvalidOid); + addFamilyMember(&operators, member); + break; + case OPCLASS_ITEM_FUNCTION: + if (item->number <= 0 || item->number > maxProcNumber) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("invalid function number %d," + " must be between 1 and %d", + item->number, maxProcNumber))); + funcOid = LookupFuncWithArgs(OBJECT_FUNCTION, item->name, false); +#ifdef NOT_USED + /* XXX this is unnecessary given the superuser check above */ + /* Caller must own function */ + if (!pg_proc_ownercheck(funcOid, GetUserId())) + aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_FUNCTION, + get_func_name(funcOid)); +#endif + + /* Save the info */ + member = (OpFamilyMember *) palloc0(sizeof(OpFamilyMember)); + member->is_func = true; + member->object = funcOid; + member->number = item->number; + /* We can set up dependency fields immediately */ + /* Historically, ALTER ADD has created soft dependencies */ + member->ref_is_hard = false; + member->ref_is_family = true; + member->refobjid = opfamilyoid; + + /* allow overriding of the function's actual arg types */ + if (item->class_args) + processTypesSpec(item->class_args, + &member->lefttype, &member->righttype); + + assignProcTypes(member, amoid, InvalidOid, optsProcNumber); + addFamilyMember(&procedures, member); + break; + case OPCLASS_ITEM_STORAGETYPE: + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("STORAGE cannot be specified in ALTER OPERATOR FAMILY"))); + break; + default: + elog(ERROR, "unrecognized item type: %d", item->itemtype); + break; + } + } + + /* + * Let the index AM editorialize on the dependency choices. It could also + * do further validation on the operators and functions, if it likes. + */ + if (amroutine->amadjustmembers) + amroutine->amadjustmembers(opfamilyoid, + InvalidOid, /* no specific opclass */ + operators, + procedures); + + /* + * Add tuples to pg_amop and pg_amproc tying in the operators and + * functions. Dependencies on them are inserted, too. + */ + storeOperators(stmt->opfamilyname, amoid, opfamilyoid, + operators, true); + storeProcedures(stmt->opfamilyname, amoid, opfamilyoid, + procedures, true); + + /* make information available to event triggers */ + EventTriggerCollectAlterOpFam(stmt, opfamilyoid, + operators, procedures); +} + +/* + * DROP part of ALTER OP FAMILY + */ +static void +AlterOpFamilyDrop(AlterOpFamilyStmt *stmt, Oid amoid, Oid opfamilyoid, + int maxOpNumber, int maxProcNumber, List *items) +{ + List *operators; /* OpFamilyMember list for operators */ + List *procedures; /* OpFamilyMember list for support procs */ + ListCell *l; + + operators = NIL; + procedures = NIL; + + /* + * Scan the "items" list to obtain additional info. + */ + foreach(l, items) + { + CreateOpClassItem *item = lfirst_node(CreateOpClassItem, l); + Oid lefttype, + righttype; + OpFamilyMember *member; + + switch (item->itemtype) + { + case OPCLASS_ITEM_OPERATOR: + if (item->number <= 0 || item->number > maxOpNumber) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("invalid operator number %d," + " must be between 1 and %d", + item->number, maxOpNumber))); + processTypesSpec(item->class_args, &lefttype, &righttype); + /* Save the info */ + member = (OpFamilyMember *) palloc0(sizeof(OpFamilyMember)); + member->is_func = false; + member->number = item->number; + member->lefttype = lefttype; + member->righttype = righttype; + addFamilyMember(&operators, member); + break; + case OPCLASS_ITEM_FUNCTION: + if (item->number <= 0 || item->number > maxProcNumber) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("invalid function number %d," + " must be between 1 and %d", + item->number, maxProcNumber))); + processTypesSpec(item->class_args, &lefttype, &righttype); + /* Save the info */ + member = (OpFamilyMember *) palloc0(sizeof(OpFamilyMember)); + member->is_func = true; + member->number = item->number; + member->lefttype = lefttype; + member->righttype = righttype; + addFamilyMember(&procedures, member); + break; + case OPCLASS_ITEM_STORAGETYPE: + /* grammar prevents this from appearing */ + default: + elog(ERROR, "unrecognized item type: %d", item->itemtype); + break; + } + } + + /* + * Remove tuples from pg_amop and pg_amproc. + */ + dropOperators(stmt->opfamilyname, amoid, opfamilyoid, operators); + dropProcedures(stmt->opfamilyname, amoid, opfamilyoid, procedures); + + /* make information available to event triggers */ + EventTriggerCollectAlterOpFam(stmt, opfamilyoid, + operators, procedures); +} + + +/* + * Deal with explicit arg types used in ALTER ADD/DROP + */ +static void +processTypesSpec(List *args, Oid *lefttype, Oid *righttype) +{ + TypeName *typeName; + + Assert(args != NIL); + + typeName = (TypeName *) linitial(args); + *lefttype = typenameTypeId(NULL, typeName); + + if (list_length(args) > 1) + { + typeName = (TypeName *) lsecond(args); + *righttype = typenameTypeId(NULL, typeName); + } + else + *righttype = *lefttype; + + if (list_length(args) > 2) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("one or two argument types must be specified"))); +} + + +/* + * Determine the lefttype/righttype to assign to an operator, + * and do any validity checking we can manage. + */ +static void +assignOperTypes(OpFamilyMember *member, Oid amoid, Oid typeoid) +{ + Operator optup; + Form_pg_operator opform; + + /* Fetch the operator definition */ + optup = SearchSysCache1(OPEROID, ObjectIdGetDatum(member->object)); + if (!HeapTupleIsValid(optup)) + elog(ERROR, "cache lookup failed for operator %u", member->object); + opform = (Form_pg_operator) GETSTRUCT(optup); + + /* + * Opfamily operators must be binary. + */ + if (opform->oprkind != 'b') + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("index operators must be binary"))); + + if (OidIsValid(member->sortfamily)) + { + /* + * Ordering op, check index supports that. (We could perhaps also + * check that the operator returns a type supported by the sortfamily, + * but that seems more trouble than it's worth here. If it does not, + * the operator will never be matchable to any ORDER BY clause, but no + * worse consequences can ensue. Also, trying to check that would + * create an ordering hazard during dump/reload: it's possible that + * the family has been created but not yet populated with the required + * operators.) + */ + IndexAmRoutine *amroutine = GetIndexAmRoutineByAmId(amoid, false); + + if (!amroutine->amcanorderbyop) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("access method \"%s\" does not support ordering operators", + get_am_name(amoid)))); + } + else + { + /* + * Search operators must return boolean. + */ + if (opform->oprresult != BOOLOID) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("index search operators must return boolean"))); + } + + /* + * If lefttype/righttype isn't specified, use the operator's input types + */ + if (!OidIsValid(member->lefttype)) + member->lefttype = opform->oprleft; + if (!OidIsValid(member->righttype)) + member->righttype = opform->oprright; + + ReleaseSysCache(optup); +} + +/* + * Determine the lefttype/righttype to assign to a support procedure, + * and do any validity checking we can manage. + */ +static void +assignProcTypes(OpFamilyMember *member, Oid amoid, Oid typeoid, + int opclassOptsProcNum) +{ + HeapTuple proctup; + Form_pg_proc procform; + + /* Fetch the procedure definition */ + proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(member->object)); + if (!HeapTupleIsValid(proctup)) + elog(ERROR, "cache lookup failed for function %u", member->object); + procform = (Form_pg_proc) GETSTRUCT(proctup); + + /* Check the signature of the opclass options parsing function */ + if (member->number == opclassOptsProcNum) + { + if (OidIsValid(typeoid)) + { + if ((OidIsValid(member->lefttype) && member->lefttype != typeoid) || + (OidIsValid(member->righttype) && member->righttype != typeoid)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("associated data types for operator class options parsing functions must match opclass input type"))); + } + else + { + if (member->lefttype != member->righttype) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("left and right associated data types for operator class options parsing functions must match"))); + } + + if (procform->prorettype != VOIDOID || + procform->pronargs != 1 || + procform->proargtypes.values[0] != INTERNALOID) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("invalid operator class options parsing function"), + errhint("Valid signature of operator class options parsing function is %s.", + "(internal) RETURNS void"))); + } + + /* + * btree comparison procs must be 2-arg procs returning int4. btree + * sortsupport procs must take internal and return void. btree in_range + * procs must be 5-arg procs returning bool. btree equalimage procs must + * take 1 arg and return bool. hash support proc 1 must be a 1-arg proc + * returning int4, while proc 2 must be a 2-arg proc returning int8. + * Otherwise we don't know. + */ + else if (amoid == BTREE_AM_OID) + { + if (member->number == BTORDER_PROC) + { + if (procform->pronargs != 2) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("btree comparison functions must have two arguments"))); + if (procform->prorettype != INT4OID) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("btree comparison functions must return integer"))); + + /* + * If lefttype/righttype isn't specified, use the proc's input + * types + */ + if (!OidIsValid(member->lefttype)) + member->lefttype = procform->proargtypes.values[0]; + if (!OidIsValid(member->righttype)) + member->righttype = procform->proargtypes.values[1]; + } + else if (member->number == BTSORTSUPPORT_PROC) + { + if (procform->pronargs != 1 || + procform->proargtypes.values[0] != INTERNALOID) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("btree sort support functions must accept type \"internal\""))); + if (procform->prorettype != VOIDOID) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("btree sort support functions must return void"))); + + /* + * Can't infer lefttype/righttype from proc, so use default rule + */ + } + else if (member->number == BTINRANGE_PROC) + { + if (procform->pronargs != 5) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("btree in_range functions must have five arguments"))); + if (procform->prorettype != BOOLOID) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("btree in_range functions must return boolean"))); + + /* + * If lefttype/righttype isn't specified, use the proc's input + * types (we look at the test-value and offset arguments) + */ + if (!OidIsValid(member->lefttype)) + member->lefttype = procform->proargtypes.values[0]; + if (!OidIsValid(member->righttype)) + member->righttype = procform->proargtypes.values[2]; + } + else if (member->number == BTEQUALIMAGE_PROC) + { + if (procform->pronargs != 1) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("btree equal image functions must have one argument"))); + if (procform->prorettype != BOOLOID) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("btree equal image functions must return boolean"))); + + /* + * pg_amproc functions are indexed by (lefttype, righttype), but + * an equalimage function can only be called at CREATE INDEX time. + * The same opclass opcintype OID is always used for leftype and + * righttype. Providing a cross-type routine isn't sensible. + * Reject cross-type ALTER OPERATOR FAMILY ... ADD FUNCTION 4 + * statements here. + */ + if (member->lefttype != member->righttype) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("btree equal image functions must not be cross-type"))); + } + } + else if (amoid == HASH_AM_OID) + { + if (member->number == HASHSTANDARD_PROC) + { + if (procform->pronargs != 1) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("hash function 1 must have one argument"))); + if (procform->prorettype != INT4OID) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("hash function 1 must return integer"))); + } + else if (member->number == HASHEXTENDED_PROC) + { + if (procform->pronargs != 2) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("hash function 2 must have two arguments"))); + if (procform->prorettype != INT8OID) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("hash function 2 must return bigint"))); + } + + /* + * If lefttype/righttype isn't specified, use the proc's input type + */ + if (!OidIsValid(member->lefttype)) + member->lefttype = procform->proargtypes.values[0]; + if (!OidIsValid(member->righttype)) + member->righttype = procform->proargtypes.values[0]; + } + + /* + * The default in CREATE OPERATOR CLASS is to use the class' opcintype as + * lefttype and righttype. In CREATE or ALTER OPERATOR FAMILY, opcintype + * isn't available, so make the user specify the types. + */ + if (!OidIsValid(member->lefttype)) + member->lefttype = typeoid; + if (!OidIsValid(member->righttype)) + member->righttype = typeoid; + + if (!OidIsValid(member->lefttype) || !OidIsValid(member->righttype)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("associated data types must be specified for index support function"))); + + ReleaseSysCache(proctup); +} + +/* + * Add a new family member to the appropriate list, after checking for + * duplicated strategy or proc number. + */ +static void +addFamilyMember(List **list, OpFamilyMember *member) +{ + ListCell *l; + + foreach(l, *list) + { + OpFamilyMember *old = (OpFamilyMember *) lfirst(l); + + if (old->number == member->number && + old->lefttype == member->lefttype && + old->righttype == member->righttype) + { + if (member->is_func) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("function number %d for (%s,%s) appears more than once", + member->number, + format_type_be(member->lefttype), + format_type_be(member->righttype)))); + else + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("operator number %d for (%s,%s) appears more than once", + member->number, + format_type_be(member->lefttype), + format_type_be(member->righttype)))); + } + } + *list = lappend(*list, member); +} + +/* + * Dump the operators to pg_amop + * + * We also make dependency entries in pg_depend for the pg_amop entries. + */ +static void +storeOperators(List *opfamilyname, Oid amoid, Oid opfamilyoid, + List *operators, bool isAdd) +{ + Relation rel; + Datum values[Natts_pg_amop]; + bool nulls[Natts_pg_amop]; + HeapTuple tup; + Oid entryoid; + ObjectAddress myself, + referenced; + ListCell *l; + + rel = table_open(AccessMethodOperatorRelationId, RowExclusiveLock); + + foreach(l, operators) + { + OpFamilyMember *op = (OpFamilyMember *) lfirst(l); + char oppurpose; + + /* + * If adding to an existing family, check for conflict with an + * existing pg_amop entry (just to give a nicer error message) + */ + if (isAdd && + SearchSysCacheExists4(AMOPSTRATEGY, + ObjectIdGetDatum(opfamilyoid), + ObjectIdGetDatum(op->lefttype), + ObjectIdGetDatum(op->righttype), + Int16GetDatum(op->number))) + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_OBJECT), + errmsg("operator %d(%s,%s) already exists in operator family \"%s\"", + op->number, + format_type_be(op->lefttype), + format_type_be(op->righttype), + NameListToString(opfamilyname)))); + + oppurpose = OidIsValid(op->sortfamily) ? AMOP_ORDER : AMOP_SEARCH; + + /* Create the pg_amop entry */ + memset(values, 0, sizeof(values)); + memset(nulls, false, sizeof(nulls)); + + entryoid = GetNewOidWithIndex(rel, AccessMethodOperatorOidIndexId, + Anum_pg_amop_oid); + values[Anum_pg_amop_oid - 1] = ObjectIdGetDatum(entryoid); + values[Anum_pg_amop_amopfamily - 1] = ObjectIdGetDatum(opfamilyoid); + values[Anum_pg_amop_amoplefttype - 1] = ObjectIdGetDatum(op->lefttype); + values[Anum_pg_amop_amoprighttype - 1] = ObjectIdGetDatum(op->righttype); + values[Anum_pg_amop_amopstrategy - 1] = Int16GetDatum(op->number); + values[Anum_pg_amop_amoppurpose - 1] = CharGetDatum(oppurpose); + values[Anum_pg_amop_amopopr - 1] = ObjectIdGetDatum(op->object); + values[Anum_pg_amop_amopmethod - 1] = ObjectIdGetDatum(amoid); + values[Anum_pg_amop_amopsortfamily - 1] = ObjectIdGetDatum(op->sortfamily); + + tup = heap_form_tuple(rel->rd_att, values, nulls); + + CatalogTupleInsert(rel, tup); + + heap_freetuple(tup); + + /* Make its dependencies */ + myself.classId = AccessMethodOperatorRelationId; + myself.objectId = entryoid; + myself.objectSubId = 0; + + referenced.classId = OperatorRelationId; + referenced.objectId = op->object; + referenced.objectSubId = 0; + + /* see comments in amapi.h about dependency strength */ + recordDependencyOn(&myself, &referenced, + op->ref_is_hard ? DEPENDENCY_NORMAL : DEPENDENCY_AUTO); + + referenced.classId = op->ref_is_family ? OperatorFamilyRelationId : + OperatorClassRelationId; + referenced.objectId = op->refobjid; + referenced.objectSubId = 0; + + recordDependencyOn(&myself, &referenced, + op->ref_is_hard ? DEPENDENCY_INTERNAL : DEPENDENCY_AUTO); + + /* A search operator also needs a dep on the referenced opfamily */ + if (OidIsValid(op->sortfamily)) + { + referenced.classId = OperatorFamilyRelationId; + referenced.objectId = op->sortfamily; + referenced.objectSubId = 0; + + recordDependencyOn(&myself, &referenced, + op->ref_is_hard ? DEPENDENCY_NORMAL : DEPENDENCY_AUTO); + } + + /* Post create hook of this access method operator */ + InvokeObjectPostCreateHook(AccessMethodOperatorRelationId, + entryoid, 0); + } + + table_close(rel, RowExclusiveLock); +} + +/* + * Dump the procedures (support routines) to pg_amproc + * + * We also make dependency entries in pg_depend for the pg_amproc entries. + */ +static void +storeProcedures(List *opfamilyname, Oid amoid, Oid opfamilyoid, + List *procedures, bool isAdd) +{ + Relation rel; + Datum values[Natts_pg_amproc]; + bool nulls[Natts_pg_amproc]; + HeapTuple tup; + Oid entryoid; + ObjectAddress myself, + referenced; + ListCell *l; + + rel = table_open(AccessMethodProcedureRelationId, RowExclusiveLock); + + foreach(l, procedures) + { + OpFamilyMember *proc = (OpFamilyMember *) lfirst(l); + + /* + * If adding to an existing family, check for conflict with an + * existing pg_amproc entry (just to give a nicer error message) + */ + if (isAdd && + SearchSysCacheExists4(AMPROCNUM, + ObjectIdGetDatum(opfamilyoid), + ObjectIdGetDatum(proc->lefttype), + ObjectIdGetDatum(proc->righttype), + Int16GetDatum(proc->number))) + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_OBJECT), + errmsg("function %d(%s,%s) already exists in operator family \"%s\"", + proc->number, + format_type_be(proc->lefttype), + format_type_be(proc->righttype), + NameListToString(opfamilyname)))); + + /* Create the pg_amproc entry */ + memset(values, 0, sizeof(values)); + memset(nulls, false, sizeof(nulls)); + + entryoid = GetNewOidWithIndex(rel, AccessMethodProcedureOidIndexId, + Anum_pg_amproc_oid); + values[Anum_pg_amproc_oid - 1] = ObjectIdGetDatum(entryoid); + values[Anum_pg_amproc_amprocfamily - 1] = ObjectIdGetDatum(opfamilyoid); + values[Anum_pg_amproc_amproclefttype - 1] = ObjectIdGetDatum(proc->lefttype); + values[Anum_pg_amproc_amprocrighttype - 1] = ObjectIdGetDatum(proc->righttype); + values[Anum_pg_amproc_amprocnum - 1] = Int16GetDatum(proc->number); + values[Anum_pg_amproc_amproc - 1] = ObjectIdGetDatum(proc->object); + + tup = heap_form_tuple(rel->rd_att, values, nulls); + + CatalogTupleInsert(rel, tup); + + heap_freetuple(tup); + + /* Make its dependencies */ + myself.classId = AccessMethodProcedureRelationId; + myself.objectId = entryoid; + myself.objectSubId = 0; + + referenced.classId = ProcedureRelationId; + referenced.objectId = proc->object; + referenced.objectSubId = 0; + + /* see comments in amapi.h about dependency strength */ + recordDependencyOn(&myself, &referenced, + proc->ref_is_hard ? DEPENDENCY_NORMAL : DEPENDENCY_AUTO); + + referenced.classId = proc->ref_is_family ? OperatorFamilyRelationId : + OperatorClassRelationId; + referenced.objectId = proc->refobjid; + referenced.objectSubId = 0; + + recordDependencyOn(&myself, &referenced, + proc->ref_is_hard ? DEPENDENCY_INTERNAL : DEPENDENCY_AUTO); + + /* Post create hook of access method procedure */ + InvokeObjectPostCreateHook(AccessMethodProcedureRelationId, + entryoid, 0); + } + + table_close(rel, RowExclusiveLock); +} + + +/* + * Remove operator entries from an opfamily. + * + * Note: this is only allowed for "loose" members of an opfamily, hence + * behavior is always RESTRICT. + */ +static void +dropOperators(List *opfamilyname, Oid amoid, Oid opfamilyoid, + List *operators) +{ + ListCell *l; + + foreach(l, operators) + { + OpFamilyMember *op = (OpFamilyMember *) lfirst(l); + Oid amopid; + ObjectAddress object; + + amopid = GetSysCacheOid4(AMOPSTRATEGY, Anum_pg_amop_oid, + ObjectIdGetDatum(opfamilyoid), + ObjectIdGetDatum(op->lefttype), + ObjectIdGetDatum(op->righttype), + Int16GetDatum(op->number)); + if (!OidIsValid(amopid)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("operator %d(%s,%s) does not exist in operator family \"%s\"", + op->number, + format_type_be(op->lefttype), + format_type_be(op->righttype), + NameListToString(opfamilyname)))); + + object.classId = AccessMethodOperatorRelationId; + object.objectId = amopid; + object.objectSubId = 0; + + performDeletion(&object, DROP_RESTRICT, 0); + } +} + +/* + * Remove procedure entries from an opfamily. + * + * Note: this is only allowed for "loose" members of an opfamily, hence + * behavior is always RESTRICT. + */ +static void +dropProcedures(List *opfamilyname, Oid amoid, Oid opfamilyoid, + List *procedures) +{ + ListCell *l; + + foreach(l, procedures) + { + OpFamilyMember *op = (OpFamilyMember *) lfirst(l); + Oid amprocid; + ObjectAddress object; + + amprocid = GetSysCacheOid4(AMPROCNUM, Anum_pg_amproc_oid, + ObjectIdGetDatum(opfamilyoid), + ObjectIdGetDatum(op->lefttype), + ObjectIdGetDatum(op->righttype), + Int16GetDatum(op->number)); + if (!OidIsValid(amprocid)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("function %d(%s,%s) does not exist in operator family \"%s\"", + op->number, + format_type_be(op->lefttype), + format_type_be(op->righttype), + NameListToString(opfamilyname)))); + + object.classId = AccessMethodProcedureRelationId; + object.objectId = amprocid; + object.objectSubId = 0; + + performDeletion(&object, DROP_RESTRICT, 0); + } +} + +/* + * Subroutine for ALTER OPERATOR CLASS SET SCHEMA/RENAME + * + * Is there an operator class with the given name and signature already + * in the given namespace? If so, raise an appropriate error message. + */ +void +IsThereOpClassInNamespace(const char *opcname, Oid opcmethod, + Oid opcnamespace) +{ + /* make sure the new name doesn't exist */ + if (SearchSysCacheExists3(CLAAMNAMENSP, + ObjectIdGetDatum(opcmethod), + CStringGetDatum(opcname), + ObjectIdGetDatum(opcnamespace))) + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_OBJECT), + errmsg("operator class \"%s\" for access method \"%s\" already exists in schema \"%s\"", + opcname, + get_am_name(opcmethod), + get_namespace_name(opcnamespace)))); +} + +/* + * Subroutine for ALTER OPERATOR FAMILY SET SCHEMA/RENAME + * + * Is there an operator family with the given name and signature already + * in the given namespace? If so, raise an appropriate error message. + */ +void +IsThereOpFamilyInNamespace(const char *opfname, Oid opfmethod, + Oid opfnamespace) +{ + /* make sure the new name doesn't exist */ + if (SearchSysCacheExists3(OPFAMILYAMNAMENSP, + ObjectIdGetDatum(opfmethod), + CStringGetDatum(opfname), + ObjectIdGetDatum(opfnamespace))) + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_OBJECT), + errmsg("operator family \"%s\" for access method \"%s\" already exists in schema \"%s\"", + opfname, + get_am_name(opfmethod), + get_namespace_name(opfnamespace)))); +} |