blob: 58f031da0a0919162fcfe7682aabc961a6c464f4 [file] [log] [blame]
/*-------------------------------------------------------------------------
*
* comment.c
*
* PostgreSQL object comments utility code.
*
* Copyright (c) 1996-2008, PostgreSQL Global Development Group
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/commands/comment.c,v 1.93 2006/11/12 06:55:54 neilc Exp $
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "access/genam.h"
#include "access/heapam.h"
#include "catalog/catquery.h"
#include "catalog/indexing.h"
#include "catalog/pg_authid.h"
#include "catalog/pg_cast.h"
#include "catalog/pg_constraint.h"
#include "catalog/pg_conversion.h"
#include "catalog/pg_database.h"
#include "catalog/pg_description.h"
#include "catalog/pg_filespace.h"
#include "catalog/pg_language.h"
#include "catalog/pg_largeobject.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_opclass.h"
#include "catalog/pg_operator.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_resqueue.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_shdescription.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_trigger.h"
#include "catalog/pg_type.h"
#include "commands/comment.h"
#include "commands/dbcommands.h"
#include "commands/filespace.h"
#include "commands/tablespace.h"
#include "miscadmin.h"
#include "nodes/makefuncs.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/rel.h"
#include "utils/syscache.h"
#include "cdb/cdbvars.h"
/*
* Static Function Prototypes --
*
* The following prototypes are declared static so as not to conflict
* with any other routines outside this module. These routines are
* called by the public function CommentObject() routine to create
* the appropriate comment for the specific object type.
*/
static void CommentRelation(int objtype, List *relname, char *comment);
static void CommentAttribute(List *qualname, char *comment);
static void CommentDatabase(List *qualname, char *comment);
static void CommentNamespace(List *qualname, char *comment);
static void CommentRule(List *qualname, char *comment);
static void CommentType(List *typename, char *comment);
static void CommentAggregate(List *aggregate, List *arguments, char *comment);
static void CommentProc(List *function, List *arguments, char *comment);
static void CommentOperator(List *opername, List *arguments, char *comment);
static void CommentTrigger(List *qualname, char *comment);
static void CommentConstraint(List *qualname, char *comment);
static void CommentConversion(List *qualname, char *comment);
static void CommentLanguage(List *qualname, char *comment);
static void CommentOpClass(List *qualname, List *arguments, char *comment);
static void CommentLargeObject(List *qualname, char *comment);
static void CommentCast(List *qualname, List *arguments, char *comment);
static void CommentTablespace(List *qualname, char *comment);
static void CommentFilespace(List *qualname, char *comment);
static void CommentRole(List *qualname, char *comment);
static void CommentResourceQueue(List *qualname, char *comment);
/*
* CommentObject --
*
* This routine is used to add the associated comment into
* pg_description for the object specified by the given SQL command.
*/
void
CommentObject(CommentStmt *stmt)
{
switch (stmt->objtype)
{
case OBJECT_INDEX:
case OBJECT_SEQUENCE:
case OBJECT_TABLE:
case OBJECT_VIEW:
CommentRelation(stmt->objtype, stmt->objname, stmt->comment);
break;
case OBJECT_COLUMN:
CommentAttribute(stmt->objname, stmt->comment);
break;
case OBJECT_DATABASE:
CommentDatabase(stmt->objname, stmt->comment);
break;
case OBJECT_RULE:
CommentRule(stmt->objname, stmt->comment);
break;
case OBJECT_TYPE:
CommentType(stmt->objname, stmt->comment);
break;
case OBJECT_AGGREGATE:
CommentAggregate(stmt->objname, stmt->objargs, stmt->comment);
break;
case OBJECT_FUNCTION:
CommentProc(stmt->objname, stmt->objargs, stmt->comment);
break;
case OBJECT_OPERATOR:
CommentOperator(stmt->objname, stmt->objargs, stmt->comment);
break;
case OBJECT_TRIGGER:
CommentTrigger(stmt->objname, stmt->comment);
break;
case OBJECT_SCHEMA:
CommentNamespace(stmt->objname, stmt->comment);
break;
case OBJECT_CONSTRAINT:
CommentConstraint(stmt->objname, stmt->comment);
break;
case OBJECT_CONVERSION:
CommentConversion(stmt->objname, stmt->comment);
break;
case OBJECT_LANGUAGE:
CommentLanguage(stmt->objname, stmt->comment);
break;
case OBJECT_OPCLASS:
CommentOpClass(stmt->objname, stmt->objargs, stmt->comment);
break;
case OBJECT_LARGEOBJECT:
CommentLargeObject(stmt->objname, stmt->comment);
break;
case OBJECT_CAST:
CommentCast(stmt->objname, stmt->objargs, stmt->comment);
break;
case OBJECT_TABLESPACE:
CommentTablespace(stmt->objname, stmt->comment);
break;
case OBJECT_FILESPACE:
CommentFilespace(stmt->objname, stmt->comment);
break;
case OBJECT_ROLE:
CommentRole(stmt->objname, stmt->comment);
break;
case OBJECT_RESQUEUE:
CommentResourceQueue(stmt->objname, stmt->comment);
break;
default:
elog(ERROR, "unrecognized object type: %d",
(int) stmt->objtype);
}
}
/*
* CreateComments --
*
* Create a comment for the specified object descriptor. Inserts a new
* pg_description tuple, or replaces an existing one with the same key.
*
* If the comment given is null or an empty string, instead delete any
* existing comment for the specified key.
*/
void
CreateComments(Oid oid, Oid classoid, int32 subid, char *comment)
{
Relation description;
HeapTuple oldtuple;
HeapTuple newtuple = NULL;
Datum values[Natts_pg_description];
bool nulls[Natts_pg_description];
bool replaces[Natts_pg_description];
int i;
cqContext cqc;
cqContext *pcqCtx;
/* Reduce empty-string to NULL case */
if (comment != NULL && strlen(comment) == 0)
comment = NULL;
/* Prepare to form or update a tuple, if necessary */
if (comment != NULL)
{
for (i = 0; i < Natts_pg_description; i++)
{
nulls[i] = false;
replaces[i] = true;
}
i = 0;
values[i++] = ObjectIdGetDatum(oid);
values[i++] = ObjectIdGetDatum(classoid);
values[i++] = Int32GetDatum(subid);
values[i++] = CStringGetTextDatum(comment);
}
/* Use the index to search for a matching old tuple */
description = heap_open(DescriptionRelationId, RowExclusiveLock);
pcqCtx = caql_beginscan(
caql_addrel(cqclr(&cqc), description),
cql("SELECT * FROM pg_description"
" where objoid = :1 AND "
" classoid = :2 AND "
" objsubid = :3 FOR UPDATE",
ObjectIdGetDatum(oid),
ObjectIdGetDatum(classoid),
Int32GetDatum(subid)));
while (HeapTupleIsValid(oldtuple = caql_getnext(pcqCtx)))
{
/* Found the old tuple, so delete or update it */
if (comment == NULL)
caql_delete_current(pcqCtx);
else
{
newtuple = caql_modify_current(pcqCtx,
values, nulls, replaces);
caql_update_current(pcqCtx, newtuple);
/* update_current updates the index -- don't do it twice */
}
break; /* Assume there can be only one match */
}
caql_endscan(pcqCtx);
/* If we didn't find an old tuple, insert a new one */
if (newtuple == NULL && comment != NULL)
{
pcqCtx = caql_beginscan(
caql_addrel(cqclr(&cqc), description),
cql("INSERT INTO pg_description",
NULL));
newtuple = caql_form_tuple(pcqCtx, values, nulls);
caql_insert(pcqCtx, newtuple); /* implicit update of index as well */
heap_freetuple(newtuple);
caql_endscan(pcqCtx);
}
/* Done */
heap_close(description, NoLock);
}
/*
* CreateSharedComments --
*
* Create a comment for the specified shared object descriptor. Inserts a
* new pg_shdescription tuple, or replaces an existing one with the same key.
*
* If the comment given is null or an empty string, instead delete any
* existing comment for the specified key.
*/
void
CreateSharedComments(Oid oid, Oid classoid, char *comment)
{
Relation shdescription;
HeapTuple oldtuple;
HeapTuple newtuple = NULL;
Datum values[Natts_pg_shdescription];
bool nulls[Natts_pg_shdescription];
bool replaces[Natts_pg_shdescription];
int i;
cqContext cqc;
cqContext *pcqCtx;
/* Reduce empty-string to NULL case */
if (comment != NULL && strlen(comment) == 0)
comment = NULL;
/* Prepare to form or update a tuple, if necessary */
if (comment != NULL)
{
for (i = 0; i < Natts_pg_shdescription; i++)
{
nulls[i] = false;
replaces[i] = true;
}
i = 0;
values[i++] = ObjectIdGetDatum(oid);
values[i++] = ObjectIdGetDatum(classoid);
values[i++] = CStringGetTextDatum(comment);
}
/* Use the index to search for a matching old tuple */
shdescription = heap_open(SharedDescriptionRelationId, RowExclusiveLock);
pcqCtx = caql_beginscan(
caql_addrel(cqclr(&cqc), shdescription),
cql("SELECT * FROM pg_shdescription"
" where objoid = :1 AND "
" classoid = :2 FOR UPDATE",
ObjectIdGetDatum(oid),
ObjectIdGetDatum(classoid)));
while (HeapTupleIsValid(oldtuple = caql_getnext(pcqCtx)))
{
/* Found the old tuple, so delete or update it */
if (comment == NULL)
caql_delete_current(pcqCtx);
else
{
newtuple = caql_modify_current(pcqCtx,
values, nulls, replaces);
caql_update_current(pcqCtx, newtuple);
/* update_current updates the index -- don't do it twice */
}
break; /* Assume there can be only one match */
}
caql_endscan(pcqCtx);
/* If we didn't find an old tuple, insert a new one */
if (newtuple == NULL && comment != NULL)
{
pcqCtx = caql_beginscan(
caql_addrel(cqclr(&cqc), shdescription),
cql("INSERT INTO pg_shdescription",
NULL));
newtuple = caql_form_tuple(pcqCtx, values, nulls);
caql_insert(pcqCtx, newtuple); /* implicit update of index as well */
heap_freetuple(newtuple);
caql_endscan(pcqCtx);
}
/* Done */
heap_close(shdescription, NoLock);
}
/*
* DeleteComments -- remove comments for an object
*
* If subid is nonzero then only comments matching it will be removed.
* If subid is zero, all comments matching the oid/classoid will be removed
* (this corresponds to deleting a whole object).
*/
void
DeleteComments(Oid oid, Oid classoid, int32 subid)
{
/* Use the index to search for all matching old tuples */
if (subid != 0)
{
int numDel;
numDel = caql_getcount(
NULL,
cql("DELETE FROM pg_description"
" where objoid = :1 AND "
" classoid = :2 AND "
" objsubid = :3",
ObjectIdGetDatum(oid),
ObjectIdGetDatum(classoid),
Int32GetDatum(subid)));
}
else
{
int numDel;
numDel = caql_getcount(
NULL,
cql("DELETE FROM pg_description"
" where objoid = :1 AND "
" classoid = :2",
ObjectIdGetDatum(oid),
ObjectIdGetDatum(classoid)));
}
/* Done */
}
/*
* DeleteSharedComments -- remove comments for a shared object
*/
void
DeleteSharedComments(Oid oid, Oid classoid)
{
int numDel;
/* Use the index to search for all matching old tuples */
numDel = caql_getcount(
NULL,
cql("DELETE FROM pg_shdescription"
" where objoid = :1 AND "
" classoid = :2",
ObjectIdGetDatum(oid),
ObjectIdGetDatum(classoid)));
/* Done */
}
/*
* CommentRelation --
*
* This routine is used to add/drop a comment from a relation, where
* a relation is a TABLE, SEQUENCE, VIEW or INDEX. The routine simply
* finds the relation name by searching the system cache, locating
* the appropriate tuple, and inserting a comment using that
* tuple's oid. Its parameters are the relation name and comments.
*/
static void
CommentRelation(int objtype, List *relname, char *comment)
{
Relation relation;
RangeVar *tgtrel;
tgtrel = makeRangeVarFromNameList(relname);
/*
* Open the relation. We do this mainly to acquire a lock that ensures no
* one else drops the relation before we commit. (If they did, they'd
* fail to remove the entry we are about to make in pg_description.)
*/
/* make sure this is not an hcatalog table */
#ifdef USE_ASSERT_CHECKING
Oid relid =
#endif // USE_ASSERT_CHECKING
RangeVarGetRelid(tgtrel, false /*failOk*/, false /*allowHcatalog*/);
Assert(OidIsValid(relid));
relation = relation_openrv(tgtrel, AccessShareLock);
/* Check object security */
if (!pg_class_ownercheck(RelationGetRelid(relation), GetUserId()))
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS,
RelationGetRelationName(relation));
/* Next, verify that the relation type matches the intent */
switch (objtype)
{
case OBJECT_INDEX:
if (relation->rd_rel->relkind != RELKIND_INDEX)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not an index",
RelationGetRelationName(relation))));
break;
case OBJECT_SEQUENCE:
if (relation->rd_rel->relkind != RELKIND_SEQUENCE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a sequence",
RelationGetRelationName(relation))));
break;
case OBJECT_TABLE:
if (relation->rd_rel->relkind != RELKIND_RELATION)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table",
RelationGetRelationName(relation))));
break;
case OBJECT_VIEW:
if (relation->rd_rel->relkind != RELKIND_VIEW)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a view",
RelationGetRelationName(relation))));
break;
}
/* Create the comment using the relation's oid */
CreateComments(RelationGetRelid(relation), RelationRelationId,
0, comment);
/* Done, but hold lock until commit */
relation_close(relation, NoLock);
}
/*
* CommentAttribute --
*
* This routine is used to add/drop a comment from an attribute
* such as a table's column. The routine will check security
* restrictions and then attempt to look up the specified
* attribute. If successful, a comment is added/dropped, else an
* ereport() exception is thrown. The parameters are the relation
* and attribute names, and the comment
*/
static void
CommentAttribute(List *qualname, char *comment)
{
int nnames;
List *relname;
char *attrname;
RangeVar *rel;
Relation relation;
AttrNumber attnum;
/* Separate relname and attr name */
nnames = list_length(qualname);
if (nnames < 2) /* parser messed up */
elog(ERROR, "must specify relation and attribute");
relname = list_truncate(list_copy(qualname), nnames - 1);
attrname = strVal(lfirst(list_tail(qualname)));
/* Open the containing relation to ensure it won't go away meanwhile */
rel = makeRangeVarFromNameList(relname);
relation = relation_openrv(rel, AccessShareLock);
/* Check object security */
if (!pg_class_ownercheck(RelationGetRelid(relation), GetUserId()))
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS,
RelationGetRelationName(relation));
/* Now, fetch the attribute number from the system cache */
attnum = get_attnum(RelationGetRelid(relation), attrname);
if (attnum == InvalidAttrNumber)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("column \"%s\" of relation \"%s\" does not exist",
attrname, RelationGetRelationName(relation))));
/* Create the comment using the relation's oid */
CreateComments(RelationGetRelid(relation), RelationRelationId,
(int32) attnum, comment);
/* Done, but hold lock until commit */
relation_close(relation, NoLock);
}
/*
* CommentDatabase --
*
* This routine is used to add/drop any user-comments a user might
* have regarding the specified database. The routine will check
* security for owner permissions, and, if successful, will then
* attempt to find the oid of the database specified. Once found,
* a comment is added/dropped using the CreateSharedComments() routine.
*/
static void
CommentDatabase(List *qualname, char *comment)
{
char *database;
Oid oid;
if (list_length(qualname) != 1)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("database name may not be qualified")));
database = strVal(linitial(qualname));
/*
* When loading a dump, we may see a COMMENT ON DATABASE for the old name
* of the database. Erroring out would prevent pg_restore from completing
* (which is really pg_restore's fault, but for now we will work around
* the problem here). Consensus is that the best fix is to treat wrong
* database name as a WARNING not an ERROR.
*/
/* First get the database OID */
oid = get_database_oid(database);
if (!OidIsValid(oid))
{
ereport(WARNING,
(errcode(ERRCODE_UNDEFINED_DATABASE),
errmsg("database \"%s\" does not exist", database)));
return;
}
/* Check object security */
if (!pg_database_ownercheck(oid, GetUserId()))
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_DATABASE,
database);
/* Call CreateSharedComments() to create/drop the comments */
CreateSharedComments(oid, DatabaseRelationId, comment);
}
/*
* CommentTablespace --
*
* This routine is used to add/drop any user-comments a user might
* have regarding a tablespace. The tablepace is specified by name
* and, if found, and the user has appropriate permissions, a
* comment will be added/dropped using the CreateSharedComments() routine.
*
*/
static void
CommentTablespace(List *qualname, char *comment)
{
char *tablespace;
Oid oid;
if (list_length(qualname) != 1)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("tablespace name may not be qualified")));
tablespace = strVal(linitial(qualname));
oid = get_tablespace_oid(tablespace);
if (!OidIsValid(oid))
{
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("tablespace \"%s\" does not exist", tablespace)));
return;
}
/* Check object security */
if (!pg_tablespace_ownercheck(oid, GetUserId()))
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TABLESPACE, tablespace);
/* Call CreateSharedComments() to create/drop the comments */
CreateSharedComments(oid, TableSpaceRelationId, comment);
}
/*
* CommentFilespace --
*
* This routine is used to add/drop any user-comments a user might
* have regarding a filespace. The filespace is specified by name
* and, if found, and the user has appropriate permissions, a
* comment will be added/dropped using the CreateSharedComments() routine.
*
*/
static void
CommentFilespace(List *qualname, char *comment)
{
Relation rel;
char *filespace;
Oid oid;
if (list_length(qualname) != 1)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("filespace name may not be qualified")));
filespace = strVal(linitial(qualname));
rel = heap_open(FileSpaceRelationId, RowShareLock);
oid = get_filespace_oid(rel, filespace);
heap_close(rel, RowShareLock);
if (!OidIsValid(oid))
{
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("filespace \"%s\" does not exist", filespace)));
return;
}
/* Check object security */
if (!pg_filespace_ownercheck(oid, GetUserId()))
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_FILESPACE, filespace);
/* Call CreateSharedComments() to create/drop the comments */
CreateSharedComments(oid, FileSpaceRelationId, comment);
}
/*
* CommentRole --
*
* This routine is used to add/drop any user-comments a user might
* have regarding a role. The role is specified by name
* and, if found, and the user has appropriate permissions, a
* comment will be added/dropped using the CreateSharedComments() routine.
*/
static void
CommentRole(List *qualname, char *comment)
{
char *role;
Oid oid;
if (list_length(qualname) != 1)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("role name may not be qualified")));
role = strVal(linitial(qualname));
oid = get_roleid_checked(role);
/* Check object security */
if (!has_privs_of_role(GetUserId(), oid))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("must be member of role \"%s\" to comment upon it", role)));
/* Call CreateSharedComments() to create/drop the comments */
CreateSharedComments(oid, AuthIdRelationId, comment);
}
/*
* CommentNamespace --
*
* This routine is used to add/drop any user-comments a user might
* have regarding the specified namespace. The routine will check
* security for owner permissions, and, if successful, will then
* attempt to find the oid of the namespace specified. Once found,
* a comment is added/dropped using the CreateComments() routine.
*/
static void
CommentNamespace(List *qualname, char *comment)
{
Oid oid;
char *namespace;
if (list_length(qualname) != 1)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("schema name may not be qualified")));
namespace = strVal(linitial(qualname));
oid = LookupInternalNamespaceId(namespace);
if (!OidIsValid(oid))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_SCHEMA),
errmsg("schema \"%s\" does not exist", namespace)));
/* Check object security */
if (!pg_namespace_ownercheck(oid, GetUserId()))
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_NAMESPACE,
namespace);
/* Call CreateComments() to create/drop the comments */
CreateComments(oid, NamespaceRelationId, 0, comment);
}
/*
* CommentRule --
*
* This routine is used to add/drop any user-comments a user might
* have regarding a specified RULE. The rule for commenting is determined by
* both its name and the relation to which it refers. The arguments to this
* function are the rule name and relation name (merged into a qualified
* name), and the comment to add/drop.
*
* Before PG 7.3, rules had unique names across the whole database, and so
* the syntax was just COMMENT ON RULE rulename, with no relation name.
* For purposes of backwards compatibility, we support that as long as there
* is only one rule by the specified name in the database.
*/
static void
CommentRule(List *qualname, char *comment)
{
int nnames;
List *relname;
char *rulename;
RangeVar *rel;
Relation relation;
HeapTuple tuple;
Oid reloid;
Oid ruleoid;
/* Separate relname and trig name */
nnames = list_length(qualname);
if (nnames == 1)
{
/* Old-style: only a rule name is given */
bool bOnly;
rulename = strVal(linitial(qualname));
/* Search pg_rewrite for such a rule */
tuple = caql_getfirst_only(
NULL,
&bOnly,
cql("SELECT * FROM pg_rewrite "
" WHERE rulename = :1 ",
PointerGetDatum(rulename)));
if (HeapTupleIsValid(tuple))
{
reloid = ((Form_pg_rewrite) GETSTRUCT(tuple))->ev_class;
ruleoid = HeapTupleGetOid(tuple);
}
else
{
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("rule \"%s\" does not exist", rulename)));
reloid = ruleoid = 0; /* keep compiler quiet */
}
if (!bOnly)
{
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_OBJECT),
errmsg("there are multiple rules named \"%s\"", rulename),
errhint("Specify a relation name as well as a rule name.")));
}
/* heap_endscan(scanDesc); */
/* Open the owning relation to ensure it won't go away meanwhile */
relation = heap_open(reloid, AccessShareLock);
}
else
{
cqContext *pcqCtx;
/* New-style: rule and relname both provided */
Assert(nnames >= 2);
relname = list_truncate(list_copy(qualname), nnames - 1);
rulename = strVal(lfirst(list_tail(qualname)));
/* Open the owning relation to ensure it won't go away meanwhile */
rel = makeRangeVarFromNameList(relname);
relation = heap_openrv(rel, AccessShareLock);
reloid = RelationGetRelid(relation);
/* Find the rule's pg_rewrite tuple, get its OID */
pcqCtx = caql_beginscan(
NULL,
cql("SELECT * FROM pg_rewrite "
" WHERE ev_class = :1 "
" AND rulename = :2 ",
ObjectIdGetDatum(reloid),
PointerGetDatum(rulename)));
tuple = caql_getnext(pcqCtx);
if (!HeapTupleIsValid(tuple))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("rule \"%s\" for relation \"%s\" does not exist",
rulename, RelationGetRelationName(relation))));
Assert(reloid == ((Form_pg_rewrite) GETSTRUCT(tuple))->ev_class);
ruleoid = HeapTupleGetOid(tuple);
caql_endscan(pcqCtx);
}
/* Check object security */
if (!pg_class_ownercheck(reloid, GetUserId()))
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS,
get_rel_name(reloid));
/* Call CreateComments() to create/drop the comments */
CreateComments(ruleoid, RewriteRelationId, 0, comment);
heap_close(relation, NoLock);
}
/*
* CommentType --
*
* This routine is used to add/drop any user-comments a user might
* have regarding a TYPE. The type is specified by name
* and, if found, and the user has appropriate permissions, a
* comment will be added/dropped using the CreateComments() routine.
* The type's name and the comments are the parameters to this routine.
*/
static void
CommentType(List *typename, char *comment)
{
TypeName *tname;
Oid oid;
/* XXX a bit of a crock; should accept TypeName in COMMENT syntax */
tname = makeTypeNameFromNameList(typename);
/* Find the type's oid */
oid = typenameTypeId(NULL, tname);
/* Check object security */
if (!pg_type_ownercheck(oid, GetUserId()))
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TYPE,
TypeNameToString(tname));
/* Call CreateComments() to create/drop the comments */
CreateComments(oid, TypeRelationId, 0, comment);
}
/*
* CommentAggregate --
*
* This routine is used to allow a user to provide comments on an
* aggregate function. The aggregate function is determined by both
* its name and its argument type(s).
*/
static void
CommentAggregate(List *aggregate, List *arguments, char *comment)
{
Oid oid;
/* Look up function and make sure it's an aggregate */
oid = LookupAggNameTypeNames(aggregate, arguments, false);
/* Next, validate the user's attempt to comment */
if (!pg_proc_ownercheck(oid, GetUserId()))
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_PROC,
NameListToString(aggregate));
/* Call CreateComments() to create/drop the comments */
CreateComments(oid, ProcedureRelationId, 0, comment);
}
/*
* CommentProc --
*
* This routine is used to allow a user to provide comments on an
* procedure (function). The procedure is determined by both
* its name and its argument list. The argument list is expected to
* be a series of parsed nodes pointed to by a List object. If the
* comments string is empty, the associated comment is dropped.
*/
static void
CommentProc(List *function, List *arguments, char *comment)
{
Oid oid;
/* Look up the procedure */
oid = LookupFuncNameTypeNames(function, arguments, false);
/* Now, validate the user's ability to comment on this function */
if (!pg_proc_ownercheck(oid, GetUserId()))
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_PROC,
NameListToString(function));
/* Call CreateComments() to create/drop the comments */
CreateComments(oid, ProcedureRelationId, 0, comment);
}
/*
* CommentOperator --
*
* This routine is used to allow a user to provide comments on an
* operator. The operator for commenting is determined by both
* its name and its argument list which defines the left and right
* hand types the operator will operate on. The argument list is
* expected to be a couple of parse nodes pointed to be a List
* object.
*/
static void
CommentOperator(List *opername, List *arguments, char *comment)
{
TypeName *typenode1 = (TypeName *) linitial(arguments);
TypeName *typenode2 = (TypeName *) lsecond(arguments);
Oid oid;
/* Look up the operator */
oid = LookupOperNameTypeNames(NULL, opername,
typenode1, typenode2,
false, -1);
/* Check user's privilege to comment on this operator */
if (!pg_oper_ownercheck(oid, GetUserId()))
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_OPER,
NameListToString(opername));
/* Call CreateComments() to create/drop the comments */
CreateComments(oid, OperatorRelationId, 0, comment);
}
/*
* CommentTrigger --
*
* This routine is used to allow a user to provide comments on a
* trigger event. The trigger for commenting is determined by both
* its name and the relation to which it refers. The arguments to this
* function are the trigger name and relation name (merged into a qualified
* name), and the comment to add/drop.
*/
static void
CommentTrigger(List *qualname, char *comment)
{
int nnames;
List *relname;
char *trigname;
RangeVar *rel;
Relation pg_trigger,
relation;
cqContext cqc;
Oid oid;
int fetchCount = 0;
/* Separate relname and trig name */
nnames = list_length(qualname);
if (nnames < 2) /* parser messed up */
elog(ERROR, "must specify relation and trigger");
relname = list_truncate(list_copy(qualname), nnames - 1);
trigname = strVal(lfirst(list_tail(qualname)));
/* Open the owning relation to ensure it won't go away meanwhile */
rel = makeRangeVarFromNameList(relname);
relation = heap_openrv(rel, AccessShareLock);
/* Check object security */
if (!pg_class_ownercheck(RelationGetRelid(relation), GetUserId()))
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS,
RelationGetRelationName(relation));
/*
* Fetch the trigger tuple from pg_trigger. There can be only one because
* of the unique index.
*/
pg_trigger = heap_open(TriggerRelationId, AccessShareLock);
oid = caql_getoid_plus(
caql_addrel(cqclr(&cqc), pg_trigger),
&fetchCount,
NULL,
cql("SELECT oid FROM pg_trigger "
" WHERE tgrelid = :1 "
" AND tgname = :2 ",
ObjectIdGetDatum(RelationGetRelid(relation)),
CStringGetDatum(trigname)));
/* If no trigger exists for the relation specified, notify user */
if (0 == fetchCount)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("trigger \"%s\" for table \"%s\" does not exist",
trigname, RelationGetRelationName(relation))));
/* Call CreateComments() to create/drop the comments */
CreateComments(oid, TriggerRelationId, 0, comment);
/* Done, but hold lock on relation */
heap_close(pg_trigger, AccessShareLock);
heap_close(relation, NoLock);
}
/*
* CommentConstraint --
*
* Enable commenting on constraints held within the pg_constraint
* table. A qualified name is required as constraint names are
* unique per relation.
*/
static void
CommentConstraint(List *qualname, char *comment)
{
int nnames;
List *relName;
char *conName;
RangeVar *rel;
Relation pg_constraint,
relation;
HeapTuple tuple;
cqContext cqc;
cqContext *pcqCtx;
Oid conOid = InvalidOid;
/* Separate relname and constraint name */
nnames = list_length(qualname);
if (nnames < 2) /* parser messed up */
elog(ERROR, "must specify relation and constraint");
relName = list_truncate(list_copy(qualname), nnames - 1);
conName = strVal(lfirst(list_tail(qualname)));
/* Open the owning relation to ensure it won't go away meanwhile */
rel = makeRangeVarFromNameList(relName);
relation = heap_openrv(rel, AccessShareLock);
/* Check object security */
if (!pg_class_ownercheck(RelationGetRelid(relation), GetUserId()))
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS,
RelationGetRelationName(relation));
/*
* Fetch the constraint tuple from pg_constraint. There may be more than
* one match, because constraints are not required to have unique names;
* if so, error out.
*/
pg_constraint = heap_open(ConstraintRelationId, AccessShareLock);
pcqCtx = caql_beginscan(
caql_addrel(cqclr(&cqc), pg_constraint),
cql("SELECT * FROM pg_constraint "
" WHERE conrelid = :1 ",
ObjectIdGetDatum(RelationGetRelid(relation))));
while (HeapTupleIsValid(tuple = caql_getnext(pcqCtx)))
{
Form_pg_constraint con = (Form_pg_constraint) GETSTRUCT(tuple);
if (strcmp(NameStr(con->conname), conName) == 0)
{
if (OidIsValid(conOid))
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_OBJECT),
errmsg("table \"%s\" has multiple constraints named \"%s\"",
RelationGetRelationName(relation), conName)));
conOid = HeapTupleGetOid(tuple);
}
}
caql_endscan(pcqCtx);
/* If no constraint exists for the relation specified, notify user */
if (!OidIsValid(conOid))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("constraint \"%s\" for table \"%s\" does not exist",
conName, RelationGetRelationName(relation))));
/* Call CreateComments() to create/drop the comments */
CreateComments(conOid, ConstraintRelationId, 0, comment);
/* Done, but hold lock on relation */
heap_close(pg_constraint, AccessShareLock);
heap_close(relation, NoLock);
}
/*
* CommentConversion --
*
* This routine is used to add/drop any user-comments a user might
* have regarding a CONVERSION. The conversion is specified by name
* and, if found, and the user has appropriate permissions, a
* comment will be added/dropped using the CreateComments() routine.
* The conversion's name and the comment are the parameters to this routine.
*/
static void
CommentConversion(List *qualname, char *comment)
{
Oid conversionOid;
conversionOid = FindConversionByName(qualname);
if (!OidIsValid(conversionOid))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("conversion \"%s\" does not exist",
NameListToString(qualname)),
errOmitLocation(true)));
/* Check object security */
if (!pg_conversion_ownercheck(conversionOid, GetUserId()))
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CONVERSION,
NameListToString(qualname));
/* Call CreateComments() to create/drop the comments */
CreateComments(conversionOid, ConversionRelationId, 0, comment);
}
/*
* CommentLanguage --
*
* This routine is used to add/drop any user-comments a user might
* have regarding a LANGUAGE. The language is specified by name
* and, if found, and the user has appropriate permissions, a
* comment will be added/dropped using the CreateComments() routine.
* The language's name and the comment are the parameters to this routine.
*/
static void
CommentLanguage(List *qualname, char *comment)
{
Oid oid;
char *language;
if (list_length(qualname) != 1)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("language name may not be qualified")));
language = strVal(linitial(qualname));
oid = caql_getoid(
NULL,
cql("SELECT oid FROM pg_language "
" WHERE lanname = :1 ",
CStringGetDatum(language)));
if (!OidIsValid(oid))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_SCHEMA),
errmsg("language \"%s\" does not exist", language)));
/* Check object security */
if (!superuser())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("must be superuser to comment on procedural language")));
/* Call CreateComments() to create/drop the comments */
CreateComments(oid, LanguageRelationId, 0, comment);
}
/*
* CommentOpClass --
*
* This routine is used to allow a user to provide comments on an
* operator class. The operator class for commenting is determined by both
* its name and its argument list which defines the index method
* the operator class is used for. The argument list is expected to contain
* a single name (represented as a string Value node).
*/
static void
CommentOpClass(List *qualname, List *arguments, char *comment)
{
char *amname;
char *schemaname;
char *opcname;
Oid amID;
Oid opcID;
int fetchCount = 0;
Assert(list_length(arguments) == 1);
amname = strVal(linitial(arguments));
/*
* Get the access method's OID.
*/
amID = caql_getoid(
NULL,
cql("SELECT oid FROM pg_am "
" WHERE amname = :1 ",
CStringGetDatum(amname)));
if (!OidIsValid(amID))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("access method \"%s\" does not exist",
amname)));
/*
* Look up the opclass.
*/
/* deconstruct the name list */
DeconstructQualifiedName(qualname, &schemaname, &opcname);
if (schemaname)
{
/* Look in specific schema only */
Oid namespaceId;
namespaceId = LookupExplicitNamespace(schemaname, NSPDBOID_CURRENT);
opcID = caql_getoid_plus(
NULL,
&fetchCount,
NULL,
cql("SELECT oid 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))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("operator class \"%s\" does not exist for access method \"%s\"",
opcname, amname)));
opcID = caql_getoid_plus(
NULL,
&fetchCount,
NULL,
cql("SELECT oid FROM pg_opclass "
" WHERE oid = :1 ",
ObjectIdGetDatum(opcID1)));
}
if (0 == fetchCount)
{
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("operator class \"%s\" does not exist for access method \"%s\"",
NameListToString(qualname), amname)));
}
/* Permission check: must own opclass */
if (!pg_opclass_ownercheck(opcID, GetUserId()))
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_OPCLASS,
NameListToString(qualname));
/* Call CreateComments() to create/drop the comments */
CreateComments(opcID, OperatorClassRelationId, 0, comment);
}
/*
* CommentLargeObject --
*
* This routine is used to add/drop any user-comments a user might
* have regarding a LARGE OBJECT. The large object is specified by OID
* and, if found, and the user has appropriate permissions, a
* comment will be added/dropped using the CreateComments() routine.
* The large object's OID and the comment are the parameters to this routine.
*/
static void
CommentLargeObject(List *qualname, char *comment)
{
Oid loid;
Node *node;
Assert(list_length(qualname) == 1);
node = (Node *) linitial(qualname);
switch (nodeTag(node))
{
case T_Integer:
loid = intVal(node);
break;
case T_Float:
/*
* Values too large for int4 will be represented as Float
* constants by the lexer. Accept these if they are valid OID
* strings.
*/
loid = DatumGetObjectId(DirectFunctionCall1(oidin,
CStringGetDatum(strVal(node))));
break;
default:
elog(ERROR, "unrecognized node type: %d",
(int) nodeTag(node));
/* keep compiler quiet */
loid = InvalidOid;
}
/* check that the large object exists */
if (!LargeObjectExists(loid))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("large object %u does not exist", loid)));
/* Call CreateComments() to create/drop the comments */
CreateComments(loid, LargeObjectRelationId, 0, comment);
}
/*
* CommentCast --
*
* This routine is used to add/drop any user-comments a user might
* have regarding a CAST. The cast is specified by source and destination types
* and, if found, and the user has appropriate permissions, a
* comment will be added/dropped using the CreateComments() routine.
* The cast's source type is passed as the "name", the destination type
* as the "arguments".
*/
static void
CommentCast(List *qualname, List *arguments, char *comment)
{
TypeName *sourcetype;
TypeName *targettype;
Oid sourcetypeid;
Oid targettypeid;
int fetchCount = 0;
Oid castOid;
Assert(list_length(qualname) == 1);
sourcetype = (TypeName *) linitial(qualname);
Assert(IsA(sourcetype, TypeName));
Assert(list_length(arguments) == 1);
targettype = (TypeName *) linitial(arguments);
Assert(IsA(targettype, TypeName));
sourcetypeid = typenameTypeId(NULL, sourcetype);
targettypeid = typenameTypeId(NULL, targettype);
castOid = caql_getoid_plus(
NULL,
&fetchCount,
NULL,
cql("SELECT * FROM pg_cast "
" WHERE castsource = :1 "
" AND casttarget = :2 ",
ObjectIdGetDatum(sourcetypeid),
ObjectIdGetDatum(targettypeid)));
if (0 == fetchCount)
{
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("cast from type %s to type %s does not exist",
TypeNameToString(sourcetype),
TypeNameToString(targettype))));
}
/* Permission check */
if (!pg_type_ownercheck(sourcetypeid, GetUserId())
&& !pg_type_ownercheck(targettypeid, GetUserId()))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("must be owner of type %s or type %s",
TypeNameToString(sourcetype),
TypeNameToString(targettype))));
/* Call CreateComments() to create/drop the comments */
CreateComments(castOid, CastRelationId, 0, comment);
}
/*
* CommentResourceQueue --
*
* This routine is used to add/drop any user-comments a user might
* have regarding a RESOURCE QUEUE. The resource queue is specified by name
* and, if found, and the user has appropriate permissions, a
* comment will be added/dropped using the CreateSharedComments() routine.
*
*/
static void
CommentResourceQueue(List *qualname, char *comment)
{
char *queueName;
Oid oid = InvalidOid;
int fetchCount = 0;
if (list_length(qualname) != 1)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("resource queue name may not be qualified")));
queueName = strVal(linitial(qualname));
oid = caql_getoid_plus(
NULL,
&fetchCount,
NULL,
cql("SELECT oid FROM pg_resqueue "
" WHERE rsqname = :1 ",
CStringGetDatum(queueName)));
if (0 == fetchCount)
{
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("resource queue \"%s\" does not exist", queueName)));
return;
}
/* Check object security */
if (!superuser())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("must be superuser to comment on resource queue")));
/* Call CreateSharedComments() to create/drop the comments */
CreateSharedComments(oid, ResQueueRelationId, comment);
}