postgresql/src/backend/commands/comment.c
Tom Lane f2d70d32eb Functions live in namespaces. Qualified function names work, eg
SELECT schema1.func2(...).  Aggregate names can be qualified at the
syntactic level, but the qualification is ignored for the moment.
2002-04-09 20:35:55 +00:00

795 lines
22 KiB
C

/*-------------------------------------------------------------------------
*
* comment.c
*
* PostgreSQL object comments utility code.
*
* Copyright (c) 1999-2001, PostgreSQL Global Development Group
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/commands/comment.c,v 1.39 2002/04/09 20:35:47 tgl Exp $
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "access/genam.h"
#include "access/heapam.h"
#include "catalog/catname.h"
#include "catalog/indexing.h"
#include "catalog/namespace.h"
#include "catalog/pg_database.h"
#include "catalog/pg_description.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_operator.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_trigger.h"
#include "catalog/pg_type.h"
#include "commands/comment.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "parser/parse_agg.h"
#include "parser/parse_func.h"
#include "parser/parse_type.h"
#include "parser/parse.h"
#include "rewrite/rewriteRemove.h"
#include "utils/acl.h"
#include "utils/builtins.h"
#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
#include "utils/syscache.h"
/*
* Static Function Prototypes --
*
* The following protoypes 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 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 *qualname, List *arguments, char *comment);
static void CommentTrigger(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 INDEX:
case SEQUENCE:
case TABLE:
case VIEW:
CommentRelation(stmt->objtype, stmt->objname, stmt->comment);
break;
case COLUMN:
CommentAttribute(stmt->objname, stmt->comment);
break;
case DATABASE:
CommentDatabase(stmt->objname, stmt->comment);
break;
case RULE:
CommentRule(stmt->objname, stmt->comment);
break;
case TYPE_P:
CommentType(stmt->objname, stmt->comment);
break;
case AGGREGATE:
CommentAggregate(stmt->objname, stmt->objargs, stmt->comment);
break;
case FUNCTION:
CommentProc(stmt->objname, stmt->objargs, stmt->comment);
break;
case OPERATOR:
CommentOperator(stmt->objname, stmt->objargs, stmt->comment);
break;
case TRIGGER:
CommentTrigger(stmt->objname, stmt->comment);
break;
default:
elog(ERROR, "An attempt was made to comment on a unknown type: %d",
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;
Relation descriptionindex;
ScanKeyData skey[3];
IndexScanDesc sd;
RetrieveIndexResult indexRes;
HeapTupleData oldtuple;
Buffer buffer;
HeapTuple newtuple = NULL;
Datum values[Natts_pg_description];
char nulls[Natts_pg_description];
char replaces[Natts_pg_description];
int i;
/* 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] = ' ';
replaces[i] = 'r';
}
i = 0;
values[i++] = ObjectIdGetDatum(oid);
values[i++] = ObjectIdGetDatum(classoid);
values[i++] = Int32GetDatum(subid);
values[i++] = DirectFunctionCall1(textin, CStringGetDatum(comment));
}
/* Open pg_description and its index */
description = heap_openr(DescriptionRelationName, RowExclusiveLock);
descriptionindex = index_openr(DescriptionObjIndex);
/* Use the index to search for a matching old tuple */
ScanKeyEntryInitialize(&skey[0],
(bits16) 0x0,
(AttrNumber) 1,
(RegProcedure) F_OIDEQ,
ObjectIdGetDatum(oid));
ScanKeyEntryInitialize(&skey[1],
(bits16) 0x0,
(AttrNumber) 2,
(RegProcedure) F_OIDEQ,
ObjectIdGetDatum(classoid));
ScanKeyEntryInitialize(&skey[2],
(bits16) 0x0,
(AttrNumber) 3,
(RegProcedure) F_INT4EQ,
Int32GetDatum(subid));
sd = index_beginscan(descriptionindex, false, 3, skey);
oldtuple.t_datamcxt = CurrentMemoryContext;
oldtuple.t_data = NULL;
while ((indexRes = index_getnext(sd, ForwardScanDirection)))
{
oldtuple.t_self = indexRes->heap_iptr;
heap_fetch(description, SnapshotNow, &oldtuple, &buffer, sd);
pfree(indexRes);
if (oldtuple.t_data == NULL)
continue; /* time qual failed */
/* Found the old tuple, so delete or update it */
if (comment == NULL)
simple_heap_delete(description, &oldtuple.t_self);
else
{
newtuple = heap_modifytuple(&oldtuple, description, values,
nulls, replaces);
simple_heap_update(description, &oldtuple.t_self, newtuple);
}
ReleaseBuffer(buffer);
break; /* Assume there can be only one match */
}
index_endscan(sd);
/* If we didn't find an old tuple, insert a new one */
if (oldtuple.t_data == NULL && comment != NULL)
{
newtuple = heap_formtuple(RelationGetDescr(description),
values, nulls);
heap_insert(description, newtuple);
}
/* Update indexes, if necessary */
if (newtuple != NULL)
{
if (RelationGetForm(description)->relhasindex)
{
Relation idescs[Num_pg_description_indices];
CatalogOpenIndices(Num_pg_description_indices,
Name_pg_description_indices, idescs);
CatalogIndexInsert(idescs, Num_pg_description_indices, description,
newtuple);
CatalogCloseIndices(Num_pg_description_indices, idescs);
}
heap_freetuple(newtuple);
}
/* Done */
index_close(descriptionindex);
heap_close(description, NoLock);
}
/*
* DeleteComments --
*
* This routine is used to purge all comments associated with an object,
* regardless of their objsubid. It is called, for example, when a relation
* is destroyed.
*/
void
DeleteComments(Oid oid, Oid classoid)
{
Relation description;
Relation descriptionindex;
ScanKeyData skey[2];
IndexScanDesc sd;
RetrieveIndexResult indexRes;
HeapTupleData oldtuple;
Buffer buffer;
/* Open pg_description and its index */
description = heap_openr(DescriptionRelationName, RowExclusiveLock);
descriptionindex = index_openr(DescriptionObjIndex);
/* Use the index to search for all matching old tuples */
ScanKeyEntryInitialize(&skey[0],
(bits16) 0x0,
(AttrNumber) 1,
(RegProcedure) F_OIDEQ,
ObjectIdGetDatum(oid));
ScanKeyEntryInitialize(&skey[1],
(bits16) 0x0,
(AttrNumber) 2,
(RegProcedure) F_OIDEQ,
ObjectIdGetDatum(classoid));
sd = index_beginscan(descriptionindex, false, 2, skey);
while ((indexRes = index_getnext(sd, ForwardScanDirection)))
{
oldtuple.t_self = indexRes->heap_iptr;
heap_fetch(description, SnapshotNow, &oldtuple, &buffer, sd);
pfree(indexRes);
if (oldtuple.t_data == NULL)
continue; /* time qual failed */
simple_heap_delete(description, &oldtuple.t_self);
ReleaseBuffer(buffer);
}
/* Done */
index_endscan(sd);
index_close(descriptionindex);
heap_close(description, NoLock);
}
/*
* 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.)
*/
relation = relation_openrv(tgtrel, AccessShareLock);
/* Check object security */
if (!pg_class_ownercheck(RelationGetRelid(relation), GetUserId()))
elog(ERROR, "you are not permitted to comment on class '%s'",
RelationGetRelationName(relation));
/* Next, verify that the relation type matches the intent */
switch (objtype)
{
case INDEX:
if (relation->rd_rel->relkind != RELKIND_INDEX)
elog(ERROR, "relation '%s' is not an index",
RelationGetRelationName(relation));
break;
case TABLE:
if (relation->rd_rel->relkind != RELKIND_RELATION)
elog(ERROR, "relation '%s' is not a table",
RelationGetRelationName(relation));
break;
case VIEW:
if (relation->rd_rel->relkind != RELKIND_VIEW)
elog(ERROR, "relation '%s' is not a view",
RelationGetRelationName(relation));
break;
case SEQUENCE:
if (relation->rd_rel->relkind != RELKIND_SEQUENCE)
elog(ERROR, "relation '%s' is not a sequence",
RelationGetRelationName(relation));
break;
}
/* Create the comment using the relation's oid */
CreateComments(RelationGetRelid(relation), RelOid_pg_class, 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
* elog() exception is thrown. The parameters are the relation
* and attribute names, and the comments
*/
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 = length(qualname);
if (nnames < 2)
elog(ERROR, "CommentAttribute: must specify relation.attribute");
relname = ltruncate(nnames-1, listCopy(qualname));
attrname = strVal(nth(nnames-1, qualname));
/* Open the containing 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()))
elog(ERROR, "you are not permitted to comment on class '%s'",
RelationGetRelationName(relation));
/* Now, fetch the attribute number from the system cache */
attnum = get_attnum(RelationGetRelid(relation), attrname);
if (attnum == InvalidAttrNumber)
elog(ERROR, "'%s' is not an attribute of class '%s'",
attrname, RelationGetRelationName(relation));
/* Create the comment using the relation's oid */
CreateComments(RelationGetRelid(relation), RelOid_pg_class,
(int32) attnum, comment);
/* Done, but hold lock until commit */
heap_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 succesful, will then
* attempt to find the oid of the database specified. Once found,
* a comment is added/dropped using the CreateComments() routine.
*/
static void
CommentDatabase(List *qualname, char *comment)
{
char *database;
Relation pg_database;
ScanKeyData entry;
HeapScanDesc scan;
HeapTuple dbtuple;
Oid oid;
if (length(qualname) != 1)
elog(ERROR, "CommentDatabase: database name may not be qualified");
database = strVal(lfirst(qualname));
/* First find the tuple in pg_database for the database */
pg_database = heap_openr(DatabaseRelationName, AccessShareLock);
ScanKeyEntryInitialize(&entry, 0, Anum_pg_database_datname,
F_NAMEEQ, CStringGetDatum(database));
scan = heap_beginscan(pg_database, 0, SnapshotNow, 1, &entry);
dbtuple = heap_getnext(scan, 0);
/* Validate database exists, and fetch the db oid */
if (!HeapTupleIsValid(dbtuple))
elog(ERROR, "database '%s' does not exist", database);
oid = dbtuple->t_data->t_oid;
/* Allow if the user matches the database dba or is a superuser */
if (!(superuser() || is_dbadmin(oid)))
elog(ERROR, "you are not permitted to comment on database '%s'",
database);
/* Create the comments with the pg_database oid */
CreateComments(oid, RelOid_pg_database, 0, comment);
/* Complete the scan and close any opened relations */
heap_endscan(scan);
heap_close(pg_database, AccessShareLock);
}
/*
* CommentRule --
*
* This routine is used to add/drop any user-comments a user might
* have regarding a specified RULE. The rule is specified by name
* and, if found, and the user has appropriate permissions, a
* comment will be added/dropped using the CreateComments() routine.
*/
static void
CommentRule(List *qualname, char *comment)
{
char *rule;
HeapTuple tuple;
Oid reloid;
Oid ruleoid;
Oid classoid;
int32 aclcheck;
/* XXX this is gonna change soon */
if (length(qualname) != 1)
elog(ERROR, "CommentRule: rule name may not be qualified");
rule = strVal(lfirst(qualname));
/* Find the rule's pg_rewrite tuple, get its OID and its table's OID */
tuple = SearchSysCache(RULENAME,
PointerGetDatum(rule),
0, 0, 0);
if (!HeapTupleIsValid(tuple))
elog(ERROR, "rule '%s' does not exist", rule);
reloid = ((Form_pg_rewrite) GETSTRUCT(tuple))->ev_class;
ruleoid = tuple->t_data->t_oid;
ReleaseSysCache(tuple);
/* Check object security */
aclcheck = pg_class_aclcheck(reloid, GetUserId(), ACL_RULE);
if (aclcheck != ACLCHECK_OK)
elog(ERROR, "you are not permitted to comment on rule '%s'",
rule);
/* pg_rewrite doesn't have a hard-coded OID, so must look it up */
classoid = get_relname_relid(RewriteRelationName, PG_CATALOG_NAMESPACE);
Assert(OidIsValid(classoid));
/* Call CreateComments() to create/drop the comments */
CreateComments(ruleoid, classoid, 0, comment);
}
/*
* 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 paramters 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 = makeNode(TypeName);
tname->names = typename;
tname->typmod = -1;
/* Find the type's oid */
oid = typenameTypeId(tname);
/* Check object security */
if (!pg_type_ownercheck(oid, GetUserId()))
elog(ERROR, "you are not permitted to comment on type %s",
TypeNameToString(tname));
/* Call CreateComments() to create/drop the comments */
CreateComments(oid, RelOid_pg_type, 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, which, with the comments are
* the three parameters handed to this routine.
*/
static void
CommentAggregate(List *aggregate, List *arguments, char *comment)
{
TypeName *aggtype = (TypeName *) lfirst(arguments);
Oid baseoid,
oid;
Oid classoid;
/* First, attempt to determine the base aggregate oid */
if (aggtype)
baseoid = typenameTypeId(aggtype);
else
baseoid = InvalidOid;
/* Now, attempt to find the actual tuple in pg_aggregate */
oid = GetSysCacheOid(AGGNAME,
PointerGetDatum(strVal(lfirst(aggregate))), /* XXX */
ObjectIdGetDatum(baseoid),
0, 0);
if (!OidIsValid(oid))
agg_error("CommentAggregate", aggregate, baseoid);
/* Next, validate the user's attempt to comment */
if (!pg_aggr_ownercheck(oid, GetUserId()))
{
if (baseoid == InvalidOid)
elog(ERROR, "you are not permitted to comment on aggregate %s for all types",
NameListToString(aggregate));
else
elog(ERROR, "you are not permitted to comment on aggregate %s for type %s",
NameListToString(aggregate), format_type_be(baseoid));
}
/* pg_aggregate doesn't have a hard-coded OID, so must look it up */
classoid = get_relname_relid(AggregateRelationName, PG_CATALOG_NAMESPACE);
Assert(OidIsValid(classoid));
/* Call CreateComments() to create/drop the comments */
CreateComments(oid, classoid, 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,
true, "CommentProc");
/* Now, validate the user's ability to comment on this function */
if (!pg_proc_ownercheck(oid, GetUserId()))
elog(ERROR, "you are not permitted to comment on function %s",
NameListToString(function));
/* Call CreateComments() to create/drop the comments */
CreateComments(oid, RelOid_pg_proc, 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. If the comments string is empty, the associated comment
* is dropped.
*
* NOTE: we actually attach the comment to the procedure that underlies
* the operator. This is a feature, not a bug: we want the same comment
* to be visible for both operator and function.
*/
static void
CommentOperator(List *qualname, List *arguments, char *comment)
{
char *opername = strVal(lfirst(qualname)); /* XXX */
TypeName *typenode1 = (TypeName *) lfirst(arguments);
TypeName *typenode2 = (TypeName *) lsecond(arguments);
char oprtype = 0;
Form_pg_operator data;
HeapTuple optuple;
Oid oid,
leftoid = InvalidOid,
rightoid = InvalidOid;
/* Attempt to fetch the left type oid, if specified */
if (typenode1 != NULL)
leftoid = typenameTypeId(typenode1);
/* Attempt to fetch the right type oid, if specified */
if (typenode2 != NULL)
rightoid = typenameTypeId(typenode2);
/* Determine operator type */
if (OidIsValid(leftoid) && (OidIsValid(rightoid)))
oprtype = 'b';
else if (OidIsValid(leftoid))
oprtype = 'r';
else if (OidIsValid(rightoid))
oprtype = 'l';
else
elog(ERROR, "operator '%s' is of an illegal type'", opername);
/* Attempt to fetch the operator oid */
optuple = SearchSysCache(OPERNAME,
PointerGetDatum(opername),
ObjectIdGetDatum(leftoid),
ObjectIdGetDatum(rightoid),
CharGetDatum(oprtype));
if (!HeapTupleIsValid(optuple))
elog(ERROR, "operator '%s' does not exist", opername);
oid = optuple->t_data->t_oid;
/* Valid user's ability to comment on this operator */
if (!pg_oper_ownercheck(oid, GetUserId()))
elog(ERROR, "you are not permitted to comment on operator '%s'",
opername);
/* Get the procedure associated with the operator */
data = (Form_pg_operator) GETSTRUCT(optuple);
oid = data->oprcode;
if (oid == InvalidOid)
elog(ERROR, "operator '%s' does not have an underlying function", opername);
ReleaseSysCache(optuple);
/* Call CreateComments() to create/drop the comments */
CreateComments(oid, RelOid_pg_proc, 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;
HeapTuple triggertuple;
HeapScanDesc scan;
ScanKeyData entry[2];
Oid oid;
/* Separate relname and trig name */
nnames = length(qualname);
if (nnames < 2)
elog(ERROR, "CommentTrigger: must specify relation and trigger");
relname = ltruncate(nnames-1, listCopy(qualname));
trigname = strVal(nth(nnames-1, 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()))
elog(ERROR, "you are not permitted to comment on trigger '%s' for relation '%s'",
trigname, RelationGetRelationName(relation));
/* Fetch the trigger oid from pg_trigger */
pg_trigger = heap_openr(TriggerRelationName, AccessShareLock);
ScanKeyEntryInitialize(&entry[0], 0x0, Anum_pg_trigger_tgrelid,
F_OIDEQ,
ObjectIdGetDatum(RelationGetRelid(relation)));
ScanKeyEntryInitialize(&entry[1], 0x0, Anum_pg_trigger_tgname,
F_NAMEEQ,
CStringGetDatum(trigname));
scan = heap_beginscan(pg_trigger, 0, SnapshotNow, 2, entry);
triggertuple = heap_getnext(scan, 0);
/* If no trigger exists for the relation specified, notify user */
if (!HeapTupleIsValid(triggertuple))
elog(ERROR, "trigger '%s' for relation '%s' does not exist",
trigname, RelationGetRelationName(relation));
oid = triggertuple->t_data->t_oid;
heap_endscan(scan);
/* Create the comments with the pg_trigger oid */
CreateComments(oid, RelationGetRelid(pg_trigger), 0, comment);
/* Done, but hold lock on relation */
heap_close(pg_trigger, AccessShareLock);
heap_close(relation, NoLock);
}