Skip to content
Snippets Groups Projects
Select Git revision
  • benchmark-tools
  • postgres-lambda
  • master default
  • REL9_4_25
  • REL9_5_20
  • REL9_6_16
  • REL_10_11
  • REL_11_6
  • REL_12_1
  • REL_12_0
  • REL_12_RC1
  • REL_12_BETA4
  • REL9_4_24
  • REL9_5_19
  • REL9_6_15
  • REL_10_10
  • REL_11_5
  • REL_12_BETA3
  • REL9_4_23
  • REL9_5_18
  • REL9_6_14
  • REL_10_9
  • REL_11_4
23 results

comment.c

Blame
  • comment.c 34.90 KiB
    /*-------------------------------------------------------------------------
     *
     * comment.c
     *
     * PostgreSQL object comments utility code.
     *
     * Copyright (c) 1996-2005, PostgreSQL Global Development Group
     *
     * IDENTIFICATION
     *	  $PostgreSQL: pgsql/src/backend/commands/comment.c,v 1.83 2005/04/14 20:03:23 tgl Exp $
     *
     *-------------------------------------------------------------------------
     */
    
    #include "postgres.h"
    
    #include "access/genam.h"
    #include "access/heapam.h"
    #include "catalog/indexing.h"
    #include "catalog/namespace.h"
    #include "catalog/pg_cast.h"
    #include "catalog/pg_constraint.h"
    #include "catalog/pg_conversion.h"
    #include "catalog/pg_database.h"
    #include "catalog/pg_description.h"
    #include "catalog/pg_language.h"
    #include "catalog/pg_largeobject.h"
    #include "catalog/pg_namespace.h"
    #include "catalog/pg_opclass.h"
    #include "catalog/pg_operator.h"
    #include "catalog/pg_proc.h"
    #include "catalog/pg_rewrite.h"
    #include "catalog/pg_trigger.h"
    #include "catalog/pg_type.h"
    #include "commands/comment.h"
    #include "commands/dbcommands.h"
    #include "miscadmin.h"
    #include "parser/parse_func.h"
    #include "parser/parse_oper.h"
    #include "parser/parse_type.h"
    #include "utils/acl.h"
    #include "utils/builtins.h"
    #include "utils/fmgroids.h"
    #include "utils/lsyscache.h"
    #include "utils/syscache.h"
    
    
    /*
     * Static Function Prototypes --
     *
     * 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
     * the appropriate comment for the specific object type.
     */
    
    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 CommentNamespace(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);
    static void CommentOperator(List *opername, List *arguments, char *comment);
    static void CommentTrigger(List *qualname, char *comment);
    static void CommentConstraint(List *qualname, char *comment);
    static void CommentConversion(List *qualname, char *comment);
    static void CommentLanguage(List *qualname, char *comment);
    static void CommentOpClass(List *qualname, List *arguments, char *comment);
    static void CommentLargeObject(List *qualname, char *comment);
    static void CommentCast(List *qualname, List *arguments, char *comment);
    
    
    /*
     * CommentObject --
     *
     * This routine is used to add the associated comment into
     * pg_description for the object specified by the given SQL command.
     */
    void
    CommentObject(CommentStmt *stmt)
    {
    	switch (stmt->objtype)
    	{
    		case OBJECT_INDEX:
    		case OBJECT_SEQUENCE:
    		case OBJECT_TABLE:
    		case OBJECT_VIEW:
    			CommentRelation(stmt->objtype, stmt->objname, stmt->comment);
    			break;
    		case OBJECT_COLUMN:
    			CommentAttribute(stmt->objname, stmt->comment);
    			break;
    		case OBJECT_DATABASE:
    			CommentDatabase(stmt->objname, stmt->comment);
    			break;
    		case OBJECT_RULE:
    			CommentRule(stmt->objname, stmt->comment);
    			break;
    		case OBJECT_TYPE:
    			CommentType(stmt->objname, stmt->comment);
    			break;
    		case OBJECT_AGGREGATE:
    			CommentAggregate(stmt->objname, stmt->objargs, stmt->comment);
    			break;
    		case OBJECT_FUNCTION:
    			CommentProc(stmt->objname, stmt->objargs, stmt->comment);
    			break;
    		case OBJECT_OPERATOR:
    			CommentOperator(stmt->objname, stmt->objargs, stmt->comment);
    			break;
    		case OBJECT_TRIGGER:
    			CommentTrigger(stmt->objname, stmt->comment);
    			break;
    		case OBJECT_SCHEMA:
    			CommentNamespace(stmt->objname, stmt->comment);
    			break;
    		case OBJECT_CONSTRAINT:
    			CommentConstraint(stmt->objname, stmt->comment);
    			break;
    		case OBJECT_CONVERSION:
    			CommentConversion(stmt->objname, stmt->comment);
    			break;
    		case OBJECT_LANGUAGE:
    			CommentLanguage(stmt->objname, stmt->comment);
    			break;
    		case OBJECT_OPCLASS:
    			CommentOpClass(stmt->objname, stmt->objargs, stmt->comment);
    			break;
    		case OBJECT_LARGEOBJECT:
    			CommentLargeObject(stmt->objname, stmt->comment);
    			break;
    		case OBJECT_CAST:
    			CommentCast(stmt->objname, stmt->objargs, stmt->comment);
    			break;
    		default:
    			elog(ERROR, "unrecognized object type: %d",
    				 (int) stmt->objtype);
    	}
    }
    
    /*
     * CreateComments --
     *
     * Create a comment for the specified object descriptor.  Inserts a new
     * pg_description tuple, or replaces an existing one with the same key.
     *
     * If the comment given is null or an empty string, instead delete any
     * existing comment for the specified key.
     */
    void
    CreateComments(Oid oid, Oid classoid, int32 subid, char *comment)
    {
    	Relation	description;
    	ScanKeyData skey[3];
    	SysScanDesc sd;
    	HeapTuple	oldtuple;
    	HeapTuple	newtuple = NULL;
    	Datum		values[Natts_pg_description];
    	char		nulls[Natts_pg_description];
    	char		replaces[Natts_pg_description];
    	int			i;
    
    	/* Reduce empty-string to NULL case */
    	if (comment != NULL && strlen(comment) == 0)
    		comment = NULL;
    
    	/* Prepare to form or update a tuple, if necessary */
    	if (comment != NULL)
    	{
    		for (i = 0; i < Natts_pg_description; i++)
    		{
    			nulls[i] = ' ';
    			replaces[i] = 'r';
    		}
    		i = 0;
    		values[i++] = ObjectIdGetDatum(oid);
    		values[i++] = ObjectIdGetDatum(classoid);
    		values[i++] = Int32GetDatum(subid);
    		values[i++] = DirectFunctionCall1(textin, CStringGetDatum(comment));
    	}
    
    	/* Use the index to search for a matching old tuple */
    
    	ScanKeyInit(&skey[0],
    				Anum_pg_description_objoid,
    				BTEqualStrategyNumber, F_OIDEQ,
    				ObjectIdGetDatum(oid));
    	ScanKeyInit(&skey[1],
    				Anum_pg_description_classoid,
    				BTEqualStrategyNumber, F_OIDEQ,
    				ObjectIdGetDatum(classoid));
    	ScanKeyInit(&skey[2],
    				Anum_pg_description_objsubid,
    				BTEqualStrategyNumber, F_INT4EQ,
    				Int32GetDatum(subid));
    
    	description = heap_open(DescriptionRelationId, RowExclusiveLock);
    
    	sd = systable_beginscan(description, DescriptionObjIndexId, true,
    							SnapshotNow, 3, skey);
    
    	while ((oldtuple = systable_getnext(sd)) != NULL)
    	{
    		/* Found the old tuple, so delete or update it */
    
    		if (comment == NULL)
    			simple_heap_delete(description, &oldtuple->t_self);
    		else
    		{
    			newtuple = heap_modifytuple(oldtuple, RelationGetDescr(description), values,
    										nulls, replaces);
    			simple_heap_update(description, &oldtuple->t_self, newtuple);
    		}
    
    		break;					/* Assume there can be only one match */
    	}
    
    	systable_endscan(sd);
    
    	/* If we didn't find an old tuple, insert a new one */
    
    	if (newtuple == NULL && comment != NULL)
    	{
    		newtuple = heap_formtuple(RelationGetDescr(description),
    								  values, nulls);
    		simple_heap_insert(description, newtuple);
    	}
    
    	/* Update indexes, if necessary */
    	if (newtuple != NULL)
    	{
    		CatalogUpdateIndexes(description, newtuple);
    		heap_freetuple(newtuple);
    	}
    
    	/* Done */
    
    	heap_close(description, NoLock);
    }
    
    /*
     * DeleteComments -- remove comments for an object
     *
     * If subid is nonzero then only comments matching it will be removed.
     * If subid is zero, all comments matching the oid/classoid will be removed
     * (this corresponds to deleting a whole object).
     */
    void
    DeleteComments(Oid oid, Oid classoid, int32 subid)
    {
    	Relation	description;
    	ScanKeyData skey[3];
    	int			nkeys;
    	SysScanDesc sd;
    	HeapTuple	oldtuple;
    
    	/* Use the index to search for all matching old tuples */
    
    	ScanKeyInit(&skey[0],
    				Anum_pg_description_objoid,
    				BTEqualStrategyNumber, F_OIDEQ,
    				ObjectIdGetDatum(oid));
    	ScanKeyInit(&skey[1],
    				Anum_pg_description_classoid,
    				BTEqualStrategyNumber, F_OIDEQ,
    				ObjectIdGetDatum(classoid));
    
    	if (subid != 0)
    	{
    		ScanKeyInit(&skey[2],
    					Anum_pg_description_objsubid,
    					BTEqualStrategyNumber, F_INT4EQ,
    					Int32GetDatum(subid));
    		nkeys = 3;
    	}
    	else
    		nkeys = 2;
    
    	description = heap_open(DescriptionRelationId, RowExclusiveLock);
    
    	sd = systable_beginscan(description, DescriptionObjIndexId, true,
    							SnapshotNow, nkeys, skey);
    
    	while ((oldtuple = systable_getnext(sd)) != NULL)
    		simple_heap_delete(description, &oldtuple->t_self);
    
    	/* Done */
    
    	systable_endscan(sd);
    	heap_close(description, RowExclusiveLock);
    }
    
    /*
     * CommentRelation --
     *
     * This routine is used to add/drop a comment from a relation, where
     * a relation is a TABLE, SEQUENCE, VIEW or INDEX. The routine simply
     * finds the relation name by searching the system cache, locating
     * the appropriate tuple, and inserting a comment using that
     * tuple's oid. Its parameters are the relation name and comments.
     */
    static void
    CommentRelation(int objtype, List *relname, char *comment)
    {
    	Relation	relation;
    	RangeVar   *tgtrel;
    
    	tgtrel = makeRangeVarFromNameList(relname);
    
    	/*
    	 * 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.)
    	 */
    	relation = relation_openrv(tgtrel, AccessShareLock);
    
    	/* Check object security */
    	if (!pg_class_ownercheck(RelationGetRelid(relation), GetUserId()))
    		aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS,
    					   RelationGetRelationName(relation));
    
    	/* Next, verify that the relation type matches the intent */
    
    	switch (objtype)
    	{
    		case OBJECT_INDEX:
    			if (relation->rd_rel->relkind != RELKIND_INDEX)
    				ereport(ERROR,
    						(errcode(ERRCODE_WRONG_OBJECT_TYPE),
    						 errmsg("\"%s\" is not an index",
    								RelationGetRelationName(relation))));
    			break;
    		case OBJECT_SEQUENCE:
    			if (relation->rd_rel->relkind != RELKIND_SEQUENCE)
    				ereport(ERROR,
    						(errcode(ERRCODE_WRONG_OBJECT_TYPE),
    						 errmsg("\"%s\" is not a sequence",
    								RelationGetRelationName(relation))));
    			break;
    		case OBJECT_TABLE:
    			if (relation->rd_rel->relkind != RELKIND_RELATION)
    				ereport(ERROR,
    						(errcode(ERRCODE_WRONG_OBJECT_TYPE),
    						 errmsg("\"%s\" is not a table",
    								RelationGetRelationName(relation))));
    			break;
    		case OBJECT_VIEW:
    			if (relation->rd_rel->relkind != RELKIND_VIEW)
    				ereport(ERROR,
    						(errcode(ERRCODE_WRONG_OBJECT_TYPE),
    						 errmsg("\"%s\" is not a view",
    								RelationGetRelationName(relation))));
    			break;
    	}
    
    	/* Create the comment using the relation's oid */
    	CreateComments(RelationGetRelid(relation), RelationRelationId,
    				   0, comment);
    
    	/* Done, but hold lock until commit */
    	relation_close(relation, NoLock);
    }
    
    /*
     * CommentAttribute --
     *
     * This routine is used to add/drop a comment from an attribute
     * such as a table's column. The routine will check security
     * restrictions and then attempt to look up the specified
     * attribute. If successful, a comment is added/dropped, else an
     * ereport() exception is thrown.	The parameters are the relation
     * and attribute names, and the comment
     */
    static void
    CommentAttribute(List *qualname, char *comment)
    {
    	int			nnames;
    	List	   *relname;
    	char	   *attrname;
    	RangeVar   *rel;
    	Relation	relation;
    	AttrNumber	attnum;
    
    	/* Separate relname and attr name */
    	nnames = list_length(qualname);
    	if (nnames < 2)				/* parser messed up */
    		elog(ERROR, "must specify relation and attribute");
    	relname = list_truncate(list_copy(qualname), nnames - 1);
    	attrname = strVal(lfirst(list_tail(qualname)));
    
    	/* Open the containing relation to ensure it won't go away meanwhile */
    	rel = makeRangeVarFromNameList(relname);
    	relation = relation_openrv(rel, AccessShareLock);
    
    	/* Check object security */
    
    	if (!pg_class_ownercheck(RelationGetRelid(relation), GetUserId()))
    		aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS,
    					   RelationGetRelationName(relation));
    
    	/* Now, fetch the attribute number from the system cache */
    
    	attnum = get_attnum(RelationGetRelid(relation), attrname);
    	if (attnum == InvalidAttrNumber)
    		ereport(ERROR,
    				(errcode(ERRCODE_UNDEFINED_COLUMN),
    				 errmsg("column \"%s\" of relation \"%s\" does not exist",
    						attrname, RelationGetRelationName(relation))));
    
    	/* Create the comment using the relation's oid */
    	CreateComments(RelationGetRelid(relation), RelationRelationId,
    				   (int32) attnum, comment);
    
    	/* Done, but hold lock until commit */
    
    	relation_close(relation, NoLock);
    }
    
    /*
     * CommentDatabase --
     *
     * This routine is used to add/drop any user-comments a user might
     * have regarding the specified database. The routine will check
     * security for owner permissions, and, if successful, will then
     * attempt to find the oid of the database specified. Once found,
     * a comment is added/dropped using the CreateComments() routine.
     */
    static void
    CommentDatabase(List *qualname, char *comment)
    {
    	char	   *database;
    	Oid			oid;
    
    	if (list_length(qualname) != 1)
    		ereport(ERROR,
    				(errcode(ERRCODE_SYNTAX_ERROR),
    				 errmsg("database name may not be qualified")));
    	database = strVal(linitial(qualname));
    
    	/*
    	 * We cannot currently support cross-database comments (since other
    	 * DBs cannot see pg_description of this database).  So, we reject
    	 * attempts to comment on a database other than the current one.
    	 * Someday this might be improved, but it would take a redesigned
    	 * infrastructure.
    	 *
    	 * When loading a dump, we may see a COMMENT ON DATABASE for the old name
    	 * of the database.  Erroring out would prevent pg_restore from
    	 * completing (which is really pg_restore's fault, but for now we will
    	 * work around the problem here).  Consensus is that the best fix is
    	 * to treat wrong database name as a WARNING not an ERROR.
    	 */
    
    	/* First get the database OID */
    	oid = get_database_oid(database);
    	if (!OidIsValid(oid))
    	{
    		ereport(WARNING,
    				(errcode(ERRCODE_UNDEFINED_DATABASE),
    				 errmsg("database \"%s\" does not exist", database)));
    		return;
    	}
    
    	/* Only allow comments on the current database */
    	if (oid != MyDatabaseId)
    	{
    		ereport(WARNING,		/* throw just a warning so pg_restore
    								 * doesn't fail */
    				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
    				 errmsg("database comments may only be applied to the current database")));
    		return;
    	}
    
    	/* Check object security */
    	if (!pg_database_ownercheck(oid, GetUserId()))
    		aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_DATABASE,
    					   database);
    
    	/* Call CreateComments() to create/drop the comments */
    	CreateComments(oid, DatabaseRelationId, 0, comment);
    }
    
    /*
     * CommentNamespace --
     *
     * This routine is used to add/drop any user-comments a user might
     * have regarding the specified namespace. The routine will check
     * security for owner permissions, and, if successful, will then
     * attempt to find the oid of the namespace specified. Once found,
     * a comment is added/dropped using the CreateComments() routine.
     */
    static void
    CommentNamespace(List *qualname, char *comment)
    {
    	Oid			oid;
    	char	   *namespace;
    
    	if (list_length(qualname) != 1)
    		ereport(ERROR,
    				(errcode(ERRCODE_SYNTAX_ERROR),
    				 errmsg("schema name may not be qualified")));
    	namespace = strVal(linitial(qualname));
    
    	oid = GetSysCacheOid(NAMESPACENAME,
    						 CStringGetDatum(namespace),
    						 0, 0, 0);
    	if (!OidIsValid(oid))
    		ereport(ERROR,
    				(errcode(ERRCODE_UNDEFINED_SCHEMA),
    				 errmsg("schema \"%s\" does not exist", namespace)));
    
    	/* Check object security */
    	if (!pg_namespace_ownercheck(oid, GetUserId()))
    		aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_NAMESPACE,
    					   namespace);
    
    	/* Call CreateComments() to create/drop the comments */
    	CreateComments(oid, NamespaceRelationId, 0, comment);
    }
    
    /*
     * CommentRule --
     *
     * This routine is used to add/drop any user-comments a user might
     * 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.
     */
    static void
    CommentRule(List *qualname, char *comment)
    {
    	int			nnames;
    	List	   *relname;
    	char	   *rulename;
    	RangeVar   *rel;
    	Relation	relation;
    	HeapTuple	tuple;
    	Oid			reloid;
    	Oid			ruleoid;
    	AclResult	aclcheck;
    
    	/* Separate relname and trig name */
    	nnames = list_length(qualname);
    	if (nnames == 1)
    	{
    		/* Old-style: only a rule name is given */
    		Relation	RewriteRelation;
    		HeapScanDesc scanDesc;
    		ScanKeyData scanKeyData;
    
    		rulename = strVal(linitial(qualname));
    
    		/* Search pg_rewrite for such a rule */
    		ScanKeyInit(&scanKeyData,
    					Anum_pg_rewrite_rulename,
    					BTEqualStrategyNumber, F_NAMEEQ,
    					PointerGetDatum(rulename));
    
    		RewriteRelation = heap_open(RewriteRelationId, AccessShareLock);
    		scanDesc = heap_beginscan(RewriteRelation, SnapshotNow,
    								  1, &scanKeyData);
    
    		tuple = heap_getnext(scanDesc, ForwardScanDirection);
    		if (HeapTupleIsValid(tuple))
    		{
    			reloid = ((Form_pg_rewrite) GETSTRUCT(tuple))->ev_class;
    			ruleoid = HeapTupleGetOid(tuple);
    		}
    		else
    		{
    			ereport(ERROR,
    					(errcode(ERRCODE_UNDEFINED_OBJECT),
    					 errmsg("rule \"%s\" does not exist", rulename)));
    			reloid = ruleoid = 0;		/* keep compiler quiet */
    		}
    
    		if (HeapTupleIsValid(tuple = heap_getnext(scanDesc,
    												  ForwardScanDirection)))
    			ereport(ERROR,
    					(errcode(ERRCODE_DUPLICATE_OBJECT),
    			   errmsg("there are multiple rules named \"%s\"", rulename),
    			errhint("Specify a relation name as well as a rule name.")));
    
    		heap_endscan(scanDesc);
    		heap_close(RewriteRelation, AccessShareLock);
    
    		/* 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 = list_truncate(list_copy(qualname), nnames - 1);
    		rulename = strVal(lfirst(list_tail(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))
    			ereport(ERROR,
    					(errcode(ERRCODE_UNDEFINED_OBJECT),
    				 errmsg("rule \"%s\" for relation \"%s\" does not exist",
    						rulename, RelationGetRelationName(relation))));
    		Assert(reloid == ((Form_pg_rewrite) GETSTRUCT(tuple))->ev_class);
    		ruleoid = HeapTupleGetOid(tuple);
    		ReleaseSysCache(tuple);
    	}
    
    	/* Check object security */
    	aclcheck = pg_class_aclcheck(reloid, GetUserId(), ACL_RULE);
    	if (aclcheck != ACLCHECK_OK)
    		aclcheck_error(aclcheck, ACL_KIND_CLASS,
    					   get_rel_name(reloid));
    
    	/* Call CreateComments() to create/drop the comments */
    	CreateComments(ruleoid, RewriteRelationId, 0, comment);
    
    	heap_close(relation, NoLock);
    }
    
    /*
     * CommentType --
     *
     * This routine is used to add/drop any user-comments a user might
     * have regarding a TYPE. The type is specified by name
     * and, if found, and the user has appropriate permissions, a
     * comment will be added/dropped using the CreateComments() routine.
     * The type's name and the comments are the parameters to this routine.
     */
    static void
    CommentType(List *typename, char *comment)
    {
    	TypeName   *tname;
    	Oid			oid;
    
    	/* XXX a bit of a crock; should accept TypeName in COMMENT syntax */
    	tname = makeNode(TypeName);
    	tname->names = typename;
    	tname->typmod = -1;
    
    	/* Find the type's oid */
    
    	oid = typenameTypeId(tname);
    
    	/* Check object security */
    
    	if (!pg_type_ownercheck(oid, GetUserId()))
    		aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TYPE,
    					   TypeNameToString(tname));
    
    	/* Call CreateComments() to create/drop the comments */
    	CreateComments(oid, TypeRelationId, 0, comment);
    }
    
    /*
     * CommentAggregate --
     *
     * This routine is used to allow a user to provide comments on an
     * aggregate function. The aggregate function is determined by both
     * its name and its argument type, which, with the comments are
     * the three parameters handed to this routine.
     */
    static void
    CommentAggregate(List *aggregate, List *arguments, char *comment)
    {
    	TypeName   *aggtype = (TypeName *) linitial(arguments);
    	Oid			baseoid,
    				oid;
    
    	/* First, attempt to determine the base aggregate oid */
    	if (aggtype)
    		baseoid = typenameTypeId(aggtype);
    	else
    		baseoid = ANYOID;
    
    	/* Now, attempt to find the actual tuple in pg_proc */
    
    	oid = find_aggregate_func(aggregate, baseoid, false);
    
    	/* Next, validate the user's attempt to comment */
    
    	if (!pg_proc_ownercheck(oid, GetUserId()))
    		aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_PROC,
    					   NameListToString(aggregate));
    
    	/* Call CreateComments() to create/drop the comments */
    	CreateComments(oid, ProcedureRelationId, 0, comment);
    }
    
    /*
     * CommentProc --
     *
     * This routine is used to allow a user to provide comments on an
     * procedure (function). The procedure is determined by both
     * its name and its argument list. The argument list is expected to
     * be a series of parsed nodes pointed to by a List object. If the
     * comments string is empty, the associated comment is dropped.
     */
    static void
    CommentProc(List *function, List *arguments, char *comment)
    {
    	Oid			oid;
    
    	/* Look up the procedure */
    
    	oid = LookupFuncNameTypeNames(function, arguments, false);
    
    	/* Now, validate the user's ability to comment on this function */
    
    	if (!pg_proc_ownercheck(oid, GetUserId()))
    		aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_PROC,
    					   NameListToString(function));
    
    	/* Call CreateComments() to create/drop the comments */
    	CreateComments(oid, ProcedureRelationId, 0, comment);
    }
    
    /*
     * CommentOperator --
     *
     * This routine is used to allow a user to provide comments on an
     * operator. The operator for commenting is determined by both
     * its name and its argument list which defines the left and right
     * 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.
     */
    static void
    CommentOperator(List *opername, List *arguments, char *comment)
    {
    	TypeName   *typenode1 = (TypeName *) linitial(arguments);
    	TypeName   *typenode2 = (TypeName *) lsecond(arguments);
    	Oid			oid;
    
    	/* Look up the operator */
    	oid = LookupOperNameTypeNames(opername, typenode1, typenode2, false);
    
    	/* Valid user's ability to comment on this operator */
    	if (!pg_oper_ownercheck(oid, GetUserId()))
    		aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_OPER,
    					   NameListToString(opername));
    
    	/* Call CreateComments() to create/drop the comments */
    	CreateComments(oid, OperatorRelationId, 0, comment);
    }
    
    /*
     * 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
     * function are the trigger name and relation name (merged into a qualified
     * name), and the comment to add/drop.
     */
    static void
    CommentTrigger(List *qualname, char *comment)
    {
    	int			nnames;
    	List	   *relname;
    	char	   *trigname;
    	RangeVar   *rel;
    	Relation	pg_trigger,
    				relation;
    	HeapTuple	triggertuple;
    	SysScanDesc scan;
    	ScanKeyData entry[2];
    	Oid			oid;
    
    	/* Separate relname and trig name */
    	nnames = list_length(qualname);
    	if (nnames < 2)				/* parser messed up */
    		elog(ERROR, "must specify relation and trigger");
    	relname = list_truncate(list_copy(qualname), nnames - 1);
    	trigname = strVal(lfirst(list_tail(qualname)));
    
    	/* Open the owning relation to ensure it won't go away meanwhile */
    	rel = makeRangeVarFromNameList(relname);
    	relation = heap_openrv(rel, AccessShareLock);
    
    	/* Check object security */
    
    	if (!pg_class_ownercheck(RelationGetRelid(relation), GetUserId()))
    		aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS,
    					   RelationGetRelationName(relation));
    
    	/*
    	 * Fetch the trigger tuple from pg_trigger.  There can be only one
    	 * because of the unique index.
    	 */
    	pg_trigger = heap_open(TriggerRelationId, AccessShareLock);
    	ScanKeyInit(&entry[0],
    				Anum_pg_trigger_tgrelid,
    				BTEqualStrategyNumber, F_OIDEQ,
    				ObjectIdGetDatum(RelationGetRelid(relation)));
    	ScanKeyInit(&entry[1],
    				Anum_pg_trigger_tgname,
    				BTEqualStrategyNumber, F_NAMEEQ,
    				CStringGetDatum(trigname));
    	scan = systable_beginscan(pg_trigger, TriggerRelidNameIndexId, true,
    							  SnapshotNow, 2, entry);
    	triggertuple = systable_getnext(scan);
    
    	/* If no trigger exists for the relation specified, notify user */
    
    	if (!HeapTupleIsValid(triggertuple))
    		ereport(ERROR,
    				(errcode(ERRCODE_UNDEFINED_OBJECT),
    				 errmsg("trigger \"%s\" for table \"%s\" does not exist",
    						trigname, RelationGetRelationName(relation))));
    
    	oid = HeapTupleGetOid(triggertuple);
    
    	systable_endscan(scan);
    
    	/* Call CreateComments() to create/drop the comments */
    	CreateComments(oid, TriggerRelationId, 0, comment);
    
    	/* Done, but hold lock on relation */
    
    	heap_close(pg_trigger, AccessShareLock);
    	heap_close(relation, NoLock);
    }
    
    
    /*
     * CommentConstraint --
     *
     * Enable commenting on constraints held within the pg_constraint
     * table.  A qualified name is required as constraint names are
     * unique per relation.
     */
    static void
    CommentConstraint(List *qualname, char *comment)
    {
    	int			nnames;
    	List	   *relName;
    	char	   *conName;
    	RangeVar   *rel;
    	Relation	pg_constraint,
    				relation;
    	HeapTuple	tuple;
    	SysScanDesc scan;
    	ScanKeyData skey[1];
    	Oid			conOid = InvalidOid;
    
    	/* Separate relname and constraint name */
    	nnames = list_length(qualname);
    	if (nnames < 2)				/* parser messed up */
    		elog(ERROR, "must specify relation and constraint");
    	relName = list_truncate(list_copy(qualname), nnames - 1);
    	conName = strVal(lfirst(list_tail(qualname)));
    
    	/* Open the owning relation to ensure it won't go away meanwhile */
    	rel = makeRangeVarFromNameList(relName);
    	relation = heap_openrv(rel, AccessShareLock);
    
    	/* Check object security */
    
    	if (!pg_class_ownercheck(RelationGetRelid(relation), GetUserId()))
    		aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS,
    					   RelationGetRelationName(relation));
    
    	/*
    	 * Fetch the constraint tuple from pg_constraint.  There may be more
    	 * than one match, because constraints are not required to have unique
    	 * names; if so, error out.
    	 */
    	pg_constraint = heap_open(ConstraintRelationId, AccessShareLock);
    
    	ScanKeyInit(&skey[0],
    				Anum_pg_constraint_conrelid,
    				BTEqualStrategyNumber, F_OIDEQ,
    				ObjectIdGetDatum(RelationGetRelid(relation)));
    
    	scan = systable_beginscan(pg_constraint, ConstraintRelidIndexId, true,
    							  SnapshotNow, 1, skey);
    
    	while (HeapTupleIsValid(tuple = systable_getnext(scan)))
    	{
    		Form_pg_constraint con = (Form_pg_constraint) GETSTRUCT(tuple);
    
    		if (strcmp(NameStr(con->conname), conName) == 0)
    		{
    			if (OidIsValid(conOid))
    				ereport(ERROR,
    						(errcode(ERRCODE_DUPLICATE_OBJECT),
    						 errmsg("table \"%s\" has multiple constraints named \"%s\"",
    						   RelationGetRelationName(relation), conName)));
    			conOid = HeapTupleGetOid(tuple);
    		}
    	}
    
    	systable_endscan(scan);
    
    	/* If no constraint exists for the relation specified, notify user */
    	if (!OidIsValid(conOid))
    		ereport(ERROR,
    				(errcode(ERRCODE_UNDEFINED_OBJECT),
    			  errmsg("constraint \"%s\" for table \"%s\" does not exist",
    					 conName, RelationGetRelationName(relation))));
    
    	/* Call CreateComments() to create/drop the comments */
    	CreateComments(conOid, ConstraintRelationId, 0, comment);
    
    	/* Done, but hold lock on relation */
    	heap_close(pg_constraint, AccessShareLock);
    	heap_close(relation, NoLock);
    }
    
    /*
     * CommentConversion --
     *
     * This routine is used to add/drop any user-comments a user might
     * have regarding a CONVERSION. The conversion is specified by name
     * and, if found, and the user has appropriate permissions, a
     * comment will be added/dropped using the CreateComments() routine.
     * The conversion's name and the comment are the parameters to this routine.
     */
    static void
    CommentConversion(List *qualname, char *comment)
    {
    	Oid			conversionOid;
    
    	conversionOid = FindConversionByName(qualname);
    	if (!OidIsValid(conversionOid))
    		ereport(ERROR,
    				(errcode(ERRCODE_UNDEFINED_OBJECT),
    				 errmsg("conversion \"%s\" does not exist",
    						NameListToString(qualname))));
    
    	/* Check object security */
    	if (!pg_conversion_ownercheck(conversionOid, GetUserId()))
    		aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CONVERSION,
    					   NameListToString(qualname));
    
    	/* Call CreateComments() to create/drop the comments */
    	CreateComments(conversionOid, ConversionRelationId, 0, comment);
    }
    
    /*
     * CommentLanguage --
     *
     * This routine is used to add/drop any user-comments a user might
     * have regarding a LANGUAGE. The language is specified by name
     * and, if found, and the user has appropriate permissions, a
     * comment will be added/dropped using the CreateComments() routine.
     * The language's name and the comment are the parameters to this routine.
     */
    static void
    CommentLanguage(List *qualname, char *comment)
    {
    	Oid			oid;
    	char	   *language;
    
    	if (list_length(qualname) != 1)
    		ereport(ERROR,
    				(errcode(ERRCODE_SYNTAX_ERROR),
    				 errmsg("language name may not be qualified")));
    	language = strVal(linitial(qualname));
    
    	oid = GetSysCacheOid(LANGNAME,
    						 CStringGetDatum(language),
    						 0, 0, 0);
    	if (!OidIsValid(oid))
    		ereport(ERROR,
    				(errcode(ERRCODE_UNDEFINED_SCHEMA),
    				 errmsg("language \"%s\" does not exist", language)));
    
    	/* Check object security */
    	if (!superuser())
    		ereport(ERROR,
    				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
    		 errmsg("must be superuser to comment on procedural language")));
    
    	/* Call CreateComments() to create/drop the comments */
    	CreateComments(oid, LanguageRelationId, 0, comment);
    }
    
    /*
     * CommentOpClass --
     *
     * This routine is used to allow a user to provide comments on an
     * operator class. The operator class for commenting is determined by both
     * its name and its argument list which defines the index method
     * the operator class is used for. The argument list is expected to contain
     * a single name (represented as a string Value node).
     */
    static void
    CommentOpClass(List *qualname, List *arguments, char *comment)
    {
    	char	   *amname;
    	char	   *schemaname;
    	char	   *opcname;
    	Oid			amID;
    	Oid			opcID;
    	HeapTuple	tuple;
    
    	Assert(list_length(arguments) == 1);
    	amname = strVal(linitial(arguments));
    
    	/*
    	 * Get the access method's OID.
    	 */
    	amID = GetSysCacheOid(AMNAME,
    						  CStringGetDatum(amname),
    						  0, 0, 0);
    	if (!OidIsValid(amID))
    		ereport(ERROR,
    				(errcode(ERRCODE_UNDEFINED_OBJECT),
    				 errmsg("access method \"%s\" does not exist",
    						amname)));
    
    	/*
    	 * Look up the opclass.
    	 */
    
    	/* deconstruct the name list */
    	DeconstructQualifiedName(qualname, &schemaname, &opcname);
    
    	if (schemaname)
    	{
    		/* Look in specific schema only */
    		Oid			namespaceId;
    
    		namespaceId = LookupExplicitNamespace(schemaname);
    		tuple = SearchSysCache(CLAAMNAMENSP,
    							   ObjectIdGetDatum(amID),
    							   PointerGetDatum(opcname),
    							   ObjectIdGetDatum(namespaceId),
    							   0);
    	}
    	else
    	{
    		/* Unqualified opclass name, so search the search path */
    		opcID = OpclassnameGetOpcid(amID, opcname);
    		if (!OidIsValid(opcID))
    			ereport(ERROR,
    					(errcode(ERRCODE_UNDEFINED_OBJECT),
    					 errmsg("operator class \"%s\" does not exist for access method \"%s\"",
    							opcname, amname)));
    		tuple = SearchSysCache(CLAOID,
    							   ObjectIdGetDatum(opcID),
    							   0, 0, 0);
    	}
    
    	if (!HeapTupleIsValid(tuple))
    		ereport(ERROR,
    				(errcode(ERRCODE_UNDEFINED_OBJECT),
    				 errmsg("operator class \"%s\" does not exist for access method \"%s\"",
    						NameListToString(qualname), amname)));
    
    	opcID = HeapTupleGetOid(tuple);
    
    	/* Permission check: must own opclass */
    	if (!pg_opclass_ownercheck(opcID, GetUserId()))
    		aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_OPCLASS,
    					   NameListToString(qualname));
    
    	ReleaseSysCache(tuple);
    
    	/* Call CreateComments() to create/drop the comments */
    	CreateComments(opcID, OperatorClassRelationId, 0, comment);
    }
    
    /*
     * CommentLargeObject --
     *
     * This routine is used to add/drop any user-comments a user might
     * have regarding a LARGE OBJECT. The large object is specified by OID
     * and, if found, and the user has appropriate permissions, a
     * comment will be added/dropped using the CreateComments() routine.
     * The large object's OID and the comment are the parameters to this routine.
     */
    static void
    CommentLargeObject(List *qualname, char *comment)
    {
    	Oid			loid;
    	Node	   *node;
    
    	Assert(list_length(qualname) == 1);
    	node = (Node *) linitial(qualname);
    
    	switch (nodeTag(node))
    	{
    		case T_Integer:
    			loid = intVal(node);
    			break;
    		case T_Float:
    
    			/*
    			 * Values too large for int4 will be represented as Float
    			 * constants by the lexer.	Accept these if they are valid OID
    			 * strings.
    			 */
    			loid = DatumGetObjectId(DirectFunctionCall1(oidin,
    										 CStringGetDatum(strVal(node))));
    			break;
    		default:
    			elog(ERROR, "unrecognized node type: %d",
    				 (int) nodeTag(node));
    			/* keep compiler quiet */
    			loid = InvalidOid;
    	}
    
    	/* check that the large object exists */
    	if (!LargeObjectExists(loid))
    		ereport(ERROR,
    				(errcode(ERRCODE_UNDEFINED_OBJECT),
    				 errmsg("large object %u does not exist", loid)));
    
    	/* Call CreateComments() to create/drop the comments */
    	CreateComments(loid, LargeObjectRelationId, 0, comment);
    }
    
    /*
     * CommentCast --
     *
     * This routine is used to add/drop any user-comments a user might
     * have regarding a CAST. The cast is specified by source and destination types
     * and, if found, and the user has appropriate permissions, a
     * comment will be added/dropped using the CreateComments() routine.
     * The cast's source type is passed as the "name", the destination type
     * as the "arguments".
     */
    static void
    CommentCast(List *qualname, List *arguments, char *comment)
    {
    	TypeName   *sourcetype;
    	TypeName   *targettype;
    	Oid			sourcetypeid;
    	Oid			targettypeid;
    	HeapTuple	tuple;
    	Oid			castOid;
    
    	Assert(list_length(qualname) == 1);
    	sourcetype = (TypeName *) linitial(qualname);
    	Assert(IsA(sourcetype, TypeName));
    	Assert(list_length(arguments) == 1);
    	targettype = (TypeName *) linitial(arguments);
    	Assert(IsA(targettype, TypeName));
    
    	sourcetypeid = typenameTypeId(sourcetype);
    	if (!OidIsValid(sourcetypeid))
    		ereport(ERROR,
    				(errcode(ERRCODE_UNDEFINED_OBJECT),
    				 errmsg("source data type %s does not exist",
    						TypeNameToString(sourcetype))));
    
    	targettypeid = typenameTypeId(targettype);
    	if (!OidIsValid(targettypeid))
    		ereport(ERROR,
    				(errcode(ERRCODE_UNDEFINED_OBJECT),
    				 errmsg("target data type %s does not exist",
    						TypeNameToString(targettype))));
    
    	tuple = SearchSysCache(CASTSOURCETARGET,
    						   ObjectIdGetDatum(sourcetypeid),
    						   ObjectIdGetDatum(targettypeid),
    						   0, 0);
    	if (!HeapTupleIsValid(tuple))
    		ereport(ERROR,
    				(errcode(ERRCODE_UNDEFINED_OBJECT),
    				 errmsg("cast from type %s to type %s does not exist",
    						TypeNameToString(sourcetype),
    						TypeNameToString(targettype))));
    
    	/* Get the OID of the cast */
    	castOid = HeapTupleGetOid(tuple);
    
    	/* Permission check */
    	if (!pg_type_ownercheck(sourcetypeid, GetUserId())
    		&& !pg_type_ownercheck(targettypeid, GetUserId()))
    		ereport(ERROR,
    				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
    				 errmsg("must be owner of type %s or type %s",
    						TypeNameToString(sourcetype),
    						TypeNameToString(targettype))));
    
    	ReleaseSysCache(tuple);
    
    	/* Call CreateComments() to create/drop the comments */
    	CreateComments(castOid, CastRelationId, 0, comment);
    }