comment.c 22.8 KB
Newer Older
Bruce Momjian's avatar
Bruce Momjian committed
1 2 3
/*-------------------------------------------------------------------------
 *
 * comment.c
4
 *
Bruce Momjian's avatar
Bruce Momjian committed
5 6
 * PostgreSQL object comments utility code.
 *
7
 * Copyright (c) 1999-2001, PostgreSQL Global Development Group
Bruce Momjian's avatar
Bruce Momjian committed
8
 *
9
 * IDENTIFICATION
10
 *	  $Header: /cvsroot/pgsql/src/backend/commands/comment.c,v 1.42 2002/04/18 20:01:09 tgl Exp $
11
 *
Bruce Momjian's avatar
Bruce Momjian committed
12 13 14 15 16
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

17
#include "access/genam.h"
Bruce Momjian's avatar
Bruce Momjian committed
18 19 20
#include "access/heapam.h"
#include "catalog/catname.h"
#include "catalog/indexing.h"
21
#include "catalog/namespace.h"
Bruce Momjian's avatar
Bruce Momjian committed
22 23
#include "catalog/pg_database.h"
#include "catalog/pg_description.h"
24
#include "catalog/pg_namespace.h"
Bruce Momjian's avatar
Bruce Momjian committed
25
#include "catalog/pg_operator.h"
26
#include "catalog/pg_rewrite.h"
Bruce Momjian's avatar
Bruce Momjian committed
27 28 29
#include "catalog/pg_trigger.h"
#include "commands/comment.h"
#include "miscadmin.h"
30
#include "parser/parse_func.h"
31
#include "parser/parse_oper.h"
32
#include "parser/parse_type.h"
33
#include "parser/parse.h"
Bruce Momjian's avatar
Bruce Momjian committed
34
#include "utils/acl.h"
35
#include "utils/builtins.h"
36
#include "utils/fmgroids.h"
37
#include "utils/lsyscache.h"
Bruce Momjian's avatar
Bruce Momjian committed
38 39 40
#include "utils/syscache.h"


41
/*
Bruce Momjian's avatar
Bruce Momjian committed
42
 * Static Function Prototypes --
43 44 45 46
 *
 * The following protoypes are declared static so as not to conflict
 * with any other routines outside this module. These routines are
 * called by the public function CommentObject() routine to create
Bruce Momjian's avatar
Bruce Momjian committed
47 48 49
 * the appropriate comment for the specific object type.
 */

50 51 52 53 54 55 56
static void CommentRelation(int objtype, List *relname, char *comment);
static void CommentAttribute(List *qualname, char *comment);
static void CommentDatabase(List *qualname, char *comment);
static void CommentRule(List *qualname, char *comment);
static void CommentType(List *typename, char *comment);
static void CommentAggregate(List *aggregate, List *arguments, char *comment);
static void CommentProc(List *function, List *arguments, char *comment);
57
static void CommentOperator(List *opername, List *arguments, char *comment);
58 59 60 61
static void CommentTrigger(List *qualname, char *comment);


/*
Bruce Momjian's avatar
Bruce Momjian committed
62 63
 * CommentObject --
 *
64
 * This routine is used to add the associated comment into
65 66
 * pg_description for the object specified by the given SQL command.
 */
67
void
68
CommentObject(CommentStmt *stmt)
69
{
70
	switch (stmt->objtype)
71
	{
72 73 74 75
		case INDEX:
		case SEQUENCE:
		case TABLE:
		case VIEW:
76
			CommentRelation(stmt->objtype, stmt->objname, stmt->comment);
77
			break;
78
		case COLUMN:
79
			CommentAttribute(stmt->objname, stmt->comment);
80
			break;
81
		case DATABASE:
82
			CommentDatabase(stmt->objname, stmt->comment);
83
			break;
84
		case RULE:
85
			CommentRule(stmt->objname, stmt->comment);
86
			break;
87
		case TYPE_P:
88
			CommentType(stmt->objname, stmt->comment);
89
			break;
90
		case AGGREGATE:
91
			CommentAggregate(stmt->objname, stmt->objargs, stmt->comment);
92
			break;
93
		case FUNCTION:
94
			CommentProc(stmt->objname, stmt->objargs, stmt->comment);
95
			break;
96
		case OPERATOR:
97
			CommentOperator(stmt->objname, stmt->objargs, stmt->comment);
98
			break;
99
		case TRIGGER:
100
			CommentTrigger(stmt->objname, stmt->comment);
101 102
			break;
		default:
103
			elog(ERROR, "An attempt was made to comment on a unknown type: %d",
104
				 stmt->objtype);
105
	}
Bruce Momjian's avatar
Bruce Momjian committed
106 107
}

