1 /*-------------------------------------------------------------------------
5 * PostgreSQL object comments utility code.
7 * Copyright (c) 1999-2001, PostgreSQL Global Development Group
10 * $Header: /cvsroot/pgsql/src/backend/commands/comment.c,v 1.38 2002/03/29 19:06:04 tgl Exp $
12 *-------------------------------------------------------------------------
17 #include "access/genam.h"
18 #include "access/heapam.h"
19 #include "catalog/catname.h"
20 #include "catalog/indexing.h"
21 #include "catalog/pg_database.h"
22 #include "catalog/pg_description.h"
23 #include "catalog/pg_namespace.h"
24 #include "catalog/pg_operator.h"
25 #include "catalog/pg_rewrite.h"
26 #include "catalog/pg_trigger.h"
27 #include "catalog/pg_type.h"
28 #include "commands/comment.h"
29 #include "miscadmin.h"
30 #include "nodes/makefuncs.h"
31 #include "parser/parse_agg.h"
32 #include "parser/parse_func.h"
33 #include "parser/parse_type.h"
34 #include "parser/parse.h"
35 #include "rewrite/rewriteRemove.h"
36 #include "utils/acl.h"
37 #include "utils/builtins.h"
38 #include "utils/fmgroids.h"
39 #include "utils/lsyscache.h"
40 #include "utils/syscache.h"
43 /*------------------------------------------------------------------
44 * Static Function Prototypes --
46 * The following protoypes are declared static so as not to conflict
47 * with any other routines outside this module. These routines are
48 * called by the public function CommentObject() routine to create
49 * the appropriate comment for the specific object type.
50 *------------------------------------------------------------------
53 static void CommentRelation(int objtype, char * schemaname, char *relation,
55 static void CommentAttribute(char * schemaname, char *relation,
56 char *attrib, char *comment);
57 static void CommentDatabase(char *database, char *comment);
58 static void CommentRewrite(char *rule, char *comment);
59 static void CommentType(char *type, char *comment);
60 static void CommentAggregate(char *aggregate, List *arguments, char *comment);
61 static void CommentProc(char *function, List *arguments, char *comment);
62 static void CommentOperator(char *opname, List *arguments, char *comment);
63 static void CommentTrigger(char *trigger, char *schemaname, char *relation,
67 /*------------------------------------------------------------------
70 * This routine is used to add the associated comment into
71 * pg_description for the object specified by the paramters handed
72 * to this routine. If the routine cannot determine an Oid to
73 * associated with the parameters handed to this routine, an
74 * error is thrown. Otherwise the comment is added to pg_description
75 * by calling the CreateComments() routine. If the comment string is
76 * empty, CreateComments() will drop any comments associated with
78 *------------------------------------------------------------------
82 CommentObject(int objtype, char *schemaname, char *objname, char *objproperty,
83 List *objlist, char *comment)
91 CommentRelation(objtype, schemaname, objname, comment);
94 CommentAttribute(schemaname, objname, objproperty, comment);
97 CommentDatabase(objname, comment);
100 CommentRewrite(objname, comment);
103 CommentType(objname, comment);
106 CommentAggregate(objname, objlist, comment);
109 CommentProc(objname, objlist, comment);
112 CommentOperator(objname, objlist, comment);
115 CommentTrigger(objname, schemaname, objproperty, comment);
118 elog(ERROR, "An attempt was made to comment on a unknown type: %d",
123 /*------------------------------------------------------------------
126 * Create a comment for the specified object descriptor. Inserts a new
127 * pg_description tuple, or replaces an existing one with the same key.
129 * If the comment given is null or an empty string, instead delete any
130 * existing comment for the specified key.
131 *------------------------------------------------------------------
135 CreateComments(Oid oid, Oid classoid, int32 subid, char *comment)
137 Relation description;
138 Relation descriptionindex;
141 RetrieveIndexResult indexRes;
142 HeapTupleData oldtuple;
144 HeapTuple newtuple = NULL;
145 Datum values[Natts_pg_description];
146 char nulls[Natts_pg_description];
147 char replaces[Natts_pg_description];
150 /* Reduce empty-string to NULL case */
151 if (comment != NULL && strlen(comment) == 0)
154 /* Prepare to form or update a tuple, if necessary */
157 for (i = 0; i < Natts_pg_description; i++)
163 values[i++] = ObjectIdGetDatum(oid);
164 values[i++] = ObjectIdGetDatum(classoid);
165 values[i++] = Int32GetDatum(subid);
166 values[i++] = DirectFunctionCall1(textin, CStringGetDatum(comment));
169 /* Open pg_description and its index */
171 description = heap_openr(DescriptionRelationName, RowExclusiveLock);
172 descriptionindex = index_openr(DescriptionObjIndex);
174 /* Use the index to search for a matching old tuple */
176 ScanKeyEntryInitialize(&skey[0],
179 (RegProcedure) F_OIDEQ,
180 ObjectIdGetDatum(oid));
182 ScanKeyEntryInitialize(&skey[1],
185 (RegProcedure) F_OIDEQ,
186 ObjectIdGetDatum(classoid));
188 ScanKeyEntryInitialize(&skey[2],
191 (RegProcedure) F_INT4EQ,
192 Int32GetDatum(subid));
194 sd = index_beginscan(descriptionindex, false, 3, skey);
196 oldtuple.t_datamcxt = CurrentMemoryContext;
197 oldtuple.t_data = NULL;
199 while ((indexRes = index_getnext(sd, ForwardScanDirection)))
201 oldtuple.t_self = indexRes->heap_iptr;
202 heap_fetch(description, SnapshotNow, &oldtuple, &buffer, sd);
205 if (oldtuple.t_data == NULL)
206 continue; /* time qual failed */
208 /* Found the old tuple, so delete or update it */
211 simple_heap_delete(description, &oldtuple.t_self);
214 newtuple = heap_modifytuple(&oldtuple, description, values,
216 simple_heap_update(description, &oldtuple.t_self, newtuple);
219 ReleaseBuffer(buffer);
220 break; /* Assume there can be only one match */
225 /* If we didn't find an old tuple, insert a new one */
227 if (oldtuple.t_data == NULL && comment != NULL)
229 newtuple = heap_formtuple(RelationGetDescr(description),
231 heap_insert(description, newtuple);
234 /* Update indexes, if necessary */
236 if (newtuple != NULL)
238 if (RelationGetForm(description)->relhasindex)
240 Relation idescs[Num_pg_description_indices];
242 CatalogOpenIndices(Num_pg_description_indices,
243 Name_pg_description_indices, idescs);
244 CatalogIndexInsert(idescs, Num_pg_description_indices, description,
246 CatalogCloseIndices(Num_pg_description_indices, idescs);
248 heap_freetuple(newtuple);
253 index_close(descriptionindex);
254 heap_close(description, NoLock);
257 /*------------------------------------------------------------------
260 * This routine is used to purge all comments associated with an object,
261 * regardless of their objsubid. It is called, for example, when a relation
263 *------------------------------------------------------------------
267 DeleteComments(Oid oid, Oid classoid)
269 Relation description;
270 Relation descriptionindex;
273 RetrieveIndexResult indexRes;
274 HeapTupleData oldtuple;
277 /* Open pg_description and its index */
279 description = heap_openr(DescriptionRelationName, RowExclusiveLock);
280 descriptionindex = index_openr(DescriptionObjIndex);
282 /* Use the index to search for all matching old tuples */
284 ScanKeyEntryInitialize(&skey[0],
287 (RegProcedure) F_OIDEQ,
288 ObjectIdGetDatum(oid));
290 ScanKeyEntryInitialize(&skey[1],
293 (RegProcedure) F_OIDEQ,
294 ObjectIdGetDatum(classoid));
296 sd = index_beginscan(descriptionindex, false, 2, skey);
298 while ((indexRes = index_getnext(sd, ForwardScanDirection)))
300 oldtuple.t_self = indexRes->heap_iptr;
301 heap_fetch(description, SnapshotNow, &oldtuple, &buffer, sd);
304 if (oldtuple.t_data == NULL)
305 continue; /* time qual failed */
307 simple_heap_delete(description, &oldtuple.t_self);
309 ReleaseBuffer(buffer);
315 index_close(descriptionindex);
316 heap_close(description, NoLock);
319 /*------------------------------------------------------------------
322 * This routine is used to add/drop a comment from a relation, where
323 * a relation is a TABLE, SEQUENCE, VIEW or INDEX. The routine simply
324 * finds the relation name by searching the system cache, locating
325 * the appropriate tuple, and inserting a comment using that
326 * tuple's oid. Its parameters are the relation name and comments.
327 *------------------------------------------------------------------
331 CommentRelation(int reltype, char *schemaname, char *relname, char *comment)
334 RangeVar *tgtrel = makeNode(RangeVar);
337 tgtrel->relname = relname;
338 tgtrel->schemaname = schemaname;
339 /* FIXME SCHEMA: Can we add comments to temp relations? */
340 tgtrel->istemp = false;
343 * Open the relation. We do this mainly to acquire a lock that
344 * ensures no one else drops the relation before we commit. (If they
345 * did, they'd fail to remove the entry we are about to make in
348 relation = relation_openrv(tgtrel, AccessShareLock);
350 /* Check object security */
351 if (!pg_class_ownercheck(RelationGetRelid(relation), GetUserId()))
352 elog(ERROR, "you are not permitted to comment on class '%s'", relname);
354 /* Next, verify that the relation type matches the intent */
359 if (relation->rd_rel->relkind != RELKIND_INDEX)
360 elog(ERROR, "relation '%s' is not an index", relname);
363 if (relation->rd_rel->relkind != RELKIND_RELATION)
364 elog(ERROR, "relation '%s' is not a table", relname);
367 if (relation->rd_rel->relkind != RELKIND_VIEW)
368 elog(ERROR, "relation '%s' is not a view", relname);
371 if (relation->rd_rel->relkind != RELKIND_SEQUENCE)
372 elog(ERROR, "relation '%s' is not a sequence", relname);
376 /* Create the comment using the relation's oid */
378 CreateComments(RelationGetRelid(relation), RelOid_pg_class, 0, comment);
380 /* Done, but hold lock until commit */
381 relation_close(relation, NoLock);
384 /*------------------------------------------------------------------
385 * CommentAttribute --
387 * This routine is used to add/drop a comment from an attribute
388 * such as a table's column. The routine will check security
389 * restrictions and then attempt to look up the specified
390 * attribute. If successful, a comment is added/dropped, else an
391 * elog() exception is thrown. The parameters are the relation
392 * and attribute names, and the comments
393 *------------------------------------------------------------------
397 CommentAttribute(char *schemaname, char *relname, char *attrname, char *comment)
399 RangeVar *rel = makeNode(RangeVar);
403 /* Open the containing relation to ensure it won't go away meanwhile */
405 rel->relname = relname;
406 rel->schemaname = schemaname;
408 relation = heap_openrv(rel, AccessShareLock);
410 /* Check object security */
412 if (!pg_class_ownercheck(RelationGetRelid(relation), GetUserId()))
413 elog(ERROR, "you are not permitted to comment on class '%s'", relname);
415 /* Now, fetch the attribute number from the system cache */
417 attnum = get_attnum(RelationGetRelid(relation), attrname);
418 if (attnum == InvalidAttrNumber)
419 elog(ERROR, "'%s' is not an attribute of class '%s'",
422 /* Create the comment using the relation's oid */
424 CreateComments(RelationGetRelid(relation), RelOid_pg_class,
425 (int32) attnum, comment);
427 /* Done, but hold lock until commit */
429 heap_close(relation, NoLock);
432 /*------------------------------------------------------------------
435 * This routine is used to add/drop any user-comments a user might
436 * have regarding the specified database. The routine will check
437 * security for owner permissions, and, if succesful, will then
438 * attempt to find the oid of the database specified. Once found,
439 * a comment is added/dropped using the CreateComments() routine.
440 *------------------------------------------------------------------
444 CommentDatabase(char *database, char *comment)
446 Relation pg_database;
452 /* First find the tuple in pg_database for the database */
454 pg_database = heap_openr(DatabaseRelationName, AccessShareLock);
455 ScanKeyEntryInitialize(&entry, 0, Anum_pg_database_datname,
456 F_NAMEEQ, NameGetDatum(database));
457 scan = heap_beginscan(pg_database, 0, SnapshotNow, 1, &entry);
458 dbtuple = heap_getnext(scan, 0);
460 /* Validate database exists, and fetch the db oid */
462 if (!HeapTupleIsValid(dbtuple))
463 elog(ERROR, "database '%s' does not exist", database);
464 oid = dbtuple->t_data->t_oid;
466 /* Allow if the user matches the database dba or is a superuser */
468 if (!(superuser() || is_dbadmin(oid)))
469 elog(ERROR, "you are not permitted to comment on database '%s'",
472 /* Create the comments with the pg_database oid */
474 CreateComments(oid, RelOid_pg_database, 0, comment);
476 /* Complete the scan and close any opened relations */
479 heap_close(pg_database, AccessShareLock);
482 /*------------------------------------------------------------------
485 * This routine is used to add/drop any user-comments a user might
486 * have regarding a specified RULE. The rule is specified by name
487 * and, if found, and the user has appropriate permissions, a
488 * comment will be added/dropped using the CreateComments() routine.
489 *------------------------------------------------------------------
493 CommentRewrite(char *rule, char *comment)
501 /* Find the rule's pg_rewrite tuple, get its OID and its table's OID */
503 tuple = SearchSysCache(RULENAME,
504 PointerGetDatum(rule),
506 if (!HeapTupleIsValid(tuple))
507 elog(ERROR, "rule '%s' does not exist", rule);
509 reloid = ((Form_pg_rewrite) GETSTRUCT(tuple))->ev_class;
510 ruleoid = tuple->t_data->t_oid;
512 ReleaseSysCache(tuple);
514 /* Check object security */
516 aclcheck = pg_class_aclcheck(reloid, GetUserId(), ACL_RULE);
517 if (aclcheck != ACLCHECK_OK)
518 elog(ERROR, "you are not permitted to comment on rule '%s'",
521 /* pg_rewrite doesn't have a hard-coded OID, so must look it up */
523 classoid = get_relname_relid(RewriteRelationName, PG_CATALOG_NAMESPACE);
524 Assert(OidIsValid(classoid));
526 /* Call CreateComments() to create/drop the comments */
528 CreateComments(ruleoid, classoid, 0, comment);
531 /*------------------------------------------------------------------
534 * This routine is used to add/drop any user-comments a user might
535 * have regarding a TYPE. The type is specified by name
536 * and, if found, and the user has appropriate permissions, a
537 * comment will be added/dropped using the CreateComments() routine.
538 * The type's name and the comments are the paramters to this routine.
539 *------------------------------------------------------------------
543 CommentType(char *type, char *comment)
547 /* Find the type's oid */
549 /* XXX WRONG: need to deal with qualified type names */
550 oid = typenameTypeId(makeTypeName(type));
552 /* Check object security */
554 if (!pg_type_ownercheck(oid, GetUserId()))
555 elog(ERROR, "you are not permitted to comment on type '%s'",
558 /* Call CreateComments() to create/drop the comments */
560 CreateComments(oid, RelOid_pg_type, 0, comment);
563 /*------------------------------------------------------------------
564 * CommentAggregate --
566 * This routine is used to allow a user to provide comments on an
567 * aggregate function. The aggregate function is determined by both
568 * its name and its argument type, which, with the comments are
569 * the three parameters handed to this routine.
570 *------------------------------------------------------------------
574 CommentAggregate(char *aggregate, List *arguments, char *comment)
576 TypeName *aggtype = (TypeName *) lfirst(arguments);
581 /* First, attempt to determine the base aggregate oid */
583 baseoid = typenameTypeId(aggtype);
585 baseoid = InvalidOid;
587 /* Now, attempt to find the actual tuple in pg_aggregate */
589 oid = GetSysCacheOid(AGGNAME,
590 PointerGetDatum(aggregate),
591 ObjectIdGetDatum(baseoid),
593 if (!OidIsValid(oid))
594 agg_error("CommentAggregate", aggregate, baseoid);
596 /* Next, validate the user's attempt to comment */
598 if (!pg_aggr_ownercheck(oid, GetUserId()))
600 if (baseoid == InvalidOid)
601 elog(ERROR, "you are not permitted to comment on aggregate '%s' for all types",
604 elog(ERROR, "you are not permitted to comment on aggregate '%s' for type %s",
605 aggregate, format_type_be(baseoid));
608 /* pg_aggregate doesn't have a hard-coded OID, so must look it up */
610 classoid = get_relname_relid(AggregateRelationName, PG_CATALOG_NAMESPACE);
611 Assert(OidIsValid(classoid));
613 /* Call CreateComments() to create/drop the comments */
615 CreateComments(oid, classoid, 0, comment);
618 /*------------------------------------------------------------------
621 * This routine is used to allow a user to provide comments on an
622 * procedure (function). The procedure is determined by both
623 * its name and its argument list. The argument list is expected to
624 * be a series of parsed nodes pointed to by a List object. If the
625 * comments string is empty, the associated comment is dropped.
626 *------------------------------------------------------------------
630 CommentProc(char *function, List *arguments, char *comment)
633 argoids[FUNC_MAX_ARGS];
637 /* First, initialize function's argument list with their type oids */
639 MemSet(argoids, 0, FUNC_MAX_ARGS * sizeof(Oid));
640 argcount = length(arguments);
641 if (argcount > FUNC_MAX_ARGS)
642 elog(ERROR, "functions cannot have more than %d arguments",
644 for (i = 0; i < argcount; i++)
646 TypeName *t = (TypeName *) lfirst(arguments);
648 argoids[i] = LookupTypeName(t);
649 if (!OidIsValid(argoids[i]))
651 char *typnam = TypeNameToString(t);
653 if (strcmp(typnam, "opaque") == 0)
654 argoids[i] = InvalidOid;
656 elog(ERROR, "Type \"%s\" does not exist", typnam);
659 arguments = lnext(arguments);
662 /* Now, find the corresponding oid for this procedure */
664 oid = GetSysCacheOid(PROCNAME,
665 PointerGetDatum(function),
666 Int32GetDatum(argcount),
667 PointerGetDatum(argoids),
669 if (!OidIsValid(oid))
670 func_error("CommentProc", function, argcount, argoids, NULL);
672 /* Now, validate the user's ability to comment on this function */
674 if (!pg_proc_ownercheck(oid, GetUserId()))
675 elog(ERROR, "you are not permitted to comment on function '%s'",
678 /* Call CreateComments() to create/drop the comments */
680 CreateComments(oid, RelOid_pg_proc, 0, comment);
683 /*------------------------------------------------------------------
686 * This routine is used to allow a user to provide comments on an
687 * operator. The operator for commenting is determined by both
688 * its name and its argument list which defines the left and right
689 * hand types the operator will operate on. The argument list is
690 * expected to be a couple of parse nodes pointed to be a List
691 * object. If the comments string is empty, the associated comment
694 * NOTE: we actually attach the comment to the procedure that underlies
695 * the operator. This is a feature, not a bug: we want the same comment
696 * to be visible for both operator and function.
697 *------------------------------------------------------------------
701 CommentOperator(char *opername, List *arguments, char *comment)
703 TypeName *typenode1 = (TypeName *) lfirst(arguments);
704 TypeName *typenode2 = (TypeName *) lsecond(arguments);
706 Form_pg_operator data;
709 leftoid = InvalidOid,
710 rightoid = InvalidOid;
712 /* Attempt to fetch the left type oid, if specified */
713 if (typenode1 != NULL)
714 leftoid = typenameTypeId(typenode1);
716 /* Attempt to fetch the right type oid, if specified */
717 if (typenode2 != NULL)
718 rightoid = typenameTypeId(typenode2);
720 /* Determine operator type */
722 if (OidIsValid(leftoid) && (OidIsValid(rightoid)))
724 else if (OidIsValid(leftoid))
726 else if (OidIsValid(rightoid))
729 elog(ERROR, "operator '%s' is of an illegal type'", opername);
731 /* Attempt to fetch the operator oid */
733 optuple = SearchSysCache(OPERNAME,
734 PointerGetDatum(opername),
735 ObjectIdGetDatum(leftoid),
736 ObjectIdGetDatum(rightoid),
737 CharGetDatum(oprtype));
738 if (!HeapTupleIsValid(optuple))
739 elog(ERROR, "operator '%s' does not exist", opername);
741 oid = optuple->t_data->t_oid;
743 /* Valid user's ability to comment on this operator */
745 if (!pg_oper_ownercheck(oid, GetUserId()))
746 elog(ERROR, "you are not permitted to comment on operator '%s'",
749 /* Get the procedure associated with the operator */
751 data = (Form_pg_operator) GETSTRUCT(optuple);
753 if (oid == InvalidOid)
754 elog(ERROR, "operator '%s' does not have an underlying function", opername);
756 ReleaseSysCache(optuple);
758 /* Call CreateComments() to create/drop the comments */
760 CreateComments(oid, RelOid_pg_proc, 0, comment);
763 /*------------------------------------------------------------------
766 * This routine is used to allow a user to provide comments on a
767 * trigger event. The trigger for commenting is determined by both
768 * its name and the relation to which it refers. The arguments to this
769 * function are the trigger name, the relation name, and the comments
771 *------------------------------------------------------------------
775 CommentTrigger(char *trigger, char *schemaname, char *relname, char *comment)
777 RangeVar *rel = makeNode(RangeVar);
780 HeapTuple triggertuple;
782 ScanKeyData entry[2];
785 /* First, validate the user's action */
787 rel->relname = relname;
788 rel->schemaname = schemaname;
790 relation = heap_openrv(rel, AccessShareLock);
792 if (!pg_class_ownercheck(RelationGetRelid(relation), GetUserId()))
793 elog(ERROR, "you are not permitted to comment on trigger '%s' %s '%s'",
794 trigger, "defined for relation", relname);
796 /* Fetch the trigger oid from pg_trigger */
798 pg_trigger = heap_openr(TriggerRelationName, AccessShareLock);
799 ScanKeyEntryInitialize(&entry[0], 0x0, Anum_pg_trigger_tgrelid,
801 ObjectIdGetDatum(RelationGetRelid(relation)));
802 ScanKeyEntryInitialize(&entry[1], 0x0, Anum_pg_trigger_tgname,
804 NameGetDatum(trigger));
805 scan = heap_beginscan(pg_trigger, 0, SnapshotNow, 2, entry);
806 triggertuple = heap_getnext(scan, 0);
808 /* If no trigger exists for the relation specified, notify user */
810 if (!HeapTupleIsValid(triggertuple))
811 elog(ERROR, "trigger '%s' defined for relation '%s' does not exist",
814 oid = triggertuple->t_data->t_oid;
818 /* Create the comments with the pg_trigger oid */
820 CreateComments(oid, RelationGetRelid(pg_trigger), 0, comment);
822 /* Done, but hold lock on relation */
824 heap_close(pg_trigger, AccessShareLock);
825 heap_close(relation, NoLock);