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-2024, 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
8041 tgl@sss.pgh.pa.us 40 :CBC 2792 : CommentObject(CommentStmt *stmt)
41 : : {
42 : : Relation relation;
3330 alvherre@alvh.no-ip. 43 : 2792 : 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 : : */
2710 peter_e@gmx.net 53 [ + + ]: 2792 : if (stmt->objtype == OBJECT_DATABASE)
54 : : {
948 peter@eisentraut.org 55 : 86 : char *database = strVal(stmt->object);
56 : :
4979 rhaas@postgresql.org 57 [ - + ]: 86 : if (!OidIsValid(get_database_oid(database, true)))
58 : : {
4979 rhaas@postgresql.org 59 [ # # ]:UBC 0 : ereport(WARNING,
60 : : (errcode(ERRCODE_UNDEFINED_DATABASE),
61 : : errmsg("database \"%s\" does not exist", database)));
3330 alvherre@alvh.no-ip. 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 : : */
2710 peter_e@gmx.net 72 :CBC 2792 : address = get_object_address(stmt->objtype, stmt->object,
73 : : &relation, ShareUpdateExclusiveLock, false);
74 : :
75 : : /* Require ownership of the target object. */
4790 tgl@sss.pgh.pa.us 76 : 2725 : check_object_ownership(GetUserId(), stmt->objtype, address,
77 : : stmt->object, relation);
78 : :
79 : : /* Perform other integrity checks as needed. */
8041 80 [ + + ]: 2713 : switch (stmt->objtype)
81 : : {
7597 peter_e@gmx.net 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 : : */
4790 tgl@sss.pgh.pa.us 93 [ + + ]: 84 : if (relation->rd_rel->relkind != RELKIND_RELATION &&
94 [ + - ]: 19 : relation->rd_rel->relkind != RELKIND_VIEW &&
4060 kgrittn@postgresql.o 95 [ + - ]: 19 : relation->rd_rel->relkind != RELKIND_MATVIEW &&
4790 tgl@sss.pgh.pa.us 96 [ + + ]: 19 : relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
2553 simon@2ndQuadrant.co 97 [ + + ]: 12 : relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&
98 [ - + ]: 3 : relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
4979 rhaas@postgresql.org 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)));
6081 tgl@sss.pgh.pa.us 104 :CBC 84 : break;
8768 bruce@momjian.us 105 : 2629 : default:
4790 tgl@sss.pgh.pa.us 106 : 2629 : 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 : : */
4979 rhaas@postgresql.org 114 [ + + + - ]: 2713 : if (stmt->objtype == OBJECT_DATABASE || stmt->objtype == OBJECT_TABLESPACE
4891 peter_e@gmx.net 115 [ + + ]: 2627 : || stmt->objtype == OBJECT_ROLE)
4979 rhaas@postgresql.org 116 : 90 : CreateSharedComments(address.objectId, address.classId, stmt->comment);
117 : : else
118 : 2623 : CreateComments(address.objectId, address.classId, address.objectSubId,
119 : 2623 : 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 [ + + ]: 2713 : if (relation != NULL)
128 : 250 : relation_close(relation, NoLock);
129 : :
3330 alvherre@alvh.no-ip. 130 : 2713 : 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
2357 peter_e@gmx.net 143 : 32249 : CreateComments(Oid oid, Oid classoid, int32 subid, const char *comment)
144 : : {
145 : : Relation description;
146 : : ScanKeyData skey[3];
147 : : SysScanDesc sd;
148 : : HeapTuple oldtuple;
8283 tgl@sss.pgh.pa.us 149 : 32249 : 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 [ + + - + ]: 32249 : if (comment != NULL && strlen(comment) == 0)
8283 tgl@sss.pgh.pa.us 157 :UBC 0 : comment = NULL;
158 : :
159 : : /* Prepare to form or update a tuple, if necessary */
8283 tgl@sss.pgh.pa.us 160 [ + + ]:CBC 32249 : if (comment != NULL)
161 : : {
8768 bruce@momjian.us 162 [ + + ]: 161000 : for (i = 0; i < Natts_pg_description; i++)
163 : : {
5642 tgl@sss.pgh.pa.us 164 : 128800 : nulls[i] = false;
165 : 128800 : replaces[i] = true;
166 : : }
4686 167 : 32200 : values[Anum_pg_description_objoid - 1] = ObjectIdGetDatum(oid);
168 : 32200 : values[Anum_pg_description_classoid - 1] = ObjectIdGetDatum(classoid);
169 : 32200 : values[Anum_pg_description_objsubid - 1] = Int32GetDatum(subid);
170 : 32200 : values[Anum_pg_description_description - 1] = CStringGetTextDatum(comment);
171 : : }
172 : :
173 : : /* Use the index to search for a matching old tuple */
174 : :
7459 175 : 32249 : ScanKeyInit(&skey[0],
176 : : Anum_pg_description_objoid,
177 : : BTEqualStrategyNumber, F_OIDEQ,
178 : : ObjectIdGetDatum(oid));
179 : 32249 : ScanKeyInit(&skey[1],
180 : : Anum_pg_description_classoid,
181 : : BTEqualStrategyNumber, F_OIDEQ,
182 : : ObjectIdGetDatum(classoid));
183 : 32249 : ScanKeyInit(&skey[2],
184 : : Anum_pg_description_objsubid,
185 : : BTEqualStrategyNumber, F_INT4EQ,
186 : : Int32GetDatum(subid));
187 : :
1910 andres@anarazel.de 188 : 32249 : description = table_open(DescriptionRelationId, RowExclusiveLock);
189 : :
6940 tgl@sss.pgh.pa.us 190 : 32249 : sd = systable_beginscan(description, DescriptionObjIndexId, true,
191 : : NULL, 3, skey);
192 : :
8000 193 [ + + ]: 32249 : while ((oldtuple = systable_getnext(sd)) != NULL)
194 : : {
195 : : /* Found the old tuple, so delete or update it */
196 : :
8283 197 [ + + ]: 65 : if (comment == NULL)
2629 198 : 49 : CatalogTupleDelete(description, &oldtuple->t_self);
199 : : else
200 : : {
5642 201 : 16 : newtuple = heap_modify_tuple(oldtuple, RelationGetDescr(description), values,
202 : : nulls, replaces);
2630 alvherre@alvh.no-ip. 203 : 16 : CatalogTupleUpdate(description, &oldtuple->t_self, newtuple);
204 : : }
205 : :
8283 tgl@sss.pgh.pa.us 206 : 65 : break; /* Assume there can be only one match */
207 : : }
208 : :
8000 209 : 32249 : systable_endscan(sd);
210 : :
211 : : /* If we didn't find an old tuple, insert a new one */
212 : :
213 [ + + + + ]: 32249 : if (newtuple == NULL && comment != NULL)
214 : : {
5642 215 : 32184 : newtuple = heap_form_tuple(RelationGetDescr(description),
216 : : values, nulls);
2630 alvherre@alvh.no-ip. 217 : 32184 : CatalogTupleInsert(description, newtuple);
218 : : }
219 : :
8283 tgl@sss.pgh.pa.us 220 [ + + ]: 32249 : if (newtuple != NULL)
221 : 32200 : heap_freetuple(newtuple);
222 : :
223 : : /* Done */
224 : :
1910 andres@anarazel.de 225 : 32249 : table_close(description, NoLock);
8937 bruce@momjian.us 226 : 32249 : }
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
2357 peter_e@gmx.net 238 : 90 : CreateSharedComments(Oid oid, Oid classoid, const char *comment)
239 : : {
240 : : Relation shdescription;
241 : : ScanKeyData skey[2];
242 : : SysScanDesc sd;
243 : : HeapTuple oldtuple;
6636 bruce@momjian.us 244 : 90 : 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 [ + + - + ]: 90 : if (comment != NULL && strlen(comment) == 0)
6636 bruce@momjian.us 252 :UBC 0 : comment = NULL;
253 : :
254 : : /* Prepare to form or update a tuple, if necessary */
6636 bruce@momjian.us 255 [ + + ]:CBC 90 : if (comment != NULL)
256 : : {
257 [ + + ]: 348 : for (i = 0; i < Natts_pg_shdescription; i++)
258 : : {
5642 tgl@sss.pgh.pa.us 259 : 261 : nulls[i] = false;
260 : 261 : replaces[i] = true;
261 : : }
4686 262 : 87 : values[Anum_pg_shdescription_objoid - 1] = ObjectIdGetDatum(oid);
263 : 87 : values[Anum_pg_shdescription_classoid - 1] = ObjectIdGetDatum(classoid);
264 : 87 : values[Anum_pg_shdescription_description - 1] = CStringGetTextDatum(comment);
265 : : }
266 : :
267 : : /* Use the index to search for a matching old tuple */
268 : :
6636 bruce@momjian.us 269 : 90 : ScanKeyInit(&skey[0],
270 : : Anum_pg_shdescription_objoid,
271 : : BTEqualStrategyNumber, F_OIDEQ,
272 : : ObjectIdGetDatum(oid));
273 : 90 : ScanKeyInit(&skey[1],
274 : : Anum_pg_shdescription_classoid,
275 : : BTEqualStrategyNumber, F_OIDEQ,
276 : : ObjectIdGetDatum(classoid));
277 : :
1910 andres@anarazel.de 278 : 90 : shdescription = table_open(SharedDescriptionRelationId, RowExclusiveLock);
279 : :
6636 bruce@momjian.us 280 : 90 : sd = systable_beginscan(shdescription, SharedDescriptionObjIndexId, true,
281 : : NULL, 2, skey);
282 : :
283 [ + + ]: 90 : while ((oldtuple = systable_getnext(sd)) != NULL)
284 : : {
285 : : /* Found the old tuple, so delete or update it */
286 : :
287 [ + - ]: 3 : if (comment == NULL)
2629 tgl@sss.pgh.pa.us 288 : 3 : CatalogTupleDelete(shdescription, &oldtuple->t_self);
289 : : else
290 : : {
5642 tgl@sss.pgh.pa.us 291 :UBC 0 : newtuple = heap_modify_tuple(oldtuple, RelationGetDescr(shdescription),
292 : : values, nulls, replaces);
2630 alvherre@alvh.no-ip. 293 : 0 : CatalogTupleUpdate(shdescription, &oldtuple->t_self, newtuple);
294 : : }
295 : :
6402 bruce@momjian.us 296 :CBC 3 : break; /* Assume there can be only one match */
297 : : }
298 : :
6636 299 : 90 : systable_endscan(sd);
300 : :
301 : : /* If we didn't find an old tuple, insert a new one */
302 : :
303 [ + - + + ]: 90 : if (newtuple == NULL && comment != NULL)
304 : : {
5642 tgl@sss.pgh.pa.us 305 : 87 : newtuple = heap_form_tuple(RelationGetDescr(shdescription),
306 : : values, nulls);
2630 alvherre@alvh.no-ip. 307 : 87 : CatalogTupleInsert(shdescription, newtuple);
308 : : }
309 : :
6636 bruce@momjian.us 310 [ + + ]: 90 : if (newtuple != NULL)
311 : 87 : heap_freetuple(newtuple);
312 : :
313 : : /* Done */
314 : :
1910 andres@anarazel.de 315 : 90 : table_close(shdescription, NoLock);
6636 bruce@momjian.us 316 : 90 : }
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
7947 tgl@sss.pgh.pa.us 326 : 91814 : 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 : :
7459 336 : 91814 : ScanKeyInit(&skey[0],
337 : : Anum_pg_description_objoid,
338 : : BTEqualStrategyNumber, F_OIDEQ,
339 : : ObjectIdGetDatum(oid));
340 : 91814 : ScanKeyInit(&skey[1],
341 : : Anum_pg_description_classoid,
342 : : BTEqualStrategyNumber, F_OIDEQ,
343 : : ObjectIdGetDatum(classoid));
344 : :
7947 345 [ + + ]: 91814 : if (subid != 0)
346 : : {
7459 347 : 1001 : ScanKeyInit(&skey[2],
348 : : Anum_pg_description_objsubid,
349 : : BTEqualStrategyNumber, F_INT4EQ,
350 : : Int32GetDatum(subid));
7947 351 : 1001 : nkeys = 3;
352 : : }
353 : : else
354 : 90813 : nkeys = 2;
355 : :
1910 andres@anarazel.de 356 : 91814 : description = table_open(DescriptionRelationId, RowExclusiveLock);
357 : :
6940 tgl@sss.pgh.pa.us 358 : 91814 : sd = systable_beginscan(description, DescriptionObjIndexId, true,
359 : : NULL, nkeys, skey);
360 : :
8000 361 [ + + ]: 92150 : while ((oldtuple = systable_getnext(sd)) != NULL)
2629 362 : 337 : CatalogTupleDelete(description, &oldtuple->t_self);
363 : :
364 : : /* Done */
365 : :
8000 366 : 91813 : systable_endscan(sd);
1910 andres@anarazel.de 367 : 91813 : table_close(description, RowExclusiveLock);
8937 bruce@momjian.us 368 : 91813 : }
369 : :
370 : : /*
371 : : * DeleteSharedComments -- remove comments for a shared object
372 : : */
373 : : void
6636 374 : 706 : 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 : 706 : ScanKeyInit(&skey[0],
384 : : Anum_pg_shdescription_objoid,
385 : : BTEqualStrategyNumber, F_OIDEQ,
386 : : ObjectIdGetDatum(oid));
387 : 706 : ScanKeyInit(&skey[1],
388 : : Anum_pg_shdescription_classoid,
389 : : BTEqualStrategyNumber, F_OIDEQ,
390 : : ObjectIdGetDatum(classoid));
391 : :
1910 andres@anarazel.de 392 : 706 : shdescription = table_open(SharedDescriptionRelationId, RowExclusiveLock);
393 : :
6636 bruce@momjian.us 394 : 706 : sd = systable_beginscan(shdescription, SharedDescriptionObjIndexId, true,
395 : : NULL, 2, skey);
396 : :
397 [ + + ]: 716 : while ((oldtuple = systable_getnext(sd)) != NULL)
2629 tgl@sss.pgh.pa.us 398 : 10 : CatalogTupleDelete(shdescription, &oldtuple->t_self);
399 : :
400 : : /* Done */
401 : :
6636 bruce@momjian.us 402 : 706 : systable_endscan(sd);
1910 andres@anarazel.de 403 : 706 : table_close(shdescription, RowExclusiveLock);
6636 bruce@momjian.us 404 : 706 : }
405 : :
406 : : /*
407 : : * GetComment -- get the comment for an object, or null if not found.
408 : : */
409 : : char *
5298 andrew@dunslane.net 410 : 1309 : 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 : 1309 : ScanKeyInit(&skey[0],
422 : : Anum_pg_description_objoid,
423 : : BTEqualStrategyNumber, F_OIDEQ,
424 : : ObjectIdGetDatum(oid));
425 : 1309 : ScanKeyInit(&skey[1],
426 : : Anum_pg_description_classoid,
427 : : BTEqualStrategyNumber, F_OIDEQ,
428 : : ObjectIdGetDatum(classoid));
429 : 1309 : ScanKeyInit(&skey[2],
430 : : Anum_pg_description_objsubid,
431 : : BTEqualStrategyNumber, F_INT4EQ,
432 : : Int32GetDatum(subid));
433 : :
1910 andres@anarazel.de 434 : 1309 : description = table_open(DescriptionRelationId, AccessShareLock);
5298 andrew@dunslane.net 435 : 1309 : tupdesc = RelationGetDescr(description);
436 : :
437 : 1309 : sd = systable_beginscan(description, DescriptionObjIndexId, true,
438 : : NULL, 3, skey);
439 : :
5161 bruce@momjian.us 440 : 1309 : comment = NULL;
5298 andrew@dunslane.net 441 [ + + ]: 1309 : 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 : 1309 : systable_endscan(sd);
454 : :
455 : : /* Done */
1910 andres@anarazel.de 456 : 1309 : table_close(description, AccessShareLock);
457 : :
5298 andrew@dunslane.net 458 : 1309 : return comment;
459 : : }
|