Age Owner Branch data TLA Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * comment.c
4 : : *
5 : : * PostgreSQL object comments utility code.
6 : : *
7 : : * Copyright (c) 1996-2026, PostgreSQL Global Development Group
8 : : *
9 : : * IDENTIFICATION
10 : : * src/backend/commands/comment.c
11 : : *
12 : : *-------------------------------------------------------------------------
13 : : */
14 : :
15 : : #include "postgres.h"
16 : :
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_database.h"
24 : : #include "catalog/pg_description.h"
25 : : #include "catalog/pg_shdescription.h"
26 : : #include "commands/comment.h"
27 : : #include "miscadmin.h"
28 : : #include "utils/builtins.h"
29 : : #include "utils/fmgroids.h"
30 : : #include "utils/rel.h"
31 : :
32 : :
33 : : /*
34 : : * CommentObject --
35 : : *
36 : : * This routine is used to add the associated comment into
37 : : * pg_description for the object specified by the given SQL command.
38 : : */
39 : : ObjectAddress
8741 tgl@sss.pgh.pa.us 40 :CBC 3938 : CommentObject(CommentStmt *stmt)
41 : : {
42 : : Relation relation;
4030 alvherre@alvh.no-ip. 43 : 3938 : ObjectAddress address = InvalidObjectAddress;
44 : : bool missing_ok;
45 : :
46 : : /*
47 : : * When loading a dump, we may see a COMMENT ON DATABASE for the old name
48 : : * of the database. Erroring out would prevent pg_restore from completing
49 : : * (which is really pg_restore's fault, but for now we will work around
50 : : * the problem here). Consensus is that the best fix is to treat wrong
51 : : * database name as a WARNING not an ERROR; hence, the following special
52 : : * case.
53 : : */
3410 peter_e@gmx.net 54 [ + + ]: 3938 : if (stmt->objtype == OBJECT_DATABASE)
55 : : {
1648 peter@eisentraut.org 56 : 122 : char *database = strVal(stmt->object);
57 : :
5679 rhaas@postgresql.org 58 [ - + ]: 122 : if (!OidIsValid(get_database_oid(database, true)))
59 : : {
5679 rhaas@postgresql.org 60 [ # # ]:UBC 0 : ereport(WARNING,
61 : : (errcode(ERRCODE_UNDEFINED_DATABASE),
62 : : errmsg("database \"%s\" does not exist", database)));
4030 alvherre@alvh.no-ip. 63 : 0 : return address;
64 : : }
65 : : }
66 : :
67 : : /*
68 : : * During binary upgrade, allow nonexistent large objects so that we don't
69 : : * have to create them during schema restoration. pg_upgrade will
70 : : * transfer the contents of pg_largeobject_metadata via COPY or by
71 : : * copying/linking its files from the old cluster later on.
72 : : */
34 nathan@postgresql.or 73 [ + + + + ]:GNC 3938 : missing_ok = IsBinaryUpgrade && stmt->objtype == OBJECT_LARGEOBJECT;
74 : :
75 : : /*
76 : : * Translate the parser representation that identifies this object into an
77 : : * ObjectAddress. get_object_address() will throw an error if the object
78 : : * does not exist, and will also acquire a lock on the target to guard
79 : : * against concurrent DROP operations.
80 : : */
3410 peter_e@gmx.net 81 :CBC 3938 : address = get_object_address(stmt->objtype, stmt->object,
82 : : &relation, ShareUpdateExclusiveLock,
83 : : missing_ok);
84 : :
85 : : /* Require ownership of the target object. */
5490 tgl@sss.pgh.pa.us 86 : 3871 : check_object_ownership(GetUserId(), stmt->objtype, address,
87 : : stmt->object, relation);
88 : :
89 : : /* Perform other integrity checks as needed. */
8741 90 [ + + ]: 3859 : switch (stmt->objtype)
91 : : {
8297 peter_e@gmx.net 92 : 105 : case OBJECT_COLUMN:
93 : :
94 : : /*
95 : : * Allow comments only on columns of tables, views, materialized
96 : : * views, composite types, and foreign tables (which are the only
97 : : * relkinds for which pg_dump will dump per-column comments). In
98 : : * particular we wish to disallow comments on index columns,
99 : : * because the naming of an index's columns may change across PG
100 : : * versions, so dumping per-column comments could create reload
101 : : * failures.
102 : : */
5490 tgl@sss.pgh.pa.us 103 [ + + ]: 105 : if (relation->rd_rel->relkind != RELKIND_RELATION &&
104 [ + - ]: 28 : relation->rd_rel->relkind != RELKIND_VIEW &&
4760 kgrittn@postgresql.o 105 [ + - ]: 28 : relation->rd_rel->relkind != RELKIND_MATVIEW &&
5490 tgl@sss.pgh.pa.us 106 [ + + ]: 28 : relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
3253 simon@2ndQuadrant.co 107 [ + + ]: 21 : relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&
108 [ - + ]: 9 : relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
5679 rhaas@postgresql.org 109 [ # # ]:UBC 0 : ereport(ERROR,
110 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
111 : : errmsg("cannot set comment on relation \"%s\"",
112 : : RelationGetRelationName(relation)),
113 : : errdetail_relkind_not_supported(relation->rd_rel->relkind)));
6781 tgl@sss.pgh.pa.us 114 :CBC 105 : break;
9468 bruce@momjian.us 115 : 3754 : default:
5490 tgl@sss.pgh.pa.us 116 : 3754 : break;
117 : : }
118 : :
119 : : /*
120 : : * Databases, tablespaces, and roles are cluster-wide objects, so any
121 : : * comments on those objects are recorded in the shared pg_shdescription
122 : : * catalog. Comments on all other objects are recorded in pg_description.
123 : : */
5679 rhaas@postgresql.org 124 [ + + + - ]: 3859 : if (stmt->objtype == OBJECT_DATABASE || stmt->objtype == OBJECT_TABLESPACE
5591 peter_e@gmx.net 125 [ + + ]: 3737 : || stmt->objtype == OBJECT_ROLE)
5679 rhaas@postgresql.org 126 : 135 : CreateSharedComments(address.objectId, address.classId, stmt->comment);
127 : : else
128 : 3724 : CreateComments(address.objectId, address.classId, address.objectSubId,
129 : 3724 : stmt->comment);
130 : :
131 : : /*
132 : : * If get_object_address() opened the relation for us, we close it to keep
133 : : * the reference count correct - but we retain any locks acquired by
134 : : * get_object_address() until commit time, to guard against concurrent
135 : : * activity.
136 : : */
137 [ + + ]: 3859 : if (relation != NULL)
138 : 322 : relation_close(relation, NoLock);
139 : :
4030 alvherre@alvh.no-ip. 140 : 3859 : return address;
141 : : }
142 : :
143 : : /*
144 : : * CreateComments --
145 : : *
146 : : * Create a comment for the specified object descriptor. Inserts a new
147 : : * pg_description tuple, or replaces an existing one with the same key.
148 : : *
149 : : * If the comment given is null or an empty string, instead delete any
150 : : * existing comment for the specified key.
151 : : */
152 : : void
3057 peter_e@gmx.net 153 : 45002 : CreateComments(Oid oid, Oid classoid, int32 subid, const char *comment)
154 : : {
155 : : Relation description;
156 : : ScanKeyData skey[3];
157 : : SysScanDesc sd;
158 : : HeapTuple oldtuple;
8983 tgl@sss.pgh.pa.us 159 : 45002 : HeapTuple newtuple = NULL;
160 : : Datum values[Natts_pg_description];
161 : : bool nulls[Natts_pg_description];
162 : : bool replaces[Natts_pg_description];
163 : : int i;
164 : :
165 : : /* Reduce empty-string to NULL case */
166 [ + + + + ]: 45002 : if (comment != NULL && strlen(comment) == 0)
167 : 3 : comment = NULL;
168 : :
169 : : /* Prepare to form or update a tuple, if necessary */
170 [ + + ]: 45002 : if (comment != NULL)
171 : : {
9468 bruce@momjian.us 172 [ + + ]: 224750 : for (i = 0; i < Natts_pg_description; i++)
173 : : {
6342 tgl@sss.pgh.pa.us 174 : 179800 : nulls[i] = false;
175 : 179800 : replaces[i] = true;
176 : : }
5386 177 : 44950 : values[Anum_pg_description_objoid - 1] = ObjectIdGetDatum(oid);
178 : 44950 : values[Anum_pg_description_classoid - 1] = ObjectIdGetDatum(classoid);
179 : 44950 : values[Anum_pg_description_objsubid - 1] = Int32GetDatum(subid);
180 : 44950 : values[Anum_pg_description_description - 1] = CStringGetTextDatum(comment);
181 : : }
182 : :
183 : : /* Use the index to search for a matching old tuple */
184 : :
8159 185 : 45002 : ScanKeyInit(&skey[0],
186 : : Anum_pg_description_objoid,
187 : : BTEqualStrategyNumber, F_OIDEQ,
188 : : ObjectIdGetDatum(oid));
189 : 45002 : ScanKeyInit(&skey[1],
190 : : Anum_pg_description_classoid,
191 : : BTEqualStrategyNumber, F_OIDEQ,
192 : : ObjectIdGetDatum(classoid));
193 : 45002 : ScanKeyInit(&skey[2],
194 : : Anum_pg_description_objsubid,
195 : : BTEqualStrategyNumber, F_INT4EQ,
196 : : Int32GetDatum(subid));
197 : :
2610 andres@anarazel.de 198 : 45002 : description = table_open(DescriptionRelationId, RowExclusiveLock);
199 : :
7640 tgl@sss.pgh.pa.us 200 : 45002 : sd = systable_beginscan(description, DescriptionObjIndexId, true,
201 : : NULL, 3, skey);
202 : :
8700 203 [ + + ]: 45002 : while ((oldtuple = systable_getnext(sd)) != NULL)
204 : : {
205 : : /* Found the old tuple, so delete or update it */
206 : :
8983 207 [ + + ]: 89 : if (comment == NULL)
3329 208 : 52 : CatalogTupleDelete(description, &oldtuple->t_self);
209 : : else
210 : : {
6342 211 : 37 : newtuple = heap_modify_tuple(oldtuple, RelationGetDescr(description), values,
212 : : nulls, replaces);
3330 alvherre@alvh.no-ip. 213 : 37 : CatalogTupleUpdate(description, &oldtuple->t_self, newtuple);
214 : : }
215 : :
8983 tgl@sss.pgh.pa.us 216 : 89 : break; /* Assume there can be only one match */
217 : : }
218 : :
8700 219 : 45002 : systable_endscan(sd);
220 : :
221 : : /* If we didn't find an old tuple, insert a new one */
222 : :
223 [ + + + + ]: 45002 : if (newtuple == NULL && comment != NULL)
224 : : {
6342 225 : 44913 : newtuple = heap_form_tuple(RelationGetDescr(description),
226 : : values, nulls);
3330 alvherre@alvh.no-ip. 227 : 44913 : CatalogTupleInsert(description, newtuple);
228 : : }
229 : :
8983 tgl@sss.pgh.pa.us 230 [ + + ]: 45002 : if (newtuple != NULL)
231 : 44950 : heap_freetuple(newtuple);
232 : :
233 : : /* Done */
234 : :
2610 andres@anarazel.de 235 : 45002 : table_close(description, NoLock);
9637 bruce@momjian.us 236 : 45002 : }
237 : :
238 : : /*
239 : : * CreateSharedComments --
240 : : *
241 : : * Create a comment for the specified shared object descriptor. Inserts a
242 : : * new pg_shdescription tuple, or replaces an existing one with the same key.
243 : : *
244 : : * If the comment given is null or an empty string, instead delete any
245 : : * existing comment for the specified key.
246 : : */
247 : : void
3057 peter_e@gmx.net 248 : 135 : CreateSharedComments(Oid oid, Oid classoid, const char *comment)
249 : : {
250 : : Relation shdescription;
251 : : ScanKeyData skey[2];
252 : : SysScanDesc sd;
253 : : HeapTuple oldtuple;
7336 bruce@momjian.us 254 : 135 : HeapTuple newtuple = NULL;
255 : : Datum values[Natts_pg_shdescription];
256 : : bool nulls[Natts_pg_shdescription];
257 : : bool replaces[Natts_pg_shdescription];
258 : : int i;
259 : :
260 : : /* Reduce empty-string to NULL case */
261 [ + + + + ]: 135 : if (comment != NULL && strlen(comment) == 0)
262 : 3 : comment = NULL;
263 : :
264 : : /* Prepare to form or update a tuple, if necessary */
265 [ + + ]: 135 : if (comment != NULL)
266 : : {
267 [ + + ]: 504 : for (i = 0; i < Natts_pg_shdescription; i++)
268 : : {
6342 tgl@sss.pgh.pa.us 269 : 378 : nulls[i] = false;
270 : 378 : replaces[i] = true;
271 : : }
5386 272 : 126 : values[Anum_pg_shdescription_objoid - 1] = ObjectIdGetDatum(oid);
273 : 126 : values[Anum_pg_shdescription_classoid - 1] = ObjectIdGetDatum(classoid);
274 : 126 : values[Anum_pg_shdescription_description - 1] = CStringGetTextDatum(comment);
275 : : }
276 : :
277 : : /* Use the index to search for a matching old tuple */
278 : :
7336 bruce@momjian.us 279 : 135 : ScanKeyInit(&skey[0],
280 : : Anum_pg_shdescription_objoid,
281 : : BTEqualStrategyNumber, F_OIDEQ,
282 : : ObjectIdGetDatum(oid));
283 : 135 : ScanKeyInit(&skey[1],
284 : : Anum_pg_shdescription_classoid,
285 : : BTEqualStrategyNumber, F_OIDEQ,
286 : : ObjectIdGetDatum(classoid));
287 : :
2610 andres@anarazel.de 288 : 135 : shdescription = table_open(SharedDescriptionRelationId, RowExclusiveLock);
289 : :
7336 bruce@momjian.us 290 : 135 : sd = systable_beginscan(shdescription, SharedDescriptionObjIndexId, true,
291 : : NULL, 2, skey);
292 : :
293 [ + + ]: 135 : while ((oldtuple = systable_getnext(sd)) != NULL)
294 : : {
295 : : /* Found the old tuple, so delete or update it */
296 : :
297 [ + - ]: 9 : if (comment == NULL)
3329 tgl@sss.pgh.pa.us 298 : 9 : CatalogTupleDelete(shdescription, &oldtuple->t_self);
299 : : else
300 : : {
6342 tgl@sss.pgh.pa.us 301 :UBC 0 : newtuple = heap_modify_tuple(oldtuple, RelationGetDescr(shdescription),
302 : : values, nulls, replaces);
3330 alvherre@alvh.no-ip. 303 : 0 : CatalogTupleUpdate(shdescription, &oldtuple->t_self, newtuple);
304 : : }
305 : :
7102 bruce@momjian.us 306 :CBC 9 : break; /* Assume there can be only one match */
307 : : }
308 : :
7336 309 : 135 : systable_endscan(sd);
310 : :
311 : : /* If we didn't find an old tuple, insert a new one */
312 : :
313 [ + - + + ]: 135 : if (newtuple == NULL && comment != NULL)
314 : : {
6342 tgl@sss.pgh.pa.us 315 : 126 : newtuple = heap_form_tuple(RelationGetDescr(shdescription),
316 : : values, nulls);
3330 alvherre@alvh.no-ip. 317 : 126 : CatalogTupleInsert(shdescription, newtuple);
318 : : }
319 : :
7336 bruce@momjian.us 320 [ + + ]: 135 : if (newtuple != NULL)
321 : 126 : heap_freetuple(newtuple);
322 : :
323 : : /* Done */
324 : :
2610 andres@anarazel.de 325 : 135 : table_close(shdescription, NoLock);
7336 bruce@momjian.us 326 : 135 : }
327 : :
328 : : /*
329 : : * DeleteComments -- remove comments for an object
330 : : *
331 : : * If subid is nonzero then only comments matching it will be removed.
332 : : * If subid is zero, all comments matching the oid/classoid will be removed
333 : : * (this corresponds to deleting a whole object).
334 : : */
335 : : void
8647 tgl@sss.pgh.pa.us 336 : 116695 : DeleteComments(Oid oid, Oid classoid, int32 subid)
337 : : {
338 : : Relation description;
339 : : ScanKeyData skey[3];
340 : : int nkeys;
341 : : SysScanDesc sd;
342 : : HeapTuple oldtuple;
343 : :
344 : : /* Use the index to search for all matching old tuples */
345 : :
8159 346 : 116695 : ScanKeyInit(&skey[0],
347 : : Anum_pg_description_objoid,
348 : : BTEqualStrategyNumber, F_OIDEQ,
349 : : ObjectIdGetDatum(oid));
350 : 116695 : ScanKeyInit(&skey[1],
351 : : Anum_pg_description_classoid,
352 : : BTEqualStrategyNumber, F_OIDEQ,
353 : : ObjectIdGetDatum(classoid));
354 : :
8647 355 [ + + ]: 116695 : if (subid != 0)
356 : : {
8159 357 : 1070 : ScanKeyInit(&skey[2],
358 : : Anum_pg_description_objsubid,
359 : : BTEqualStrategyNumber, F_INT4EQ,
360 : : Int32GetDatum(subid));
8647 361 : 1070 : nkeys = 3;
362 : : }
363 : : else
364 : 115625 : nkeys = 2;
365 : :
2610 andres@anarazel.de 366 : 116695 : description = table_open(DescriptionRelationId, RowExclusiveLock);
367 : :
7640 tgl@sss.pgh.pa.us 368 : 116695 : sd = systable_beginscan(description, DescriptionObjIndexId, true,
369 : : NULL, nkeys, skey);
370 : :
8700 371 [ + + ]: 117109 : while ((oldtuple = systable_getnext(sd)) != NULL)
3329 372 : 414 : CatalogTupleDelete(description, &oldtuple->t_self);
373 : :
374 : : /* Done */
375 : :
8700 376 : 116695 : systable_endscan(sd);
2610 andres@anarazel.de 377 : 116695 : table_close(description, RowExclusiveLock);
9637 bruce@momjian.us 378 : 116695 : }
379 : :
380 : : /*
381 : : * DeleteSharedComments -- remove comments for a shared object
382 : : */
383 : : void
7336 384 : 788 : DeleteSharedComments(Oid oid, Oid classoid)
385 : : {
386 : : Relation shdescription;
387 : : ScanKeyData skey[2];
388 : : SysScanDesc sd;
389 : : HeapTuple oldtuple;
390 : :
391 : : /* Use the index to search for all matching old tuples */
392 : :
393 : 788 : ScanKeyInit(&skey[0],
394 : : Anum_pg_shdescription_objoid,
395 : : BTEqualStrategyNumber, F_OIDEQ,
396 : : ObjectIdGetDatum(oid));
397 : 788 : ScanKeyInit(&skey[1],
398 : : Anum_pg_shdescription_classoid,
399 : : BTEqualStrategyNumber, F_OIDEQ,
400 : : ObjectIdGetDatum(classoid));
401 : :
2610 andres@anarazel.de 402 : 788 : shdescription = table_open(SharedDescriptionRelationId, RowExclusiveLock);
403 : :
7336 bruce@momjian.us 404 : 788 : sd = systable_beginscan(shdescription, SharedDescriptionObjIndexId, true,
405 : : NULL, 2, skey);
406 : :
407 [ + + ]: 807 : while ((oldtuple = systable_getnext(sd)) != NULL)
3329 tgl@sss.pgh.pa.us 408 : 19 : CatalogTupleDelete(shdescription, &oldtuple->t_self);
409 : :
410 : : /* Done */
411 : :
7336 bruce@momjian.us 412 : 788 : systable_endscan(sd);
2610 andres@anarazel.de 413 : 788 : table_close(shdescription, RowExclusiveLock);
7336 bruce@momjian.us 414 : 788 : }
415 : :
416 : : /*
417 : : * GetComment -- get the comment for an object, or null if not found.
418 : : */
419 : : char *
5998 andrew@dunslane.net 420 : 843 : GetComment(Oid oid, Oid classoid, int32 subid)
421 : : {
422 : : Relation description;
423 : : ScanKeyData skey[3];
424 : : SysScanDesc sd;
425 : : TupleDesc tupdesc;
426 : : HeapTuple tuple;
427 : : char *comment;
428 : :
429 : : /* Use the index to search for a matching old tuple */
430 : :
431 : 843 : ScanKeyInit(&skey[0],
432 : : Anum_pg_description_objoid,
433 : : BTEqualStrategyNumber, F_OIDEQ,
434 : : ObjectIdGetDatum(oid));
435 : 843 : ScanKeyInit(&skey[1],
436 : : Anum_pg_description_classoid,
437 : : BTEqualStrategyNumber, F_OIDEQ,
438 : : ObjectIdGetDatum(classoid));
439 : 843 : ScanKeyInit(&skey[2],
440 : : Anum_pg_description_objsubid,
441 : : BTEqualStrategyNumber, F_INT4EQ,
442 : : Int32GetDatum(subid));
443 : :
2610 andres@anarazel.de 444 : 843 : description = table_open(DescriptionRelationId, AccessShareLock);
5998 andrew@dunslane.net 445 : 843 : tupdesc = RelationGetDescr(description);
446 : :
447 : 843 : sd = systable_beginscan(description, DescriptionObjIndexId, true,
448 : : NULL, 3, skey);
449 : :
5861 bruce@momjian.us 450 : 843 : comment = NULL;
5998 andrew@dunslane.net 451 [ + + ]: 843 : while ((tuple = systable_getnext(sd)) != NULL)
452 : : {
453 : : Datum value;
454 : : bool isnull;
455 : :
456 : : /* Found the tuple, get description field */
457 : 174 : value = heap_getattr(tuple, Anum_pg_description_description, tupdesc, &isnull);
458 [ + - ]: 174 : if (!isnull)
459 : 174 : comment = TextDatumGetCString(value);
460 : 174 : break; /* Assume there can be only one match */
461 : : }
462 : :
463 : 843 : systable_endscan(sd);
464 : :
465 : : /* Done */
2610 andres@anarazel.de 466 : 843 : table_close(description, AccessShareLock);
467 : :
5998 andrew@dunslane.net 468 : 843 : return comment;
469 : : }
|