]> granicus.if.org Git - postgresql/blob - src/backend/commands/comment.c
has_table_privilege spawns scions has_database_privilege, has_function_privilege,
[postgresql] / src / backend / commands / comment.c
1 /*-------------------------------------------------------------------------
2  *
3  * comment.c
4  *
5  * PostgreSQL object comments utility code.
6  *
7  * Copyright (c) 1996-2001, PostgreSQL Global Development Group
8  *
9  * IDENTIFICATION
10  *        $Header: /cvsroot/pgsql/src/backend/commands/comment.c,v 1.56 2002/08/09 16:45:14 tgl Exp $
11  *
12  *-------------------------------------------------------------------------
13  */
14
15 #include "postgres.h"
16
17 #include "access/genam.h"
18 #include "access/heapam.h"
19 #include "catalog/catname.h"
20 #include "catalog/indexing.h"
21 #include "catalog/namespace.h"
22 #include "catalog/pg_constraint.h"
23 #include "catalog/pg_description.h"
24 #include "catalog/pg_operator.h"
25 #include "catalog/pg_rewrite.h"
26 #include "catalog/pg_trigger.h"
27 #include "commands/comment.h"
28 #include "commands/dbcommands.h"
29 #include "miscadmin.h"
30 #include "parser/parse_func.h"
31 #include "parser/parse_oper.h"
32 #include "parser/parse_type.h"
33 #include "utils/acl.h"
34 #include "utils/builtins.h"
35 #include "utils/fmgroids.h"
36 #include "utils/lsyscache.h"
37 #include "utils/syscache.h"
38
39
40 /*
41  * Static Function Prototypes --
42  *
43  * The following protoypes are declared static so as not to conflict
44  * with any other routines outside this module. These routines are
45  * called by the public function CommentObject() routine to create
46  * the appropriate comment for the specific object type.
47  */
48
49 static void CommentRelation(int objtype, List *relname, char *comment);
50 static void CommentAttribute(List *qualname, char *comment);
51 static void CommentDatabase(List *qualname, char *comment);
52 static void CommentNamespace(List *qualname, char *comment);
53 static void CommentRule(List *qualname, char *comment);
54 static void CommentType(List *typename, char *comment);
55 static void CommentAggregate(List *aggregate, List *arguments, char *comment);
56 static void CommentProc(List *function, List *arguments, char *comment);
57 static void CommentOperator(List *opername, List *arguments, char *comment);
58 static void CommentTrigger(List *qualname, char *comment);
59 static void CommentConstraint(List *qualname, char *comment);
60
61
62 /*
63  * CommentObject --
64  *
65  * This routine is used to add the associated comment into
66  * pg_description for the object specified by the given SQL command.
67  */
68 void
69 CommentObject(CommentStmt *stmt)
70 {
71         switch (stmt->objtype)
72         {
73                 case COMMENT_ON_INDEX:
74                 case COMMENT_ON_SEQUENCE:
75                 case COMMENT_ON_TABLE:
76                 case COMMENT_ON_VIEW:
77                         CommentRelation(stmt->objtype, stmt->objname, stmt->comment);
78                         break;
79                 case COMMENT_ON_COLUMN:
80                         CommentAttribute(stmt->objname, stmt->comment);
81                         break;
82                 case COMMENT_ON_DATABASE:
83                         CommentDatabase(stmt->objname, stmt->comment);
84                         break;
85                 case COMMENT_ON_RULE:
86                         CommentRule(stmt->objname, stmt->comment);
87                         break;
88                 case COMMENT_ON_TYPE:
89                         CommentType(stmt->objname, stmt->comment);
90                         break;
91                 case COMMENT_ON_AGGREGATE:
92                         CommentAggregate(stmt->objname, stmt->objargs, stmt->comment);
93                         break;
94                 case COMMENT_ON_FUNCTION:
95                         CommentProc(stmt->objname, stmt->objargs, stmt->comment);
96                         break;
97                 case COMMENT_ON_OPERATOR:
98                         CommentOperator(stmt->objname, stmt->objargs, stmt->comment);
99                         break;
100                 case COMMENT_ON_TRIGGER:
101                         CommentTrigger(stmt->objname, stmt->comment);
102                         break;
103                 case COMMENT_ON_SCHEMA:
104                         CommentNamespace(stmt->objname, stmt->comment);
105                         break;
106                 case COMMENT_ON_CONSTRAINT:
107                         CommentConstraint(stmt->objname, stmt->comment);
108                         break;
109                 default:
110                         elog(ERROR, "An attempt was made to comment on a unknown type: %d",
111                                  stmt->objtype);
112         }
113 }
114
115 /*
116  * CreateComments --
117  *
118  * Create a comment for the specified object descriptor.  Inserts a new
119  * pg_description tuple, or replaces an existing one with the same key.
120  *
121  * If the comment given is null or an empty string, instead delete any
122  * existing comment for the specified key.
123  */
124 void
125 CreateComments(Oid oid, Oid classoid, int32 subid, char *comment)
126 {
127         Relation        description;
128         ScanKeyData skey[3];
129         SysScanDesc sd;
130         HeapTuple       oldtuple;
131         HeapTuple       newtuple = NULL;
132         Datum           values[Natts_pg_description];
133         char            nulls[Natts_pg_description];
134         char            replaces[Natts_pg_description];
135         int                     i;
136
137         /* Reduce empty-string to NULL case */
138         if (comment != NULL && strlen(comment) == 0)
139                 comment = NULL;
140
141         /* Prepare to form or update a tuple, if necessary */
142         if (comment != NULL)
143         {
144                 for (i = 0; i < Natts_pg_description; i++)
145                 {
146                         nulls[i] = ' ';
147                         replaces[i] = 'r';
148                 }
149                 i = 0;
150                 values[i++] = ObjectIdGetDatum(oid);
151                 values[i++] = ObjectIdGetDatum(classoid);
152                 values[i++] = Int32GetDatum(subid);
153                 values[i++] = DirectFunctionCall1(textin, CStringGetDatum(comment));
154         }
155
156         /* Use the index to search for a matching old tuple */
157
158         ScanKeyEntryInitialize(&skey[0],
159                                                    (bits16) 0x0,
160                                                    (AttrNumber) 1,
161                                                    (RegProcedure) F_OIDEQ,
162                                                    ObjectIdGetDatum(oid));
163
164         ScanKeyEntryInitialize(&skey[1],
165                                                    (bits16) 0x0,
166                                                    (AttrNumber) 2,
167                                                    (RegProcedure) F_OIDEQ,
168                                                    ObjectIdGetDatum(classoid));
169
170         ScanKeyEntryInitialize(&skey[2],
171                                                    (bits16) 0x0,
172                                                    (AttrNumber) 3,
173                                                    (RegProcedure) F_INT4EQ,
174                                                    Int32GetDatum(subid));
175
176         description = heap_openr(DescriptionRelationName, RowExclusiveLock);
177
178         sd = systable_beginscan(description, DescriptionObjIndex, true,
179                                                         SnapshotNow, 3, skey);
180
181         while ((oldtuple = systable_getnext(sd)) != NULL)
182         {
183                 /* Found the old tuple, so delete or update it */
184
185                 if (comment == NULL)
186                         simple_heap_delete(description, &oldtuple->t_self);
187                 else
188                 {
189                         newtuple = heap_modifytuple(oldtuple, description, values,
190                                                                                 nulls, replaces);
191                         simple_heap_update(description, &oldtuple->t_self, newtuple);
192                 }
193
194                 break;                                  /* Assume there can be only one match */
195         }
196
197         systable_endscan(sd);
198
199         /* If we didn't find an old tuple, insert a new one */
200
201         if (newtuple == NULL && comment != NULL)
202         {
203                 newtuple = heap_formtuple(RelationGetDescr(description),
204                                                                   values, nulls);
205                 simple_heap_insert(description, newtuple);
206         }
207
208         /* Update indexes, if necessary */
209         if (newtuple != NULL)
210         {
211                 CatalogUpdateIndexes(description, newtuple);
212                 heap_freetuple(newtuple);
213         }
214
215         /* Done */
216
217         heap_close(description, NoLock);
218 }
219
220 /*
221  * DeleteComments -- remove comments for an object
222  *
223  * If subid is nonzero then only comments matching it will be removed.
224  * If subid is zero, all comments matching the oid/classoid will be removed
225  * (this corresponds to deleting a whole object).
226  */
227 void
228 DeleteComments(Oid oid, Oid classoid, int32 subid)
229 {
230         Relation        description;
231         ScanKeyData skey[3];
232         int                     nkeys;
233         SysScanDesc     sd;
234         HeapTuple       oldtuple;
235
236         /* Use the index to search for all matching old tuples */
237
238         ScanKeyEntryInitialize(&skey[0], 0x0,
239                                                    Anum_pg_description_objoid, F_OIDEQ,
240                                                    ObjectIdGetDatum(oid));
241
242         ScanKeyEntryInitialize(&skey[1], 0x0,
243                                                    Anum_pg_description_classoid, F_OIDEQ,
244                                                    ObjectIdGetDatum(classoid));
245
246         if (subid != 0)
247         {
248                 ScanKeyEntryInitialize(&skey[2], 0x0,
249                                                            Anum_pg_description_objsubid, F_INT4EQ,
250                                                            Int32GetDatum(subid));
251                 nkeys = 3;
252         }
253         else
254                 nkeys = 2;
255
256         description = heap_openr(DescriptionRelationName, RowExclusiveLock);
257
258         sd = systable_beginscan(description, DescriptionObjIndex, true,
259                                                         SnapshotNow, nkeys, skey);
260
261         while ((oldtuple = systable_getnext(sd)) != NULL)
262         {
263                 simple_heap_delete(description, &oldtuple->t_self);
264         }
265
266         /* Done */
267
268         systable_endscan(sd);
269         heap_close(description, RowExclusiveLock);
270 }
271
272 /*
273  * CommentRelation --
274  *
275  * This routine is used to add/drop a comment from a relation, where
276  * a relation is a TABLE, SEQUENCE, VIEW or INDEX. The routine simply
277  * finds the relation name by searching the system cache, locating
278  * the appropriate tuple, and inserting a comment using that
279  * tuple's oid. Its parameters are the relation name and comments.
280  */
281 static void
282 CommentRelation(int objtype, List *relname, char *comment)
283 {
284         Relation        relation;
285         RangeVar   *tgtrel;
286
287         tgtrel = makeRangeVarFromNameList(relname);
288
289         /*
290          * Open the relation.  We do this mainly to acquire a lock that
291          * ensures no one else drops the relation before we commit.  (If they
292          * did, they'd fail to remove the entry we are about to make in
293          * pg_description.)
294          */
295         relation = relation_openrv(tgtrel, AccessShareLock);
296
297         /* Check object security */
298         if (!pg_class_ownercheck(RelationGetRelid(relation), GetUserId()))
299                 aclcheck_error(ACLCHECK_NOT_OWNER, RelationGetRelationName(relation));
300
301         /* Next, verify that the relation type matches the intent */
302
303         switch (objtype)
304         {
305                 case COMMENT_ON_INDEX:
306                         if (relation->rd_rel->relkind != RELKIND_INDEX)
307                                 elog(ERROR, "relation \"%s\" is not an index",
308                                          RelationGetRelationName(relation));
309                         break;
310                 case COMMENT_ON_SEQUENCE:
311                         if (relation->rd_rel->relkind != RELKIND_SEQUENCE)
312                                 elog(ERROR, "relation \"%s\" is not a sequence",
313                                          RelationGetRelationName(relation));
314                         break;
315                 case COMMENT_ON_TABLE:
316                         if (relation->rd_rel->relkind != RELKIND_RELATION)
317                                 elog(ERROR, "relation \"%s\" is not a table",
318                                          RelationGetRelationName(relation));
319                         break;
320                 case COMMENT_ON_VIEW:
321                         if (relation->rd_rel->relkind != RELKIND_VIEW)
322                                 elog(ERROR, "relation \"%s\" is not a view",
323                                          RelationGetRelationName(relation));
324                         break;
325         }
326
327         /* Create the comment using the relation's oid */
328
329         CreateComments(RelationGetRelid(relation), RelOid_pg_class, 0, comment);
330
331         /* Done, but hold lock until commit */
332         relation_close(relation, NoLock);
333 }
334
335 /*
336  * CommentAttribute --
337  *
338  * This routine is used to add/drop a comment from an attribute
339  * such as a table's column. The routine will check security
340  * restrictions and then attempt to look up the specified
341  * attribute. If successful, a comment is added/dropped, else an
342  * elog() exception is thrown.  The parameters are the relation
343  * and attribute names, and the comments
344  */
345 static void
346 CommentAttribute(List *qualname, char *comment)
347 {
348         int                     nnames;
349         List       *relname;
350         char       *attrname;
351         RangeVar   *rel;
352         Relation        relation;
353         AttrNumber      attnum;
354
355         /* Separate relname and attr name */
356         nnames = length(qualname);
357         if (nnames < 2)
358                 elog(ERROR, "CommentAttribute: must specify relation.attribute");
359         relname = ltruncate(nnames-1, listCopy(qualname));
360         attrname = strVal(nth(nnames-1, qualname));
361
362         /* Open the containing relation to ensure it won't go away meanwhile */
363         rel = makeRangeVarFromNameList(relname);
364         relation = heap_openrv(rel, AccessShareLock);
365
366         /* Check object security */
367
368         if (!pg_class_ownercheck(RelationGetRelid(relation), GetUserId()))
369                 aclcheck_error(ACLCHECK_NOT_OWNER, RelationGetRelationName(relation));
370
371         /* Now, fetch the attribute number from the system cache */
372
373         attnum = get_attnum(RelationGetRelid(relation), attrname);
374         if (attnum == InvalidAttrNumber)
375                 elog(ERROR, "Relation \"%s\" has no column \"%s\"",
376                          RelationGetRelationName(relation), attrname);
377
378         /* Create the comment using the relation's oid */
379
380         CreateComments(RelationGetRelid(relation), RelOid_pg_class,
381                                    (int32) attnum, comment);
382
383         /* Done, but hold lock until commit */
384
385         heap_close(relation, NoLock);
386 }
387
388 /*
389  * CommentDatabase --
390  *
391  * This routine is used to add/drop any user-comments a user might
392  * have regarding the specified database. The routine will check
393  * security for owner permissions, and, if succesful, will then
394  * attempt to find the oid of the database specified. Once found,
395  * a comment is added/dropped using the CreateComments() routine.
396  */
397 static void
398 CommentDatabase(List *qualname, char *comment)
399 {
400         char       *database;
401         Oid                     oid;
402
403         if (length(qualname) != 1)
404                 elog(ERROR, "CommentDatabase: database name may not be qualified");
405         database = strVal(lfirst(qualname));
406
407         /* First get the database OID */
408         oid = get_database_oid(database);
409         if (!OidIsValid(oid))
410                 elog(ERROR, "database \"%s\" does not exist", database);
411
412         /* Allow if the user matches the database dba or is a superuser */
413
414         if (!(superuser() || is_dbadmin(oid)))
415                 elog(ERROR, "you are not permitted to comment on database \"%s\"",
416                          database);
417
418         /* Only allow comments on the current database */
419         if (oid != MyDatabaseId)
420                 elog(ERROR, "Database comments may only be applied to the current database");
421
422         /* Create the comment with the pg_database oid */
423         CreateComments(oid, RelOid_pg_database, 0, comment);
424 }
425
426 /*
427  * CommentNamespace --
428  *
429  * This routine is used to add/drop any user-comments a user might
430  * have regarding the specified namespace. The routine will check
431  * security for owner permissions, and, if succesful, will then
432  * attempt to find the oid of the namespace specified. Once found,
433  * a comment is added/dropped using the CreateComments() routine.
434  */
435 static void
436 CommentNamespace(List *qualname, char *comment)
437 {
438         Oid                     oid;
439         Oid                     classoid;
440         char       *namespace;
441
442         if (length(qualname) != 1)
443                 elog(ERROR, "CommentSchema: schema name may not be qualified");
444         namespace = strVal(lfirst(qualname));
445
446         oid = GetSysCacheOid(NAMESPACENAME,
447                                                  CStringGetDatum(namespace),
448                                                  0, 0, 0);
449         if (!OidIsValid(oid))
450                 elog(ERROR, "CommentSchema: Schema \"%s\" could not be found",
451                          namespace);
452
453         /* Check object security */
454         if (!pg_namespace_ownercheck(oid, GetUserId()))
455                 aclcheck_error(ACLCHECK_NOT_OWNER, namespace);
456
457         /* pg_namespace doesn't have a hard-coded OID, so must look it up */
458         classoid = get_system_catalog_relid(NamespaceRelationName);
459
460         /* Call CreateComments() to create/drop the comments */
461         CreateComments(oid, classoid, 0, comment);
462 }
463
464 /*
465  * CommentRule --
466  *
467  * This routine is used to add/drop any user-comments a user might
468  * have regarding a specified RULE. The rule for commenting is determined by
469  * both its name and the relation to which it refers. The arguments to this
470  * function are the rule name and relation name (merged into a qualified
471  * name), and the comment to add/drop.
472  *
473  * Before PG 7.3, rules had unique names across the whole database, and so
474  * the syntax was just COMMENT ON RULE rulename, with no relation name.
475  * For purposes of backwards compatibility, we support that as long as there
476  * is only one rule by the specified name in the database.
477  */
478 static void
479 CommentRule(List *qualname, char *comment)
480 {
481         int                     nnames;
482         List       *relname;
483         char       *rulename;
484         RangeVar   *rel;
485         Relation        relation;
486         HeapTuple       tuple;
487         Oid                     reloid;
488         Oid                     ruleoid;
489         Oid                     classoid;
490         AclResult       aclcheck;
491
492         /* Separate relname and trig name */
493         nnames = length(qualname);
494         if (nnames == 1)
495         {
496                 /* Old-style: only a rule name is given */
497                 Relation        RewriteRelation;
498                 HeapScanDesc scanDesc;
499                 ScanKeyData scanKeyData;
500
501                 rulename = strVal(lfirst(qualname));
502
503                 /* Search pg_rewrite for such a rule */
504                 ScanKeyEntryInitialize(&scanKeyData,
505                                                            0,
506                                                            Anum_pg_rewrite_rulename,
507                                                            F_NAMEEQ,
508                                                            PointerGetDatum(rulename));
509
510                 RewriteRelation = heap_openr(RewriteRelationName, AccessShareLock);
511                 scanDesc = heap_beginscan(RewriteRelation, SnapshotNow,
512                                                                   1, &scanKeyData);
513
514                 tuple = heap_getnext(scanDesc, ForwardScanDirection);
515                 if (HeapTupleIsValid(tuple))
516                 {
517                         reloid = ((Form_pg_rewrite) GETSTRUCT(tuple))->ev_class;
518                         AssertTupleDescHasOid(RewriteRelation->rd_att);
519                         ruleoid = HeapTupleGetOid(tuple);
520                 }
521                 else
522                 {
523                         elog(ERROR, "rule \"%s\" does not exist", rulename);
524                         reloid = ruleoid = 0; /* keep compiler quiet */
525                 }
526
527                 if (HeapTupleIsValid(tuple = heap_getnext(scanDesc,
528                                                                                                   ForwardScanDirection)))
529                         elog(ERROR, "There are multiple rules \"%s\""
530                                  "\n\tPlease specify a relation name as well as a rule name",
531                                  rulename);
532
533                 heap_endscan(scanDesc);
534                 heap_close(RewriteRelation, AccessShareLock);
535
536                 /* Open the owning relation to ensure it won't go away meanwhile */
537                 relation = heap_open(reloid, AccessShareLock);
538         }
539         else
540         {
541                 /* New-style: rule and relname both provided */
542                 Assert(nnames >= 2);
543                 relname = ltruncate(nnames-1, listCopy(qualname));
544                 rulename = strVal(nth(nnames-1, qualname));
545
546                 /* Open the owning relation to ensure it won't go away meanwhile */
547                 rel = makeRangeVarFromNameList(relname);
548                 relation = heap_openrv(rel, AccessShareLock);
549                 reloid = RelationGetRelid(relation);
550
551                 /* Find the rule's pg_rewrite tuple, get its OID */
552                 tuple = SearchSysCache(RULERELNAME,
553                                                            ObjectIdGetDatum(reloid),
554                                                            PointerGetDatum(rulename),
555                                                            0, 0);
556                 if (!HeapTupleIsValid(tuple))
557                         elog(ERROR, "rule \"%s\" does not exist", rulename);
558                 Assert(reloid == ((Form_pg_rewrite) GETSTRUCT(tuple))->ev_class);
559                 AssertTupleDescHasOid(relation->rd_att);
560                 ruleoid = HeapTupleGetOid(tuple);
561                 ReleaseSysCache(tuple);
562         }
563
564         /* Check object security */
565
566         aclcheck = pg_class_aclcheck(reloid, GetUserId(), ACL_RULE);
567         if (aclcheck != ACLCHECK_OK)
568                 aclcheck_error(aclcheck, rulename);
569
570         /* pg_rewrite doesn't have a hard-coded OID, so must look it up */
571         classoid = get_system_catalog_relid(RewriteRelationName);
572
573         /* Call CreateComments() to create/drop the comments */
574
575         CreateComments(ruleoid, classoid, 0, comment);
576 }
577
578 /*
579  * CommentType --
580  *
581  * This routine is used to add/drop any user-comments a user might
582  * have regarding a TYPE. The type is specified by name
583  * and, if found, and the user has appropriate permissions, a
584  * comment will be added/dropped using the CreateComments() routine.
585  * The type's name and the comments are the paramters to this routine.
586  */
587 static void
588 CommentType(List *typename, char *comment)
589 {
590         TypeName   *tname;
591         Oid                     oid;
592
593         /* XXX a bit of a crock; should accept TypeName in COMMENT syntax */
594         tname = makeNode(TypeName);
595         tname->names = typename;
596         tname->typmod = -1;
597
598         /* Find the type's oid */
599
600         oid = typenameTypeId(tname);
601
602         /* Check object security */
603
604         if (!pg_type_ownercheck(oid, GetUserId()))
605                 aclcheck_error(ACLCHECK_NOT_OWNER, TypeNameToString(tname));
606
607         /* Call CreateComments() to create/drop the comments */
608
609         CreateComments(oid, RelOid_pg_type, 0, comment);
610 }
611
612 /*
613  * CommentAggregate --
614  *
615  * This routine is used to allow a user to provide comments on an
616  * aggregate function. The aggregate function is determined by both
617  * its name and its argument type, which, with the comments are
618  * the three parameters handed to this routine.
619  */
620 static void
621 CommentAggregate(List *aggregate, List *arguments, char *comment)
622 {
623         TypeName   *aggtype = (TypeName *) lfirst(arguments);
624         Oid                     baseoid,
625                                 oid;
626
627         /* First, attempt to determine the base aggregate oid */
628         if (aggtype)
629                 baseoid = typenameTypeId(aggtype);
630         else
631                 baseoid = InvalidOid;
632
633         /* Now, attempt to find the actual tuple in pg_proc */
634
635         oid = find_aggregate_func("CommentAggregate", aggregate, baseoid);
636
637         /* Next, validate the user's attempt to comment */
638
639         if (!pg_proc_ownercheck(oid, GetUserId()))
640                 aclcheck_error(ACLCHECK_NOT_OWNER, NameListToString(aggregate));
641
642         /* Call CreateComments() to create/drop the comments */
643
644         CreateComments(oid, RelOid_pg_proc, 0, comment);
645 }
646
647 /*
648  * CommentProc --
649  *
650  * This routine is used to allow a user to provide comments on an
651  * procedure (function). The procedure is determined by both
652  * its name and its argument list. The argument list is expected to
653  * be a series of parsed nodes pointed to by a List object. If the
654  * comments string is empty, the associated comment is dropped.
655  */
656 static void
657 CommentProc(List *function, List *arguments, char *comment)
658 {
659         Oid                     oid;
660
661         /* Look up the procedure */
662
663         oid = LookupFuncNameTypeNames(function, arguments,
664                                                                   true, "CommentProc");
665
666         /* Now, validate the user's ability to comment on this function */
667
668         if (!pg_proc_ownercheck(oid, GetUserId()))
669                 aclcheck_error(ACLCHECK_NOT_OWNER, NameListToString(function));
670
671         /* Call CreateComments() to create/drop the comments */
672
673         CreateComments(oid, RelOid_pg_proc, 0, comment);
674 }
675
676 /*
677  * CommentOperator --
678  *
679  * This routine is used to allow a user to provide comments on an
680  * operator. The operator for commenting is determined by both
681  * its name and its argument list which defines the left and right
682  * hand types the operator will operate on. The argument list is
683  * expected to be a couple of parse nodes pointed to be a List
684  * object.
685  */
686 static void
687 CommentOperator(List *opername, List *arguments, char *comment)
688 {
689         TypeName   *typenode1 = (TypeName *) lfirst(arguments);
690         TypeName   *typenode2 = (TypeName *) lsecond(arguments);
691         Oid                     oid;
692         Oid                     classoid;
693
694         /* Look up the operator */
695         oid = LookupOperNameTypeNames(opername, typenode1, typenode2,
696                                                                   "CommentOperator");
697
698         /* Valid user's ability to comment on this operator */
699         if (!pg_oper_ownercheck(oid, GetUserId()))
700                 aclcheck_error(ACLCHECK_NOT_OWNER, NameListToString(opername));
701
702         /* pg_operator doesn't have a hard-coded OID, so must look it up */
703         classoid = get_system_catalog_relid(OperatorRelationName);
704
705         /* Call CreateComments() to create/drop the comments */
706         CreateComments(oid, classoid, 0, comment);
707 }
708
709 /*
710  * CommentTrigger --
711  *
712  * This routine is used to allow a user to provide comments on a
713  * trigger event. The trigger for commenting is determined by both
714  * its name and the relation to which it refers. The arguments to this
715  * function are the trigger name and relation name (merged into a qualified
716  * name), and the comment to add/drop.
717  */
718 static void
719 CommentTrigger(List *qualname, char *comment)
720 {
721         int                     nnames;
722         List       *relname;
723         char       *trigname;
724         RangeVar   *rel;
725         Relation        pg_trigger,
726                                 relation;
727         HeapTuple       triggertuple;
728         SysScanDesc     scan;
729         ScanKeyData entry[2];
730         Oid                     oid;
731
732         /* Separate relname and trig name */
733         nnames = length(qualname);
734         if (nnames < 2)
735                 elog(ERROR, "CommentTrigger: must specify relation and trigger");
736         relname = ltruncate(nnames-1, listCopy(qualname));
737         trigname = strVal(nth(nnames-1, qualname));
738
739         /* Open the owning relation to ensure it won't go away meanwhile */
740         rel = makeRangeVarFromNameList(relname);
741         relation = heap_openrv(rel, AccessShareLock);
742
743         /* Check object security */
744
745         if (!pg_class_ownercheck(RelationGetRelid(relation), GetUserId()))
746                 aclcheck_error(ACLCHECK_NOT_OWNER, RelationGetRelationName(relation));
747
748         /*
749          * Fetch the trigger tuple from pg_trigger.  There can be only one
750          * because of the unique index.
751          */
752         pg_trigger = heap_openr(TriggerRelationName, AccessShareLock);
753         ScanKeyEntryInitialize(&entry[0], 0x0,
754                                                    Anum_pg_trigger_tgrelid,
755                                                    F_OIDEQ,
756                                                    ObjectIdGetDatum(RelationGetRelid(relation)));
757         ScanKeyEntryInitialize(&entry[1], 0x0,
758                                                    Anum_pg_trigger_tgname,
759                                                    F_NAMEEQ,
760                                                    CStringGetDatum(trigname));
761         scan = systable_beginscan(pg_trigger, TriggerRelidNameIndex, true,
762                                                           SnapshotNow, 2, entry);
763         triggertuple = systable_getnext(scan);
764
765         /* If no trigger exists for the relation specified, notify user */
766
767         if (!HeapTupleIsValid(triggertuple))
768                 elog(ERROR, "trigger \"%s\" for relation \"%s\" does not exist",
769                          trigname, RelationGetRelationName(relation));
770
771         AssertTupleDescHasOid(pg_trigger->rd_att);
772         oid = HeapTupleGetOid(triggertuple);
773
774         systable_endscan(scan);
775
776         /* Create the comment with the pg_trigger oid */
777
778         CreateComments(oid, RelationGetRelid(pg_trigger), 0, comment);
779
780         /* Done, but hold lock on relation */
781
782         heap_close(pg_trigger, AccessShareLock);
783         heap_close(relation, NoLock);
784 }
785
786
787 /*
788  * CommentConstraint --
789  *
790  * Enable commenting on constraints held within the pg_constraint
791  * table.  A qualified name is required as constraint names are
792  * unique per relation.
793  */
794 static void
795 CommentConstraint(List *qualname, char *comment)
796 {
797         int                     nnames;
798         List       *relName;
799         char       *conName;
800         RangeVar   *rel;
801         Relation        pg_constraint,
802                                 relation;
803         HeapTuple       tuple;
804         SysScanDesc     scan;
805         ScanKeyData skey[1];
806         Oid                     conOid = InvalidOid;
807
808         /* Separate relname and constraint name */
809         nnames = length(qualname);
810         if (nnames < 2)
811                 elog(ERROR, "CommentConstraint: must specify relation and constraint");
812         relName = ltruncate(nnames-1, listCopy(qualname));
813         conName = strVal(nth(nnames-1, qualname));
814
815         /* Open the owning relation to ensure it won't go away meanwhile */
816         rel = makeRangeVarFromNameList(relName);
817         relation = heap_openrv(rel, AccessShareLock);
818
819         /* Check object security */
820
821         if (!pg_class_ownercheck(RelationGetRelid(relation), GetUserId()))
822                 aclcheck_error(ACLCHECK_NOT_OWNER, RelationGetRelationName(relation));
823
824         /*
825          * Fetch the constraint tuple from pg_constraint.  There may be more than
826          * one match, because constraints are not required to have unique names;
827          * if so, error out.
828          */
829         pg_constraint = heap_openr(ConstraintRelationName, AccessShareLock);
830
831         ScanKeyEntryInitialize(&skey[0], 0x0,
832                                                    Anum_pg_constraint_conrelid, F_OIDEQ,
833                                                    ObjectIdGetDatum(RelationGetRelid(relation)));
834
835         scan = systable_beginscan(pg_constraint, ConstraintRelidIndex, true,
836                                                           SnapshotNow, 1, skey);
837
838         while (HeapTupleIsValid(tuple = systable_getnext(scan)))
839         {
840                 Form_pg_constraint      con = (Form_pg_constraint) GETSTRUCT(tuple);
841
842                 if (strcmp(NameStr(con->conname), conName) == 0)
843                 {
844                         if (OidIsValid(conOid))
845                                 elog(ERROR, "Relation \"%s\" has multiple constraints named \"%s\"",
846                                          RelationGetRelationName(relation), conName);
847                         conOid = HeapTupleGetOid(tuple);
848                 }
849         }
850
851         systable_endscan(scan);
852
853         /* If no constraint exists for the relation specified, notify user */
854         if (!OidIsValid(conOid))
855                 elog(ERROR, "constraint \"%s\" for relation \"%s\" does not exist",
856                          conName, RelationGetRelationName(relation));
857
858         /* Create the comment with the pg_constraint oid */
859         CreateComments(conOid, RelationGetRelid(pg_constraint), 0, comment);
860
861         /* Done, but hold lock on relation */
862         heap_close(pg_constraint, AccessShareLock);
863         heap_close(relation, NoLock);
864 }