Age Owner TLA Line data Source code
1 : /*-------------------------------------------------------------------------
2 : *
3 : * comment.c
4 : *
5 : * PostgreSQL object comments utility code.
6 : *
7 : * Copyright (c) 1996-2023, 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_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"
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
7670 tgl 40 CBC 18726 : CommentObject(CommentStmt *stmt)
41 : {
42 : Relation relation;
2959 alvherre 43 18726 : ObjectAddress address = InvalidObjectAddress;
44 :
45 : /*
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.
52 : */
2339 peter_e 53 18726 : if (stmt->objtype == OBJECT_DATABASE)
54 : {
577 peter 55 608 : char *database = strVal(stmt->object);
56 :
4608 rhaas 57 608 : if (!OidIsValid(get_database_oid(database, true)))
58 : {
4608 rhaas 59 UBC 0 : ereport(WARNING,
60 : (errcode(ERRCODE_UNDEFINED_DATABASE),
61 : errmsg("database \"%s\" does not exist", database)));
2959 alvherre 62 0 : return address;
63 : }
64 : }
65 :
66 : /*
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.
71 : */
2339 peter_e 72 CBC 18726 : address = get_object_address(stmt->objtype, stmt->object,
73 : &relation, ShareUpdateExclusiveLock, false);
74 :
75 : /* Require ownership of the target object. */
4419 tgl 76 18659 : check_object_ownership(GetUserId(), stmt->objtype, address,
77 : stmt->object, relation);
78 :
79 : /* Perform other integrity checks as needed. */
7670 80 18647 : switch (stmt->objtype)
81 : {
7226 peter_e 82 84 : case OBJECT_COLUMN:
83 :
84 : /*
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.
92 : */
4419 tgl 93 84 : if (relation->rd_rel->relkind != RELKIND_RELATION &&
94 19 : relation->rd_rel->relkind != RELKIND_VIEW &&
3689 kgrittn 95 19 : relation->rd_rel->relkind != RELKIND_MATVIEW &&
4419 tgl 96 19 : relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
2182 simon 97 12 : relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&
98 3 : relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
4608 rhaas 99 UBC 0 : 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)));
5710 tgl 104 CBC 84 : break;
8397 bruce 105 18563 : default:
4419 tgl 106 18563 : break;
107 : }
108 :
109 : /*
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.
113 : */
4608 rhaas 114 18647 : if (stmt->objtype == OBJECT_DATABASE || stmt->objtype == OBJECT_TABLESPACE
4520 peter_e 115 18039 : || stmt->objtype == OBJECT_ROLE)
4608 rhaas 116 612 : CreateSharedComments(address.objectId, address.classId, stmt->comment);
117 : else
118 18035 : CreateComments(address.objectId, address.classId, address.objectSubId,
119 18035 : stmt->comment);
120 :
121 : /*
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.
126 : */
127 18647 : if (relation != NULL)
128 250 : relation_close(relation, NoLock);
129 :
2959 alvherre 130 18647 : return address;
131 : }
132 :
133 : /*
134 : * CreateComments --
135 : *
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.
138 : *
139 : * If the comment given is null or an empty string, instead delete any
140 : * existing comment for the specified key.
141 : */
142 : void
1986 peter_e 143 258839 : CreateComments(Oid oid, Oid classoid, int32 subid, const char *comment)
144 : {
145 : Relation description;
146 : ScanKeyData skey[3];
147 : SysScanDesc sd;
148 : HeapTuple oldtuple;
7912 tgl 149 258839 : HeapTuple newtuple = NULL;
150 : Datum values[Natts_pg_description];
151 : bool nulls[Natts_pg_description];
152 : bool replaces[Natts_pg_description];
153 : int i;
154 :
155 : /* Reduce empty-string to NULL case */
156 258839 : if (comment != NULL && strlen(comment) == 0)
7912 tgl 157 UBC 0 : comment = NULL;
158 :
159 : /* Prepare to form or update a tuple, if necessary */
7912 tgl 160 CBC 258839 : if (comment != NULL)
161 : {
8397 bruce 162 1293950 : for (i = 0; i < Natts_pg_description; i++)
163 : {
5271 tgl 164 1035160 : nulls[i] = false;
165 1035160 : replaces[i] = true;
166 : }
4315 167 258790 : values[Anum_pg_description_objoid - 1] = ObjectIdGetDatum(oid);
168 258790 : values[Anum_pg_description_classoid - 1] = ObjectIdGetDatum(classoid);
169 258790 : values[Anum_pg_description_objsubid - 1] = Int32GetDatum(subid);
170 258790 : values[Anum_pg_description_description - 1] = CStringGetTextDatum(comment);
171 : }
172 :
173 : /* Use the index to search for a matching old tuple */
174 :
7088 175 258839 : ScanKeyInit(&skey[0],
176 : Anum_pg_description_objoid,
177 : BTEqualStrategyNumber, F_OIDEQ,
178 : ObjectIdGetDatum(oid));
179 258839 : ScanKeyInit(&skey[1],
180 : Anum_pg_description_classoid,
181 : BTEqualStrategyNumber, F_OIDEQ,
182 : ObjectIdGetDatum(classoid));
183 258839 : ScanKeyInit(&skey[2],
184 : Anum_pg_description_objsubid,
185 : BTEqualStrategyNumber, F_INT4EQ,
186 : Int32GetDatum(subid));
187 :
1539 andres 188 258839 : description = table_open(DescriptionRelationId, RowExclusiveLock);
189 :
6569 tgl 190 258839 : sd = systable_beginscan(description, DescriptionObjIndexId, true,
191 : NULL, 3, skey);
192 :
7629 193 258839 : while ((oldtuple = systable_getnext(sd)) != NULL)
194 : {
195 : /* Found the old tuple, so delete or update it */
196 :
7912 197 61 : if (comment == NULL)
2258 198 49 : CatalogTupleDelete(description, &oldtuple->t_self);
199 : else
200 : {
5271 201 12 : newtuple = heap_modify_tuple(oldtuple, RelationGetDescr(description), values,
202 : nulls, replaces);
2259 alvherre 203 12 : CatalogTupleUpdate(description, &oldtuple->t_self, newtuple);
204 : }
205 :
7912 tgl 206 61 : break; /* Assume there can be only one match */
207 : }
208 :
7629 209 258839 : systable_endscan(sd);
210 :
211 : /* If we didn't find an old tuple, insert a new one */
212 :
213 258839 : if (newtuple == NULL && comment != NULL)
214 : {
5271 215 258778 : newtuple = heap_form_tuple(RelationGetDescr(description),
216 : values, nulls);
2259 alvherre 217 258778 : CatalogTupleInsert(description, newtuple);
218 : }
219 :
7912 tgl 220 258839 : if (newtuple != NULL)
221 258790 : heap_freetuple(newtuple);
222 :
223 : /* Done */
224 :
1539 andres 225 258839 : table_close(description, NoLock);
8566 bruce 226 258839 : }
227 :
228 : /*
229 : * CreateSharedComments --
230 : *
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.
233 : *
234 : * If the comment given is null or an empty string, instead delete any
235 : * existing comment for the specified key.
236 : */
237 : void
1986 peter_e 238 612 : CreateSharedComments(Oid oid, Oid classoid, const char *comment)
239 : {
240 : Relation shdescription;
241 : ScanKeyData skey[2];
242 : SysScanDesc sd;
243 : HeapTuple oldtuple;
6265 bruce 244 612 : HeapTuple newtuple = NULL;
245 : Datum values[Natts_pg_shdescription];
246 : bool nulls[Natts_pg_shdescription];
247 : bool replaces[Natts_pg_shdescription];
248 : int i;
249 :
250 : /* Reduce empty-string to NULL case */
251 612 : if (comment != NULL && strlen(comment) == 0)
6265 bruce 252 UBC 0 : comment = NULL;
253 :
254 : /* Prepare to form or update a tuple, if necessary */
6265 bruce 255 CBC 612 : if (comment != NULL)
256 : {
257 2448 : for (i = 0; i < Natts_pg_shdescription; i++)
258 : {
5271 tgl 259 1836 : nulls[i] = false;
260 1836 : replaces[i] = true;
261 : }
4315 262 612 : values[Anum_pg_shdescription_objoid - 1] = ObjectIdGetDatum(oid);
263 612 : values[Anum_pg_shdescription_classoid - 1] = ObjectIdGetDatum(classoid);
264 612 : values[Anum_pg_shdescription_description - 1] = CStringGetTextDatum(comment);
265 : }
266 :
267 : /* Use the index to search for a matching old tuple */
268 :
6265 bruce 269 612 : ScanKeyInit(&skey[0],
270 : Anum_pg_shdescription_objoid,
271 : BTEqualStrategyNumber, F_OIDEQ,
272 : ObjectIdGetDatum(oid));
273 612 : ScanKeyInit(&skey[1],
274 : Anum_pg_shdescription_classoid,
275 : BTEqualStrategyNumber, F_OIDEQ,
276 : ObjectIdGetDatum(classoid));
277 :
1539 andres 278 612 : shdescription = table_open(SharedDescriptionRelationId, RowExclusiveLock);
279 :
6265 bruce 280 612 : sd = systable_beginscan(shdescription, SharedDescriptionObjIndexId, true,
281 : NULL, 2, skey);
282 :
283 612 : while ((oldtuple = systable_getnext(sd)) != NULL)
284 : {
285 : /* Found the old tuple, so delete or update it */
286 :
6265 bruce 287 UBC 0 : if (comment == NULL)
2258 tgl 288 0 : CatalogTupleDelete(shdescription, &oldtuple->t_self);
289 : else
290 : {
5271 291 0 : newtuple = heap_modify_tuple(oldtuple, RelationGetDescr(shdescription),
292 : values, nulls, replaces);
2259 alvherre 293 0 : CatalogTupleUpdate(shdescription, &oldtuple->t_self, newtuple);
294 : }
295 :
6031 bruce 296 0 : break; /* Assume there can be only one match */
297 : }
298 :
6265 bruce 299 CBC 612 : systable_endscan(sd);
300 :
301 : /* If we didn't find an old tuple, insert a new one */
302 :
303 612 : if (newtuple == NULL && comment != NULL)
304 : {
5271 tgl 305 612 : newtuple = heap_form_tuple(RelationGetDescr(shdescription),
306 : values, nulls);
2259 alvherre 307 612 : CatalogTupleInsert(shdescription, newtuple);
308 : }
309 :
6265 bruce 310 612 : if (newtuple != NULL)
311 612 : heap_freetuple(newtuple);
312 :
313 : /* Done */
314 :
1539 andres 315 612 : table_close(shdescription, NoLock);
6265 bruce 316 612 : }
317 :
318 : /*
319 : * DeleteComments -- remove comments for an object
320 : *
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).
324 : */
325 : void
7576 tgl 326 83396 : DeleteComments(Oid oid, Oid classoid, int32 subid)
327 : {
328 : Relation description;
329 : ScanKeyData skey[3];
330 : int nkeys;
331 : SysScanDesc sd;
332 : HeapTuple oldtuple;
333 :
334 : /* Use the index to search for all matching old tuples */
335 :
7088 336 83396 : ScanKeyInit(&skey[0],
337 : Anum_pg_description_objoid,
338 : BTEqualStrategyNumber, F_OIDEQ,
339 : ObjectIdGetDatum(oid));
340 83396 : ScanKeyInit(&skey[1],
341 : Anum_pg_description_classoid,
342 : BTEqualStrategyNumber, F_OIDEQ,
343 : ObjectIdGetDatum(classoid));
344 :
7576 345 83396 : if (subid != 0)
346 : {
7088 347 977 : ScanKeyInit(&skey[2],
348 : Anum_pg_description_objsubid,
349 : BTEqualStrategyNumber, F_INT4EQ,
350 : Int32GetDatum(subid));
7576 351 977 : nkeys = 3;
352 : }
353 : else
354 82419 : nkeys = 2;
355 :
1539 andres 356 83396 : description = table_open(DescriptionRelationId, RowExclusiveLock);
357 :
6569 tgl 358 83396 : sd = systable_beginscan(description, DescriptionObjIndexId, true,
359 : NULL, nkeys, skey);
360 :
7629 361 83715 : while ((oldtuple = systable_getnext(sd)) != NULL)
2258 362 319 : CatalogTupleDelete(description, &oldtuple->t_self);
363 :
364 : /* Done */
365 :
7629 366 83396 : systable_endscan(sd);
1539 andres 367 83396 : table_close(description, RowExclusiveLock);
8566 bruce 368 83396 : }
369 :
370 : /*
371 : * DeleteSharedComments -- remove comments for a shared object
372 : */
373 : void
6265 374 666 : DeleteSharedComments(Oid oid, Oid classoid)
375 : {
376 : Relation shdescription;
377 : ScanKeyData skey[2];
378 : SysScanDesc sd;
379 : HeapTuple oldtuple;
380 :
381 : /* Use the index to search for all matching old tuples */
382 :
383 666 : ScanKeyInit(&skey[0],
384 : Anum_pg_shdescription_objoid,
385 : BTEqualStrategyNumber, F_OIDEQ,
386 : ObjectIdGetDatum(oid));
387 666 : ScanKeyInit(&skey[1],
388 : Anum_pg_shdescription_classoid,
389 : BTEqualStrategyNumber, F_OIDEQ,
390 : ObjectIdGetDatum(classoid));
391 :
1539 andres 392 666 : shdescription = table_open(SharedDescriptionRelationId, RowExclusiveLock);
393 :
6265 bruce 394 666 : sd = systable_beginscan(shdescription, SharedDescriptionObjIndexId, true,
395 : NULL, 2, skey);
396 :
397 672 : while ((oldtuple = systable_getnext(sd)) != NULL)
2258 tgl 398 6 : CatalogTupleDelete(shdescription, &oldtuple->t_self);
399 :
400 : /* Done */
401 :
6265 bruce 402 666 : systable_endscan(sd);
1539 andres 403 666 : table_close(shdescription, RowExclusiveLock);
6265 bruce 404 666 : }
405 :
406 : /*
407 : * GetComment -- get the comment for an object, or null if not found.
408 : */
409 : char *
4927 andrew 410 538 : GetComment(Oid oid, Oid classoid, int32 subid)
411 : {
412 : Relation description;
413 : ScanKeyData skey[3];
414 : SysScanDesc sd;
415 : TupleDesc tupdesc;
416 : HeapTuple tuple;
417 : char *comment;
418 :
419 : /* Use the index to search for a matching old tuple */
420 :
421 538 : ScanKeyInit(&skey[0],
422 : Anum_pg_description_objoid,
423 : BTEqualStrategyNumber, F_OIDEQ,
424 : ObjectIdGetDatum(oid));
425 538 : ScanKeyInit(&skey[1],
426 : Anum_pg_description_classoid,
427 : BTEqualStrategyNumber, F_OIDEQ,
428 : ObjectIdGetDatum(classoid));
429 538 : ScanKeyInit(&skey[2],
430 : Anum_pg_description_objsubid,
431 : BTEqualStrategyNumber, F_INT4EQ,
432 : Int32GetDatum(subid));
433 :
1539 andres 434 538 : description = table_open(DescriptionRelationId, AccessShareLock);
4927 andrew 435 538 : tupdesc = RelationGetDescr(description);
436 :
437 538 : sd = systable_beginscan(description, DescriptionObjIndexId, true,
438 : NULL, 3, skey);
439 :
4790 bruce 440 538 : comment = NULL;
4927 andrew 441 538 : while ((tuple = systable_getnext(sd)) != NULL)
442 : {
443 : Datum value;
444 : bool isnull;
445 :
446 : /* Found the tuple, get description field */
447 144 : value = heap_getattr(tuple, Anum_pg_description_description, tupdesc, &isnull);
448 144 : if (!isnull)
449 144 : comment = TextDatumGetCString(value);
450 144 : break; /* Assume there can be only one match */
451 : }
452 :
453 538 : systable_endscan(sd);
454 :
455 : /* Done */
1539 andres 456 538 : table_close(description, AccessShareLock);
457 :
4927 andrew 458 538 : return comment;
459 : }
|