Line data Source code
1 : /*-------------------------------------------------------------------------
2 : *
3 : * comment.c
4 : *
5 : * PostgreSQL object comments utility code.
6 : *
7 : * Copyright (c) 1996-2017, 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/heapam.h"
19 : #include "access/htup_details.h"
20 : #include "catalog/indexing.h"
21 : #include "catalog/objectaddress.h"
22 : #include "catalog/pg_description.h"
23 : #include "catalog/pg_shdescription.h"
24 : #include "commands/comment.h"
25 : #include "commands/dbcommands.h"
26 : #include "miscadmin.h"
27 : #include "utils/builtins.h"
28 : #include "utils/fmgroids.h"
29 : #include "utils/rel.h"
30 : #include "utils/tqual.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
40 403 : CommentObject(CommentStmt *stmt)
41 : {
42 : Relation relation;
43 403 : 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 403 : if (stmt->objtype == OBJECT_DATABASE)
54 : {
55 2 : char *database = strVal((Value *) stmt->object);
56 :
57 2 : if (!OidIsValid(get_database_oid(database, true)))
58 : {
59 0 : ereport(WARNING,
60 : (errcode(ERRCODE_UNDEFINED_DATABASE),
61 : errmsg("database \"%s\" does not exist", database)));
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 : */
72 403 : address = get_object_address(stmt->objtype, stmt->object,
73 : &relation, ShareUpdateExclusiveLock, false);
74 :
75 : /* Require ownership of the target object. */
76 383 : check_object_ownership(GetUserId(), stmt->objtype, address,
77 : stmt->object, relation);
78 :
79 : /* Perform other integrity checks as needed. */
80 383 : 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 28 : if (relation->rd_rel->relkind != RELKIND_RELATION &&
94 12 : relation->rd_rel->relkind != RELKIND_VIEW &&
95 12 : relation->rd_rel->relkind != RELKIND_MATVIEW &&
96 10 : relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
97 5 : relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&
98 1 : relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
99 0 : 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 22 : break;
104 : default:
105 361 : 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 383 : if (stmt->objtype == OBJECT_DATABASE || stmt->objtype == OBJECT_TABLESPACE
114 381 : || stmt->objtype == OBJECT_ROLE)
115 2 : CreateSharedComments(address.objectId, address.classId, stmt->comment);
116 : else
117 381 : 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 383 : if (relation != NULL)
127 63 : relation_close(relation, NoLock);
128 :
129 383 : 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 : */
141 : void
142 390 : CreateComments(Oid oid, Oid classoid, int32 subid, char *comment)
143 : {
144 : Relation description;
145 : ScanKeyData skey[3];
146 : SysScanDesc sd;
147 : HeapTuple oldtuple;
148 390 : 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 390 : if (comment != NULL && strlen(comment) == 0)
156 0 : comment = NULL;
157 :
158 : /* Prepare to form or update a tuple, if necessary */
159 390 : if (comment != NULL)
160 : {
161 1870 : for (i = 0; i < Natts_pg_description; i++)
162 : {
163 1496 : nulls[i] = false;
164 1496 : replaces[i] = true;
165 : }
166 374 : values[Anum_pg_description_objoid - 1] = ObjectIdGetDatum(oid);
167 374 : values[Anum_pg_description_classoid - 1] = ObjectIdGetDatum(classoid);
168 374 : values[Anum_pg_description_objsubid - 1] = Int32GetDatum(subid);
169 374 : values[Anum_pg_description_description - 1] = CStringGetTextDatum(comment);
170 : }
171 :
172 : /* Use the index to search for a matching old tuple */
173 :
174 390 : ScanKeyInit(&skey[0],
175 : Anum_pg_description_objoid,
176 : BTEqualStrategyNumber, F_OIDEQ,
177 : ObjectIdGetDatum(oid));
178 390 : ScanKeyInit(&skey[1],
179 : Anum_pg_description_classoid,
180 : BTEqualStrategyNumber, F_OIDEQ,
181 : ObjectIdGetDatum(classoid));
182 390 : ScanKeyInit(&skey[2],
183 : Anum_pg_description_objsubid,
184 : BTEqualStrategyNumber, F_INT4EQ,
185 : Int32GetDatum(subid));
186 :
187 390 : description = heap_open(DescriptionRelationId, RowExclusiveLock);
188 :
189 390 : sd = systable_beginscan(description, DescriptionObjIndexId, true,
190 : NULL, 3, skey);
191 :
192 390 : while ((oldtuple = systable_getnext(sd)) != NULL)
193 : {
194 : /* Found the old tuple, so delete or update it */
195 :
196 62 : if (comment == NULL)
197 16 : CatalogTupleDelete(description, &oldtuple->t_self);
198 : else
199 : {
200 46 : newtuple = heap_modify_tuple(oldtuple, RelationGetDescr(description), values,
201 : nulls, replaces);
202 46 : CatalogTupleUpdate(description, &oldtuple->t_self, newtuple);
203 : }
204 :
205 62 : break; /* Assume there can be only one match */
206 : }
207 :
208 390 : systable_endscan(sd);
209 :
210 : /* If we didn't find an old tuple, insert a new one */
211 :
212 390 : if (newtuple == NULL && comment != NULL)
213 : {
214 328 : newtuple = heap_form_tuple(RelationGetDescr(description),
215 : values, nulls);
216 328 : CatalogTupleInsert(description, newtuple);
217 : }
218 :
219 390 : if (newtuple != NULL)
220 374 : heap_freetuple(newtuple);
221 :
222 : /* Done */
223 :
224 390 : heap_close(description, NoLock);
225 390 : }
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 : */
236 : void
237 2 : CreateSharedComments(Oid oid, Oid classoid, char *comment)
238 : {
239 : Relation shdescription;
240 : ScanKeyData skey[2];
241 : SysScanDesc sd;
242 : HeapTuple oldtuple;
243 2 : 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 2 : if (comment != NULL && strlen(comment) == 0)
251 0 : comment = NULL;
252 :
253 : /* Prepare to form or update a tuple, if necessary */
254 2 : if (comment != NULL)
255 : {
256 8 : for (i = 0; i < Natts_pg_shdescription; i++)
257 : {
258 6 : nulls[i] = false;
259 6 : replaces[i] = true;
260 : }
261 2 : values[Anum_pg_shdescription_objoid - 1] = ObjectIdGetDatum(oid);
262 2 : values[Anum_pg_shdescription_classoid - 1] = ObjectIdGetDatum(classoid);
263 2 : values[Anum_pg_shdescription_description - 1] = CStringGetTextDatum(comment);
264 : }
265 :
266 : /* Use the index to search for a matching old tuple */
267 :
268 2 : ScanKeyInit(&skey[0],
269 : Anum_pg_shdescription_objoid,
270 : BTEqualStrategyNumber, F_OIDEQ,
271 : ObjectIdGetDatum(oid));
272 2 : ScanKeyInit(&skey[1],
273 : Anum_pg_shdescription_classoid,
274 : BTEqualStrategyNumber, F_OIDEQ,
275 : ObjectIdGetDatum(classoid));
276 :
277 2 : shdescription = heap_open(SharedDescriptionRelationId, RowExclusiveLock);
278 :
279 2 : sd = systable_beginscan(shdescription, SharedDescriptionObjIndexId, true,
280 : NULL, 2, skey);
281 :
282 2 : while ((oldtuple = systable_getnext(sd)) != NULL)
283 : {
284 : /* Found the old tuple, so delete or update it */
285 :
286 0 : if (comment == NULL)
287 0 : CatalogTupleDelete(shdescription, &oldtuple->t_self);
288 : else
289 : {
290 0 : newtuple = heap_modify_tuple(oldtuple, RelationGetDescr(shdescription),
291 : values, nulls, replaces);
292 0 : CatalogTupleUpdate(shdescription, &oldtuple->t_self, newtuple);
293 : }
294 :
295 0 : break; /* Assume there can be only one match */
296 : }
297 :
298 2 : systable_endscan(sd);
299 :
300 : /* If we didn't find an old tuple, insert a new one */
301 :
302 2 : if (newtuple == NULL && comment != NULL)
303 : {
304 2 : newtuple = heap_form_tuple(RelationGetDescr(shdescription),
305 : values, nulls);
306 2 : CatalogTupleInsert(shdescription, newtuple);
307 : }
308 :
309 2 : if (newtuple != NULL)
310 2 : heap_freetuple(newtuple);
311 :
312 : /* Done */
313 :
314 2 : heap_close(shdescription, NoLock);
315 2 : }
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 : */
324 : void
325 9057 : DeleteComments(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 9057 : ScanKeyInit(&skey[0],
336 : Anum_pg_description_objoid,
337 : BTEqualStrategyNumber, F_OIDEQ,
338 : ObjectIdGetDatum(oid));
339 9057 : ScanKeyInit(&skey[1],
340 : Anum_pg_description_classoid,
341 : BTEqualStrategyNumber, F_OIDEQ,
342 : ObjectIdGetDatum(classoid));
343 :
344 9057 : if (subid != 0)
345 : {
346 117 : ScanKeyInit(&skey[2],
347 : Anum_pg_description_objsubid,
348 : BTEqualStrategyNumber, F_INT4EQ,
349 : Int32GetDatum(subid));
350 117 : nkeys = 3;
351 : }
352 : else
353 8940 : nkeys = 2;
354 :
355 9057 : description = heap_open(DescriptionRelationId, RowExclusiveLock);
356 :
357 9057 : sd = systable_beginscan(description, DescriptionObjIndexId, true,
358 : NULL, nkeys, skey);
359 :
360 18159 : while ((oldtuple = systable_getnext(sd)) != NULL)
361 45 : CatalogTupleDelete(description, &oldtuple->t_self);
362 :
363 : /* Done */
364 :
365 9057 : systable_endscan(sd);
366 9057 : heap_close(description, RowExclusiveLock);
367 9057 : }
368 :
369 : /*
370 : * DeleteSharedComments -- remove comments for a shared object
371 : */
372 : void
373 109 : DeleteSharedComments(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 109 : ScanKeyInit(&skey[0],
383 : Anum_pg_shdescription_objoid,
384 : BTEqualStrategyNumber, F_OIDEQ,
385 : ObjectIdGetDatum(oid));
386 109 : ScanKeyInit(&skey[1],
387 : Anum_pg_shdescription_classoid,
388 : BTEqualStrategyNumber, F_OIDEQ,
389 : ObjectIdGetDatum(classoid));
390 :
391 109 : shdescription = heap_open(SharedDescriptionRelationId, RowExclusiveLock);
392 :
393 109 : sd = systable_beginscan(shdescription, SharedDescriptionObjIndexId, true,
394 : NULL, 2, skey);
395 :
396 218 : while ((oldtuple = systable_getnext(sd)) != NULL)
397 0 : CatalogTupleDelete(shdescription, &oldtuple->t_self);
398 :
399 : /* Done */
400 :
401 109 : systable_endscan(sd);
402 109 : heap_close(shdescription, RowExclusiveLock);
403 109 : }
404 :
405 : /*
406 : * GetComment -- get the comment for an object, or null if not found.
407 : */
408 : char *
409 56 : GetComment(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 56 : ScanKeyInit(&skey[0],
421 : Anum_pg_description_objoid,
422 : BTEqualStrategyNumber, F_OIDEQ,
423 : ObjectIdGetDatum(oid));
424 56 : ScanKeyInit(&skey[1],
425 : Anum_pg_description_classoid,
426 : BTEqualStrategyNumber, F_OIDEQ,
427 : ObjectIdGetDatum(classoid));
428 56 : ScanKeyInit(&skey[2],
429 : Anum_pg_description_objsubid,
430 : BTEqualStrategyNumber, F_INT4EQ,
431 : Int32GetDatum(subid));
432 :
433 56 : description = heap_open(DescriptionRelationId, AccessShareLock);
434 56 : tupdesc = RelationGetDescr(description);
435 :
436 56 : sd = systable_beginscan(description, DescriptionObjIndexId, true,
437 : NULL, 3, skey);
438 :
439 56 : comment = NULL;
440 56 : while ((tuple = systable_getnext(sd)) != NULL)
441 : {
442 : Datum value;
443 : bool isnull;
444 :
445 : /* Found the tuple, get description field */
446 28 : value = heap_getattr(tuple, Anum_pg_description_description, tupdesc, &isnull);
447 28 : if (!isnull)
448 28 : comment = TextDatumGetCString(value);
449 28 : break; /* Assume there can be only one match */
450 : }
451 :
452 56 : systable_endscan(sd);
453 :
454 : /* Done */
455 56 : heap_close(description, AccessShareLock);
456 :
457 56 : return comment;
458 : }
|