1 /*-------------------------------------------------------------------------
2  *
3  * comment.c
4  *
5  * PostgreSQL object comments utility code.
6  *
7  * Copyright (c) 1996-2016, 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
CommentObject(CommentStmt * stmt)40 CommentObject(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.  (If the length of stmt->objname is not 1, get_object_address
52 	 * will throw an error below; that's OK.)
53 	 */
54 	if (stmt->objtype == OBJECT_DATABASE && list_length(stmt->objname) == 1)
55 	{
56 		char	   *database = strVal(linitial(stmt->objname));
57 
58 		if (!OidIsValid(get_database_oid(database, true)))
59 		{
60 			ereport(WARNING,
61 					(errcode(ERRCODE_UNDEFINED_DATABASE),
62 					 errmsg("database \"%s\" does not exist", database)));
63 			return address;
64 		}
65 	}
66 
67 	/*
68 	 * Translate the parser representation that identifies this object into an
69 	 * ObjectAddress.  get_object_address() will throw an error if the object
70 	 * does not exist, and will also acquire a lock on the target to guard
71 	 * against concurrent DROP operations.
72 	 */
73 	address = get_object_address(stmt->objtype, stmt->objname, stmt->objargs,
74 								 &relation, ShareUpdateExclusiveLock, false);
75 
76 	/* Require ownership of the target object. */
77 	check_object_ownership(GetUserId(), stmt->objtype, address,
78 						   stmt->objname, stmt->objargs, relation);
79 
80 	/* Perform other integrity checks as needed. */
81 	switch (stmt->objtype)
82 	{
83 		case OBJECT_COLUMN:
84 
85 			/*
86 			 * Allow comments only on columns of tables, views, materialized
87 			 * views, composite types, and foreign tables (which are the only
88 			 * relkinds for which pg_dump will dump per-column comments).  In
89 			 * particular we wish to disallow comments on index columns,
90 			 * because the naming of an index's columns may change across PG
91 			 * versions, so dumping per-column comments could create reload
92 			 * failures.
93 			 */
94 			if (relation->rd_rel->relkind != RELKIND_RELATION &&
95 				relation->rd_rel->relkind != RELKIND_VIEW &&
96 				relation->rd_rel->relkind != RELKIND_MATVIEW &&
97 				relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
98 				relation->rd_rel->relkind != RELKIND_FOREIGN_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  */
141 void
CreateComments(Oid oid,Oid classoid,int32 subid,char * comment)142 CreateComments(Oid oid, Oid classoid, int32 subid, 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 = heap_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 			simple_heap_delete(description, &oldtuple->t_self);
198 		else
199 		{
200 			newtuple = heap_modify_tuple(oldtuple, RelationGetDescr(description), values,
201 										 nulls, replaces);
202 			simple_heap_update(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 		simple_heap_insert(description, newtuple);
217 	}
218 
219 	/* Update indexes, if necessary */
220 	if (newtuple != NULL)
221 	{
222 		CatalogUpdateIndexes(description, newtuple);
223 		heap_freetuple(newtuple);
224 	}
225 
226 	/* Done */
227 
228 	heap_close(description, NoLock);
229 }
230 
231 /*
232  * CreateSharedComments --
233  *
234  * Create a comment for the specified shared object descriptor.  Inserts a
235  * new pg_shdescription tuple, or replaces an existing one with the same key.
236  *
237  * If the comment given is null or an empty string, instead delete any
238  * existing comment for the specified key.
239  */
240 void
CreateSharedComments(Oid oid,Oid classoid,char * comment)241 CreateSharedComments(Oid oid, Oid classoid, char *comment)
242 {
243 	Relation	shdescription;
244 	ScanKeyData skey[2];
245 	SysScanDesc sd;
246 	HeapTuple	oldtuple;
247 	HeapTuple	newtuple = NULL;
248 	Datum		values[Natts_pg_shdescription];
249 	bool		nulls[Natts_pg_shdescription];
250 	bool		replaces[Natts_pg_shdescription];
251 	int			i;
252 
253 	/* Reduce empty-string to NULL case */
254 	if (comment != NULL && strlen(comment) == 0)
255 		comment = NULL;
256 
257 	/* Prepare to form or update a tuple, if necessary */
258 	if (comment != NULL)
259 	{
260 		for (i = 0; i < Natts_pg_shdescription; i++)
261 		{
262 			nulls[i] = false;
263 			replaces[i] = true;
264 		}
265 		values[Anum_pg_shdescription_objoid - 1] = ObjectIdGetDatum(oid);
266 		values[Anum_pg_shdescription_classoid - 1] = ObjectIdGetDatum(classoid);
267 		values[Anum_pg_shdescription_description - 1] = CStringGetTextDatum(comment);
268 	}
269 
270 	/* Use the index to search for a matching old tuple */
271 
272 	ScanKeyInit(&skey[0],
273 				Anum_pg_shdescription_objoid,
274 				BTEqualStrategyNumber, F_OIDEQ,
275 				ObjectIdGetDatum(oid));
276 	ScanKeyInit(&skey[1],
277 				Anum_pg_shdescription_classoid,
278 				BTEqualStrategyNumber, F_OIDEQ,
279 				ObjectIdGetDatum(classoid));
280 
281 	shdescription = heap_open(SharedDescriptionRelationId, RowExclusiveLock);
282 
283 	sd = systable_beginscan(shdescription, SharedDescriptionObjIndexId, true,
284 							NULL, 2, skey);
285 
286 	while ((oldtuple = systable_getnext(sd)) != NULL)
287 	{
288 		/* Found the old tuple, so delete or update it */
289 
290 		if (comment == NULL)
291 			simple_heap_delete(shdescription, &oldtuple->t_self);
292 		else
293 		{
294 			newtuple = heap_modify_tuple(oldtuple, RelationGetDescr(shdescription),
295 										 values, nulls, replaces);
296 			simple_heap_update(shdescription, &oldtuple->t_self, newtuple);
297 		}
298 
299 		break;					/* Assume there can be only one match */
300 	}
301 
302 	systable_endscan(sd);
303 
304 	/* If we didn't find an old tuple, insert a new one */
305 
306 	if (newtuple == NULL && comment != NULL)
307 	{
308 		newtuple = heap_form_tuple(RelationGetDescr(shdescription),
309 								   values, nulls);
310 		simple_heap_insert(shdescription, newtuple);
311 	}
312 
313 	/* Update indexes, if necessary */
314 	if (newtuple != NULL)
315 	{
316 		CatalogUpdateIndexes(shdescription, newtuple);
317 		heap_freetuple(newtuple);
318 	}
319 
320 	/* Done */
321 
322 	heap_close(shdescription, NoLock);
323 }
324 
325 /*
326  * DeleteComments -- remove comments for an object
327  *
328  * If subid is nonzero then only comments matching it will be removed.
329  * If subid is zero, all comments matching the oid/classoid will be removed
330  * (this corresponds to deleting a whole object).
331  */
332 void
DeleteComments(Oid oid,Oid classoid,int32 subid)333 DeleteComments(Oid oid, Oid classoid, int32 subid)
334 {
335 	Relation	description;
336 	ScanKeyData skey[3];
337 	int			nkeys;
338 	SysScanDesc sd;
339 	HeapTuple	oldtuple;
340 
341 	/* Use the index to search for all matching old tuples */
342 
343 	ScanKeyInit(&skey[0],
344 				Anum_pg_description_objoid,
345 				BTEqualStrategyNumber, F_OIDEQ,
346 				ObjectIdGetDatum(oid));
347 	ScanKeyInit(&skey[1],
348 				Anum_pg_description_classoid,
349 				BTEqualStrategyNumber, F_OIDEQ,
350 				ObjectIdGetDatum(classoid));
351 
352 	if (subid != 0)
353 	{
354 		ScanKeyInit(&skey[2],
355 					Anum_pg_description_objsubid,
356 					BTEqualStrategyNumber, F_INT4EQ,
357 					Int32GetDatum(subid));
358 		nkeys = 3;
359 	}
360 	else
361 		nkeys = 2;
362 
363 	description = heap_open(DescriptionRelationId, RowExclusiveLock);
364 
365 	sd = systable_beginscan(description, DescriptionObjIndexId, true,
366 							NULL, nkeys, skey);
367 
368 	while ((oldtuple = systable_getnext(sd)) != NULL)
369 		simple_heap_delete(description, &oldtuple->t_self);
370 
371 	/* Done */
372 
373 	systable_endscan(sd);
374 	heap_close(description, RowExclusiveLock);
375 }
376 
377 /*
378  * DeleteSharedComments -- remove comments for a shared object
379  */
380 void
DeleteSharedComments(Oid oid,Oid classoid)381 DeleteSharedComments(Oid oid, Oid classoid)
382 {
383 	Relation	shdescription;
384 	ScanKeyData skey[2];
385 	SysScanDesc sd;
386 	HeapTuple	oldtuple;
387 
388 	/* Use the index to search for all matching old tuples */
389 
390 	ScanKeyInit(&skey[0],
391 				Anum_pg_shdescription_objoid,
392 				BTEqualStrategyNumber, F_OIDEQ,
393 				ObjectIdGetDatum(oid));
394 	ScanKeyInit(&skey[1],
395 				Anum_pg_shdescription_classoid,
396 				BTEqualStrategyNumber, F_OIDEQ,
397 				ObjectIdGetDatum(classoid));
398 
399 	shdescription = heap_open(SharedDescriptionRelationId, RowExclusiveLock);
400 
401 	sd = systable_beginscan(shdescription, SharedDescriptionObjIndexId, true,
402 							NULL, 2, skey);
403 
404 	while ((oldtuple = systable_getnext(sd)) != NULL)
405 		simple_heap_delete(shdescription, &oldtuple->t_self);
406 
407 	/* Done */
408 
409 	systable_endscan(sd);
410 	heap_close(shdescription, RowExclusiveLock);
411 }
412 
413 /*
414  * GetComment -- get the comment for an object, or null if not found.
415  */
416 char *
GetComment(Oid oid,Oid classoid,int32 subid)417 GetComment(Oid oid, Oid classoid, int32 subid)
418 {
419 	Relation	description;
420 	ScanKeyData skey[3];
421 	SysScanDesc sd;
422 	TupleDesc	tupdesc;
423 	HeapTuple	tuple;
424 	char	   *comment;
425 
426 	/* Use the index to search for a matching old tuple */
427 
428 	ScanKeyInit(&skey[0],
429 				Anum_pg_description_objoid,
430 				BTEqualStrategyNumber, F_OIDEQ,
431 				ObjectIdGetDatum(oid));
432 	ScanKeyInit(&skey[1],
433 				Anum_pg_description_classoid,
434 				BTEqualStrategyNumber, F_OIDEQ,
435 				ObjectIdGetDatum(classoid));
436 	ScanKeyInit(&skey[2],
437 				Anum_pg_description_objsubid,
438 				BTEqualStrategyNumber, F_INT4EQ,
439 				Int32GetDatum(subid));
440 
441 	description = heap_open(DescriptionRelationId, AccessShareLock);
442 	tupdesc = RelationGetDescr(description);
443 
444 	sd = systable_beginscan(description, DescriptionObjIndexId, true,
445 							NULL, 3, skey);
446 
447 	comment = NULL;
448 	while ((tuple = systable_getnext(sd)) != NULL)
449 	{
450 		Datum		value;
451 		bool		isnull;
452 
453 		/* Found the tuple, get description field */
454 		value = heap_getattr(tuple, Anum_pg_description_description, tupdesc, &isnull);
455 		if (!isnull)
456 			comment = TextDatumGetCString(value);
457 		break;					/* Assume there can be only one match */
458 	}
459 
460 	systable_endscan(sd);
461 
462 	/* Done */
463 	heap_close(description, AccessShareLock);
464 
465 	return comment;
466 }
467