108
/*
Bruce Momjian's avatar
Bruce Momjian committed
109
 * CreateComments --
110
 *
111 112
 * Create a comment for the specified object descriptor.  Inserts a new
 * pg_description tuple, or replaces an existing one with the same key.
Bruce Momjian's avatar
Bruce Momjian committed
113
 *
114 115
 * If the comment given is null or an empty string, instead delete any
 * existing comment for the specified key.
Bruce Momjian's avatar
Bruce Momjian committed
116
 */
117 118
void
CreateComments(Oid oid, Oid classoid, int32 subid, char *comment)
119 120
{
	Relation	description;
121 122 123 124 125 126 127
	Relation	descriptionindex;
	ScanKeyData skey[3];
	IndexScanDesc sd;
	RetrieveIndexResult indexRes;
	HeapTupleData oldtuple;
	Buffer		buffer;
	HeapTuple	newtuple = NULL;
128 129 130 131 132
	Datum		values[Natts_pg_description];
	char		nulls[Natts_pg_description];
	char		replaces[Natts_pg_description];
	int			i;

133 134 135
	/* Reduce empty-string to NULL case */
	if (comment != NULL && strlen(comment) == 0)
		comment = NULL;
136

137 138
	/* Prepare to form or update a tuple, if necessary */
	if (comment != NULL)
139 140 141 142 143 144 145 146
	{
		for (i = 0; i < Natts_pg_description; i++)
		{
			nulls[i] = ' ';
			replaces[i] = 'r';
		}
		i = 0;
		values[i++] = ObjectIdGetDatum(oid);
147 148
		values[i++] = ObjectIdGetDatum(classoid);
		values[i++] = Int32GetDatum(subid);
149
		values[i++] = DirectFunctionCall1(textin, CStringGetDatum(comment));
150 151
	}

152
	/* Open pg_description and its index */
153

154 155 156 157 158 159 160 161 162
	description = heap_openr(DescriptionRelationName, RowExclusiveLock);
	descriptionindex = index_openr(DescriptionObjIndex);

	/* Use the index to search for a matching old tuple */

	ScanKeyEntryInitialize(&skey[0],
						   (bits16) 0x0,
						   (AttrNumber) 1,
						   (RegProcedure) F_OIDEQ,
163 164
						   ObjectIdGetDatum(oid));

165 166 167 168 169 170 171 172 173 174 175 176 177
	ScanKeyEntryInitialize(&skey[1],
						   (bits16) 0x0,
						   (AttrNumber) 2,
						   (RegProcedure) F_OIDEQ,
						   ObjectIdGetDatum(classoid));

	ScanKeyEntryInitialize(&skey[2],
						   (bits16) 0x0,
						   (AttrNumber) 3,
						   (RegProcedure) F_INT4EQ,
						   Int32GetDatum(subid));

	sd = index_beginscan(descriptionindex, false, 3, skey);
178

179 180 181 182
	oldtuple.t_datamcxt = CurrentMemoryContext;
	oldtuple.t_data = NULL;

	while ((indexRes = index_getnext(sd, ForwardScanDirection)))
183
	{
184 185 186 187 188 189
		oldtuple.t_self = indexRes->heap_iptr;
		heap_fetch(description, SnapshotNow, &oldtuple, &buffer, sd);
		pfree(indexRes);

		if (oldtuple.t_data == NULL)
			continue;			/* time qual failed */
190

191
		/* Found the old tuple, so delete or update it */
192

193 194
		if (comment == NULL)
			simple_heap_delete(description, &oldtuple.t_self);
195 196
		else
		{
197 198 199
			newtuple = heap_modifytuple(&oldtuple, description, values,
										nulls, replaces);
			simple_heap_update(description, &oldtuple.t_self, newtuple);
200 201
		}

202 203
		ReleaseBuffer(buffer);
		break;					/* Assume there can be only one match */
Bruce Momjian's avatar
Bruce Momjian committed
204
	}
205

206
	index_endscan(sd);
207

208
	/* If we didn't find an old tuple, insert a new one */
Bruce Momjian's avatar
Bruce Momjian committed
209

210 211 212 213 214
	if (oldtuple.t_data == NULL && comment != NULL)
	{
		newtuple = heap_formtuple(RelationGetDescr(description),
								  values, nulls);
		heap_insert(description, newtuple);
215
	}
Bruce Momjian's avatar
Bruce Momjian committed
216

217
	/* Update indexes, if necessary */
Bruce Momjian's avatar
Bruce Momjian committed
218

219
	if (newtuple != NULL)
220 221 222 223
	{
		if (RelationGetForm(description)->relhasindex)
		{
			Relation	idescs[Num_pg_description_indices];
224

225 226 227
			CatalogOpenIndices(Num_pg_description_indices,
							   Name_pg_description_indices, idescs);
			CatalogIndexInsert(idescs, Num_pg_description_indices, description,
228
							   newtuple);
229 230
			CatalogCloseIndices(Num_pg_description_indices, idescs);
		}
231
		heap_freetuple(newtuple);
232
	}
Bruce Momjian's avatar
Bruce Momjian committed
233

234
	/* Done */
Bruce Momjian's avatar
Bruce Momjian committed
235

236 237
	index_close(descriptionindex);
	heap_close(description, NoLock);
Bruce Momjian's avatar
Bruce Momjian committed
238 239
}

