Move routines to manipulate WAL into PostgreSQL::Test::Cluster
[pgsql.git] / src / backend / commands / comment.c
blobf67a8b95d29de9e4fffd707eee39611a8a287f44
1 /*-------------------------------------------------------------------------
3 * comment.c
5 * PostgreSQL object comments utility code.
7 * Copyright (c) 1996-2025, PostgreSQL Global Development Group
9 * IDENTIFICATION
10 * src/backend/commands/comment.c
12 *-------------------------------------------------------------------------
15 #include "postgres.h"
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"
34 * CommentObject --
36 * This routine is used to add the associated comment into
37 * pg_description for the object specified by the given SQL command.
39 ObjectAddress
40 CommentObject(CommentStmt *stmt)
42 Relation relation;
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
51 * case.
53 if (stmt->objtype == OBJECT_DATABASE)
55 char *database = strVal(stmt->object);
57 if (!OidIsValid(get_database_oid(database, true)))
59 ereport(WARNING,
60 (errcode(ERRCODE_UNDEFINED_DATABASE),
61 errmsg("database \"%s\" does not exist", database)));
62 return address;
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)
82 case OBJECT_COLUMN:
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
91 * failures.
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)
99 ereport(ERROR,
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)));
104 break;
105 default:
106 break;
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);
117 else
118 CreateComments(address.objectId, address.classId, address.objectSubId,
119 stmt->comment);
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
125 * activity.
127 if (relation != NULL)
128 relation_close(relation, NoLock);
130 return address;
134 * CreateComments --
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.
142 void
143 CreateComments(Oid oid, Oid classoid, int32 subid, const char *comment)
145 Relation description;
146 ScanKeyData skey[3];
147 SysScanDesc sd;
148 HeapTuple oldtuple;
149 HeapTuple newtuple = NULL;
150 Datum values[Natts_pg_description];
151 bool nulls[Natts_pg_description];
152 bool replaces[Natts_pg_description];
153 int i;
155 /* Reduce empty-string to NULL case */
156 if (comment != NULL && strlen(comment) == 0)
157 comment = NULL;
159 /* Prepare to form or update a tuple, if necessary */
160 if (comment != NULL)
162 for (i = 0; i < Natts_pg_description; i++)
164 nulls[i] = false;
165 replaces[i] = true;
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,
191 NULL, 3, skey);
193 while ((oldtuple = systable_getnext(sd)) != NULL)
195 /* Found the old tuple, so delete or update it */
197 if (comment == NULL)
198 CatalogTupleDelete(description, &oldtuple->t_self);
199 else
201 newtuple = heap_modify_tuple(oldtuple, RelationGetDescr(description), values,
202 nulls, replaces);
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),
216 values, nulls);
217 CatalogTupleInsert(description, newtuple);
220 if (newtuple != NULL)
221 heap_freetuple(newtuple);
223 /* Done */
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.
237 void
238 CreateSharedComments(Oid oid, Oid classoid, const char *comment)
240 Relation shdescription;
241 ScanKeyData skey[2];
242 SysScanDesc sd;
243 HeapTuple oldtuple;
244 HeapTuple newtuple = NULL;
245 Datum values[Natts_pg_shdescription];
246 bool nulls[Natts_pg_shdescription];
247 bool replaces[Natts_pg_shdescription];
248 int i;
250 /* Reduce empty-string to NULL case */
251 if (comment != NULL && strlen(comment) == 0)
252 comment = NULL;
254 /* Prepare to form or update a tuple, if necessary */
255 if (comment != NULL)
257 for (i = 0; i < Natts_pg_shdescription; i++)
259 nulls[i] = false;
260 replaces[i] = true;
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,
281 NULL, 2, skey);
283 while ((oldtuple = systable_getnext(sd)) != NULL)
285 /* Found the old tuple, so delete or update it */
287 if (comment == NULL)
288 CatalogTupleDelete(shdescription, &oldtuple->t_self);
289 else
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),
306 values, nulls);
307 CatalogTupleInsert(shdescription, newtuple);
310 if (newtuple != NULL)
311 heap_freetuple(newtuple);
313 /* Done */
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).
325 void
326 DeleteComments(Oid oid, Oid classoid, int32 subid)
328 Relation description;
329 ScanKeyData skey[3];
330 int nkeys;
331 SysScanDesc sd;
332 HeapTuple oldtuple;
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));
345 if (subid != 0)
347 ScanKeyInit(&skey[2],
348 Anum_pg_description_objsubid,
349 BTEqualStrategyNumber, F_INT4EQ,
350 Int32GetDatum(subid));
351 nkeys = 3;
353 else
354 nkeys = 2;
356 description = table_open(DescriptionRelationId, RowExclusiveLock);
358 sd = systable_beginscan(description, DescriptionObjIndexId, true,
359 NULL, nkeys, skey);
361 while ((oldtuple = systable_getnext(sd)) != NULL)
362 CatalogTupleDelete(description, &oldtuple->t_self);
364 /* Done */
366 systable_endscan(sd);
367 table_close(description, RowExclusiveLock);
371 * DeleteSharedComments -- remove comments for a shared object
373 void
374 DeleteSharedComments(Oid oid, Oid classoid)
376 Relation shdescription;
377 ScanKeyData skey[2];
378 SysScanDesc sd;
379 HeapTuple oldtuple;
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,
395 NULL, 2, skey);
397 while ((oldtuple = systable_getnext(sd)) != NULL)
398 CatalogTupleDelete(shdescription, &oldtuple->t_self);
400 /* Done */
402 systable_endscan(sd);
403 table_close(shdescription, RowExclusiveLock);
407 * GetComment -- get the comment for an object, or null if not found.
409 char *
410 GetComment(Oid oid, Oid classoid, int32 subid)
412 Relation description;
413 ScanKeyData skey[3];
414 SysScanDesc sd;
415 TupleDesc tupdesc;
416 HeapTuple tuple;
417 char *comment;
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,
438 NULL, 3, skey);
440 comment = NULL;
441 while ((tuple = systable_getnext(sd)) != NULL)
443 Datum value;
444 bool isnull;
446 /* Found the tuple, get description field */
447 value = heap_getattr(tuple, Anum_pg_description_description, tupdesc, &isnull);
448 if (!isnull)
449 comment = TextDatumGetCString(value);
450 break; /* Assume there can be only one match */
453 systable_endscan(sd);
455 /* Done */
456 table_close(description, AccessShareLock);
458 return comment;