1/*-------------------------------------------------------------------------
2 *
3 * comment.c
4 *
5 * PostgreSQL object comments utility code.
6 *
7 * Copyright (c) 1996-2019, 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 */
39ObjectAddress
40CommentObject(CommentStmt *stmt)
41{
42 Relation relation;
43 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 */
53 if (stmt->objtype == OBJECT_DATABASE)
54 {
55 char *database = strVal((Value *) stmt->object);
56
57 if (!OidIsValid(get_database_oid(database, true)))
58 {
59 ereport(WARNING,
60 (errcode(ERRCODE_UNDEFINED_DATABASE),
61 errmsg("database \"%s\" does not exist", database)));
62 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 */
72 address = get_object_address(stmt->objtype, stmt->object,
73 &relation, ShareUpdateExclusiveLock, false);
74
75 /* Require ownership of the target object. */
76 check_object_ownership(GetUserId(), stmt->objtype, address,
77 stmt->object, relation);
78
79 /* Perform other integrity checks as needed. */
80 switch (stmt->objtype)
81 {
82 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 */
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("\"%s\" is not a table, view, materialized view, composite type, or foreign table",
102 RelationGetRelationName(relation))));
103 break;
104 default:
105 break;
106 }
107
108 /*
109 * Databases, tablespaces, and roles are cluster-wide objects, so any
110 * comments on those objects are recorded in the shared pg_shdescription
111 * catalog. Comments on all other objects are recorded in pg_description.
112 */
113 if (stmt->objtype == OBJECT_DATABASE || stmt->objtype == OBJECT_TABLESPACE
114 || stmt->objtype == OBJECT_ROLE)
115 CreateSharedComments(address.objectId, address.classId, stmt->comment);
116 else
117 CreateComments(address.objectId, address.classId, address.objectSubId,
118 stmt->comment);
119
120 /*
121 * If get_object_address() opened the relation for us, we close it to keep
122 * the reference count correct - but we retain any locks acquired by
123 * get_object_address() until commit time, to guard against concurrent
124 * activity.
125 */
126 if (relation != NULL)
127 relation_close(relation, NoLock);
128
129 return address;
130}
131
132/*
133 * CreateComments --
134 *
135 * Create a comment for the specified object descriptor. Inserts a new
136 * pg_description tuple, or replaces an existing one with the same key.
137 *
138 * If the comment given is null or an empty string, instead delete any
139 * existing comment for the specified key.
140 */
141void
142CreateComments(Oid oid, Oid classoid, int32 subid, const char *comment)
143{
144 Relation description;
145 ScanKeyData skey[3];
146 SysScanDesc sd;
147 HeapTuple oldtuple;
148 HeapTuple newtuple = NULL;
149 Datum values[Natts_pg_description];
150 bool nulls[Natts_pg_description];
151 bool replaces[Natts_pg_description];
152 int i;
153
154 /* Reduce empty-string to NULL case */
155 if (comment != NULL && strlen(comment) == 0)
156 comment = NULL;
157
158 /* Prepare to form or update a tuple, if necessary */
159 if (comment != NULL)
160 {
161 for (i = 0; i < Natts_pg_description; i++)
162 {
163 nulls[i] = false;
164 replaces[i] = true;
165 }
166 values[Anum_pg_description_objoid - 1] = ObjectIdGetDatum(oid);
167 values[Anum_pg_description_classoid - 1] = ObjectIdGetDatum(classoid);
168 values[Anum_pg_description_objsubid - 1] = Int32GetDatum(subid);
169 values[Anum_pg_description_description - 1] = CStringGetTextDatum(comment);
170 }
171
172 /* Use the index to search for a matching old tuple */
173
174 ScanKeyInit(&skey[0],
175 Anum_pg_description_objoid,
176 BTEqualStrategyNumber, F_OIDEQ,
177 ObjectIdGetDatum(oid));
178 ScanKeyInit(&skey[1],
179 Anum_pg_description_classoid,
180 BTEqualStrategyNumber, F_OIDEQ,
181 ObjectIdGetDatum(classoid));
182 ScanKeyInit(&skey[2],
183 Anum_pg_description_objsubid,
184 BTEqualStrategyNumber, F_INT4EQ,
185 Int32GetDatum(subid));
186
187 description = table_open(DescriptionRelationId, RowExclusiveLock);
188
189 sd = systable_beginscan(description, DescriptionObjIndexId, true,
190 NULL, 3, skey);
191
192 while ((oldtuple = systable_getnext(sd)) != NULL)
193 {
194 /* Found the old tuple, so delete or update it */
195
196 if (comment == NULL)
197 CatalogTupleDelete(description, &oldtuple->t_self);
198 else
199 {
200 newtuple = heap_modify_tuple(oldtuple, RelationGetDescr(description), values,
201 nulls, replaces);
202 CatalogTupleUpdate(description, &oldtuple->t_self, newtuple);
203 }
204
205 break; /* Assume there can be only one match */
206 }
207
208 systable_endscan(sd);
209
210 /* If we didn't find an old tuple, insert a new one */
211
212 if (newtuple == NULL && comment != NULL)
213 {
214 newtuple = heap_form_tuple(RelationGetDescr(description),
215 values, nulls);
216 CatalogTupleInsert(description, newtuple);
217 }
218
219 if (newtuple != NULL)
220 heap_freetuple(newtuple);
221
222 /* Done */
223
224 table_close(description, NoLock);
225}
226
227/*
228 * CreateSharedComments --
229 *
230 * Create a comment for the specified shared object descriptor. Inserts a
231 * new pg_shdescription tuple, or replaces an existing one with the same key.
232 *
233 * If the comment given is null or an empty string, instead delete any
234 * existing comment for the specified key.
235 */
236void
237CreateSharedComments(Oid oid, Oid classoid, const char *comment)
238{
239 Relation shdescription;
240 ScanKeyData skey[2];
241 SysScanDesc sd;
242 HeapTuple oldtuple;
243 HeapTuple newtuple = NULL;
244 Datum values[Natts_pg_shdescription];
245 bool nulls[Natts_pg_shdescription];
246 bool replaces[Natts_pg_shdescription];
247 int i;
248
249 /* Reduce empty-string to NULL case */
250 if (comment != NULL && strlen(comment) == 0)
251 comment = NULL;
252
253 /* Prepare to form or update a tuple, if necessary */
254 if (comment != NULL)
255 {
256 for (i = 0; i < Natts_pg_shdescription; i++)
257 {
258 nulls[i] = false;
259 replaces[i] = true;
260 }
261 values[Anum_pg_shdescription_objoid - 1] = ObjectIdGetDatum(oid);
262 values[Anum_pg_shdescription_classoid - 1] = ObjectIdGetDatum(classoid);
263 values[Anum_pg_shdescription_description - 1] = CStringGetTextDatum(comment);
264 }
265
266 /* Use the index to search for a matching old tuple */
267
268 ScanKeyInit(&skey[0],
269 Anum_pg_shdescription_objoid,
270 BTEqualStrategyNumber, F_OIDEQ,
271 ObjectIdGetDatum(oid));
272 ScanKeyInit(&skey[1],
273 Anum_pg_shdescription_classoid,
274 BTEqualStrategyNumber, F_OIDEQ,
275 ObjectIdGetDatum(classoid));
276
277 shdescription = table_open(SharedDescriptionRelationId, RowExclusiveLock);
278
279 sd = systable_beginscan(shdescription, SharedDescriptionObjIndexId, true,
280 NULL, 2, skey);
281
282 while ((oldtuple = systable_getnext(sd)) != NULL)
283 {
284 /* Found the old tuple, so delete or update it */
285
286 if (comment == NULL)
287 CatalogTupleDelete(shdescription, &oldtuple->t_self);
288 else
289 {
290 newtuple = heap_modify_tuple(oldtuple, RelationGetDescr(shdescription),
291 values, nulls, replaces);
292 CatalogTupleUpdate(shdescription, &oldtuple->t_self, newtuple);
293 }
294
295 break; /* Assume there can be only one match */
296 }
297
298 systable_endscan(sd);
299
300 /* If we didn't find an old tuple, insert a new one */
301
302 if (newtuple == NULL && comment != NULL)
303 {
304 newtuple = heap_form_tuple(RelationGetDescr(shdescription),
305 values, nulls);
306 CatalogTupleInsert(shdescription, newtuple);
307 }
308
309 if (newtuple != NULL)
310 heap_freetuple(newtuple);
311
312 /* Done */
313
314 table_close(shdescription, NoLock);
315}
316
317/*
318 * DeleteComments -- remove comments for an object
319 *
320 * If subid is nonzero then only comments matching it will be removed.
321 * If subid is zero, all comments matching the oid/classoid will be removed
322 * (this corresponds to deleting a whole object).
323 */
324void
325DeleteComments(Oid oid, Oid classoid, int32 subid)
326{
327 Relation description;
328 ScanKeyData skey[3];
329 int nkeys;
330 SysScanDesc sd;
331 HeapTuple oldtuple;
332
333 /* Use the index to search for all matching old tuples */
334
335 ScanKeyInit(&skey[0],
336 Anum_pg_description_objoid,
337 BTEqualStrategyNumber, F_OIDEQ,
338 ObjectIdGetDatum(oid));
339 ScanKeyInit(&skey[1],
340 Anum_pg_description_classoid,
341 BTEqualStrategyNumber, F_OIDEQ,
342 ObjectIdGetDatum(classoid));
343
344 if (subid != 0)
345 {
346 ScanKeyInit(&skey[2],
347 Anum_pg_description_objsubid,
348 BTEqualStrategyNumber, F_INT4EQ,
349 Int32GetDatum(subid));
350 nkeys = 3;
351 }
352 else
353 nkeys = 2;
354
355 description = table_open(DescriptionRelationId, RowExclusiveLock);
356
357 sd = systable_beginscan(description, DescriptionObjIndexId, true,
358 NULL, nkeys, skey);
359
360 while ((oldtuple = systable_getnext(sd)) != NULL)
361 CatalogTupleDelete(description, &oldtuple->t_self);
362
363 /* Done */
364
365 systable_endscan(sd);
366 table_close(description, RowExclusiveLock);
367}
368
369/*
370 * DeleteSharedComments -- remove comments for a shared object
371 */
372void
373DeleteSharedComments(Oid oid, Oid classoid)
374{
375 Relation shdescription;
376 ScanKeyData skey[2];
377 SysScanDesc sd;
378 HeapTuple oldtuple;
379
380 /* Use the index to search for all matching old tuples */
381
382 ScanKeyInit(&skey[0],
383 Anum_pg_shdescription_objoid,
384 BTEqualStrategyNumber, F_OIDEQ,
385 ObjectIdGetDatum(oid));
386 ScanKeyInit(&skey[1],
387 Anum_pg_shdescription_classoid,
388 BTEqualStrategyNumber, F_OIDEQ,
389 ObjectIdGetDatum(classoid));
390
391 shdescription = table_open(SharedDescriptionRelationId, RowExclusiveLock);
392
393 sd = systable_beginscan(shdescription, SharedDescriptionObjIndexId, true,
394 NULL, 2, skey);
395
396 while ((oldtuple = systable_getnext(sd)) != NULL)
397 CatalogTupleDelete(shdescription, &oldtuple->t_self);
398
399 /* Done */
400
401 systable_endscan(sd);
402 table_close(shdescription, RowExclusiveLock);
403}
404
405/*
406 * GetComment -- get the comment for an object, or null if not found.
407 */
408char *
409GetComment(Oid oid, Oid classoid, int32 subid)
410{
411 Relation description;
412 ScanKeyData skey[3];
413 SysScanDesc sd;
414 TupleDesc tupdesc;
415 HeapTuple tuple;
416 char *comment;
417
418 /* Use the index to search for a matching old tuple */
419
420 ScanKeyInit(&skey[0],
421 Anum_pg_description_objoid,
422 BTEqualStrategyNumber, F_OIDEQ,
423 ObjectIdGetDatum(oid));
424 ScanKeyInit(&skey[1],
425 Anum_pg_description_classoid,
426 BTEqualStrategyNumber, F_OIDEQ,
427 ObjectIdGetDatum(classoid));
428 ScanKeyInit(&skey[2],
429 Anum_pg_description_objsubid,
430 BTEqualStrategyNumber, F_INT4EQ,
431 Int32GetDatum(subid));
432
433 description = table_open(DescriptionRelationId, AccessShareLock);
434 tupdesc = RelationGetDescr(description);
435
436 sd = systable_beginscan(description, DescriptionObjIndexId, true,
437 NULL, 3, skey);
438
439 comment = NULL;
440 while ((tuple = systable_getnext(sd)) != NULL)
441 {
442 Datum value;
443 bool isnull;
444
445 /* Found the tuple, get description field */
446 value = heap_getattr(tuple, Anum_pg_description_description, tupdesc, &isnull);
447 if (!isnull)
448 comment = TextDatumGetCString(value);
449 break; /* Assume there can be only one match */
450 }
451
452 systable_endscan(sd);
453
454 /* Done */
455 table_close(description, AccessShareLock);
456
457 return comment;
458}
459