240
/*
Bruce Momjian's avatar
Bruce Momjian committed
241 242
 * DeleteComments --
 *
243 244 245
 * This routine is used to purge all comments associated with an object,
 * regardless of their objsubid.  It is called, for example, when a relation
 * is destroyed.
Bruce Momjian's avatar
Bruce Momjian committed
246
 */
247
void
248
DeleteComments(Oid oid, Oid classoid)
249 250
{
	Relation	description;
251 252 253 254 255 256 257 258
	Relation	descriptionindex;
	ScanKeyData skey[2];
	IndexScanDesc sd;
	RetrieveIndexResult indexRes;
	HeapTupleData oldtuple;
	Buffer		buffer;

	/* Open pg_description and its index */
Bruce Momjian's avatar
Bruce Momjian committed
259

260
	description = heap_openr(DescriptionRelationName, RowExclusiveLock);
261
	descriptionindex = index_openr(DescriptionObjIndex);
Bruce Momjian's avatar
Bruce Momjian committed
262

263
	/* Use the index to search for all matching old tuples */
264

265 266 267 268
	ScanKeyEntryInitialize(&skey[0],
						   (bits16) 0x0,
						   (AttrNumber) 1,
						   (RegProcedure) F_OIDEQ,
269
						   ObjectIdGetDatum(oid));
Bruce Momjian's avatar
Bruce Momjian committed
270

271 272 273 274 275 276 277
	ScanKeyEntryInitialize(&skey[1],
						   (bits16) 0x0,
						   (AttrNumber) 2,
						   (RegProcedure) F_OIDEQ,
						   ObjectIdGetDatum(classoid));

	sd = index_beginscan(descriptionindex, false, 2, skey);
Bruce Momjian's avatar
Bruce Momjian committed
278

279 280 281 282 283
	while ((indexRes = index_getnext(sd, ForwardScanDirection)))
	{
		oldtuple.t_self = indexRes->heap_iptr;
		heap_fetch(description, SnapshotNow, &oldtuple, &buffer, sd);
		pfree(indexRes);
284

285 286
		if (oldtuple.t_data == NULL)
			continue;			/* time qual failed */
Bruce Momjian's avatar
Bruce Momjian committed
287

288 289 290 291 292 293
		simple_heap_delete(description, &oldtuple.t_self);

		ReleaseBuffer(buffer);
	}

	/* Done */
294

295 296 297
	index_endscan(sd);
	index_close(descriptionindex);
	heap_close(description, NoLock);
Bruce Momjian's avatar
Bruce Momjian committed
298
}
299

300
/*
Bruce Momjian's avatar
Bruce Momjian committed
301 302
 * CommentRelation --
 *
303
 * This routine is used to add/drop a comment from a relation, where
Bruce Momjian's avatar
Bruce Momjian committed
304
 * a relation is a TABLE, SEQUENCE, VIEW or INDEX. The routine simply
305 306
 * finds the relation name by searching the system cache, locating
 * the appropriate tuple, and inserting a comment using that
Bruce Momjian's avatar
Bruce Momjian committed
307
 * tuple's oid. Its parameters are the relation name and comments.
308
 */
