summaryrefslogtreecommitdiffstats
path: root/src/backend/commands/opclasscmds.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 12:17:33 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 12:17:33 +0000
commit5e45211a64149b3c659b90ff2de6fa982a5a93ed (patch)
tree739caf8c461053357daa9f162bef34516c7bf452 /src/backend/commands/opclasscmds.c
parentInitial commit. (diff)
downloadpostgresql-15-5e45211a64149b3c659b90ff2de6fa982a5a93ed.tar.xz
postgresql-15-5e45211a64149b3c659b90ff2de6fa982a5a93ed.zip
Adding upstream version 15.5.upstream/15.5
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/backend/commands/opclasscmds.c')
-rw-r--r--src/backend/commands/opclasscmds.c1745
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..7a931ab
--- /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-2022, 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))));
+}