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  */
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.
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  */
141 void
CreateComments(Oid oid,Oid classoid,int32 subid,const char * comment)142 CreateComments(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  */
236 void
CreateSharedComments(Oid oid,Oid classoid,const char * comment)237 CreateSharedComments(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  */
324 void
DeleteComments(Oid oid,Oid classoid,int32 subid)325 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 	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  */
372 void
DeleteSharedComments(Oid oid,Oid classoid)373 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 	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  */
408 char *
GetComment(Oid oid,Oid classoid,int32 subid)409 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 	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