309
static void
310
CommentRelation(int objtype, List *relname, char *comment)
311
{
312
	Relation	relation;
313 314 315
	RangeVar   *tgtrel;

	tgtrel = makeRangeVarFromNameList(relname);
316

317
	/*
318 319 320 321
	 * Open the relation.  We do this mainly to acquire a lock that
	 * ensures no one else drops the relation before we commit.  (If they
	 * did, they'd fail to remove the entry we are about to make in
	 * pg_description.)
322
	 */
323
	relation = relation_openrv(tgtrel, AccessShareLock);
324

325 326
	/* Check object security */
	if (!pg_class_ownercheck(RelationGetRelid(relation), GetUserId()))
327 328
		elog(ERROR, "you are not permitted to comment on class '%s'",
			 RelationGetRelationName(relation));
329

330
	/* Next, verify that the relation type matches the intent */
331

332
	switch (objtype)
333
	{
334 335
		case INDEX:
			if (relation->rd_rel->relkind != RELKIND_INDEX)
336 337
				elog(ERROR, "relation '%s' is not an index",
					 RelationGetRelationName(relation));
338
			break;
339 340
		case TABLE:
			if (relation->rd_rel->relkind != RELKIND_RELATION)
341 342
				elog(ERROR, "relation '%s' is not a table",
					 RelationGetRelationName(relation));
343
			break;
344 345
		case VIEW:
			if (relation->rd_rel->relkind != RELKIND_VIEW)
346 347
				elog(ERROR, "relation '%s' is not a view",
					 RelationGetRelationName(relation));
348
			break;
349 350
		case SEQUENCE:
			if (relation->rd_rel->relkind != RELKIND_SEQUENCE)
351 352
				elog(ERROR, "relation '%s' is not a sequence",
					 RelationGetRelationName(relation));
353 354 355
			break;
	}

356 357 358
	/* Create the comment using the relation's oid */

	CreateComments(RelationGetRelid(relation), RelOid_pg_class, 0, comment);
359

360
	/* Done, but hold lock until commit */
361
	relation_close(relation, NoLock);
Bruce Momjian's avatar
Bruce Momjian committed
362 363
}

364
/*
Bruce Momjian's avatar
Bruce Momjian committed
365 366 367
 * CommentAttribute --
 *
 * This routine is used to add/drop a comment from an attribute
368
 * such as a table's column. The routine will check security
369
 * restrictions and then attempt to look up the specified
Bruce Momjian's avatar
Bruce Momjian committed
370
 * attribute. If successful, a comment is added/dropped, else an
371
 * elog() exception is thrown.	The parameters are the relation
372
 * and attribute names, and the comments
373
 */
374
static void
375
CommentAttribute(List *qualname, char *comment)
376
{
377 378 379 380
	int			nnames;
	List	   *relname;
	char	   *attrname;
	RangeVar   *rel;
381
	Relation	relation;
382
	AttrNumber	attnum;
Bruce Momjian's avatar
Bruce Momjian committed
383

384 385 386 387 388 389
	/* Separate relname and attr name */
	nnames = length(qualname);
	if (nnames < 2)
		elog(ERROR, "CommentAttribute: must specify relation.attribute");
	relname = ltruncate(nnames-1, listCopy(qualname));
	attrname = strVal(nth(nnames-1, qualname));
Bruce Momjian's avatar
Bruce Momjian committed
390

391 392
	/* Open the containing relation to ensure it won't go away meanwhile */
	rel = makeRangeVarFromNameList(relname);
393
	relation = heap_openrv(rel, AccessShareLock);
394

395 396 397
	/* Check object security */

	if (!pg_class_ownercheck(RelationGetRelid(relation), GetUserId()))
398 399
		elog(ERROR, "you are not permitted to comment on class '%s'",
			 RelationGetRelationName(relation));
400

401
	/* Now, fetch the attribute number from the system cache */
402

403 404
	attnum = get_attnum(RelationGetRelid(relation), attrname);
	if (attnum == InvalidAttrNumber)
405
		elog(ERROR, "'%s' is not an attribute of class '%s'",
406
			 attrname, RelationGetRelationName(relation));
407

408
	/* Create the comment using the relation's oid */
Bruce Momjian's avatar
Bruce Momjian committed
409

410 411
	CreateComments(RelationGetRelid(relation), RelOid_pg_class,
				   (int32) attnum, comment);
Bruce Momjian's avatar
Bruce Momjian committed
412

413
	/* Done, but hold lock until commit */
Bruce Momjian's avatar
Bruce Momjian committed
414

415
	heap_close(relation, NoLock);
Bruce Momjian's avatar
Bruce Momjian committed
416 417
}

