mirror of
https://github.com/postgres/postgres.git
synced 2025-05-06 19:59:18 +03:00
cases. Operator classes now exist within "operator families". While most families are equivalent to a single class, related classes can be grouped into one family to represent the fact that they are semantically compatible. Cross-type operators are now naturally adjunct parts of a family, without having to wedge them into a particular opclass as we had done originally. This commit restructures the catalogs and cleans up enough of the fallout so that everything still works at least as well as before, but most of the work needed to actually improve the planner's behavior will come later. Also, there are not yet CREATE/DROP/ALTER OPERATOR FAMILY commands; the only way to create a new family right now is to allow CREATE OPERATOR CLASS to make one by default. I owe some more documentation work, too. But that can all be done in smaller pieces once this infrastructure is in place.
1355 lines
38 KiB
C
1355 lines
38 KiB
C
/*-------------------------------------------------------------------------
|
|
*
|
|
* opclasscmds.c
|
|
*
|
|
* Routines for opclass (and opfamily) manipulation commands
|
|
*
|
|
* Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
|
|
* Portions Copyright (c) 1994, Regents of the University of California
|
|
*
|
|
*
|
|
* IDENTIFICATION
|
|
* $PostgreSQL: pgsql/src/backend/commands/opclasscmds.c,v 1.51 2006/12/23 00:43:09 tgl Exp $
|
|
*
|
|
*-------------------------------------------------------------------------
|
|
*/
|
|
#include "postgres.h"
|
|
|
|
#include <limits.h>
|
|
|
|
#include "access/genam.h"
|
|
#include "access/heapam.h"
|
|
#include "catalog/dependency.h"
|
|
#include "catalog/indexing.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/defrem.h"
|
|
#include "miscadmin.h"
|
|
#include "parser/parse_func.h"
|
|
#include "parser/parse_oper.h"
|
|
#include "parser/parse_type.h"
|
|
#include "utils/acl.h"
|
|
#include "utils/builtins.h"
|
|
#include "utils/fmgroids.h"
|
|
#include "utils/lsyscache.h"
|
|
#include "utils/syscache.h"
|
|
|
|
|
|
/*
|
|
* We use lists of this struct type to keep track of both operators and
|
|
* procedures while building or adding to an opfamily.
|
|
*/
|
|
typedef struct
|
|
{
|
|
Oid object; /* operator or support proc's OID */
|
|
int number; /* strategy or support proc number */
|
|
Oid lefttype; /* lefttype */
|
|
Oid righttype; /* righttype */
|
|
bool recheck; /* oper recheck flag (unused for proc) */
|
|
} OpFamilyMember;
|
|
|
|
|
|
static void assignOperTypes(OpFamilyMember *member, Oid amoid, Oid typeoid);
|
|
static void assignProcTypes(OpFamilyMember *member, Oid amoid, Oid typeoid);
|
|
static void addFamilyMember(List **list, OpFamilyMember *member, bool isProc);
|
|
static void storeOperators(Oid amoid, Oid opfamilyoid, Oid opclassoid,
|
|
List *operators);
|
|
static void storeProcedures(Oid amoid, Oid opfamilyoid, Oid opclassoid,
|
|
List *procedures);
|
|
static void AlterOpClassOwner_internal(Relation rel, HeapTuple tuple,
|
|
Oid newOwnerId);
|
|
|
|
|
|
/*
|
|
* 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)
|
|
{
|
|
char *schemaname;
|
|
char *opfname;
|
|
|
|
/* deconstruct the name list */
|
|
DeconstructQualifiedName(opfamilyname, &schemaname, &opfname);
|
|
|
|
if (schemaname)
|
|
{
|
|
/* Look in specific schema only */
|
|
Oid namespaceId;
|
|
|
|
namespaceId = LookupExplicitNamespace(schemaname);
|
|
return SearchSysCache(OPFAMILYAMNAMENSP,
|
|
ObjectIdGetDatum(amID),
|
|
PointerGetDatum(opfname),
|
|
ObjectIdGetDatum(namespaceId),
|
|
0);
|
|
}
|
|
else
|
|
{
|
|
/* Unqualified opfamily name, so search the search path */
|
|
Oid opfID = OpfamilynameGetOpfid(amID, opfname);
|
|
|
|
if (!OidIsValid(opfID))
|
|
return NULL;
|
|
return SearchSysCache(OPFAMILYOID,
|
|
ObjectIdGetDatum(opfID),
|
|
0, 0, 0);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* 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)
|
|
{
|
|
char *schemaname;
|
|
char *opcname;
|
|
|
|
/* deconstruct the name list */
|
|
DeconstructQualifiedName(opclassname, &schemaname, &opcname);
|
|
|
|
if (schemaname)
|
|
{
|
|
/* Look in specific schema only */
|
|
Oid namespaceId;
|
|
|
|
namespaceId = LookupExplicitNamespace(schemaname);
|
|
return SearchSysCache(CLAAMNAMENSP,
|
|
ObjectIdGetDatum(amID),
|
|
PointerGetDatum(opcname),
|
|
ObjectIdGetDatum(namespaceId),
|
|
0);
|
|
}
|
|
else
|
|
{
|
|
/* Unqualified opclass name, so search the search path */
|
|
Oid opcID = OpclassnameGetOpcid(amID, opcname);
|
|
|
|
if (!OidIsValid(opcID))
|
|
return NULL;
|
|
return SearchSysCache(CLAOID,
|
|
ObjectIdGetDatum(opcID),
|
|
0, 0, 0);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* CreateOpFamily
|
|
* Internal routine to make the catalog entry for a new operator family.
|
|
*
|
|
* Caller must have done permissions checks etc. already.
|
|
*/
|
|
static Oid
|
|
CreateOpFamily(char *amname, char *opfname, Oid namespaceoid, Oid amoid)
|
|
{
|
|
Oid opfamilyoid;
|
|
Relation rel;
|
|
HeapTuple tup;
|
|
Datum values[Natts_pg_opfamily];
|
|
char nulls[Natts_pg_opfamily];
|
|
NameData opfName;
|
|
ObjectAddress myself,
|
|
referenced;
|
|
|
|
rel = heap_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 (SearchSysCacheExists(OPFAMILYAMNAMENSP,
|
|
ObjectIdGetDatum(amoid),
|
|
CStringGetDatum(opfname),
|
|
ObjectIdGetDatum(namespaceoid),
|
|
0))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_DUPLICATE_OBJECT),
|
|
errmsg("operator family \"%s\" for access method \"%s\" already exists",
|
|
opfname, amname)));
|
|
|
|
/*
|
|
* Okay, let's create the pg_opfamily entry.
|
|
*/
|
|
memset(values, 0, sizeof(values));
|
|
memset(nulls, ' ', sizeof(nulls));
|
|
|
|
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_formtuple(rel->rd_att, values, nulls);
|
|
|
|
opfamilyoid = simple_heap_insert(rel, tup);
|
|
|
|
CatalogUpdateIndexes(rel, tup);
|
|
|
|
heap_freetuple(tup);
|
|
|
|
/*
|
|
* Create dependencies for the opfamily proper. Note: we do not create a
|
|
* dependency link to the AM, because we don't currently support DROP
|
|
* ACCESS METHOD.
|
|
*/
|
|
myself.classId = OperatorFamilyRelationId;
|
|
myself.objectId = opfamilyoid;
|
|
myself.objectSubId = 0;
|
|
|
|
/* dependency on namespace */
|
|
referenced.classId = NamespaceRelationId;
|
|
referenced.objectId = namespaceoid;
|
|
referenced.objectSubId = 0;
|
|
recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
|
|
|
|
/* dependency on owner */
|
|
recordDependencyOnOwner(OperatorFamilyRelationId, opfamilyoid, GetUserId());
|
|
|
|
heap_close(rel, RowExclusiveLock);
|
|
|
|
return opfamilyoid;
|
|
}
|
|
|
|
/*
|
|
* DefineOpClass
|
|
* Define a new index operator class.
|
|
*/
|
|
void
|
|
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 */
|
|
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 pg_am;
|
|
Datum values[Natts_pg_opclass];
|
|
char 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, ACL_KIND_NAMESPACE,
|
|
get_namespace_name(namespaceoid));
|
|
|
|
/* Get necessary info about access method */
|
|
tup = SearchSysCache(AMNAME,
|
|
CStringGetDatum(stmt->amname),
|
|
0, 0, 0);
|
|
if (!HeapTupleIsValid(tup))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_UNDEFINED_OBJECT),
|
|
errmsg("access method \"%s\" does not exist",
|
|
stmt->amname)));
|
|
|
|
amoid = HeapTupleGetOid(tup);
|
|
pg_am = (Form_pg_am) GETSTRUCT(tup);
|
|
maxOpNumber = pg_am->amstrategies;
|
|
/* if amstrategies is zero, just enforce that op numbers fit in int16 */
|
|
if (maxOpNumber <= 0)
|
|
maxOpNumber = SHRT_MAX;
|
|
maxProcNumber = pg_am->amsupport;
|
|
amstorage = pg_am->amstorage;
|
|
|
|
/* XXX Should we make any privilege check against the AM? */
|
|
|
|
ReleaseSysCache(tup);
|
|
|
|
/*
|
|
* 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(ACLCHECK_NOT_OWNER, ACL_KIND_TYPE,
|
|
format_type_be(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)
|
|
{
|
|
tup = OpFamilyCacheLookup(amoid, stmt->opfamilyname);
|
|
if (!HeapTupleIsValid(tup))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_UNDEFINED_OBJECT),
|
|
errmsg("operator family \"%s\" does not exist for access method \"%s\"",
|
|
NameListToString(stmt->opfamilyname), stmt->amname)));
|
|
opfamilyoid = HeapTupleGetOid(tup);
|
|
/*
|
|
* XXX given the superuser check above, there's no need for an
|
|
* ownership check here
|
|
*/
|
|
ReleaseSysCache(tup);
|
|
}
|
|
else
|
|
{
|
|
/* Lookup existing family of same name and namespace */
|
|
tup = SearchSysCache(OPFAMILYAMNAMENSP,
|
|
ObjectIdGetDatum(amoid),
|
|
PointerGetDatum(opcname),
|
|
ObjectIdGetDatum(namespaceoid),
|
|
0);
|
|
if (HeapTupleIsValid(tup))
|
|
{
|
|
opfamilyoid = HeapTupleGetOid(tup);
|
|
/*
|
|
* XXX given the superuser check above, there's no need for an
|
|
* ownership check here
|
|
*/
|
|
ReleaseSysCache(tup);
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* Create it ... again no need for more permissions ...
|
|
*/
|
|
opfamilyoid = CreateOpFamily(stmt->amname, opcname,
|
|
namespaceoid, amoid);
|
|
}
|
|
}
|
|
|
|
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(l);
|
|
Oid operOid;
|
|
Oid funcOid;
|
|
OpFamilyMember *member;
|
|
|
|
Assert(IsA(item, CreateOpClassItem));
|
|
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->args != NIL)
|
|
{
|
|
TypeName *typeName1 = (TypeName *) linitial(item->args);
|
|
TypeName *typeName2 = (TypeName *) lsecond(item->args);
|
|
|
|
operOid = LookupOperNameTypeNames(NULL, item->name,
|
|
typeName1, typeName2,
|
|
false, -1);
|
|
}
|
|
else
|
|
{
|
|
/* Default to binary op on input datatype */
|
|
operOid = LookupOperName(NULL, item->name,
|
|
typeoid, typeoid,
|
|
false, -1);
|
|
}
|
|
|
|
#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, ACL_KIND_OPER,
|
|
get_opname(operOid));
|
|
funcOid = get_opcode(operOid);
|
|
if (!pg_proc_ownercheck(funcOid, GetUserId()))
|
|
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_PROC,
|
|
get_func_name(funcOid));
|
|
#endif
|
|
|
|
/* Save the info */
|
|
member = (OpFamilyMember *) palloc0(sizeof(OpFamilyMember));
|
|
member->object = operOid;
|
|
member->number = item->number;
|
|
member->recheck = item->recheck;
|
|
assignOperTypes(member, amoid, typeoid);
|
|
addFamilyMember(&operators, member, false);
|
|
break;
|
|
case OPCLASS_ITEM_FUNCTION:
|
|
if (item->number <= 0 || item->number > maxProcNumber)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
|
|
errmsg("invalid procedure number %d,"
|
|
" must be between 1 and %d",
|
|
item->number, maxProcNumber)));
|
|
funcOid = LookupFuncNameTypeNames(item->name, item->args,
|
|
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, ACL_KIND_PROC,
|
|
get_func_name(funcOid));
|
|
#endif
|
|
|
|
/* Save the info */
|
|
member = (OpFamilyMember *) palloc0(sizeof(OpFamilyMember));
|
|
member->object = funcOid;
|
|
member->number = item->number;
|
|
assignProcTypes(member, amoid, typeoid);
|
|
addFamilyMember(&procedures, member, true);
|
|
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(ACLCHECK_NOT_OWNER, ACL_KIND_TYPE,
|
|
format_type_be(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 may not be different from data type for access method \"%s\"",
|
|
stmt->amname)));
|
|
}
|
|
|
|
rel = heap_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 (SearchSysCacheExists(CLAAMNAMENSP,
|
|
ObjectIdGetDatum(amoid),
|
|
CStringGetDatum(opcname),
|
|
ObjectIdGetDatum(namespaceoid),
|
|
0))
|
|
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,
|
|
SnapshotNow, 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, ' ', sizeof(nulls));
|
|
|
|
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_formtuple(rel->rd_att, values, nulls);
|
|
|
|
opclassoid = simple_heap_insert(rel, tup);
|
|
|
|
CatalogUpdateIndexes(rel, tup);
|
|
|
|
heap_freetuple(tup);
|
|
|
|
/*
|
|
* Now add tuples to pg_amop and pg_amproc tying in the operators and
|
|
* functions. Dependencies on them are inserted, too.
|
|
*/
|
|
storeOperators(amoid, opfamilyoid, opclassoid, operators);
|
|
storeProcedures(amoid, opfamilyoid, opclassoid, procedures);
|
|
|
|
/*
|
|
* Create dependencies for the opclass proper. Note: we do not create a
|
|
* dependency link to the AM, because we don't currently support DROP
|
|
* ACCESS METHOD.
|
|
*/
|
|
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());
|
|
|
|
heap_close(rel, RowExclusiveLock);
|
|
}
|
|
|
|
/*
|
|
* 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 = SearchSysCache(OPEROID,
|
|
ObjectIdGetDatum(member->object),
|
|
0, 0, 0);
|
|
if (optup == NULL)
|
|
elog(ERROR, "cache lookup failed for operator %u", member->object);
|
|
opform = (Form_pg_operator) GETSTRUCT(optup);
|
|
|
|
/*
|
|
* Opfamily operators must be binary ops returning boolean.
|
|
*/
|
|
if (opform->oprkind != 'b')
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
|
|
errmsg("index operators must be binary")));
|
|
if (opform->oprresult != BOOLOID)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
|
|
errmsg("index 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)
|
|
{
|
|
HeapTuple proctup;
|
|
Form_pg_proc procform;
|
|
|
|
/* Fetch the procedure definition */
|
|
proctup = SearchSysCache(PROCOID,
|
|
ObjectIdGetDatum(member->object),
|
|
0, 0, 0);
|
|
if (proctup == NULL)
|
|
elog(ERROR, "cache lookup failed for function %u", member->object);
|
|
procform = (Form_pg_proc) GETSTRUCT(proctup);
|
|
|
|
/*
|
|
* btree support procs must be 2-arg procs returning int4; hash support
|
|
* procs must be 1-arg procs returning int4; otherwise we don't know.
|
|
*/
|
|
if (amoid == BTREE_AM_OID)
|
|
{
|
|
if (procform->pronargs != 2)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
|
|
errmsg("btree procedures must have two arguments")));
|
|
if (procform->prorettype != INT4OID)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
|
|
errmsg("btree procedures 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 (amoid == HASH_AM_OID)
|
|
{
|
|
if (procform->pronargs != 1)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
|
|
errmsg("hash procedures must have one argument")));
|
|
if (procform->prorettype != INT4OID)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
|
|
errmsg("hash procedures must return integer")));
|
|
|
|
/*
|
|
* 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];
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* The default for GiST and GIN 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 procedure")));
|
|
}
|
|
|
|
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, bool isProc)
|
|
{
|
|
ListCell *l;
|
|
|
|
foreach(l, *list)
|
|
{
|
|
OpFamilyMember *old = (OpFamilyMember *) lfirst(l);
|
|
|
|
if (old->number == member->number &&
|
|
old->lefttype == member->lefttype &&
|
|
old->righttype == member->righttype)
|
|
{
|
|
if (isProc)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
|
|
errmsg("procedure 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 opfamily entries.
|
|
* If opclassoid is valid then make an INTERNAL dependency on that opclass,
|
|
* else make an AUTO dependency on the opfamily.
|
|
*/
|
|
static void
|
|
storeOperators(Oid amoid, Oid opfamilyoid, Oid opclassoid, List *operators)
|
|
{
|
|
Relation rel;
|
|
Datum values[Natts_pg_amop];
|
|
char nulls[Natts_pg_amop];
|
|
HeapTuple tup;
|
|
Oid entryoid;
|
|
ObjectAddress myself,
|
|
referenced;
|
|
ListCell *l;
|
|
|
|
rel = heap_open(AccessMethodOperatorRelationId, RowExclusiveLock);
|
|
|
|
foreach(l, operators)
|
|
{
|
|
OpFamilyMember *op = (OpFamilyMember *) lfirst(l);
|
|
|
|
/* Create the pg_amop entry */
|
|
memset(values, 0, sizeof(values));
|
|
memset(nulls, ' ', sizeof(nulls));
|
|
|
|
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_amopreqcheck - 1] = BoolGetDatum(op->recheck);
|
|
values[Anum_pg_amop_amopopr - 1] = ObjectIdGetDatum(op->object);
|
|
values[Anum_pg_amop_amopmethod - 1] = ObjectIdGetDatum(amoid);
|
|
|
|
tup = heap_formtuple(rel->rd_att, values, nulls);
|
|
|
|
entryoid = simple_heap_insert(rel, tup);
|
|
|
|
CatalogUpdateIndexes(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;
|
|
|
|
if (OidIsValid(opclassoid))
|
|
{
|
|
/* if contained in an opclass, use a NORMAL dep on operator */
|
|
recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
|
|
|
|
/* ... and an INTERNAL dep on the opclass */
|
|
referenced.classId = OperatorClassRelationId;
|
|
referenced.objectId = opclassoid;
|
|
referenced.objectSubId = 0;
|
|
recordDependencyOn(&myself, &referenced, DEPENDENCY_INTERNAL);
|
|
}
|
|
else
|
|
{
|
|
/* if "loose" in the opfamily, use a AUTO dep on operator */
|
|
recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
|
|
|
|
/* ... and an AUTO dep on the opfamily */
|
|
referenced.classId = OperatorFamilyRelationId;
|
|
referenced.objectId = opfamilyoid;
|
|
referenced.objectSubId = 0;
|
|
recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
|
|
}
|
|
}
|
|
|
|
heap_close(rel, RowExclusiveLock);
|
|
}
|
|
|
|
/*
|
|
* Dump the procedures (support routines) to pg_amproc
|
|
*
|
|
* We also make dependency entries in pg_depend for the opfamily entries.
|
|
* If opclassoid is valid then make an INTERNAL dependency on that opclass,
|
|
* else make an AUTO dependency on the opfamily.
|
|
*/
|
|
static void
|
|
storeProcedures(Oid amoid, Oid opfamilyoid, Oid opclassoid, List *procedures)
|
|
{
|
|
Relation rel;
|
|
Datum values[Natts_pg_amproc];
|
|
char nulls[Natts_pg_amproc];
|
|
HeapTuple tup;
|
|
Oid entryoid;
|
|
ObjectAddress myself,
|
|
referenced;
|
|
ListCell *l;
|
|
|
|
rel = heap_open(AccessMethodProcedureRelationId, RowExclusiveLock);
|
|
|
|
foreach(l, procedures)
|
|
{
|
|
OpFamilyMember *proc = (OpFamilyMember *) lfirst(l);
|
|
|
|
/* Create the pg_amproc entry */
|
|
memset(values, 0, sizeof(values));
|
|
memset(nulls, ' ', sizeof(nulls));
|
|
|
|
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_formtuple(rel->rd_att, values, nulls);
|
|
|
|
entryoid = simple_heap_insert(rel, tup);
|
|
|
|
CatalogUpdateIndexes(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;
|
|
|
|
if (OidIsValid(opclassoid))
|
|
{
|
|
/* if contained in an opclass, use a NORMAL dep on procedure */
|
|
recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
|
|
|
|
/* ... and an INTERNAL dep on the opclass */
|
|
referenced.classId = OperatorClassRelationId;
|
|
referenced.objectId = opclassoid;
|
|
referenced.objectSubId = 0;
|
|
recordDependencyOn(&myself, &referenced, DEPENDENCY_INTERNAL);
|
|
}
|
|
else
|
|
{
|
|
/* if "loose" in the opfamily, use a AUTO dep on procedure */
|
|
recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
|
|
|
|
/* ... and an AUTO dep on the opfamily */
|
|
referenced.classId = OperatorFamilyRelationId;
|
|
referenced.objectId = opfamilyoid;
|
|
referenced.objectSubId = 0;
|
|
recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
|
|
}
|
|
}
|
|
|
|
heap_close(rel, RowExclusiveLock);
|
|
}
|
|
|
|
|
|
/*
|
|
* RemoveOpClass
|
|
* Deletes an opclass.
|
|
*/
|
|
void
|
|
RemoveOpClass(RemoveOpClassStmt *stmt)
|
|
{
|
|
Oid amID,
|
|
opcID;
|
|
HeapTuple tuple;
|
|
ObjectAddress object;
|
|
|
|
/*
|
|
* Get the access method's OID.
|
|
*/
|
|
amID = GetSysCacheOid(AMNAME,
|
|
CStringGetDatum(stmt->amname),
|
|
0, 0, 0);
|
|
if (!OidIsValid(amID))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_UNDEFINED_OBJECT),
|
|
errmsg("access method \"%s\" does not exist",
|
|
stmt->amname)));
|
|
|
|
/*
|
|
* Look up the opclass.
|
|
*/
|
|
tuple = OpClassCacheLookup(amID, stmt->opclassname);
|
|
if (!HeapTupleIsValid(tuple))
|
|
{
|
|
if (!stmt->missing_ok)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_UNDEFINED_OBJECT),
|
|
errmsg("operator class \"%s\" does not exist for access method \"%s\"",
|
|
NameListToString(stmt->opclassname), stmt->amname)));
|
|
else
|
|
ereport(NOTICE,
|
|
(errmsg("operator class \"%s\" does not exist for access method \"%s\"",
|
|
NameListToString(stmt->opclassname), stmt->amname)));
|
|
return;
|
|
}
|
|
|
|
opcID = HeapTupleGetOid(tuple);
|
|
|
|
/* Permission check: must own opclass or its namespace */
|
|
if (!pg_opclass_ownercheck(opcID, GetUserId()) &&
|
|
!pg_namespace_ownercheck(((Form_pg_opclass) GETSTRUCT(tuple))->opcnamespace,
|
|
GetUserId()))
|
|
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_OPCLASS,
|
|
NameListToString(stmt->opclassname));
|
|
|
|
ReleaseSysCache(tuple);
|
|
|
|
/*
|
|
* Do the deletion
|
|
*/
|
|
object.classId = OperatorClassRelationId;
|
|
object.objectId = opcID;
|
|
object.objectSubId = 0;
|
|
|
|
performDeletion(&object, stmt->behavior);
|
|
}
|
|
|
|
/*
|
|
* Deletion subroutines for use by dependency.c.
|
|
*/
|
|
void
|
|
RemoveOpFamilyById(Oid opfamilyOid)
|
|
{
|
|
Relation rel;
|
|
HeapTuple tup;
|
|
|
|
rel = heap_open(OperatorFamilyRelationId, RowExclusiveLock);
|
|
|
|
tup = SearchSysCache(OPFAMILYOID,
|
|
ObjectIdGetDatum(opfamilyOid),
|
|
0, 0, 0);
|
|
if (!HeapTupleIsValid(tup)) /* should not happen */
|
|
elog(ERROR, "cache lookup failed for opfamily %u", opfamilyOid);
|
|
|
|
simple_heap_delete(rel, &tup->t_self);
|
|
|
|
ReleaseSysCache(tup);
|
|
|
|
heap_close(rel, RowExclusiveLock);
|
|
}
|
|
|
|
void
|
|
RemoveOpClassById(Oid opclassOid)
|
|
{
|
|
Relation rel;
|
|
HeapTuple tup;
|
|
|
|
rel = heap_open(OperatorClassRelationId, RowExclusiveLock);
|
|
|
|
tup = SearchSysCache(CLAOID,
|
|
ObjectIdGetDatum(opclassOid),
|
|
0, 0, 0);
|
|
if (!HeapTupleIsValid(tup)) /* should not happen */
|
|
elog(ERROR, "cache lookup failed for opclass %u", opclassOid);
|
|
|
|
simple_heap_delete(rel, &tup->t_self);
|
|
|
|
ReleaseSysCache(tup);
|
|
|
|
heap_close(rel, RowExclusiveLock);
|
|
}
|
|
|
|
void
|
|
RemoveAmOpEntryById(Oid entryOid)
|
|
{
|
|
Relation rel;
|
|
HeapTuple tup;
|
|
ScanKeyData skey[1];
|
|
SysScanDesc scan;
|
|
|
|
ScanKeyInit(&skey[0],
|
|
ObjectIdAttributeNumber,
|
|
BTEqualStrategyNumber, F_OIDEQ,
|
|
ObjectIdGetDatum(entryOid));
|
|
|
|
rel = heap_open(AccessMethodOperatorRelationId, RowExclusiveLock);
|
|
|
|
scan = systable_beginscan(rel, AccessMethodOperatorOidIndexId, true,
|
|
SnapshotNow, 1, skey);
|
|
|
|
/* we expect exactly one match */
|
|
tup = systable_getnext(scan);
|
|
if (!HeapTupleIsValid(tup))
|
|
elog(ERROR, "could not find tuple for amop entry %u", entryOid);
|
|
|
|
simple_heap_delete(rel, &tup->t_self);
|
|
|
|
systable_endscan(scan);
|
|
heap_close(rel, RowExclusiveLock);
|
|
}
|
|
|
|
void
|
|
RemoveAmProcEntryById(Oid entryOid)
|
|
{
|
|
Relation rel;
|
|
HeapTuple tup;
|
|
ScanKeyData skey[1];
|
|
SysScanDesc scan;
|
|
|
|
ScanKeyInit(&skey[0],
|
|
ObjectIdAttributeNumber,
|
|
BTEqualStrategyNumber, F_OIDEQ,
|
|
ObjectIdGetDatum(entryOid));
|
|
|
|
rel = heap_open(AccessMethodProcedureRelationId, RowExclusiveLock);
|
|
|
|
scan = systable_beginscan(rel, AccessMethodProcedureOidIndexId, true,
|
|
SnapshotNow, 1, skey);
|
|
|
|
/* we expect exactly one match */
|
|
tup = systable_getnext(scan);
|
|
if (!HeapTupleIsValid(tup))
|
|
elog(ERROR, "could not find tuple for amproc entry %u", entryOid);
|
|
|
|
simple_heap_delete(rel, &tup->t_self);
|
|
|
|
systable_endscan(scan);
|
|
heap_close(rel, RowExclusiveLock);
|
|
}
|
|
|
|
|
|
/*
|
|
* Rename opclass
|
|
*/
|
|
void
|
|
RenameOpClass(List *name, const char *access_method, const char *newname)
|
|
{
|
|
Oid opcOid;
|
|
Oid amOid;
|
|
Oid namespaceOid;
|
|
char *schemaname;
|
|
char *opcname;
|
|
HeapTuple tup;
|
|
Relation rel;
|
|
AclResult aclresult;
|
|
|
|
amOid = GetSysCacheOid(AMNAME,
|
|
CStringGetDatum(access_method),
|
|
0, 0, 0);
|
|
if (!OidIsValid(amOid))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_UNDEFINED_OBJECT),
|
|
errmsg("access method \"%s\" does not exist",
|
|
access_method)));
|
|
|
|
rel = heap_open(OperatorClassRelationId, RowExclusiveLock);
|
|
|
|
/*
|
|
* Look up the opclass
|
|
*/
|
|
DeconstructQualifiedName(name, &schemaname, &opcname);
|
|
|
|
if (schemaname)
|
|
{
|
|
namespaceOid = LookupExplicitNamespace(schemaname);
|
|
|
|
tup = SearchSysCacheCopy(CLAAMNAMENSP,
|
|
ObjectIdGetDatum(amOid),
|
|
PointerGetDatum(opcname),
|
|
ObjectIdGetDatum(namespaceOid),
|
|
0);
|
|
if (!HeapTupleIsValid(tup))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_UNDEFINED_OBJECT),
|
|
errmsg("operator class \"%s\" does not exist for access method \"%s\"",
|
|
opcname, access_method)));
|
|
|
|
opcOid = HeapTupleGetOid(tup);
|
|
}
|
|
else
|
|
{
|
|
opcOid = OpclassnameGetOpcid(amOid, opcname);
|
|
if (!OidIsValid(opcOid))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_UNDEFINED_OBJECT),
|
|
errmsg("operator class \"%s\" does not exist for access method \"%s\"",
|
|
opcname, access_method)));
|
|
|
|
tup = SearchSysCacheCopy(CLAOID,
|
|
ObjectIdGetDatum(opcOid),
|
|
0, 0, 0);
|
|
if (!HeapTupleIsValid(tup)) /* should not happen */
|
|
elog(ERROR, "cache lookup failed for opclass %u", opcOid);
|
|
|
|
namespaceOid = ((Form_pg_opclass) GETSTRUCT(tup))->opcnamespace;
|
|
}
|
|
|
|
/* make sure the new name doesn't exist */
|
|
if (SearchSysCacheExists(CLAAMNAMENSP,
|
|
ObjectIdGetDatum(amOid),
|
|
CStringGetDatum(newname),
|
|
ObjectIdGetDatum(namespaceOid),
|
|
0))
|
|
{
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_DUPLICATE_OBJECT),
|
|
errmsg("operator class \"%s\" for access method \"%s\" already exists in schema \"%s\"",
|
|
newname, access_method,
|
|
get_namespace_name(namespaceOid))));
|
|
}
|
|
|
|
/* must be owner */
|
|
if (!pg_opclass_ownercheck(opcOid, GetUserId()))
|
|
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_OPCLASS,
|
|
NameListToString(name));
|
|
|
|
/* must have CREATE privilege on namespace */
|
|
aclresult = pg_namespace_aclcheck(namespaceOid, GetUserId(), ACL_CREATE);
|
|
if (aclresult != ACLCHECK_OK)
|
|
aclcheck_error(aclresult, ACL_KIND_NAMESPACE,
|
|
get_namespace_name(namespaceOid));
|
|
|
|
/* rename */
|
|
namestrcpy(&(((Form_pg_opclass) GETSTRUCT(tup))->opcname), newname);
|
|
simple_heap_update(rel, &tup->t_self, tup);
|
|
CatalogUpdateIndexes(rel, tup);
|
|
|
|
heap_close(rel, NoLock);
|
|
heap_freetuple(tup);
|
|
}
|
|
|
|
/*
|
|
* Change opclass owner by oid
|
|
*/
|
|
#ifdef NOT_USED
|
|
void
|
|
AlterOpClassOwner_oid(Oid opcOid, Oid newOwnerId)
|
|
{
|
|
Relation rel;
|
|
HeapTuple tup;
|
|
|
|
rel = heap_open(OperatorClassRelationId, RowExclusiveLock);
|
|
|
|
tup = SearchSysCacheCopy(CLAOID,
|
|
ObjectIdGetDatum(opcOid),
|
|
0, 0, 0);
|
|
if (!HeapTupleIsValid(tup)) /* shouldn't happen */
|
|
elog(ERROR, "cache lookup failed for opclass %u", opcOid);
|
|
|
|
AlterOpClassOwner_internal(rel, tup, newOwnerId);
|
|
|
|
heap_freetuple(tup);
|
|
heap_close(rel, NoLock);
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* Change opclass owner by name
|
|
*/
|
|
void
|
|
AlterOpClassOwner(List *name, const char *access_method, Oid newOwnerId)
|
|
{
|
|
Oid amOid;
|
|
Relation rel;
|
|
HeapTuple tup;
|
|
char *opcname;
|
|
char *schemaname;
|
|
|
|
amOid = GetSysCacheOid(AMNAME,
|
|
CStringGetDatum(access_method),
|
|
0, 0, 0);
|
|
if (!OidIsValid(amOid))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_UNDEFINED_OBJECT),
|
|
errmsg("access method \"%s\" does not exist",
|
|
access_method)));
|
|
|
|
rel = heap_open(OperatorClassRelationId, RowExclusiveLock);
|
|
|
|
/*
|
|
* Look up the opclass
|
|
*/
|
|
DeconstructQualifiedName(name, &schemaname, &opcname);
|
|
|
|
if (schemaname)
|
|
{
|
|
Oid namespaceOid;
|
|
|
|
namespaceOid = LookupExplicitNamespace(schemaname);
|
|
|
|
tup = SearchSysCacheCopy(CLAAMNAMENSP,
|
|
ObjectIdGetDatum(amOid),
|
|
PointerGetDatum(opcname),
|
|
ObjectIdGetDatum(namespaceOid),
|
|
0);
|
|
if (!HeapTupleIsValid(tup))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_UNDEFINED_OBJECT),
|
|
errmsg("operator class \"%s\" does not exist for access method \"%s\"",
|
|
opcname, access_method)));
|
|
}
|
|
else
|
|
{
|
|
Oid opcOid;
|
|
|
|
opcOid = OpclassnameGetOpcid(amOid, opcname);
|
|
if (!OidIsValid(opcOid))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_UNDEFINED_OBJECT),
|
|
errmsg("operator class \"%s\" does not exist for access method \"%s\"",
|
|
opcname, access_method)));
|
|
|
|
tup = SearchSysCacheCopy(CLAOID,
|
|
ObjectIdGetDatum(opcOid),
|
|
0, 0, 0);
|
|
if (!HeapTupleIsValid(tup)) /* should not happen */
|
|
elog(ERROR, "cache lookup failed for opclass %u", opcOid);
|
|
}
|
|
|
|
AlterOpClassOwner_internal(rel, tup, newOwnerId);
|
|
|
|
heap_freetuple(tup);
|
|
heap_close(rel, NoLock);
|
|
}
|
|
|
|
/*
|
|
* The first parameter is pg_opclass, opened and suitably locked. The second
|
|
* parameter is a copy of the tuple from pg_opclass we want to modify.
|
|
*/
|
|
static void
|
|
AlterOpClassOwner_internal(Relation rel, HeapTuple tup, Oid newOwnerId)
|
|
{
|
|
Oid namespaceOid;
|
|
AclResult aclresult;
|
|
Form_pg_opclass opcForm;
|
|
|
|
Assert(tup->t_tableOid == OperatorClassRelationId);
|
|
Assert(RelationGetRelid(rel) == OperatorClassRelationId);
|
|
|
|
opcForm = (Form_pg_opclass) GETSTRUCT(tup);
|
|
|
|
namespaceOid = opcForm->opcnamespace;
|
|
|
|
/*
|
|
* If the new owner is the same as the existing owner, consider the
|
|
* command to have succeeded. This is for dump restoration purposes.
|
|
*/
|
|
if (opcForm->opcowner != newOwnerId)
|
|
{
|
|
/* Superusers can always do it */
|
|
if (!superuser())
|
|
{
|
|
/* Otherwise, must be owner of the existing object */
|
|
if (!pg_opclass_ownercheck(HeapTupleGetOid(tup), GetUserId()))
|
|
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_OPCLASS,
|
|
NameStr(opcForm->opcname));
|
|
|
|
/* Must be able to become new owner */
|
|
check_is_member_of_role(GetUserId(), newOwnerId);
|
|
|
|
/* New owner must have CREATE privilege on namespace */
|
|
aclresult = pg_namespace_aclcheck(namespaceOid, newOwnerId,
|
|
ACL_CREATE);
|
|
if (aclresult != ACLCHECK_OK)
|
|
aclcheck_error(aclresult, ACL_KIND_NAMESPACE,
|
|
get_namespace_name(namespaceOid));
|
|
}
|
|
|
|
/*
|
|
* Modify the owner --- okay to scribble on tup because it's a copy
|
|
*/
|
|
opcForm->opcowner = newOwnerId;
|
|
|
|
simple_heap_update(rel, &tup->t_self, tup);
|
|
|
|
CatalogUpdateIndexes(rel, tup);
|
|
|
|
/* Update owner dependency reference */
|
|
changeDependencyOnOwner(OperatorClassRelationId, HeapTupleGetOid(tup),
|
|
newOwnerId);
|
|
}
|
|
}
|