blob: 4179c7db4e7e29f8b1912b0e7e9b87991e820f6f [file] [log] [blame]
/*-------------------------------------------------------------------------
*
* opclasscmds.c
*
* Routines for opclass manipulation commands
*
* Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/commands/opclasscmds.c,v 1.49 2006/10/04 00:29:51 momjian Exp $
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "access/genam.h"
#include "access/heapam.h"
#include "catalog/catquery.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_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"
#include "cdb/cdbvars.h"
#include "cdb/cdbdisp.h"
#include "cdb/dispatcher.h"
/*
* We use lists of this struct type to keep track of both operators and
* procedures during DefineOpClass.
*/
typedef struct
{
Oid object; /* operator or support proc's OID */
int number; /* strategy or support proc number */
Oid subtype; /* subtype */
bool recheck; /* oper recheck flag (unused for proc) */
} OpClassMember;
static Oid assignOperSubtype(Oid amoid, Oid typeoid, Oid operOid);
static Oid assignProcSubtype(Oid amoid, Oid typeoid, Oid procOid);
static void addClassMember(List **list, OpClassMember *member, bool isProc);
static void storeOperators(Oid opclassoid, List *operators);
static void storeProcedures(Oid opclassoid, List *procedures);
static void AlterOpClassOwner_internal(cqContext *pcqCtx,
Relation rel, HeapTuple tuple,
Oid newOwnerId);
/*
* 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 */
opclassoid; /* oid of opclass we create */
int numOperators, /* amstrategies value */
numProcs; /* amsupport value */
bool amstorage; /* amstorage flag */
List *operators; /* OpClassMember list for operators */
List *procedures; /* OpClassMember list for support procs */
ListCell *l;
Relation rel;
HeapTuple tup;
Form_pg_am pg_am;
Datum values[Natts_pg_opclass];
bool nulls[Natts_pg_opclass];
AclResult aclresult;
NameData opcName;
int i;
ObjectAddress myself,
referenced;
cqContext *pcqCtx;
cqContext cqc;
/* 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 */
pcqCtx = caql_beginscan(
NULL,
cql("SELECT * FROM pg_am "
" WHERE amname = :1 ",
CStringGetDatum(stmt->amname)));
tup = caql_getnext(pcqCtx);
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);
numOperators = pg_am->amstrategies;
numProcs = pg_am->amsupport;
amstorage = pg_am->amstorage;
/* XXX Should we make any privilege check against the AM? */
caql_endscan(pcqCtx);
/*
* 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
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;
OpClassMember *member;
Assert(IsA(item, CreateOpClassItem));
switch (item->itemtype)
{
case OPCLASS_ITEM_OPERATOR:
if (item->number <= 0 || item->number > numOperators)
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("invalid operator number %d,"
" must be between 1 and %d",
item->number, numOperators)));
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 = (OpClassMember *) palloc0(sizeof(OpClassMember));
member->object = operOid;
member->number = item->number;
member->subtype = assignOperSubtype(amoid, typeoid, operOid);
member->recheck = item->recheck;
addClassMember(&operators, member, false);
break;
case OPCLASS_ITEM_FUNCTION:
if (item->number <= 0 || item->number > numProcs)
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("invalid procedure number %d,"
" must be between 1 and %d",
item->number, numProcs)));
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 = (OpClassMember *) palloc0(sizeof(OpClassMember));
member->object = funcOid;
member->number = item->number;
member->subtype = assignProcSubtype(amoid, typeoid, funcOid);
addClassMember(&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);
pcqCtx = caql_beginscan(
caql_addrel(cqclr(&cqc), rel),
cql("INSERT INTO pg_opclass",
NULL));
/*
* Make sure there is no existing opclass of this name (this is just to
* give a more friendly error message than "duplicate key").
*/
cqContext cqcx2;
if (caql_getcount(
caql_addrel(cqclr(&cqcx2), rel),
cql("SELECT COUNT(*) FROM pg_opclass "
" WHERE opcamid = :1 "
" AND opcname = :2 "
" AND opcnamespace = :3 ",
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)
{
cqContext *pcqCtx2;
cqContext cqc3;
pcqCtx2 = caql_beginscan(
caql_addrel(cqclr(&cqc3), rel),
cql("SELECT * FROM pg_opclass "
" WHERE opcamid = :1 ",
ObjectIdGetDatum(amoid)));
while (HeapTupleIsValid(tup = caql_getnext(pcqCtx2)))
{
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))));
}
caql_endscan(pcqCtx2);
}
/*
* Okay, let's create the pg_opclass entry.
*/
for (i = 0; i < Natts_pg_opclass; ++i)
{
nulls[i] = false;
values[i] = 0; /* redundant, but safe */
}
i = 0;
values[i++] = ObjectIdGetDatum(amoid); /* opcamid */
namestrcpy(&opcName, opcname);
values[i++] = NameGetDatum(&opcName); /* opcname */
values[i++] = ObjectIdGetDatum(namespaceoid); /* opcnamespace */
values[i++] = ObjectIdGetDatum(GetUserId()); /* opcowner */
values[i++] = ObjectIdGetDatum(typeoid); /* opcintype */
values[i++] = BoolGetDatum(stmt->isDefault); /* opcdefault */
values[i++] = ObjectIdGetDatum(storageoid); /* opckeytype */
tup = caql_form_tuple(pcqCtx, values, nulls);
if (stmt->opclassOid !=0)
HeapTupleSetOid(tup, stmt->opclassOid);
opclassoid = caql_insert(pcqCtx, tup);
/* and Update indexes (implicit) */
stmt->opclassOid = opclassoid;
heap_freetuple(tup);
/*
* Now add tuples to pg_amop and pg_amproc tying in the operators and
* functions.
*/
storeOperators(opclassoid, operators);
storeProcedures(opclassoid, procedures);
/*
* Create dependencies. 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 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);
}
/* dependencies on operators */
foreach(l, operators)
{
OpClassMember *op = (OpClassMember *) lfirst(l);
referenced.classId = OperatorRelationId;
referenced.objectId = op->object;
referenced.objectSubId = 0;
recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
}
/* dependencies on procedures */
foreach(l, procedures)
{
OpClassMember *proc = (OpClassMember *) lfirst(l);
referenced.classId = ProcedureRelationId;
referenced.objectId = proc->object;
referenced.objectSubId = 0;
recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
}
/* dependency on owner */
recordDependencyOnOwner(OperatorClassRelationId, opclassoid, GetUserId());
caql_endscan(pcqCtx);
heap_close(rel, RowExclusiveLock);
}
/*
* Determine the subtype to assign to an operator, and do any validity
* checking we can manage
*
* Currently this is done using hardwired rules; we don't let the user
* specify it directly.
*/
static Oid
assignOperSubtype(Oid amoid, Oid typeoid, Oid operOid)
{
Oid subtype;
Operator optup;
Form_pg_operator opform;
cqContext *pcqCtx;
/* Subtypes are currently only supported by btree and bitmap, others use 0 */
if (amoid != BTREE_AM_OID && amoid != BITMAP_AM_OID)
return InvalidOid;
pcqCtx = caql_beginscan(
NULL,
cql("SELECT * FROM pg_operator "
" WHERE oid = :1 ",
ObjectIdGetDatum(operOid)));
optup = caql_getnext(pcqCtx);
if (!HeapTupleIsValid(optup))
elog(ERROR, "cache lookup failed for operator %u", operOid);
opform = (Form_pg_operator) GETSTRUCT(optup);
/*
* btree operators must be binary ops returning boolean, and the left-side
* input type must match the operator class' input type.
*/
if (opform->oprkind != 'b')
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("btree operators must be binary")));
if (opform->oprresult != BOOLOID)
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("btree operators must return boolean")));
if (opform->oprleft != typeoid)
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("btree operators must have index type as left input")));
/*
* The subtype is "default" (0) if oprright matches the operator class,
* otherwise it is oprright.
*/
if (opform->oprright == typeoid)
subtype = InvalidOid;
else
subtype = opform->oprright;
caql_endscan(pcqCtx);
return subtype;
}
/*
* Determine the subtype to assign to a support procedure, and do any validity
* checking we can manage
*
* Currently this is done using hardwired rules; we don't let the user
* specify it directly.
*/
static Oid
assignProcSubtype(Oid amoid, Oid typeoid, Oid procOid)
{
Oid subtype;
HeapTuple proctup;
Form_pg_proc procform;
cqContext *pcqCtx;
/* Subtypes are currently only supported by btree and bitmap, others use 0 */
if (amoid != BTREE_AM_OID && amoid != BITMAP_AM_OID)
return InvalidOid;
pcqCtx = caql_beginscan(
NULL,
cql("SELECT * FROM pg_proc "
" WHERE oid = :1 ",
ObjectIdGetDatum(procOid)));
proctup = caql_getnext(pcqCtx);
if (!HeapTupleIsValid(proctup))
elog(ERROR, "cache lookup failed for function %u", procOid);
procform = (Form_pg_proc) GETSTRUCT(proctup);
/*
* btree support procs must be 2-arg procs returning int4, and the first
* input type must match the operator class' input type.
*/
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 (procform->proargtypes.values[0] != typeoid)
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("btree procedures must have index type as first input")));
/*
* The subtype is "default" (0) if second input type matches the operator
* class, otherwise it is the second input type.
*/
if (procform->proargtypes.values[1] == typeoid)
subtype = InvalidOid;
else
subtype = procform->proargtypes.values[1];
caql_endscan(pcqCtx);
return subtype;
}
/*
* Add a new class member to the appropriate list, after checking for
* duplicated strategy or proc number.
*/
static void
addClassMember(List **list, OpClassMember *member, bool isProc)
{
ListCell *l;
foreach(l, *list)
{
OpClassMember *old = (OpClassMember *) lfirst(l);
if (old->number == member->number &&
old->subtype == member->subtype)
{
if (isProc)
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("procedure number %d appears more than once",
member->number)));
else
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("operator number %d appears more than once",
member->number)));
}
}
*list = lappend(*list, member);
}
/*
* Dump the operators to pg_amop
*/
static void
storeOperators(Oid opclassoid, List *operators)
{
Datum values[Natts_pg_amop];
bool nulls[Natts_pg_amop];
HeapTuple tup;
ListCell *l;
int i;
cqContext *pcqCtx;
pcqCtx = caql_beginscan(
NULL,
cql("INSERT INTO pg_amop",
NULL));
foreach(l, operators)
{
OpClassMember *op = (OpClassMember *) lfirst(l);
for (i = 0; i < Natts_pg_amop; ++i)
{
nulls[i] = false;
values[i] = 0;
}
i = 0;
values[i++] = ObjectIdGetDatum(opclassoid); /* amopclaid */
values[i++] = ObjectIdGetDatum(op->subtype); /* amopsubtype */
values[i++] = Int16GetDatum(op->number); /* amopstrategy */
values[i++] = BoolGetDatum(op->recheck); /* amopreqcheck */
values[i++] = ObjectIdGetDatum(op->object); /* amopopr */
tup = caql_form_tuple(pcqCtx, values, nulls);
caql_insert(pcqCtx, tup); /* implicit update of index as well */
heap_freetuple(tup);
}
caql_endscan(pcqCtx);
}
/*
* Dump the procedures (support routines) to pg_amproc
*/
static void
storeProcedures(Oid opclassoid, List *procedures)
{
Datum values[Natts_pg_amproc];
bool nulls[Natts_pg_amproc];
HeapTuple tup;
ListCell *l;
int i;
cqContext *pcqCtx;
pcqCtx = caql_beginscan(
NULL,
cql("INSERT INTO pg_amproc",
NULL));
foreach(l, procedures)
{
OpClassMember *proc = (OpClassMember *) lfirst(l);
for (i = 0; i < Natts_pg_amproc; ++i)
{
nulls[i] = false;
values[i] = 0;
}
i = 0;
values[i++] = ObjectIdGetDatum(opclassoid); /* amopclaid */
values[i++] = ObjectIdGetDatum(proc->subtype); /* amprocsubtype */
values[i++] = Int16GetDatum(proc->number); /* amprocnum */
values[i++] = ObjectIdGetDatum(proc->object); /* amproc */
tup = caql_form_tuple(pcqCtx, values, nulls);
caql_insert(pcqCtx, tup); /* implicit update of index as well */
heap_freetuple(tup);
}
caql_endscan(pcqCtx);
}
/*
* RemoveOpClass
* Deletes an opclass.
*/
void
RemoveOpClass(RemoveOpClassStmt *stmt)
{
Oid amID,
opcID;
char *schemaname;
char *opcname;
ObjectAddress object;
HeapTuple tuple;
cqContext *pcqCtx;
/*
* Get the access method's OID.
*/
amID = caql_getoid(
NULL,
cql("SELECT oid FROM pg_am "
" WHERE amname = :1 ",
CStringGetDatum(stmt->amname)));
if (!OidIsValid(amID))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("access method \"%s\" does not exist",
stmt->amname)));
/*
* Look up the opclass.
*/
/* deconstruct the name list */
DeconstructQualifiedName(stmt->opclassname, &schemaname, &opcname);
if (schemaname)
{
/* Look in specific schema only */
Oid namespaceId;
namespaceId = LookupExplicitNamespace(schemaname, NSPDBOID_CURRENT);
pcqCtx = caql_beginscan(
NULL,
cql("SELECT * FROM pg_opclass "
" WHERE opcamid = :1 "
" AND opcname = :2 "
" AND opcnamespace = :3 ",
ObjectIdGetDatum(amID),
PointerGetDatum(opcname),
ObjectIdGetDatum(namespaceId)));
}
else
{
Oid opcID1;
/* Unqualified opclass name, so search the search path */
opcID1 = OpclassnameGetOpcid(amID, opcname);
if (!OidIsValid(opcID1))
{
if (!stmt->missing_ok)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("operator class \"%s\" does not exist for access method \"%s\"",
opcname, stmt->amname)));
else
ereport(NOTICE,
(errmsg("operator class \"%s\" does not exist for access method \"%s\"",
opcname, stmt->amname)));
return;
}
pcqCtx = caql_beginscan(
NULL,
cql("SELECT * FROM pg_opclass "
" WHERE oid = :1 ",
ObjectIdGetDatum(opcID1)));
}
tuple = caql_getnext(pcqCtx);
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));
caql_endscan(pcqCtx);
/*
* Do the deletion
*/
object.classId = OperatorClassRelationId;
object.objectId = opcID;
object.objectSubId = 0;
performDeletion(&object, stmt->behavior);
}
/*
* Guts of opclass deletion.
*/
void
RemoveOpClassById(Oid opclassOid)
{
int numDel;
/*
* First remove the pg_opclass entry itself.
*/
numDel = caql_getcount(
NULL,
cql("DELETE FROM pg_opclass "
" WHERE oid = :1 ",
ObjectIdGetDatum(opclassOid)));
/*
* Remove associated entries in pg_amop.
*/
numDel = caql_getcount(
NULL,
cql("DELETE FROM pg_amop "
" WHERE amopclaid = :1 ",
ObjectIdGetDatum(opclassOid)));
/*
* Remove associated entries in pg_amproc.
*/
numDel = caql_getcount(
NULL,
cql("DELETE FROM pg_amproc "
" WHERE amopclaid = :1 ",
ObjectIdGetDatum(opclassOid)));
}
/*
* 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;
cqContext cqc2;
cqContext cqc;
cqContext *pcqCtx;
amOid = caql_getoid(
NULL,
cql("SELECT oid FROM pg_am "
" WHERE amname = :1 ",
CStringGetDatum((char *) access_method)));
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);
pcqCtx = caql_addrel(cqclr(&cqc), rel);
if (schemaname)
{
namespaceOid = LookupExplicitNamespace(schemaname, NSPDBOID_CURRENT);
tup = caql_getfirst(
pcqCtx,
cql("SELECT * FROM pg_opclass "
" WHERE opcamid = :1 "
" AND opcname = :2 "
" AND opcnamespace = :3 "
" FOR UPDATE ",
ObjectIdGetDatum(amOid),
PointerGetDatum(opcname),
ObjectIdGetDatum(namespaceOid)));
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 = caql_getfirst(
pcqCtx,
cql("SELECT * FROM pg_opclass "
" WHERE oid = :1 "
" FOR UPDATE ",
ObjectIdGetDatum(opcOid)));
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 (caql_getcount(
caql_addrel(cqclr(&cqc2), rel),
cql("SELECT COUNT(*) FROM pg_opclass "
" WHERE opcamid = :1 "
" AND opcname = :2 "
" AND opcnamespace = :3 ",
ObjectIdGetDatum(amOid),
CStringGetDatum((char *) newname),
ObjectIdGetDatum(namespaceOid))))
{
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);
caql_update_current(pcqCtx, tup); /* implicit update of index as well */
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;
cqContext cqc;
rel = heap_open(OperatorClassRelationId, RowExclusiveLock);
tup = caql_getfirst(
caql_addrel(cqclr(&cqc), rel),
cql("SELECT * FROM pg_opclass "
" WHERE oid = :1 "
" FOR UPDATE ",
ObjectIdGetDatum(opcOid)));
if (!HeapTupleIsValid(tup)) /* shouldn't happen */
elog(ERROR, "cache lookup failed for opclass %u", opcOid);
AlterOpClassOwner_internal(&cqc, 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;
cqContext cqc;
cqContext *pcqCtx;
amOid = caql_getoid(
NULL,
cql("SELECT oid FROM pg_am "
" WHERE amname = :1 ",
CStringGetDatum((char *) access_method)));
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);
pcqCtx = caql_addrel(cqclr(&cqc), rel);
if (schemaname)
{
Oid namespaceOid;
namespaceOid = LookupExplicitNamespace(schemaname, NSPDBOID_CURRENT);
tup = caql_getfirst(
pcqCtx,
cql("SELECT * FROM pg_opclass "
" WHERE opcamid = :1 "
" AND opcname = :2 "
" AND opcnamespace = :3 "
" FOR UPDATE ",
ObjectIdGetDatum(amOid),
PointerGetDatum(opcname),
ObjectIdGetDatum(namespaceOid)));
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 = caql_getfirst(
pcqCtx,
cql("SELECT * FROM pg_opclass "
" WHERE oid = :1 "
" FOR UPDATE ",
ObjectIdGetDatum(opcOid)));
if (!HeapTupleIsValid(tup)) /* should not happen */
elog(ERROR, "cache lookup failed for opclass %u", opcOid);
}
AlterOpClassOwner_internal(pcqCtx, rel, tup, newOwnerId);
heap_freetuple(tup);
heap_close(rel, NoLock);
}
/*
* The zeroeth parameter is the caql context, with a single valid tuple.
* The first parameter is pg_opclass, opened and suitably locked. The second
* parameter is the tuple from pg_opclass we want to modify.
*/
static void
AlterOpClassOwner_internal(cqContext *pcqCtx,
Relation rel, HeapTuple tup, Oid newOwnerId)
{
Oid namespaceOid;
AclResult aclresult;
Form_pg_opclass opcForm;
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)
return;
/* 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;
caql_update_current(pcqCtx, tup); /* implicit update of index as well */
/* Update owner dependency reference */
changeDependencyOnOwner(OperatorClassRelationId, HeapTupleGetOid(tup),
newOwnerId);
}