418
/*
Bruce Momjian's avatar
Bruce Momjian committed
419 420 421
 * CommentDatabase --
 *
 * This routine is used to add/drop any user-comments a user might
422 423 424 425
 * have regarding the specified database. The routine will check
 * security for owner permissions, and, if succesful, will then
 * attempt to find the oid of the database specified. Once found,
 * a comment is added/dropped using the CreateComments() routine.
426
 */
427
static void
428
CommentDatabase(List *qualname, char *comment)
429
{
430
	char	   *database;
431 432 433
	Relation	pg_database;
	ScanKeyData entry;
	HeapScanDesc scan;
434
	HeapTuple	dbtuple;
435
	Oid			oid;
Bruce Momjian's avatar
Bruce Momjian committed
436

437 438 439 440
	if (length(qualname) != 1)
		elog(ERROR, "CommentDatabase: database name may not be qualified");
	database = strVal(lfirst(qualname));

441
	/* First find the tuple in pg_database for the database */
Bruce Momjian's avatar
Bruce Momjian committed
442

443 444
	pg_database = heap_openr(DatabaseRelationName, AccessShareLock);
	ScanKeyEntryInitialize(&entry, 0, Anum_pg_database_datname,
445
						   F_NAMEEQ, CStringGetDatum(database));
446 447
	scan = heap_beginscan(pg_database, 0, SnapshotNow, 1, &entry);
	dbtuple = heap_getnext(scan, 0);
448

449
	/* Validate database exists, and fetch the db oid */
Bruce Momjian's avatar
Bruce Momjian committed
450

451 452 453
	if (!HeapTupleIsValid(dbtuple))
		elog(ERROR, "database '%s' does not exist", database);
	oid = dbtuple->t_data->t_oid;
454

455
	/* Allow if the user matches the database dba or is a superuser */
456

457
	if (!(superuser() || is_dbadmin(oid)))
458 459
		elog(ERROR, "you are not permitted to comment on database '%s'",
			 database);
Bruce Momjian's avatar
Bruce Momjian committed
460

461
	/* Create the comments with the pg_database oid */
462

463
	CreateComments(oid, RelOid_pg_database, 0, comment);
464

465
	/* Complete the scan and close any opened relations */
466

467 468
	heap_endscan(scan);
	heap_close(pg_database, AccessShareLock);
Bruce Momjian's avatar
Bruce Momjian committed
469 470
}

471 472
/*
 * CommentRule --
Bruce Momjian's avatar
Bruce Momjian committed
473 474
 *
 * This routine is used to add/drop any user-comments a user might
475 476 477 478 479 480 481 482 483
 * have regarding a specified RULE. The rule for commenting is determined by
 * both its name and the relation to which it refers. The arguments to this
 * function are the rule name and relation name (merged into a qualified
 * name), and the comment to add/drop.
 *
 * Before PG 7.3, rules had unique names across the whole database, and so
 * the syntax was just COMMENT ON RULE rulename, with no relation name.
 * For purposes of backwards compatibility, we support that as long as there
 * is only one rule by the specified name in the database.
484
 */
