postgresql/src/backend/commands/opclasscmds.c

1746 lines
53 KiB
C

/*-------------------------------------------------------------------------
*
* opclasscmds.c
*
* Routines for opclass (and opfamily) manipulation commands
*
* Portions Copyright (c) 1996-2023, 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 optsProcNumber, 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 = object_aclcheck(NamespaceRelationId, 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 (!object_ownercheck(TypeRelationId, 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 (!object_ownercheck(OperatorRelationId, operOid, GetUserId()))
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_OPERATOR,
get_opname(operOid));
funcOid = get_opcode(operOid);
if (!object_ownercheck(ProcedureRelationId, 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 (!object_ownercheck(ProcedureRelationId, 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 (!object_ownercheck(TypeRelationId, 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 = object_aclcheck(NamespaceRelationId, 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, /* amoptsprocnum 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 (!object_ownercheck(OperatorRelationId, operOid, GetUserId()))
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_OPERATOR,
get_opname(operOid));
funcOid = get_opcode(operOid);
if (!object_ownercheck(ProcedureRelationId, 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 (!object_ownercheck(ProcedureRelationId, 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 lefttype 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))));
}