From c10575ff005c330d0475345621b7d381eb510c48 Mon Sep 17 00:00:00 2001 From: Robert Haas Date: Fri, 27 Aug 2010 11:47:41 +0000 Subject: [PATCH] Rewrite comment code for better modularity, and add necessary locking. Review by Alvaro Herrera, KaiGai Kohei, and Tom Lane. --- src/backend/catalog/Makefile | 9 +- src/backend/catalog/objectaddress.c | 654 +++++++++++++++++++ src/backend/commands/comment.c | 943 ++++------------------------ src/include/catalog/dependency.h | 14 +- src/include/catalog/objectaddress.h | 33 + 5 files changed, 829 insertions(+), 824 deletions(-) create mode 100644 src/backend/catalog/objectaddress.c create mode 100644 src/include/catalog/objectaddress.h diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile index a97003943e..0a1546bfa7 100644 --- a/src/backend/catalog/Makefile +++ b/src/backend/catalog/Makefile @@ -2,7 +2,7 @@ # # Makefile for backend/catalog # -# $PostgreSQL: pgsql/src/backend/catalog/Makefile,v 1.78 2010/05/13 11:49:48 petere Exp $ +# $PostgreSQL: pgsql/src/backend/catalog/Makefile,v 1.79 2010/08/27 11:47:41 rhaas Exp $ # #------------------------------------------------------------------------- @@ -11,9 +11,10 @@ top_builddir = ../../.. include $(top_builddir)/src/Makefile.global OBJS = catalog.o dependency.o heap.o index.o indexing.o namespace.o aclchk.o \ - pg_aggregate.o pg_constraint.o pg_conversion.o pg_depend.o pg_enum.o \ - pg_inherits.o pg_largeobject.o pg_namespace.o pg_operator.o pg_proc.o \ - pg_db_role_setting.o pg_shdepend.o pg_type.o storage.o toasting.o + objectaddress.o pg_aggregate.o pg_constraint.o pg_conversion.o \ + pg_depend.o pg_enum.o pg_inherits.o pg_largeobject.o pg_namespace.o \ + pg_operator.o pg_proc.o pg_db_role_setting.o pg_shdepend.o pg_type.o \ + storage.o toasting.o BKIFILES = postgres.bki postgres.description postgres.shdescription diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c new file mode 100644 index 0000000000..9eec92f40a --- /dev/null +++ b/src/backend/catalog/objectaddress.c @@ -0,0 +1,654 @@ +/*------------------------------------------------------------------------- + * + * objectaddress.c + * functions for working with ObjectAddresses + * + * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $PostgreSQL: pgsql/src/backend/catalog/objectaddress.c,v 1.1 2010/08/27 11:47:41 rhaas Exp $ + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "access/heapam.h" +#include "access/sysattr.h" +#include "catalog/catalog.h" +#include "catalog/dependency.h" +#include "catalog/indexing.h" +#include "catalog/namespace.h" +#include "catalog/objectaddress.h" +#include "catalog/pg_authid.h" +#include "catalog/pg_cast.h" +#include "catalog/pg_class.h" +#include "catalog/pg_constraint.h" +#include "catalog/pg_conversion.h" +#include "catalog/pg_database.h" +#include "catalog/pg_language.h" +#include "catalog/pg_largeobject.h" +#include "catalog/pg_largeobject_metadata.h" +#include "catalog/pg_namespace.h" +#include "catalog/pg_opclass.h" +#include "catalog/pg_opfamily.h" +#include "catalog/pg_operator.h" +#include "catalog/pg_proc.h" +#include "catalog/pg_rewrite.h" +#include "catalog/pg_tablespace.h" +#include "catalog/pg_trigger.h" +#include "catalog/pg_ts_config.h" +#include "catalog/pg_ts_dict.h" +#include "catalog/pg_ts_parser.h" +#include "catalog/pg_ts_template.h" +#include "catalog/pg_type.h" +#include "commands/dbcommands.h" +#include "commands/defrem.h" +#include "commands/proclang.h" +#include "commands/tablespace.h" +#include "commands/trigger.h" +#include "nodes/makefuncs.h" +#include "parser/parse_func.h" +#include "parser/parse_oper.h" +#include "parser/parse_type.h" +#include "rewrite/rewriteSupport.h" +#include "storage/lmgr.h" +#include "utils/acl.h" +#include "utils/builtins.h" +#include "utils/fmgroids.h" +#include "utils/lsyscache.h" +#include "utils/syscache.h" +#include "utils/rel.h" +#include "utils/tqual.h" + +static ObjectAddress get_object_address_unqualified(ObjectType objtype, + List *qualname); +static Relation get_relation_by_qualified_name(ObjectType objtype, + List *objname, LOCKMODE lockmode); +static ObjectAddress get_object_address_relobject(ObjectType objtype, + List *objname, Relation *relp); +static ObjectAddress get_object_address_attribute(ObjectType objtype, + List *objname, Relation *relp, LOCKMODE lockmode); +static ObjectAddress get_object_address_opcf(ObjectType objtype, List *objname, + List *objargs); +static bool object_exists(ObjectAddress address); + +/* + * Translate an object name and arguments (as passed by the parser) to an + * ObjectAddress. + * + * The returned object will be locked using the specified lockmode. If a + * sub-object is looked up, the parent object will be locked instead. + * + * If the object is a relation or a child object of a relation (e.g. an + * attribute or contraint), the relation is also opened and *relp receives + * the open relcache entry pointer; otherwise, *relp is set to NULL. This + * is a bit grotty but it makes life simpler, since the caller will + * typically need the relcache entry too. Caller must close the relcache + * entry when done with it. The relation is locked with the specified lockmode + * if the target object is the relation itself or an attribute, but for other + * child objects, only AccessShareLock is acquired on the relation. + * + * We don't currently provide a function to release the locks acquired here; + * typically, the lock must be held until commit to guard against a concurrent + * drop operation. + */ +ObjectAddress +get_object_address(ObjectType objtype, List *objname, List *objargs, + Relation *relp, LOCKMODE lockmode) +{ + ObjectAddress address; + Relation relation = NULL; + + /* Some kind of lock must be taken. */ + Assert(lockmode != NoLock); + + switch (objtype) + { + case OBJECT_INDEX: + case OBJECT_SEQUENCE: + case OBJECT_TABLE: + case OBJECT_VIEW: + relation = + get_relation_by_qualified_name(objtype, objname, lockmode); + address.classId = RelationRelationId; + address.objectId = RelationGetRelid(relation); + address.objectSubId = 0; + break; + case OBJECT_COLUMN: + address = + get_object_address_attribute(objtype, objname, &relation, + lockmode); + break; + case OBJECT_RULE: + case OBJECT_TRIGGER: + case OBJECT_CONSTRAINT: + address = get_object_address_relobject(objtype, objname, &relation); + break; + case OBJECT_DATABASE: + case OBJECT_TABLESPACE: + case OBJECT_ROLE: + case OBJECT_SCHEMA: + case OBJECT_LANGUAGE: + address = get_object_address_unqualified(objtype, objname); + break; + case OBJECT_TYPE: + address.classId = TypeRelationId; + address.objectId = + typenameTypeId(NULL, makeTypeNameFromNameList(objname), NULL); + address.objectSubId = 0; + break; + case OBJECT_AGGREGATE: + address.classId = ProcedureRelationId; + address.objectId = LookupAggNameTypeNames(objname, objargs, false); + address.objectSubId = 0; + break; + case OBJECT_FUNCTION: + address.classId = ProcedureRelationId; + address.objectId = LookupFuncNameTypeNames(objname, objargs, false); + address.objectSubId = 0; + break; + case OBJECT_OPERATOR: + Assert(list_length(objargs) == 2); + address.classId = OperatorRelationId; + address.objectId = + LookupOperNameTypeNames(NULL, objname, + (TypeName *) linitial(objargs), + (TypeName *) lsecond(objargs), + false, -1); + address.objectSubId = 0; + break; + case OBJECT_CONVERSION: + address.classId = ConversionRelationId; + address.objectId = get_conversion_oid(objname, false); + address.objectSubId = 0; + break; + case OBJECT_OPCLASS: + case OBJECT_OPFAMILY: + address = get_object_address_opcf(objtype, objname, objargs); + break; + case OBJECT_LARGEOBJECT: + Assert(list_length(objname) == 1); + address.classId = LargeObjectRelationId; + address.objectId = oidparse(linitial(objname)); + address.objectSubId = 0; + if (!LargeObjectExists(address.objectId)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("large object %u does not exist", + address.objectId))); + break; + case OBJECT_CAST: + { + TypeName *sourcetype = (TypeName *) linitial(objname); + TypeName *targettype = (TypeName *) linitial(objargs); + Oid sourcetypeid = typenameTypeId(NULL, sourcetype, NULL); + Oid targettypeid = typenameTypeId(NULL, targettype, NULL); + + address.classId = CastRelationId; + address.objectId = + get_cast_oid(sourcetypeid, targettypeid, false); + address.objectSubId = 0; + } + break; + case OBJECT_TSPARSER: + address.classId = TSParserRelationId; + address.objectId = get_ts_parser_oid(objname, false); + address.objectSubId = 0; + break; + case OBJECT_TSDICTIONARY: + address.classId = TSDictionaryRelationId; + address.objectId = get_ts_dict_oid(objname, false); + address.objectSubId = 0; + break; + case OBJECT_TSTEMPLATE: + address.classId = TSTemplateRelationId; + address.objectId = get_ts_template_oid(objname, false); + address.objectSubId = 0; + break; + case OBJECT_TSCONFIGURATION: + address.classId = TSConfigRelationId; + address.objectId = get_ts_config_oid(objname, false); + address.objectSubId = 0; + break; + default: + elog(ERROR, "unrecognized objtype: %d", (int) objtype); + } + + /* + * If we're dealing with a relation or attribute, then the relation is + * already locked. If we're dealing with any other type of object, we need + * to lock it and then verify that it still exists. + */ + if (address.classId != RelationRelationId) + { + if (IsSharedRelation(address.classId)) + LockSharedObject(address.classId, address.objectId, 0, lockmode); + else + LockDatabaseObject(address.classId, address.objectId, 0, lockmode); + /* Did it go away while we were waiting for the lock? */ + if (!object_exists(address)) + elog(ERROR, "cache lookup failed for class %u object %u subobj %d", + address.classId, address.objectId, address.objectSubId); + } + + /* Return the object address and the relation. */ + *relp = relation; + return address; +} + +/* + * Find an ObjectAddress for a type of object that is identified by an + * unqualified name. + */ +static ObjectAddress +get_object_address_unqualified(ObjectType objtype, List *qualname) +{ + const char *name; + ObjectAddress address; + + /* + * The types of names handled by this function are not permitted to be + * schema-qualified or catalog-qualified. + */ + if (list_length(qualname) != 1) + { + const char *msg; + + switch (objtype) + { + case OBJECT_DATABASE: + msg = gettext_noop("database name cannot be qualified"); + break; + case OBJECT_TABLESPACE: + msg = gettext_noop("tablespace name cannot be qualified"); + break; + case OBJECT_ROLE: + msg = gettext_noop("role name cannot be qualified"); + break; + case OBJECT_SCHEMA: + msg = gettext_noop("schema name cannot be qualified"); + break; + case OBJECT_LANGUAGE: + msg = gettext_noop("language name cannot be qualified"); + break; + default: + elog(ERROR, "unrecognized objtype: %d", (int) objtype); + msg = NULL; /* placate compiler */ + } + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("%s", _(msg)))); + } + + /* Format is valid, extract the actual name. */ + name = strVal(linitial(qualname)); + + /* Translate name to OID. */ + switch (objtype) + { + case OBJECT_DATABASE: + address.classId = DatabaseRelationId; + address.objectId = get_database_oid(name, false); + address.objectSubId = 0; + break; + case OBJECT_TABLESPACE: + address.classId = TableSpaceRelationId; + address.objectId = get_tablespace_oid(name, false); + address.objectSubId = 0; + break; + case OBJECT_ROLE: + address.classId = AuthIdRelationId; + address.objectId = get_role_oid(name, false); + address.objectSubId = 0; + break; + case OBJECT_SCHEMA: + address.classId = NamespaceRelationId; + address.objectId = get_namespace_oid(name, false); + address.objectSubId = 0; + break; + case OBJECT_LANGUAGE: + address.classId = LanguageRelationId; + address.objectId = get_language_oid(name, false); + address.objectSubId = 0; + break; + default: + elog(ERROR, "unrecognized objtype: %d", (int) objtype); + /* placate compiler, which doesn't know elog won't return */ + address.classId = InvalidOid; + address.objectId = InvalidOid; + address.objectSubId = 0; + } + + return address; +} + +/* + * Locate a relation by qualified name. + */ +static Relation +get_relation_by_qualified_name(ObjectType objtype, List *objname, + LOCKMODE lockmode) +{ + Relation relation; + + relation = relation_openrv(makeRangeVarFromNameList(objname), lockmode); + 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; + default: + elog(ERROR, "unrecognized objtype: %d", (int) objtype); + break; + } + + return relation; +} + +/* + * Find object address for an object that is attached to a relation. + * + * Note that we take only an AccessShareLock on the relation. We need not + * pass down the LOCKMODE from get_object_address(), because that is the lock + * mode for the object itself, not the relation to which it is attached. + */ +static ObjectAddress +get_object_address_relobject(ObjectType objtype, List *objname, Relation *relp) +{ + ObjectAddress address; + Relation relation = NULL; + int nnames; + const char *depname; + + /* Extract name of dependent object. */ + depname = strVal(lfirst(list_tail(objname))); + + /* Separate relation name from dependent object name. */ + nnames = list_length(objname); + if (nnames < 2) + { + Oid reloid; + + /* + * For compatibility with very old releases, we sometimes allow users + * to attempt to specify a rule without mentioning the relation name. + * If there's only rule by that name in the entire database, this will + * work. But objects other than rules don't get this special + * treatment. + */ + if (objtype != OBJECT_RULE) + elog(ERROR, "must specify relation and object name"); + address.classId = RewriteRelationId; + address.objectId = get_rewrite_oid_without_relid(depname, &reloid); + address.objectSubId = 0; + relation = heap_open(reloid, AccessShareLock); + } + else + { + List *relname; + Oid reloid; + + /* Extract relation name and open relation. */ + relname = list_truncate(list_copy(objname), nnames - 1); + relation = heap_openrv(makeRangeVarFromNameList(relname), + AccessShareLock); + reloid = RelationGetRelid(relation); + + switch (objtype) + { + case OBJECT_RULE: + address.classId = RewriteRelationId; + address.objectId = get_rewrite_oid(reloid, depname, false); + address.objectSubId = 0; + break; + case OBJECT_TRIGGER: + address.classId = TriggerRelationId; + address.objectId = get_trigger_oid(reloid, depname, false); + address.objectSubId = 0; + break; + case OBJECT_CONSTRAINT: + address.classId = ConstraintRelationId; + address.objectId = get_constraint_oid(reloid, depname, false); + address.objectSubId = 0; + break; + default: + elog(ERROR, "unrecognized objtype: %d", (int) objtype); + /* placate compiler, which doesn't know elog won't return */ + address.classId = InvalidOid; + address.objectId = InvalidOid; + address.objectSubId = 0; + } + } + + /* Done. */ + *relp = relation; + return address; +} + +/* + * Find the ObjectAddress for an attribute. + */ +static ObjectAddress +get_object_address_attribute(ObjectType objtype, List *objname, + Relation *relp, LOCKMODE lockmode) +{ + ObjectAddress address; + List *relname; + Oid reloid; + Relation relation; + const char *attname; + + /* Extract relation name and open relation. */ + attname = strVal(lfirst(list_tail(objname))); + relname = list_truncate(list_copy(objname), list_length(objname) - 1); + relation = heap_openrv(makeRangeVarFromNameList(relname), lockmode); + reloid = RelationGetRelid(relation); + + /* Look up attribute and construct return value. */ + address.classId = RelationRelationId; + address.objectId = reloid; + address.objectSubId = get_attnum(reloid, attname); + if (address.objectSubId == InvalidAttrNumber) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_COLUMN), + errmsg("column \"%s\" of relation \"%s\" does not exist", + attname, RelationGetRelationName(relation)))); + + *relp = relation; + return address; +} + +/* + * Find the ObjectAddress for an opclass or opfamily. + */ +static ObjectAddress +get_object_address_opcf(ObjectType objtype, List *objname, List *objargs) +{ + Oid amoid; + ObjectAddress address; + + Assert(list_length(objargs) == 1); + amoid = get_am_oid(strVal(linitial(objargs)), false); + + switch (objtype) + { + case OBJECT_OPCLASS: + address.classId = OperatorClassRelationId; + address.objectId = get_opclass_oid(amoid, objname, false); + address.objectSubId = 0; + break; + case OBJECT_OPFAMILY: + address.classId = OperatorFamilyRelationId; + address.objectId = get_opfamily_oid(amoid, objname, false); + address.objectSubId = 0; + break; + default: + elog(ERROR, "unrecognized objtype: %d", (int) objtype); + /* placate compiler, which doesn't know elog won't return */ + address.classId = InvalidOid; + address.objectId = InvalidOid; + address.objectSubId = 0; + } + + return address; +} + +/* + * Test whether an object exists. + */ +static bool +object_exists(ObjectAddress address) +{ + int cache = -1; + Oid indexoid = InvalidOid; + Relation rel; + ScanKeyData skey[1]; + SysScanDesc sd; + bool found; + + /* Sub-objects require special treatment. */ + if (address.objectSubId != 0) + { + HeapTuple atttup; + + /* Currently, attributes are the only sub-objects. */ + Assert(address.classId == RelationRelationId); + atttup = SearchSysCache2(ATTNUM, ObjectIdGetDatum(address.objectId), + Int16GetDatum(address.objectSubId)); + if (!HeapTupleIsValid(atttup)) + found = false; + else + { + found = ((Form_pg_attribute) GETSTRUCT(atttup))->attisdropped; + ReleaseSysCache(atttup); + } + return found; + } + + /* + * For object types that have a relevant syscache, we use it; for + * everything else, we'll have to do an index-scan. This switch + * sets either the cache to be used for the syscache lookup, or the + * index to be used for the index scan. + */ + switch (address.classId) + { + case RelationRelationId: + cache = RELOID; + break; + case RewriteRelationId: + indexoid = RewriteOidIndexId; + break; + case TriggerRelationId: + indexoid = TriggerOidIndexId; + break; + case ConstraintRelationId: + cache = CONSTROID; + break; + case DatabaseRelationId: + cache = DATABASEOID; + break; + case TableSpaceRelationId: + cache = TABLESPACEOID; + break; + case AuthIdRelationId: + cache = AUTHOID; + break; + case NamespaceRelationId: + cache = NAMESPACEOID; + break; + case LanguageRelationId: + cache = LANGOID; + break; + case TypeRelationId: + cache = TYPEOID; + break; + case ProcedureRelationId: + cache = PROCOID; + break; + case OperatorRelationId: + cache = OPEROID; + break; + case ConversionRelationId: + cache = CONVOID; + break; + case OperatorClassRelationId: + cache = CLAOID; + break; + case OperatorFamilyRelationId: + cache = OPFAMILYOID; + break; + case LargeObjectRelationId: + /* + * Weird backward compatibility hack: ObjectAddress notation uses + * LargeObjectRelationId for large objects, but since PostgreSQL + * 9.0, the relevant catalog is actually + * LargeObjectMetadataRelationId. + */ + address.classId = LargeObjectMetadataRelationId; + indexoid = LargeObjectMetadataOidIndexId; + break; + case CastRelationId: + indexoid = CastOidIndexId; + break; + case TSParserRelationId: + cache = TSPARSEROID; + break; + case TSDictionaryRelationId: + cache = TSDICTOID; + break; + case TSTemplateRelationId: + cache = TSTEMPLATEOID; + break; + case TSConfigRelationId: + cache = TSCONFIGOID; + break; + default: + elog(ERROR, "unrecognized classid: %u", address.classId); + } + + /* Found a syscache? */ + if (cache != -1) + return SearchSysCacheExists1(cache, ObjectIdGetDatum(address.objectId)); + + /* No syscache, so examine the table directly. */ + Assert(OidIsValid(indexoid)); + ScanKeyInit(&skey[0], + ObjectIdAttributeNumber, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(address.objectId)); + rel = heap_open(address.classId, AccessShareLock); + sd = systable_beginscan(rel, indexoid, true, SnapshotNow, 1, skey); + found = HeapTupleIsValid(systable_getnext(sd)); + systable_endscan(sd); + heap_close(rel, AccessShareLock); + return found; +} diff --git a/src/backend/commands/comment.c b/src/backend/commands/comment.c index 6653495176..4a801858b6 100644 --- a/src/backend/commands/comment.c +++ b/src/backend/commands/comment.c @@ -7,7 +7,7 @@ * Copyright (c) 1996-2010, PostgreSQL Global Development Group * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/commands/comment.c,v 1.117 2010/08/05 15:25:35 rhaas Exp $ + * $PostgreSQL: pgsql/src/backend/commands/comment.c,v 1.118 2010/08/27 11:47:41 rhaas Exp $ * *------------------------------------------------------------------------- */ @@ -17,83 +17,30 @@ #include "access/genam.h" #include "access/heapam.h" #include "catalog/indexing.h" -#include "catalog/pg_authid.h" -#include "catalog/pg_cast.h" -#include "catalog/pg_constraint.h" -#include "catalog/pg_conversion.h" -#include "catalog/pg_database.h" +#include "catalog/objectaddress.h" #include "catalog/pg_description.h" -#include "catalog/pg_language.h" -#include "catalog/pg_largeobject.h" -#include "catalog/pg_largeobject_metadata.h" -#include "catalog/pg_namespace.h" -#include "catalog/pg_opclass.h" -#include "catalog/pg_operator.h" -#include "catalog/pg_opfamily.h" -#include "catalog/pg_proc.h" -#include "catalog/pg_rewrite.h" #include "catalog/pg_shdescription.h" -#include "catalog/pg_tablespace.h" -#include "catalog/pg_trigger.h" -#include "catalog/pg_ts_config.h" -#include "catalog/pg_ts_dict.h" -#include "catalog/pg_ts_parser.h" -#include "catalog/pg_ts_template.h" -#include "catalog/pg_type.h" #include "commands/comment.h" #include "commands/dbcommands.h" -#include "commands/defrem.h" -#include "commands/proclang.h" -#include "commands/tablespace.h" -#include "commands/trigger.h" #include "libpq/be-fsstubs.h" #include "miscadmin.h" -#include "nodes/makefuncs.h" #include "parser/parse_func.h" -#include "parser/parse_oper.h" #include "parser/parse_type.h" -#include "rewrite/rewriteSupport.h" #include "utils/acl.h" #include "utils/builtins.h" #include "utils/fmgroids.h" -#include "utils/lsyscache.h" #include "utils/rel.h" -#include "utils/syscache.h" #include "utils/tqual.h" - /* - * Static Function Prototypes -- - * - * The following prototypes 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. + * For most object types, the permissions-checking logic is simple enough + * that it makes sense to just include it in CommentObject(). However, a few + * object types require something more complex; for those, we define helper + * functions. */ - -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 CommentOpFamily(List *qualname, List *arguments, char *comment); -static void CommentLargeObject(List *qualname, char *comment); -static void CommentCast(List *qualname, List *arguments, char *comment); -static void CommentTablespace(List *qualname, char *comment); -static void CommentRole(List *qualname, char *comment); -static void CommentTSParser(List *qualname, char *comment); -static void CommentTSDictionary(List *qualname, char *comment); -static void CommentTSTemplate(List *qualname, char *comment); -static void CommentTSConfiguration(List *qualname, char *comment); +static void CheckRelationComment(int objtype, Relation relation); +static void CheckAttributeComment(Relation relation); +static void CheckCastComment(List *qualname, List *arguments); /* @@ -105,84 +52,175 @@ static void CommentTSConfiguration(List *qualname, char *comment); void CommentObject(CommentStmt *stmt) { + ObjectAddress address; + Relation relation; + + /* + * 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; hence, the following special + * case. (If the length of stmt->objname is not 1, get_object_address will + * throw an error below; that's OK.) + */ + if (stmt->objtype == OBJECT_DATABASE && list_length(stmt->objname) == 1) + { + char *database = strVal(linitial(stmt->objname)); + if (!OidIsValid(get_database_oid(database, true))) + { + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_DATABASE), + errmsg("database \"%s\" does not exist", database))); + return; + } + } + + /* + * Translate the parser representation which identifies this object into + * an ObjectAddress. get_object_address() will throw an error if the + * object does not exist, and will also acquire a lock on the target + * to guard against concurrent DROP operations. + */ + address = get_object_address(stmt->objtype, stmt->objname, stmt->objargs, + &relation, ShareUpdateExclusiveLock); + + /* Privilege and integrity checks. */ switch (stmt->objtype) { case OBJECT_INDEX: case OBJECT_SEQUENCE: case OBJECT_TABLE: case OBJECT_VIEW: - CommentRelation(stmt->objtype, stmt->objname, stmt->comment); + CheckRelationComment(stmt->objtype, relation); break; case OBJECT_COLUMN: - CommentAttribute(stmt->objname, stmt->comment); + CheckAttributeComment(relation); break; case OBJECT_DATABASE: - CommentDatabase(stmt->objname, stmt->comment); - break; - case OBJECT_RULE: - CommentRule(stmt->objname, stmt->comment); + if (!pg_database_ownercheck(address.objectId, GetUserId())) + aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_DATABASE, + strVal(linitial(stmt->objname))); break; case OBJECT_TYPE: - CommentType(stmt->objname, stmt->comment); + if (!pg_type_ownercheck(address.objectId, GetUserId())) + aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TYPE, + format_type_be(address.objectId)); break; case OBJECT_AGGREGATE: - CommentAggregate(stmt->objname, stmt->objargs, stmt->comment); - break; case OBJECT_FUNCTION: - CommentProc(stmt->objname, stmt->objargs, stmt->comment); + if (!pg_proc_ownercheck(address.objectId, GetUserId())) + aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_PROC, + NameListToString(stmt->objname)); break; case OBJECT_OPERATOR: - CommentOperator(stmt->objname, stmt->objargs, stmt->comment); + if (!pg_oper_ownercheck(address.objectId, GetUserId())) + aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_OPER, + NameListToString(stmt->objname)); break; + case OBJECT_RULE: case OBJECT_TRIGGER: - CommentTrigger(stmt->objname, stmt->comment); + case OBJECT_CONSTRAINT: + if (!pg_class_ownercheck(RelationGetRelid(relation), GetUserId())) + aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS, + RelationGetRelationName(relation)); break; case OBJECT_SCHEMA: - CommentNamespace(stmt->objname, stmt->comment); - break; - case OBJECT_CONSTRAINT: - CommentConstraint(stmt->objname, stmt->comment); + if (!pg_namespace_ownercheck(address.objectId, GetUserId())) + aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_NAMESPACE, + strVal(linitial(stmt->objname))); break; case OBJECT_CONVERSION: - CommentConversion(stmt->objname, stmt->comment); + if (!pg_conversion_ownercheck(address.objectId, GetUserId())) + aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CONVERSION, + NameListToString(stmt->objname)); break; case OBJECT_LANGUAGE: - CommentLanguage(stmt->objname, stmt->comment); + if (!superuser()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be superuser to comment on procedural language"))); break; case OBJECT_OPCLASS: - CommentOpClass(stmt->objname, stmt->objargs, stmt->comment); + if (!pg_opclass_ownercheck(address.objectId, GetUserId())) + aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_OPCLASS, + NameListToString(stmt->objname)); break; case OBJECT_OPFAMILY: - CommentOpFamily(stmt->objname, stmt->objargs, stmt->comment); + if (!pg_opfamily_ownercheck(address.objectId, GetUserId())) + aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_OPFAMILY, + NameListToString(stmt->objname)); break; case OBJECT_LARGEOBJECT: - CommentLargeObject(stmt->objname, stmt->comment); + if (!lo_compat_privileges && + !pg_largeobject_ownercheck(address.objectId, GetUserId())) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be owner of large object %u", + address.objectId))); break; case OBJECT_CAST: - CommentCast(stmt->objname, stmt->objargs, stmt->comment); + CheckCastComment(stmt->objname, stmt->objargs); break; case OBJECT_TABLESPACE: - CommentTablespace(stmt->objname, stmt->comment); + if (!pg_tablespace_ownercheck(address.objectId, GetUserId())) + aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TABLESPACE, + strVal(linitial(stmt->objname))); break; case OBJECT_ROLE: - CommentRole(stmt->objname, stmt->comment); + if (!has_privs_of_role(GetUserId(), address.objectId)) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be member of role \"%s\" to comment upon it", + strVal(linitial(stmt->objname))))); break; case OBJECT_TSPARSER: - CommentTSParser(stmt->objname, stmt->comment); + if (!superuser()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be superuser to comment on text search parser"))); break; case OBJECT_TSDICTIONARY: - CommentTSDictionary(stmt->objname, stmt->comment); + if (!pg_ts_dict_ownercheck(address.objectId, GetUserId())) + aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TSDICTIONARY, + NameListToString(stmt->objname)); break; case OBJECT_TSTEMPLATE: - CommentTSTemplate(stmt->objname, stmt->comment); + if (!superuser()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be superuser to comment on text search template"))); break; case OBJECT_TSCONFIGURATION: - CommentTSConfiguration(stmt->objname, stmt->comment); + if (!pg_ts_config_ownercheck(address.objectId, GetUserId())) + aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TSCONFIGURATION, + NameListToString(stmt->objname)); break; default: elog(ERROR, "unrecognized object type: %d", (int) stmt->objtype); } + + /* + * Databases, tablespaces, and roles are cluster-wide objects, so any + * comments on those objects are recorded in the shared pg_shdescription + * catalog. Comments on all other objects are recorded in pg_description. + */ + if (stmt->objtype == OBJECT_DATABASE || stmt->objtype == OBJECT_TABLESPACE + || stmt->objtype == OBJECT_ROLE) + CreateSharedComments(address.objectId, address.classId, stmt->comment); + else + CreateComments(address.objectId, address.classId, address.objectSubId, + stmt->comment); + + /* + * If get_object_address() opened the relation for us, we close it to keep + * the reference count correct - but we retain any locks acquired by + * get_object_address() until commit time, to guard against concurrent + * activity. + */ + if (relation != NULL) + relation_close(relation, NoLock); } /* @@ -524,36 +562,17 @@ GetComment(Oid oid, Oid classoid, int32 subid) } /* - * 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. + * Check whether the user is allowed to comment on this relation. */ static void -CommentRelation(int objtype, List *relname, char *comment) +CheckRelationComment(int objtype, Relation relation) { - 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: @@ -585,48 +604,15 @@ CommentRelation(int objtype, List *relname, char *comment) 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 + * Check whether the user is allowed to comment on an attribute of the + * specified relation. */ static void -CommentAttribute(List *qualname, char *comment) +CheckAttributeComment(Relation relation) { - 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)); @@ -645,613 +631,18 @@ CommentAttribute(List *qualname, char *comment) (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("\"%s\" is not a table, view, or composite type", 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 CreateSharedComments() routine. + * Check whether the user is allowed to comment on the specified cast. */ 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 cannot be qualified"))); - database = strVal(linitial(qualname)); - - /* - * 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 (thus, we tell get_database_oid - * to ignore the error so that we can handle it differently here). - */ - oid = get_database_oid(database, true); - if (!OidIsValid(oid)) - { - ereport(WARNING, - (errcode(ERRCODE_UNDEFINED_DATABASE), - errmsg("database \"%s\" does not exist", database))); - return; - } - - /* Check object security */ - if (!pg_database_ownercheck(oid, GetUserId())) - aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_DATABASE, - database); - - /* Call CreateSharedComments() to create/drop the comments */ - CreateSharedComments(oid, DatabaseRelationId, comment); -} - -/* - * CommentTablespace -- - * - * This routine is used to add/drop any user-comments a user might - * have regarding a tablespace. The tablepace is specified by name - * and, if found, and the user has appropriate permissions, a - * comment will be added/dropped using the CreateSharedComments() routine. - * - */ -static void -CommentTablespace(List *qualname, char *comment) -{ - char *tablespace; - Oid oid; - - if (list_length(qualname) != 1) - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("tablespace name cannot be qualified"))); - tablespace = strVal(linitial(qualname)); - - oid = get_tablespace_oid(tablespace, false); - - /* Check object security */ - if (!pg_tablespace_ownercheck(oid, GetUserId())) - aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TABLESPACE, tablespace); - - /* Call CreateSharedComments() to create/drop the comments */ - CreateSharedComments(oid, TableSpaceRelationId, comment); -} - -/* - * CommentRole -- - * - * This routine is used to add/drop any user-comments a user might - * have regarding a role. The role is specified by name - * and, if found, and the user has appropriate permissions, a - * comment will be added/dropped using the CreateSharedComments() routine. - */ -static void -CommentRole(List *qualname, char *comment) -{ - char *role; - Oid oid; - - if (list_length(qualname) != 1) - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("role name cannot be qualified"))); - role = strVal(linitial(qualname)); - - oid = get_role_oid(role, false); - - /* Check object security */ - if (!has_privs_of_role(GetUserId(), oid)) - ereport(ERROR, - (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), - errmsg("must be member of role \"%s\" to comment upon it", role))); - - /* Call CreateSharedComments() to create/drop the comments */ - CreateSharedComments(oid, AuthIdRelationId, 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 cannot be qualified"))); - namespace = strVal(linitial(qualname)); - - oid = get_namespace_oid(namespace, false); - - /* 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; - Oid reloid; - Oid ruleoid; - - /* Separate relname and trig name */ - nnames = list_length(qualname); - if (nnames == 1) - { - rulename = strVal(linitial(qualname)); - ruleoid = get_rewrite_oid_without_relid(rulename, &reloid); - - /* 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 */ - ruleoid = get_rewrite_oid(reloid, rulename, false); - } - - /* Check object security */ - if (!pg_class_ownercheck(reloid, GetUserId())) - aclcheck_error(ACLCHECK_NOT_OWNER, 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 = makeTypeNameFromNameList(typename); - - /* Find the type's oid */ - - oid = typenameTypeId(NULL, tname, NULL); - - /* Check object security */ - - if (!pg_type_ownercheck(oid, GetUserId())) - aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TYPE, - format_type_be(oid)); - - /* 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(s). - */ -static void -CommentAggregate(List *aggregate, List *arguments, char *comment) -{ - Oid oid; - - /* Look up function and make sure it's an aggregate */ - oid = LookupAggNameTypeNames(aggregate, arguments, 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(NULL, opername, - typenode1, typenode2, - false, -1); - - /* Check user's privilege 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 relation; - 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)); - - oid = get_trigger_oid(RelationGetRelid(relation), trigname, false); - - /* Call CreateComments() to create/drop the comments */ - CreateComments(oid, TriggerRelationId, 0, comment); - - /* Done, but hold lock on relation */ - 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 relation; - Oid conOid; - - /* 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)); - - conOid = get_constraint_oid(RelationGetRelid(relation), conName, false); - - /* Call CreateComments() to create/drop the comments */ - CreateComments(conOid, ConstraintRelationId, 0, comment); - - /* Done, but hold lock on relation */ - 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 = get_conversion_oid(qualname, false); - - /* 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 cannot be qualified"))); - language = strVal(linitial(qualname)); - - oid = get_language_oid(language, false); - - /* 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; - Oid amID; - Oid opcID; - - Assert(list_length(arguments) == 1); - amname = strVal(linitial(arguments)); - - /* - * Get the operator class OID. - */ - amID = get_am_oid(amname, false); - opcID = get_opclass_oid(amID, qualname, false); - - /* Permission check: must own opclass */ - if (!pg_opclass_ownercheck(opcID, GetUserId())) - aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_OPCLASS, - NameListToString(qualname)); - - /* Call CreateComments() to create/drop the comments */ - CreateComments(opcID, OperatorClassRelationId, 0, comment); -} - -/* - * CommentOpFamily -- - * - * This routine is used to allow a user to provide comments on an - * operator family. The operator family for commenting is determined by both - * its name and its argument list which defines the index method - * the operator family is used for. The argument list is expected to contain - * a single name (represented as a string Value node). - */ -static void -CommentOpFamily(List *qualname, List *arguments, char *comment) -{ - char *amname; - Oid amID; - Oid opfID; - - Assert(list_length(arguments) == 1); - amname = strVal(linitial(arguments)); - - /* Get the opfamily OID. */ - amID = get_am_oid(amname, false); - opfID = get_opfamily_oid(amID, qualname, false); - - /* Permission check: must own opfamily */ - if (!pg_opfamily_ownercheck(opfID, GetUserId())) - aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_OPFAMILY, - NameListToString(qualname)); - - /* Call CreateComments() to create/drop the comments */ - CreateComments(opfID, OperatorFamilyRelationId, 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; - - Assert(list_length(qualname) == 1); - loid = oidparse((Node *) linitial(qualname)); - - /* check that the large object exists */ - if (!LargeObjectExists(loid)) - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_OBJECT), - errmsg("large object %u does not exist", loid))); - - /* Permission checks */ - if (!lo_compat_privileges && - !pg_largeobject_ownercheck(loid, GetUserId())) - ereport(ERROR, - (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), - errmsg("must be owner of large object %u", loid))); - - /* - * Call CreateComments() to create/drop the comments - * - * See the comment in the inv_create() which describes the reason why - * LargeObjectRelationId is used instead of LargeObjectMetadataRelationId. - */ - 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) +CheckCastComment(List *qualname, List *arguments) { TypeName *sourcetype; TypeName *targettype; Oid sourcetypeid; Oid targettypeid; - Oid castOid; Assert(list_length(qualname) == 1); sourcetype = (TypeName *) linitial(qualname); @@ -1263,9 +654,6 @@ CommentCast(List *qualname, List *arguments, char *comment) sourcetypeid = typenameTypeId(NULL, sourcetype, NULL); targettypeid = typenameTypeId(NULL, targettype, NULL); - /* Get the OID of the cast */ - castOid = get_cast_oid(sourcetypeid, targettypeid, false); - /* Permission check */ if (!pg_type_ownercheck(sourcetypeid, GetUserId()) && !pg_type_ownercheck(targettypeid, GetUserId())) @@ -1274,65 +662,4 @@ CommentCast(List *qualname, List *arguments, char *comment) errmsg("must be owner of type %s or type %s", format_type_be(sourcetypeid), format_type_be(targettypeid)))); - - /* Call CreateComments() to create/drop the comments */ - CreateComments(castOid, CastRelationId, 0, comment); -} - -static void -CommentTSParser(List *qualname, char *comment) -{ - Oid prsId; - - prsId = get_ts_parser_oid(qualname, false); - - if (!superuser()) - ereport(ERROR, - (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), - errmsg("must be superuser to comment on text search parser"))); - - CreateComments(prsId, TSParserRelationId, 0, comment); -} - -static void -CommentTSDictionary(List *qualname, char *comment) -{ - Oid dictId; - - dictId = get_ts_dict_oid(qualname, false); - - if (!pg_ts_dict_ownercheck(dictId, GetUserId())) - aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TSDICTIONARY, - NameListToString(qualname)); - - CreateComments(dictId, TSDictionaryRelationId, 0, comment); -} - -static void -CommentTSTemplate(List *qualname, char *comment) -{ - Oid tmplId; - - tmplId = get_ts_template_oid(qualname, false); - - if (!superuser()) - ereport(ERROR, - (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), - errmsg("must be superuser to comment on text search template"))); - - CreateComments(tmplId, TSTemplateRelationId, 0, comment); -} - -static void -CommentTSConfiguration(List *qualname, char *comment) -{ - Oid cfgId; - - cfgId = get_ts_config_oid(qualname, false); - - if (!pg_ts_config_ownercheck(cfgId, GetUserId())) - aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TSCONFIGURATION, - NameListToString(qualname)); - - CreateComments(cfgId, TSConfigRelationId, 0, comment); } diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h index d8a0e0aa63..93c2073d26 100644 --- a/src/include/catalog/dependency.h +++ b/src/include/catalog/dependency.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/catalog/dependency.h,v 1.45 2010/04/05 01:09:53 tgl Exp $ + * $PostgreSQL: pgsql/src/include/catalog/dependency.h,v 1.46 2010/08/27 11:47:41 rhaas Exp $ * *------------------------------------------------------------------------- */ @@ -15,6 +15,7 @@ #define DEPENDENCY_H #include "nodes/parsenodes.h" /* for DropBehavior */ +#include "catalog/objectaddress.h" /* @@ -100,17 +101,6 @@ typedef enum SharedDependencyType SHARED_DEPENDENCY_INVALID = 0 } SharedDependencyType; - -/* - * The two objects related by a dependency are identified by ObjectAddresses. - */ -typedef struct ObjectAddress -{ - Oid classId; /* Class Id from pg_class */ - Oid objectId; /* OID of the object */ - int32 objectSubId; /* Subitem within object (eg column), or 0 */ -} ObjectAddress; - /* expansible list of ObjectAddresses (private in dependency.c) */ typedef struct ObjectAddresses ObjectAddresses; diff --git a/src/include/catalog/objectaddress.h b/src/include/catalog/objectaddress.h new file mode 100644 index 0000000000..780d915dab --- /dev/null +++ b/src/include/catalog/objectaddress.h @@ -0,0 +1,33 @@ +/*------------------------------------------------------------------------- + * + * objectaddress.h + * functions for working with object addresses + * + * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * $PostgreSQL: pgsql/src/include/catalog/objectaddress.h,v 1.1 2010/08/27 11:47:41 rhaas Exp $ + * + *------------------------------------------------------------------------- + */ +#ifndef OBJECTADDRESS_H +#define OBJECTADDRESS_H + +#include "nodes/parsenodes.h" +#include "storage/lock.h" +#include "utils/rel.h" + +/* + * An ObjectAddress represents a database object of any type. + */ +typedef struct ObjectAddress +{ + Oid classId; /* Class Id from pg_class */ + Oid objectId; /* OID of the object */ + int32 objectSubId; /* Subitem within object (eg column), or 0 */ +} ObjectAddress; + +ObjectAddress get_object_address(ObjectType objtype, List *objname, + List *objargs, Relation *relp, LOCKMODE lockmode); + +#endif /* PARSE_OBJECT_H */