485
static void
486
CommentRule(List *qualname, char *comment)
487
{
488 489 490 491 492
	int			nnames;
	List	   *relname;
	char	   *rulename;
	RangeVar   *rel;
	Relation	relation;
493 494 495
	HeapTuple	tuple;
	Oid			reloid;
	Oid			ruleoid;
496
	Oid			classoid;
497 498
	int32		aclcheck;

499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531
	/* Separate relname and trig name */
	nnames = length(qualname);
	if (nnames == 1)
	{
		/* Old-style: only a rule name is given */
		Relation	RewriteRelation;
		HeapScanDesc scanDesc;
		ScanKeyData scanKeyData;

		rulename = strVal(lfirst(qualname));

		/* Search pg_rewrite for such a rule */
		ScanKeyEntryInitialize(&scanKeyData,
							   0,
							   Anum_pg_rewrite_rulename,
							   F_NAMEEQ,
							   PointerGetDatum(rulename));

		RewriteRelation = heap_openr(RewriteRelationName, AccessShareLock);
		scanDesc = heap_beginscan(RewriteRelation,
								  0, SnapshotNow, 1, &scanKeyData);

		tuple = heap_getnext(scanDesc, 0);
		if (HeapTupleIsValid(tuple))
		{
			reloid = ((Form_pg_rewrite) GETSTRUCT(tuple))->ev_class;
			ruleoid = tuple->t_data->t_oid;
		}
		else
		{
			elog(ERROR, "rule '%s' does not exist", rulename);
			reloid = ruleoid = 0; /* keep compiler quiet */
		}
532

533 534 535 536
		if (HeapTupleIsValid(tuple = heap_getnext(scanDesc, 0)))
			elog(ERROR, "There are multiple rules '%s'"
				 "\n\tPlease specify a relation name as well as a rule name",
				 rulename);
537

538 539
		heap_endscan(scanDesc);
		heap_close(RewriteRelation, AccessShareLock);
Bruce Momjian's avatar
Bruce Momjian committed
540

541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566
		/* Open the owning relation to ensure it won't go away meanwhile */
		relation = heap_open(reloid, AccessShareLock);
	}
	else
	{
		/* New-style: rule and relname both provided */
		Assert(nnames >= 2);
		relname = ltruncate(nnames-1, listCopy(qualname));
		rulename = strVal(nth(nnames-1, qualname));

		/* Open the owning relation to ensure it won't go away meanwhile */
		rel = makeRangeVarFromNameList(relname);
		relation = heap_openrv(rel, AccessShareLock);
		reloid = RelationGetRelid(relation);

		/* Find the rule's pg_rewrite tuple, get its OID */
		tuple = SearchSysCache(RULERELNAME,
							   ObjectIdGetDatum(reloid),
							   PointerGetDatum(rulename),
							   0, 0);
		if (!HeapTupleIsValid(tuple))
			elog(ERROR, "rule '%s' does not exist", rulename);
		Assert(reloid == ((Form_pg_rewrite) GETSTRUCT(tuple))->ev_class);
		ruleoid = tuple->t_data->t_oid;
		ReleaseSysCache(tuple);
	}
567

568 569 570
	/* Check object security */

	aclcheck = pg_class_aclcheck(reloid, GetUserId(), ACL_RULE);
571 572
	if (aclcheck != ACLCHECK_OK)
		elog(ERROR, "you are not permitted to comment on rule '%s'",
573
			 rulename);
Bruce Momjian's avatar
Bruce Momjian committed
574

575 576
	/* pg_rewrite doesn't have a hard-coded OID, so must look it up */

577
	classoid = get_relname_relid(RewriteRelationName, PG_CATALOG_NAMESPACE);
578
	Assert(OidIsValid(classoid));
Bruce Momjian's avatar
Bruce Momjian committed
579

580 581
	/* Call CreateComments() to create/drop the comments */

582
	CreateComments(ruleoid, classoid, 0, comment);
Bruce Momjian's avatar
Bruce Momjian committed
583 584
}

585
/*
Bruce Momjian's avatar
Bruce Momjian committed
586 587 588 589
 * CommentType --
 *
 * This routine is used to add/drop any user-comments a user might
 * have regarding a TYPE. The type is specified by name
590
 * and, if found, and the user has appropriate permissions, a
Bruce Momjian's avatar
Bruce Momjian committed
591 592
 * comment will be added/dropped using the CreateComments() routine.
 * The type's name and the comments are the paramters to this routine.
593
 */
594
static void
595
CommentType(List *typename, char *comment)
596
{
597
	TypeName   *tname;
598
	Oid			oid;
Bruce Momjian's avatar
Bruce Momjian committed
599

600 601 602 603 604
	/* XXX a bit of a crock; should accept TypeName in COMMENT syntax */
	tname = makeNode(TypeName);
	tname->names = typename;
	tname->typmod = -1;

605
	/* Find the type's oid */
606

607
	oid = typenameTypeId(tname);
Bruce Momjian's avatar
Bruce Momjian committed
608

609 610 611
	/* Check object security */

	if (!pg_type_ownercheck(oid, GetUserId()))
612 613
		elog(ERROR, "you are not permitted to comment on type %s",
			 TypeNameToString(tname));
614

615
	/* Call CreateComments() to create/drop the comments */
Bruce Momjian's avatar
Bruce Momjian committed
616

617
	CreateComments(oid, RelOid_pg_type, 0, comment);
Bruce Momjian's avatar
Bruce Momjian committed
618 619
}

