1 /*-------------------------------------------------------------------------
5 * PostgreSQL object comments utility code.
7 * Copyright (c) 1996-2025, PostgreSQL Global Development Group
10 * src/backend/commands/comment.c
12 *-------------------------------------------------------------------------
17 #include "access/genam.h"
18 #include "access/htup_details.h"
19 #include "access/relation.h"
20 #include "access/table.h"
21 #include "catalog/indexing.h"
22 #include "catalog/objectaddress.h"
23 #include "catalog/pg_description.h"
24 #include "catalog/pg_shdescription.h"
25 #include "commands/comment.h"
26 #include "commands/dbcommands.h"
27 #include "miscadmin.h"
28 #include "utils/builtins.h"
29 #include "utils/fmgroids.h"
30 #include "utils/rel.h"
36 * This routine is used to add the associated comment into
37 * pg_description for the object specified by the given SQL command.
40 CommentObject(CommentStmt
*stmt
)
43 ObjectAddress address
= InvalidObjectAddress
;
46 * When loading a dump, we may see a COMMENT ON DATABASE for the old name
47 * of the database. Erroring out would prevent pg_restore from completing
48 * (which is really pg_restore's fault, but for now we will work around
49 * the problem here). Consensus is that the best fix is to treat wrong
50 * database name as a WARNING not an ERROR; hence, the following special
53 if (stmt
->objtype
== OBJECT_DATABASE
)
55 char *database
= strVal(stmt
->object
);
57 if (!OidIsValid(get_database_oid(database
, true)))
60 (errcode(ERRCODE_UNDEFINED_DATABASE
),
61 errmsg("database \"%s\" does not exist", database
)));
67 * Translate the parser representation that identifies this object into an
68 * ObjectAddress. get_object_address() will throw an error if the object
69 * does not exist, and will also acquire a lock on the target to guard
70 * against concurrent DROP operations.
72 address
= get_object_address(stmt
->objtype
, stmt
->object
,
73 &relation
, ShareUpdateExclusiveLock
, false);
75 /* Require ownership of the target object. */
76 check_object_ownership(GetUserId(), stmt
->objtype
, address
,
77 stmt
->object
, relation
);
79 /* Perform other integrity checks as needed. */
80 switch (stmt
->objtype
)
85 * Allow comments only on columns of tables, views, materialized
86 * views, composite types, and foreign tables (which are the only
87 * relkinds for which pg_dump will dump per-column comments). In
88 * particular we wish to disallow comments on index columns,
89 * because the naming of an index's columns may change across PG
90 * versions, so dumping per-column comments could create reload
93 if (relation
->rd_rel
->relkind
!= RELKIND_RELATION
&&
94 relation
->rd_rel
->relkind
!= RELKIND_VIEW
&&
95 relation
->rd_rel
->relkind
!= RELKIND_MATVIEW
&&
96 relation
->rd_rel
->relkind
!= RELKIND_COMPOSITE_TYPE
&&
97 relation
->rd_rel
->relkind
!= RELKIND_FOREIGN_TABLE
&&
98 relation
->rd_rel
->relkind
!= RELKIND_PARTITIONED_TABLE
)
100 (errcode(ERRCODE_WRONG_OBJECT_TYPE
),
101 errmsg("cannot set comment on relation \"%s\"",
102 RelationGetRelationName(relation
)),
103 errdetail_relkind_not_supported(relation
->rd_rel
->relkind
)));
110 * Databases, tablespaces, and roles are cluster-wide objects, so any
111 * comments on those objects are recorded in the shared pg_shdescription
112 * catalog. Comments on all other objects are recorded in pg_description.
114 if (stmt
->objtype
== OBJECT_DATABASE
|| stmt
->objtype
== OBJECT_TABLESPACE
115 || stmt
->objtype
== OBJECT_ROLE
)
116 CreateSharedComments(address
.objectId
, address
.classId
, stmt
->comment
);
118 CreateComments(address
.objectId
, address
.classId
, address
.objectSubId
,
122 * If get_object_address() opened the relation for us, we close it to keep
123 * the reference count correct - but we retain any locks acquired by
124 * get_object_address() until commit time, to guard against concurrent
127 if (relation
!= NULL
)
128 relation_close(relation
, NoLock
);
136 * Create a comment for the specified object descriptor. Inserts a new
137 * pg_description tuple, or replaces an existing one with the same key.
139 * If the comment given is null or an empty string, instead delete any
140 * existing comment for the specified key.
143 CreateComments(Oid oid
, Oid classoid
, int32 subid
, const char *comment
)
145 Relation description
;
149 HeapTuple newtuple
= NULL
;
150 Datum values
[Natts_pg_description
];
151 bool nulls
[Natts_pg_description
];
152 bool replaces
[Natts_pg_description
];
155 /* Reduce empty-string to NULL case */
156 if (comment
!= NULL
&& strlen(comment
) == 0)
159 /* Prepare to form or update a tuple, if necessary */
162 for (i
= 0; i
< Natts_pg_description
; i
++)
167 values
[Anum_pg_description_objoid
- 1] = ObjectIdGetDatum(oid
);
168 values
[Anum_pg_description_classoid
- 1] = ObjectIdGetDatum(classoid
);
169 values
[Anum_pg_description_objsubid
- 1] = Int32GetDatum(subid
);
170 values
[Anum_pg_description_description
- 1] = CStringGetTextDatum(comment
);
173 /* Use the index to search for a matching old tuple */
175 ScanKeyInit(&skey
[0],
176 Anum_pg_description_objoid
,
177 BTEqualStrategyNumber
, F_OIDEQ
,
178 ObjectIdGetDatum(oid
));
179 ScanKeyInit(&skey
[1],
180 Anum_pg_description_classoid
,
181 BTEqualStrategyNumber
, F_OIDEQ
,
182 ObjectIdGetDatum(classoid
));
183 ScanKeyInit(&skey
[2],
184 Anum_pg_description_objsubid
,
185 BTEqualStrategyNumber
, F_INT4EQ
,
186 Int32GetDatum(subid
));
188 description
= table_open(DescriptionRelationId
, RowExclusiveLock
);
190 sd
= systable_beginscan(description
, DescriptionObjIndexId
, true,
193 while ((oldtuple
= systable_getnext(sd
)) != NULL
)
195 /* Found the old tuple, so delete or update it */
198 CatalogTupleDelete(description
, &oldtuple
->t_self
);
201 newtuple
= heap_modify_tuple(oldtuple
, RelationGetDescr(description
), values
,
203 CatalogTupleUpdate(description
, &oldtuple
->t_self
, newtuple
);
206 break; /* Assume there can be only one match */
209 systable_endscan(sd
);
211 /* If we didn't find an old tuple, insert a new one */
213 if (newtuple
== NULL
&& comment
!= NULL
)
215 newtuple
= heap_form_tuple(RelationGetDescr(description
),
217 CatalogTupleInsert(description
, newtuple
);
220 if (newtuple
!= NULL
)
221 heap_freetuple(newtuple
);
225 table_close(description
, NoLock
);
229 * CreateSharedComments --
231 * Create a comment for the specified shared object descriptor. Inserts a
232 * new pg_shdescription tuple, or replaces an existing one with the same key.
234 * If the comment given is null or an empty string, instead delete any
235 * existing comment for the specified key.
238 CreateSharedComments(Oid oid
, Oid classoid
, const char *comment
)
240 Relation shdescription
;
244 HeapTuple newtuple
= NULL
;
245 Datum values
[Natts_pg_shdescription
];
246 bool nulls
[Natts_pg_shdescription
];
247 bool replaces
[Natts_pg_shdescription
];
250 /* Reduce empty-string to NULL case */
251 if (comment
!= NULL
&& strlen(comment
) == 0)
254 /* Prepare to form or update a tuple, if necessary */
257 for (i
= 0; i
< Natts_pg_shdescription
; i
++)
262 values
[Anum_pg_shdescription_objoid
- 1] = ObjectIdGetDatum(oid
);
263 values
[Anum_pg_shdescription_classoid
- 1] = ObjectIdGetDatum(classoid
);
264 values
[Anum_pg_shdescription_description
- 1] = CStringGetTextDatum(comment
);
267 /* Use the index to search for a matching old tuple */
269 ScanKeyInit(&skey
[0],
270 Anum_pg_shdescription_objoid
,
271 BTEqualStrategyNumber
, F_OIDEQ
,
272 ObjectIdGetDatum(oid
));
273 ScanKeyInit(&skey
[1],
274 Anum_pg_shdescription_classoid
,
275 BTEqualStrategyNumber
, F_OIDEQ
,
276 ObjectIdGetDatum(classoid
));
278 shdescription
= table_open(SharedDescriptionRelationId
, RowExclusiveLock
);
280 sd
= systable_beginscan(shdescription
, SharedDescriptionObjIndexId
, true,
283 while ((oldtuple
= systable_getnext(sd
)) != NULL
)
285 /* Found the old tuple, so delete or update it */
288 CatalogTupleDelete(shdescription
, &oldtuple
->t_self
);
291 newtuple
= heap_modify_tuple(oldtuple
, RelationGetDescr(shdescription
),
292 values
, nulls
, replaces
);
293 CatalogTupleUpdate(shdescription
, &oldtuple
->t_self
, newtuple
);
296 break; /* Assume there can be only one match */
299 systable_endscan(sd
);
301 /* If we didn't find an old tuple, insert a new one */
303 if (newtuple
== NULL
&& comment
!= NULL
)
305 newtuple
= heap_form_tuple(RelationGetDescr(shdescription
),
307 CatalogTupleInsert(shdescription
, newtuple
);
310 if (newtuple
!= NULL
)
311 heap_freetuple(newtuple
);
315 table_close(shdescription
, NoLock
);
319 * DeleteComments -- remove comments for an object
321 * If subid is nonzero then only comments matching it will be removed.
322 * If subid is zero, all comments matching the oid/classoid will be removed
323 * (this corresponds to deleting a whole object).
326 DeleteComments(Oid oid
, Oid classoid
, int32 subid
)
328 Relation description
;
334 /* Use the index to search for all matching old tuples */
336 ScanKeyInit(&skey
[0],
337 Anum_pg_description_objoid
,
338 BTEqualStrategyNumber
, F_OIDEQ
,
339 ObjectIdGetDatum(oid
));
340 ScanKeyInit(&skey
[1],
341 Anum_pg_description_classoid
,
342 BTEqualStrategyNumber
, F_OIDEQ
,
343 ObjectIdGetDatum(classoid
));
347 ScanKeyInit(&skey
[2],
348 Anum_pg_description_objsubid
,
349 BTEqualStrategyNumber
, F_INT4EQ
,
350 Int32GetDatum(subid
));
356 description
= table_open(DescriptionRelationId
, RowExclusiveLock
);
358 sd
= systable_beginscan(description
, DescriptionObjIndexId
, true,
361 while ((oldtuple
= systable_getnext(sd
)) != NULL
)
362 CatalogTupleDelete(description
, &oldtuple
->t_self
);
366 systable_endscan(sd
);
367 table_close(description
, RowExclusiveLock
);
371 * DeleteSharedComments -- remove comments for a shared object
374 DeleteSharedComments(Oid oid
, Oid classoid
)
376 Relation shdescription
;
381 /* Use the index to search for all matching old tuples */
383 ScanKeyInit(&skey
[0],
384 Anum_pg_shdescription_objoid
,
385 BTEqualStrategyNumber
, F_OIDEQ
,
386 ObjectIdGetDatum(oid
));
387 ScanKeyInit(&skey
[1],
388 Anum_pg_shdescription_classoid
,
389 BTEqualStrategyNumber
, F_OIDEQ
,
390 ObjectIdGetDatum(classoid
));
392 shdescription
= table_open(SharedDescriptionRelationId
, RowExclusiveLock
);
394 sd
= systable_beginscan(shdescription
, SharedDescriptionObjIndexId
, true,
397 while ((oldtuple
= systable_getnext(sd
)) != NULL
)
398 CatalogTupleDelete(shdescription
, &oldtuple
->t_self
);
402 systable_endscan(sd
);
403 table_close(shdescription
, RowExclusiveLock
);
407 * GetComment -- get the comment for an object, or null if not found.
410 GetComment(Oid oid
, Oid classoid
, int32 subid
)
412 Relation description
;
419 /* Use the index to search for a matching old tuple */
421 ScanKeyInit(&skey
[0],
422 Anum_pg_description_objoid
,
423 BTEqualStrategyNumber
, F_OIDEQ
,
424 ObjectIdGetDatum(oid
));
425 ScanKeyInit(&skey
[1],
426 Anum_pg_description_classoid
,
427 BTEqualStrategyNumber
, F_OIDEQ
,
428 ObjectIdGetDatum(classoid
));
429 ScanKeyInit(&skey
[2],
430 Anum_pg_description_objsubid
,
431 BTEqualStrategyNumber
, F_INT4EQ
,
432 Int32GetDatum(subid
));
434 description
= table_open(DescriptionRelationId
, AccessShareLock
);
435 tupdesc
= RelationGetDescr(description
);
437 sd
= systable_beginscan(description
, DescriptionObjIndexId
, true,
441 while ((tuple
= systable_getnext(sd
)) != NULL
)
446 /* Found the tuple, get description field */
447 value
= heap_getattr(tuple
, Anum_pg_description_description
, tupdesc
, &isnull
);
449 comment
= TextDatumGetCString(value
);
450 break; /* Assume there can be only one match */
453 systable_endscan(sd
);
456 table_close(description
, AccessShareLock
);