1 /*-------------------------------------------------------------------------
5 * PostgreSQL object comments utility code.
7 * Copyright (c) 1996-2001, PostgreSQL Global Development Group
10 * $Header: /cvsroot/pgsql/src/backend/commands/comment.c,v 1.56 2002/08/09 16:45:14 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/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"
41 * Static Function Prototypes --
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.
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);
65 * This routine is used to add the associated comment into
66 * pg_description for the object specified by the given SQL command.
69 CommentObject(CommentStmt *stmt)
71 switch (stmt->objtype)
73 case COMMENT_ON_INDEX:
74 case COMMENT_ON_SEQUENCE:
75 case COMMENT_ON_TABLE:
77 CommentRelation(stmt->objtype, stmt->objname, stmt->comment);
79 case COMMENT_ON_COLUMN:
80 CommentAttribute(stmt->objname, stmt->comment);
82 case COMMENT_ON_DATABASE:
83 CommentDatabase(stmt->objname, stmt->comment);
86 CommentRule(stmt->objname, stmt->comment);
89 CommentType(stmt->objname, stmt->comment);
91 case COMMENT_ON_AGGREGATE:
92 CommentAggregate(stmt->objname, stmt->objargs, stmt->comment);
94 case COMMENT_ON_FUNCTION:
95 CommentProc(stmt->objname, stmt->objargs, stmt->comment);
97 case COMMENT_ON_OPERATOR:
98 CommentOperator(stmt->objname, stmt->objargs, stmt->comment);
100 case COMMENT_ON_TRIGGER:
101 CommentTrigger(stmt->objname, stmt->comment);
103 case COMMENT_ON_SCHEMA:
104 CommentNamespace(stmt->objname, stmt->comment);
106 case COMMENT_ON_CONSTRAINT:
107 CommentConstraint(stmt->objname, stmt->comment);
110 elog(ERROR, "An attempt was made to comment on a unknown type: %d",
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.
121 * If the comment given is null or an empty string, instead delete any
122 * existing comment for the specified key.
125 CreateComments(Oid oid, Oid classoid, int32 subid, char *comment)
127 Relation description;
131 HeapTuple newtuple = NULL;
132 Datum values[Natts_pg_description];
133 char nulls[Natts_pg_description];
134 char replaces[Natts_pg_description];
137 /* Reduce empty-string to NULL case */
138 if (comment != NULL && strlen(comment) == 0)
141 /* Prepare to form or update a tuple, if necessary */
144 for (i = 0; i < Natts_pg_description; i++)
150 values[i++] = ObjectIdGetDatum(oid);
151 values[i++] = ObjectIdGetDatum(classoid);
152 values[i++] = Int32GetDatum(subid);
153 values[i++] = DirectFunctionCall1(textin, CStringGetDatum(comment));
156 /* Use the index to search for a matching old tuple */
158 ScanKeyEntryInitialize(&skey[0],
161 (RegProcedure) F_OIDEQ,
162 ObjectIdGetDatum(oid));
164 ScanKeyEntryInitialize(&skey[1],
167 (RegProcedure) F_OIDEQ,
168 ObjectIdGetDatum(classoid));
170 ScanKeyEntryInitialize(&skey[2],
173 (RegProcedure) F_INT4EQ,
174 Int32GetDatum(subid));
176 description = heap_openr(DescriptionRelationName, RowExclusiveLock);
178 sd = systable_beginscan(description, DescriptionObjIndex, true,
179 SnapshotNow, 3, skey);
181 while ((oldtuple = systable_getnext(sd)) != NULL)
183 /* Found the old tuple, so delete or update it */
186 simple_heap_delete(description, &oldtuple->t_self);
189 newtuple = heap_modifytuple(oldtuple, description, values,
191 simple_heap_update(description, &oldtuple->t_self, newtuple);
194 break; /* Assume there can be only one match */
197 systable_endscan(sd);
199 /* If we didn't find an old tuple, insert a new one */
201 if (newtuple == NULL && comment != NULL)
203 newtuple = heap_formtuple(RelationGetDescr(description),
205 simple_heap_insert(description, newtuple);
208 /* Update indexes, if necessary */
209 if (newtuple != NULL)
211 CatalogUpdateIndexes(description, newtuple);
212 heap_freetuple(newtuple);
217 heap_close(description, NoLock);
221 * DeleteComments -- remove comments for an object
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).
228 DeleteComments(Oid oid, Oid classoid, int32 subid)
230 Relation description;
236 /* Use the index to search for all matching old tuples */
238 ScanKeyEntryInitialize(&skey[0], 0x0,
239 Anum_pg_description_objoid, F_OIDEQ,
240 ObjectIdGetDatum(oid));
242 ScanKeyEntryInitialize(&skey[1], 0x0,
243 Anum_pg_description_classoid, F_OIDEQ,
244 ObjectIdGetDatum(classoid));
248 ScanKeyEntryInitialize(&skey[2], 0x0,
249 Anum_pg_description_objsubid, F_INT4EQ,
250 Int32GetDatum(subid));
256 description = heap_openr(DescriptionRelationName, RowExclusiveLock);
258 sd = systable_beginscan(description, DescriptionObjIndex, true,
259 SnapshotNow, nkeys, skey);
261 while ((oldtuple = systable_getnext(sd)) != NULL)
263 simple_heap_delete(description, &oldtuple->t_self);
268 systable_endscan(sd);
269 heap_close(description, RowExclusiveLock);
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.
282 CommentRelation(int objtype, List *relname, char *comment)
287 tgtrel = makeRangeVarFromNameList(relname);
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
295 relation = relation_openrv(tgtrel, AccessShareLock);
297 /* Check object security */
298 if (!pg_class_ownercheck(RelationGetRelid(relation), GetUserId()))
299 aclcheck_error(ACLCHECK_NOT_OWNER, RelationGetRelationName(relation));
301 /* Next, verify that the relation type matches the intent */
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));
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));
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));
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));
327 /* Create the comment using the relation's oid */
329 CreateComments(RelationGetRelid(relation), RelOid_pg_class, 0, comment);
331 /* Done, but hold lock until commit */
332 relation_close(relation, NoLock);
336 * CommentAttribute --
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
346 CommentAttribute(List *qualname, char *comment)
355 /* Separate relname and attr name */
356 nnames = length(qualname);
358 elog(ERROR, "CommentAttribute: must specify relation.attribute");
359 relname = ltruncate(nnames-1, listCopy(qualname));
360 attrname = strVal(nth(nnames-1, qualname));
362 /* Open the containing relation to ensure it won't go away meanwhile */
363 rel = makeRangeVarFromNameList(relname);
364 relation = heap_openrv(rel, AccessShareLock);
366 /* Check object security */
368 if (!pg_class_ownercheck(RelationGetRelid(relation), GetUserId()))
369 aclcheck_error(ACLCHECK_NOT_OWNER, RelationGetRelationName(relation));
371 /* Now, fetch the attribute number from the system cache */
373 attnum = get_attnum(RelationGetRelid(relation), attrname);
374 if (attnum == InvalidAttrNumber)
375 elog(ERROR, "Relation \"%s\" has no column \"%s\"",
376 RelationGetRelationName(relation), attrname);
378 /* Create the comment using the relation's oid */
380 CreateComments(RelationGetRelid(relation), RelOid_pg_class,
381 (int32) attnum, comment);
383 /* Done, but hold lock until commit */
385 heap_close(relation, NoLock);
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.
398 CommentDatabase(List *qualname, char *comment)
403 if (length(qualname) != 1)
404 elog(ERROR, "CommentDatabase: database name may not be qualified");
405 database = strVal(lfirst(qualname));
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);
412 /* Allow if the user matches the database dba or is a superuser */
414 if (!(superuser() || is_dbadmin(oid)))
415 elog(ERROR, "you are not permitted to comment on database \"%s\"",
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");
422 /* Create the comment with the pg_database oid */
423 CreateComments(oid, RelOid_pg_database, 0, comment);
427 * CommentNamespace --
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.
436 CommentNamespace(List *qualname, char *comment)
442 if (length(qualname) != 1)
443 elog(ERROR, "CommentSchema: schema name may not be qualified");
444 namespace = strVal(lfirst(qualname));
446 oid = GetSysCacheOid(NAMESPACENAME,
447 CStringGetDatum(namespace),
449 if (!OidIsValid(oid))
450 elog(ERROR, "CommentSchema: Schema \"%s\" could not be found",
453 /* Check object security */
454 if (!pg_namespace_ownercheck(oid, GetUserId()))
455 aclcheck_error(ACLCHECK_NOT_OWNER, namespace);
457 /* pg_namespace doesn't have a hard-coded OID, so must look it up */
458 classoid = get_system_catalog_relid(NamespaceRelationName);
460 /* Call CreateComments() to create/drop the comments */
461 CreateComments(oid, classoid, 0, comment);
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.
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.
479 CommentRule(List *qualname, char *comment)
492 /* Separate relname and trig name */
493 nnames = length(qualname);
496 /* Old-style: only a rule name is given */
497 Relation RewriteRelation;
498 HeapScanDesc scanDesc;
499 ScanKeyData scanKeyData;
501 rulename = strVal(lfirst(qualname));
503 /* Search pg_rewrite for such a rule */
504 ScanKeyEntryInitialize(&scanKeyData,
506 Anum_pg_rewrite_rulename,
508 PointerGetDatum(rulename));
510 RewriteRelation = heap_openr(RewriteRelationName, AccessShareLock);
511 scanDesc = heap_beginscan(RewriteRelation, SnapshotNow,
514 tuple = heap_getnext(scanDesc, ForwardScanDirection);
515 if (HeapTupleIsValid(tuple))
517 reloid = ((Form_pg_rewrite) GETSTRUCT(tuple))->ev_class;
518 AssertTupleDescHasOid(RewriteRelation->rd_att);
519 ruleoid = HeapTupleGetOid(tuple);
523 elog(ERROR, "rule \"%s\" does not exist", rulename);
524 reloid = ruleoid = 0; /* keep compiler quiet */
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",
533 heap_endscan(scanDesc);
534 heap_close(RewriteRelation, AccessShareLock);
536 /* Open the owning relation to ensure it won't go away meanwhile */
537 relation = heap_open(reloid, AccessShareLock);
541 /* New-style: rule and relname both provided */
543 relname = ltruncate(nnames-1, listCopy(qualname));
544 rulename = strVal(nth(nnames-1, qualname));
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);
551 /* Find the rule's pg_rewrite tuple, get its OID */
552 tuple = SearchSysCache(RULERELNAME,
553 ObjectIdGetDatum(reloid),
554 PointerGetDatum(rulename),
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);
564 /* Check object security */
566 aclcheck = pg_class_aclcheck(reloid, GetUserId(), ACL_RULE);
567 if (aclcheck != ACLCHECK_OK)
568 aclcheck_error(aclcheck, rulename);
570 /* pg_rewrite doesn't have a hard-coded OID, so must look it up */
571 classoid = get_system_catalog_relid(RewriteRelationName);
573 /* Call CreateComments() to create/drop the comments */
575 CreateComments(ruleoid, classoid, 0, comment);
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.
588 CommentType(List *typename, char *comment)
593 /* XXX a bit of a crock; should accept TypeName in COMMENT syntax */
594 tname = makeNode(TypeName);
595 tname->names = typename;
598 /* Find the type's oid */
600 oid = typenameTypeId(tname);
602 /* Check object security */
604 if (!pg_type_ownercheck(oid, GetUserId()))
605 aclcheck_error(ACLCHECK_NOT_OWNER, TypeNameToString(tname));
607 /* Call CreateComments() to create/drop the comments */
609 CreateComments(oid, RelOid_pg_type, 0, comment);
613 * CommentAggregate --
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.
621 CommentAggregate(List *aggregate, List *arguments, char *comment)
623 TypeName *aggtype = (TypeName *) lfirst(arguments);
627 /* First, attempt to determine the base aggregate oid */
629 baseoid = typenameTypeId(aggtype);
631 baseoid = InvalidOid;
633 /* Now, attempt to find the actual tuple in pg_proc */
635 oid = find_aggregate_func("CommentAggregate", aggregate, baseoid);
637 /* Next, validate the user's attempt to comment */
639 if (!pg_proc_ownercheck(oid, GetUserId()))
640 aclcheck_error(ACLCHECK_NOT_OWNER, NameListToString(aggregate));
642 /* Call CreateComments() to create/drop the comments */
644 CreateComments(oid, RelOid_pg_proc, 0, comment);
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.
657 CommentProc(List *function, List *arguments, char *comment)
661 /* Look up the procedure */
663 oid = LookupFuncNameTypeNames(function, arguments,
664 true, "CommentProc");
666 /* Now, validate the user's ability to comment on this function */
668 if (!pg_proc_ownercheck(oid, GetUserId()))
669 aclcheck_error(ACLCHECK_NOT_OWNER, NameListToString(function));
671 /* Call CreateComments() to create/drop the comments */
673 CreateComments(oid, RelOid_pg_proc, 0, comment);
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
687 CommentOperator(List *opername, List *arguments, char *comment)
689 TypeName *typenode1 = (TypeName *) lfirst(arguments);
690 TypeName *typenode2 = (TypeName *) lsecond(arguments);
694 /* Look up the operator */
695 oid = LookupOperNameTypeNames(opername, typenode1, typenode2,
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));
702 /* pg_operator doesn't have a hard-coded OID, so must look it up */
703 classoid = get_system_catalog_relid(OperatorRelationName);
705 /* Call CreateComments() to create/drop the comments */
706 CreateComments(oid, classoid, 0, comment);
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.
719 CommentTrigger(List *qualname, char *comment)
727 HeapTuple triggertuple;
729 ScanKeyData entry[2];
732 /* Separate relname and trig name */
733 nnames = length(qualname);
735 elog(ERROR, "CommentTrigger: must specify relation and trigger");
736 relname = ltruncate(nnames-1, listCopy(qualname));
737 trigname = strVal(nth(nnames-1, qualname));
739 /* Open the owning relation to ensure it won't go away meanwhile */
740 rel = makeRangeVarFromNameList(relname);
741 relation = heap_openrv(rel, AccessShareLock);
743 /* Check object security */
745 if (!pg_class_ownercheck(RelationGetRelid(relation), GetUserId()))
746 aclcheck_error(ACLCHECK_NOT_OWNER, RelationGetRelationName(relation));
749 * Fetch the trigger tuple from pg_trigger. There can be only one
750 * because of the unique index.
752 pg_trigger = heap_openr(TriggerRelationName, AccessShareLock);
753 ScanKeyEntryInitialize(&entry[0], 0x0,
754 Anum_pg_trigger_tgrelid,
756 ObjectIdGetDatum(RelationGetRelid(relation)));
757 ScanKeyEntryInitialize(&entry[1], 0x0,
758 Anum_pg_trigger_tgname,
760 CStringGetDatum(trigname));
761 scan = systable_beginscan(pg_trigger, TriggerRelidNameIndex, true,
762 SnapshotNow, 2, entry);
763 triggertuple = systable_getnext(scan);
765 /* If no trigger exists for the relation specified, notify user */
767 if (!HeapTupleIsValid(triggertuple))
768 elog(ERROR, "trigger \"%s\" for relation \"%s\" does not exist",
769 trigname, RelationGetRelationName(relation));
771 AssertTupleDescHasOid(pg_trigger->rd_att);
772 oid = HeapTupleGetOid(triggertuple);
774 systable_endscan(scan);
776 /* Create the comment with the pg_trigger oid */
778 CreateComments(oid, RelationGetRelid(pg_trigger), 0, comment);
780 /* Done, but hold lock on relation */
782 heap_close(pg_trigger, AccessShareLock);
783 heap_close(relation, NoLock);
788 * CommentConstraint --
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.
795 CommentConstraint(List *qualname, char *comment)
801 Relation pg_constraint,
806 Oid conOid = InvalidOid;
808 /* Separate relname and constraint name */
809 nnames = length(qualname);
811 elog(ERROR, "CommentConstraint: must specify relation and constraint");
812 relName = ltruncate(nnames-1, listCopy(qualname));
813 conName = strVal(nth(nnames-1, qualname));
815 /* Open the owning relation to ensure it won't go away meanwhile */
816 rel = makeRangeVarFromNameList(relName);
817 relation = heap_openrv(rel, AccessShareLock);
819 /* Check object security */
821 if (!pg_class_ownercheck(RelationGetRelid(relation), GetUserId()))
822 aclcheck_error(ACLCHECK_NOT_OWNER, RelationGetRelationName(relation));
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;
829 pg_constraint = heap_openr(ConstraintRelationName, AccessShareLock);
831 ScanKeyEntryInitialize(&skey[0], 0x0,
832 Anum_pg_constraint_conrelid, F_OIDEQ,
833 ObjectIdGetDatum(RelationGetRelid(relation)));
835 scan = systable_beginscan(pg_constraint, ConstraintRelidIndex, true,
836 SnapshotNow, 1, skey);
838 while (HeapTupleIsValid(tuple = systable_getnext(scan)))
840 Form_pg_constraint con = (Form_pg_constraint) GETSTRUCT(tuple);
842 if (strcmp(NameStr(con->conname), conName) == 0)
844 if (OidIsValid(conOid))
845 elog(ERROR, "Relation \"%s\" has multiple constraints named \"%s\"",
846 RelationGetRelationName(relation), conName);
847 conOid = HeapTupleGetOid(tuple);
851 systable_endscan(scan);
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));
858 /* Create the comment with the pg_constraint oid */
859 CreateComments(conOid, RelationGetRelid(pg_constraint), 0, comment);
861 /* Done, but hold lock on relation */
862 heap_close(pg_constraint, AccessShareLock);
863 heap_close(relation, NoLock);