620
/*
Bruce Momjian's avatar
Bruce Momjian committed
621 622
 * CommentAggregate --
 *
623
 * This routine is used to allow a user to provide comments on an
Bruce Momjian's avatar
Bruce Momjian committed
624
 * aggregate function. The aggregate function is determined by both
625
 * its name and its argument type, which, with the comments are
Bruce Momjian's avatar
Bruce Momjian committed
626
 * the three parameters handed to this routine.
627
 */
628
static void
629
CommentAggregate(List *aggregate, List *arguments, char *comment)
630
{
631
	TypeName   *aggtype = (TypeName *) lfirst(arguments);
632 633 634
	Oid			baseoid,
				oid;

635
	/* First, attempt to determine the base aggregate oid */
636
	if (aggtype)
637
		baseoid = typenameTypeId(aggtype);
638
	else
639
		baseoid = InvalidOid;
640

641
	/* Now, attempt to find the actual tuple in pg_proc */
642

643
	oid = find_aggregate_func("CommentAggregate", aggregate, baseoid);
644

645
	/* Next, validate the user's attempt to comment */
646

647
	if (!pg_proc_ownercheck(oid, GetUserId()))
648
	{
649
		if (baseoid == InvalidOid)
650 651
			elog(ERROR, "you are not permitted to comment on aggregate %s for all types",
				 NameListToString(aggregate));
652
		else
653 654
			elog(ERROR, "you are not permitted to comment on aggregate %s for type %s",
				 NameListToString(aggregate), format_type_be(baseoid));
655 656
	}

657 658
	/* Call CreateComments() to create/drop the comments */

659
	CreateComments(oid, RelOid_pg_proc, 0, comment);
Bruce Momjian's avatar
Bruce Momjian committed
660 661
}

662
/*
Bruce Momjian's avatar
Bruce Momjian committed
663 664
 * CommentProc --
 *
665
 * This routine is used to allow a user to provide comments on an
Bruce Momjian's avatar
Bruce Momjian committed
666
 * procedure (function). The procedure is determined by both
667
 * its name and its argument list. The argument list is expected to
Bruce Momjian's avatar
Bruce Momjian committed
668 669
 * be a series of parsed nodes pointed to by a List object. If the
 * comments string is empty, the associated comment is dropped.
670
 */
671
static void
672
CommentProc(List *function, List *arguments, char *comment)
673
{
674
	Oid			oid;
675

676
	/* Look up the procedure */
Bruce Momjian's avatar
Bruce Momjian committed
677

678 679
	oid = LookupFuncNameTypeNames(function, arguments,
								  true, "CommentProc");
680

681 682 683
	/* Now, validate the user's ability to comment on this function */

	if (!pg_proc_ownercheck(oid, GetUserId()))
684 685
		elog(ERROR, "you are not permitted to comment on function %s",
			 NameListToString(function));
686

687
	/* Call CreateComments() to create/drop the comments */
Bruce Momjian's avatar
Bruce Momjian committed
688

689
	CreateComments(oid, RelOid_pg_proc, 0, comment);
Bruce Momjian's avatar
Bruce Momjian committed
690
}
691

692
/*
Bruce Momjian's avatar
Bruce Momjian committed
693 694
 * CommentOperator --
 *
695
 * This routine is used to allow a user to provide comments on an
Bruce Momjian's avatar
Bruce Momjian committed
696 697
 * operator. The operator for commenting is determined by both
 * its name and its argument list which defines the left and right
698 699 700
 * hand types the operator will operate on. The argument list is
 * expected to be a couple of parse nodes pointed to be a List
 * object. If the comments string is empty, the associated comment
Bruce Momjian's avatar
Bruce Momjian committed
701
 * is dropped.
702 703 704 705
 *
 * NOTE: we actually attach the comment to the procedure that underlies
 * the operator.  This is a feature, not a bug: we want the same comment
 * to be visible for both operator and function.
706
 */
707
static void
708
CommentOperator(List *opername, List *arguments, char *comment)
709
{
710 711
	TypeName   *typenode1 = (TypeName *) lfirst(arguments);
	TypeName   *typenode2 = (TypeName *) lsecond(arguments);
712
	Oid			oid;
713

714
	/* Look up the operator */
Bruce Momjian's avatar
Bruce Momjian committed
715

716 717
	oid = LookupOperNameTypeNames(opername, typenode1, typenode2,
								  "CommentOperator");
Bruce Momjian's avatar
Bruce Momjian committed
718

719
	/* Valid user's ability to comment on this operator */
720

721
	if (!pg_oper_ownercheck(oid, GetUserId()))
722
		elog(ERROR, "you are not permitted to comment on operator '%s'",
723
			 NameListToString(opername));
Bruce Momjian's avatar
Bruce Momjian committed
724

725
	/* Get the procedure associated with the operator */
Bruce Momjian's avatar
Bruce Momjian committed
726

727
	oid = get_opcode(oid);
728
	if (oid == InvalidOid)
729 730
		elog(ERROR, "operator '%s' does not have an underlying function",
			 NameListToString(opername));
731

732
	/* Call CreateComments() to create/drop the comments */
Bruce Momjian's avatar
Bruce Momjian committed
733

734
	CreateComments(oid, RelOid_pg_proc, 0, comment);
Bruce Momjian's avatar
Bruce Momjian committed
735 736
}

737
/*
Bruce Momjian's avatar
Bruce Momjian committed
738 739 740 741 742
 * CommentTrigger --
 *
 * This routine is used to allow a user to provide comments on a
 * trigger event. The trigger for commenting is determined by both
 * its name and the relation to which it refers. The arguments to this
743 744 745
 * function are the trigger name and relation name (merged into a qualified
 * name), and the comment to add/drop.
 */
746
static void
747
CommentTrigger(List *qualname, char *comment)
748
{
749 750 751 752
	int			nnames;
	List	   *relname;
	char	   *trigname;
	RangeVar   *rel;
753 754 755 756
	Relation	pg_trigger,
				relation;
	HeapTuple	triggertuple;
	HeapScanDesc scan;
757 758
	ScanKeyData entry[2];
	Oid			oid;
759

760 761 762 763 764 765
	/* Separate relname and trig name */
	nnames = length(qualname);
	if (nnames < 2)
		elog(ERROR, "CommentTrigger: must specify relation and trigger");
	relname = ltruncate(nnames-1, listCopy(qualname));
	trigname = strVal(nth(nnames-1, qualname));
766

767 768
	/* Open the owning relation to ensure it won't go away meanwhile */
	rel = makeRangeVarFromNameList(relname);
769
	relation = heap_openrv(rel, AccessShareLock);
770

771 772
	/* Check object security */

773
	if (!pg_class_ownercheck(RelationGetRelid(relation), GetUserId()))
774 775
		elog(ERROR, "you are not permitted to comment on trigger '%s' for relation '%s'",
			 trigname, RelationGetRelationName(relation));
776

777
	/* Fetch the trigger oid from pg_trigger  */
778 779

	pg_trigger = heap_openr(TriggerRelationName, AccessShareLock);
780 781 782 783 784
	ScanKeyEntryInitialize(&entry[0], 0x0, Anum_pg_trigger_tgrelid,
						   F_OIDEQ,
						   ObjectIdGetDatum(RelationGetRelid(relation)));
	ScanKeyEntryInitialize(&entry[1], 0x0, Anum_pg_trigger_tgname,
						   F_NAMEEQ,
785
						   CStringGetDatum(trigname));
786
	scan = heap_beginscan(pg_trigger, 0, SnapshotNow, 2, entry);
787 788
	triggertuple = heap_getnext(scan, 0);

789
	/* If no trigger exists for the relation specified, notify user */
790

791
	if (!HeapTupleIsValid(triggertuple))
792 793
		elog(ERROR, "trigger '%s' for relation '%s' does not exist",
			 trigname, RelationGetRelationName(relation));
794

795
	oid = triggertuple->t_data->t_oid;
796

797
	heap_endscan(scan);
798

799 800 801 802 803
	/* Create the comments with the pg_trigger oid */

	CreateComments(oid, RelationGetRelid(pg_trigger), 0, comment);

	/* Done, but hold lock on relation */
804 805

	heap_close(pg_trigger, AccessShareLock);
806
	heap_close(relation, NoLock);
Bruce Momjian's avatar
Bruce Momjian committed
807
}