refactoring comment.c
At PGCon, we discussed the possibility that a minimal SE-PostgreSQL
implementation would need little more than a hook in
ExecCheckRTPerms() [which we've since added] and a security label
facility [for which KaiGai has submitted a patch]. I actually sat
down to write the security label patch myself while we were in Ottawa,
but quickly ran into difficulties: while the hook we have now can't do
anything useful with objects other than relations, it's pretty clear
from previous discussions on this topic that the demand for labels on
other kinds of objects is not going to go away. Rather than adding
additional syntax to every object type in the system (some of which
don't even have ALTER commands at present), I suggested basing the
syntax on the existing COMMENT syntax. After some discussion[1]http://archives.postgresql.org/pgsql-hackers/2010-07/msg01328.php, we
seem to have settled on the following:
SECURITY LABEL [ FOR <provider> ] ON <object class> <object name> IS '<label>';
At present, there are some difficulties with generalizing this syntax
to other object types. As I found out when I initially set out to
write this patch, it'd basically require duplicating all of comment.c,
which is an unpleasant prospect, because that file is big and crufty;
it has a large amount of internal duplication. Furthermore, the
existing locking mechanism that we're using for comments is known to
be inadequate[2]http://archives.postgresql.org/pgsql-hackers/2010-07/msg00351.php. Dropping a comment while someone else is in the
midst of commenting on it leaves orphaned comments lying around in
pg_(sh)description that could later be inherited by a new object.
That's a minor nuisance for comments and would be nice to fix, but is
obviously a far larger problem for security labels, where even a small
chance of randomly mislabeling an object is no good.
So I wrote a patch. The attached patch factors out all of the code in
comment.c that is responsible for translating parser representations
into a new file parser/parse_object.c, leaving just the
comment-specific stuff in commands/comment.c. It also adds
appropriate locking, so that concurrent COMMENT/DROP scenarios don't
leave behind leftovers. It's a fairly large patch, but the changes
are extremely localized: comment.c gets a lot smaller, and
parse_object.c gets bigger by a slightly smaller amount.
Any comments? (ha ha ha...)
[1]: http://archives.postgresql.org/pgsql-hackers/2010-07/msg01328.php
[2]: http://archives.postgresql.org/pgsql-hackers/2010-07/msg00351.php
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise Postgres Company
Attachments:
refactor_comment-v1.patchapplication/octet-stream; name=refactor_comment-v1.patchDownload
diff --git a/src/backend/commands/comment.c b/src/backend/commands/comment.c
index 7c53ce5..73e9f0f 100644
--- a/src/backend/commands/comment.c
+++ b/src/backend/commands/comment.c
@@ -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/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_object.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,174 @@ 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.
+ */
+ 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 record 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 +561,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 +603,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 +630,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.
- */
-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.
- *
+ * Check whether the user is allowed to comment on the specified cast.
*/
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 +653,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 +661,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/backend/parser/Makefile b/src/backend/parser/Makefile
index a8f4c07..006d3c1 100644
--- a/src/backend/parser/Makefile
+++ b/src/backend/parser/Makefile
@@ -14,8 +14,8 @@ override CPPFLAGS := -I. -I$(srcdir) $(CPPFLAGS)
OBJS= analyze.o gram.o keywords.o kwlookup.o parser.o \
parse_agg.o parse_clause.o parse_coerce.o parse_cte.o parse_expr.o \
- parse_func.o parse_node.o parse_oper.o parse_param.o parse_relation.o \
- parse_target.o parse_type.o parse_utilcmd.o scansup.o
+ parse_func.o parse_node.o parse_object.o parse_oper.o parse_param.o \
+ parse_relation.o parse_target.o parse_type.o parse_utilcmd.o scansup.o
FLEXFLAGS = -CF
diff --git a/src/backend/parser/parse_object.c b/src/backend/parser/parse_object.c
new file mode 100644
index 0000000..d11240b
--- /dev/null
+++ b/src/backend/parser/parse_object.c
@@ -0,0 +1,633 @@
+/*-------------------------------------------------------------------------
+ *
+ * parse_object.c
+ * transform parser representations of arbitrary objects into
+ * object addresses
+ *
+ * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * $PostgreSQL$
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#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/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_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_object.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);
+
+/*
+ * 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:
+ 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;
+}
+
+/*
+ * 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.
+ *
+ * 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;
+
+ 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;
+}
diff --git a/src/backend/storage/lmgr/lmgr.c b/src/backend/storage/lmgr/lmgr.c
index 15e53eb..ad812ee 100644
--- a/src/backend/storage/lmgr/lmgr.c
+++ b/src/backend/storage/lmgr/lmgr.c
@@ -599,6 +599,9 @@ LockDatabaseObject(Oid classid, Oid objid, uint16 objsubid,
objsubid);
(void) LockAcquire(&tag, lockmode, false, false);
+
+ /* Make sure syscaches are up-to-date with any changes we waited for */
+ AcceptInvalidationMessages();
}
/*
diff --git a/src/include/parser/parse_object.h b/src/include/parser/parse_object.h
new file mode 100644
index 0000000..01a4c9c
--- /dev/null
+++ b/src/include/parser/parse_object.h
@@ -0,0 +1,24 @@
+/*-------------------------------------------------------------------------
+ *
+ * parse_object.h
+ * transform parser representations of arbitrary objects into object
+ * addresses
+ *
+ * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * $PostgreSQL$
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PARSE_OBJECT_H
+#define PARSE_OBJECT_H
+
+#include "catalog/dependency.h"
+#include "nodes/pg_list.h"
+#include "storage/lock.h"
+
+ObjectAddress get_object_address(ObjectType objtype, List *objname,
+ List *objargs, Relation *relp, LOCKMODE lockmode);
+
+#endif /* PARSE_OBJECT_H */
Excerpts from Robert Haas's message of vie ago 06 11:02:58 -0400 2010:
Any comments? (ha ha ha...)
Interesting idea. The patch looks fine on a quick once-over. Two small
things: this comment
+ /*
+ * Databases, tablespaces, and roles are cluster-wide objects, so any
+ * comments on those objects are record in the shared pg_shdescription
+ * catalog. Comments on all other objects are recorded in pg_description.
+ */
says "record" where it should say "recorded".
Also, it strikes me that perhaps the ObjectAddress struct definition
should be moved to the new header file; seems a more natural place for
it (but then, it seems like a lot of files will need to include the new
header, so perhaps that should be left to a subsequent patch).
Thirdly, is it just me or just could replace a lot of code in DropFoo
functions with this new auxiliary code? Seems it's just missing
"missing_ok" support ...
--
Álvaro Herrera <alvherre@commandprompt.com>
The PostgreSQL Company - Command Prompt, Inc.
PostgreSQL Replication, Consulting, Custom Development, 24x7 support
On Fri, Aug 6, 2010 at 11:36 AM, Alvaro Herrera
<alvherre@commandprompt.com> wrote:
Excerpts from Robert Haas's message of vie ago 06 11:02:58 -0400 2010:
Any comments? (ha ha ha...)
Interesting idea. The patch looks fine on a quick once-over.
Thanks for taking a look.
Two small
things: this comment+ /* + * Databases, tablespaces, and roles are cluster-wide objects, so any + * comments on those objects are record in the shared pg_shdescription + * catalog. Comments on all other objects are recorded in pg_description. + */says "record" where it should say "recorded".
Thanks, good eye.
Also, it strikes me that perhaps the ObjectAddress struct definition
should be moved to the new header file; seems a more natural place for
it (but then, it seems like a lot of files will need to include the new
header, so perhaps that should be left to a subsequent patch).
I thought about that, but erred on the side of being conservative and
didn't move it. I like the idea, though.
Thirdly, is it just me or just could replace a lot of code in DropFoo
functions with this new auxiliary code? Seems it's just missing
"missing_ok" support ...
I am not sure how much code it would save you at the level of the
individual DropFoo() functions; I'd have to look through them more
carefully. But now that you mention it, what about getting rid of all
of the individual parse nodes for drop statements? Right now we have:
DropTableSpaceStmt
DropFdwStmt
DropForeignServerStmt
DropUserMappingStmt
DropPLangStmt
DropRoleStmt
DropStmt (table, sequence, view, index, domain, conversion, schema,
text search {parser, dictionary, template, configuration}
DropPropertyStmt (rules and triggers)
DropdbStmt (capitalized differently, just for fun)
DropCastStmt
RemoveFuncStmt (so you can't find it by grepping for Drop!)
RemoveOpClassStmt
RemoveOpFamilyStmt
It seems like we could probably unify all of these into a single
DropStmt, following the same pattern as CommentStmt, although I think
perhaps that should be a follow-on patch rather than doing it as part
of this one. GRANT also has some code to translate object names to
OIDs, which I thought might be able to use this machinery as well,
though I haven't really checked whether it makes sense.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise Postgres Company
On Fri, 2010-08-06 at 11:02 -0400, Robert Haas wrote:
At PGCon, we discussed the possibility that a minimal SE-PostgreSQL
implementation would need little more than a hook in
ExecCheckRTPerms() [which we've since added] and a security label
facility [for which KaiGai has submitted a patch]. I actually sat
down to write the security label patch myself while we were in Ottawa,
but quickly ran into difficulties: while the hook we have now can't do
anything useful with objects other than relations, it's pretty clear
from previous discussions on this topic that the demand for labels on
other kinds of objects is not going to go away. Rather than adding
additional syntax to every object type in the system (some of which
don't even have ALTER commands at present), I suggested basing the
syntax on the existing COMMENT syntax. After some discussion[1], we
seem to have settled on the following:SECURITY LABEL [ FOR <provider> ] ON <object class> <object name> IS '<label>';
I understand the concept and it seems like it might work. Not too keen
on pretending a noun is a verb. That leads to erroring.
<verb> SECURITY LABEL? verb = CREATE, ADD, ...
Can't objects have more than one label?
How will you set default security labels on objects?
Where do you define labels?
Will there be a new privilege to define this? Presumably object owners
would not be able to set that themselves, otherwise you could create an
object, add a security label to it and then use it to see other things
at that level.
--
Simon Riggs www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Training and Services
On Fri, Aug 6, 2010 at 12:26 PM, Simon Riggs <simon@2ndquadrant.com> wrote:
I understand the concept and it seems like it might work. Not too keen
on pretending a noun is a verb. That leads to erroring.<verb> SECURITY LABEL? verb = CREATE, ADD, ...
Can't objects have more than one label?
How will you set default security labels on objects?
Where do you define labels?
Will there be a new privilege to define this? Presumably object owners
would not be able to set that themselves, otherwise you could create an
object, add a security label to it and then use it to see other things
at that level.
Uh, these are all good questions, but I think they'd be more
appropriate on the thread about the security label patch, to which I
linked in my previous email. Many of them have already been discussed
there.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise Postgres Company
(2010/08/07 0:02), Robert Haas wrote:
At PGCon, we discussed the possibility that a minimal SE-PostgreSQL
implementation would need little more than a hook in
ExecCheckRTPerms() [which we've since added] and a security label
facility [for which KaiGai has submitted a patch]. I actually sat
down to write the security label patch myself while we were in Ottawa,
but quickly ran into difficulties: while the hook we have now can't do
anything useful with objects other than relations, it's pretty clear
from previous discussions on this topic that the demand for labels on
other kinds of objects is not going to go away. Rather than adding
additional syntax to every object type in the system (some of which
don't even have ALTER commands at present), I suggested basing the
syntax on the existing COMMENT syntax. After some discussion[1], we
seem to have settled on the following:SECURITY LABEL [ FOR<provider> ] ON<object class> <object name> IS '<label>';
At present, there are some difficulties with generalizing this syntax
to other object types. As I found out when I initially set out to
write this patch, it'd basically require duplicating all of comment.c,
which is an unpleasant prospect, because that file is big and crufty;
it has a large amount of internal duplication. Furthermore, the
existing locking mechanism that we're using for comments is known to
be inadequate[2]. Dropping a comment while someone else is in the
midst of commenting on it leaves orphaned comments lying around in
pg_(sh)description that could later be inherited by a new object.
That's a minor nuisance for comments and would be nice to fix, but is
obviously a far larger problem for security labels, where even a small
chance of randomly mislabeling an object is no good.So I wrote a patch. The attached patch factors out all of the code in
comment.c that is responsible for translating parser representations
into a new file parser/parse_object.c, leaving just the
comment-specific stuff in commands/comment.c. It also adds
appropriate locking, so that concurrent COMMENT/DROP scenarios don't
leave behind leftovers. It's a fairly large patch, but the changes
are extremely localized: comment.c gets a lot smaller, and
parse_object.c gets bigger by a slightly smaller amount.Any comments? (ha ha ha...)
[1] http://archives.postgresql.org/pgsql-hackers/2010-07/msg01328.php
[2] http://archives.postgresql.org/pgsql-hackers/2010-07/msg00351.php
Thanks for your efforts.
I believe the get_object_address() enables to implement security
label features on various kind of database objects.
I tried to look at the patch. Most part is fine, but I found out
two issues.
On the object_exists(), when we verify existence of a large object,
it needs to scan pg_largeobject_metadata, instead of pg_largeobject.
When we implement pg_largeobject_metadata catalog, we decided to set
LargeObjectRelationId on object.classId due to the backend compatibility.
| /*
| * 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 LargeObjectRelationId:
| indexoid = LargeObjectMetadataOidIndexId;
| break;
| :
| }
|
| /* 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);
^^^^^^^^^^^^^^^ <- It tries to open pg_largeobject
| sd = systable_beginscan(rel, indexoid, true, SnapshotNow, 1, skey);
| found = HeapTupleIsValid(systable_getnext(sd));
| systable_endscan(sd);
| heap_close(rel, AccessShareLock);
| return found;
| }
Although no caller invokes get_object_address() with lockmode = NoLock,
isn't it necessary to skip locking if NoLock was given.
| /*
| * 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);
| }
Thanks,
--
KaiGai Kohei <kaigai@kaigai.gr.jp>
On Fri, Aug 6, 2010 at 11:15 PM, KaiGai Kohei <kaigai@kaigai.gr.jp> wrote:
[brief review]
OK, here's an updated patch:
1. I fixed the typo Alvaro spotted.
2. I haven't done anything about moving the definition of
ObjectAddress elsewhere, as Alvaro suggested, because I'm not sure
quite where it ought to go. I still think it's a good idea, though
I'm not dead set on it, either. Suggestions?
3. I fixed the issue Kaigai Kohei spotted, regarding
LargeObjectRelationId vs. LargeObjectMetadataRelationId, by adding a
grotty hack. However, I feel that I'm not so much adding a new grotty
hack as working around an existing grotty hack which was added for
reasons I'm unclear on. Is there a pg_upgrade-related reason not to
revert the original hack instead?
4. In response to Kaigai Kohei's complaint about lockmode possibly
being NoLock, I've just added an Assert() that it isn't, in lieu of
trying to do something sensible in that case. I can't at present
think of a situation in which being able to call it that way would be
useful, and the Assert() seems like it ought to be enough warning to
anyone coming along later that they'd better think twice before
thinking that will work.
5. Since I'm hoping Tom will read this, I ran it through filterdiff. :-)
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise Postgres Company
Attachments:
refactor_comment-v2-context.patchapplication/octet-stream; name=refactor_comment-v2-context.patchDownload
*** a/src/backend/commands/comment.c
--- b/src/backend/commands/comment.c
***************
*** 17,99 ****
#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/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.
*/
!
! 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);
/*
--- 17,46 ----
#include "access/genam.h"
#include "access/heapam.h"
#include "catalog/indexing.h"
#include "catalog/pg_description.h"
#include "catalog/pg_shdescription.h"
#include "commands/comment.h"
#include "commands/dbcommands.h"
#include "libpq/be-fsstubs.h"
#include "miscadmin.h"
#include "parser/parse_func.h"
! #include "parser/parse_object.h"
#include "parser/parse_type.h"
#include "utils/acl.h"
#include "utils/builtins.h"
#include "utils/fmgroids.h"
#include "utils/rel.h"
#include "utils/tqual.h"
/*
! * 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 CheckRelationComment(int objtype, Relation relation);
! static void CheckAttributeComment(Relation relation);
! static void CheckCastComment(List *qualname, List *arguments);
/*
***************
*** 105,188 **** static void CommentTSConfiguration(List *qualname, char *comment);
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_OPFAMILY:
! CommentOpFamily(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;
case OBJECT_TABLESPACE:
! CommentTablespace(stmt->objname, stmt->comment);
break;
case OBJECT_ROLE:
! CommentRole(stmt->objname, stmt->comment);
break;
case OBJECT_TSPARSER:
! CommentTSParser(stmt->objname, stmt->comment);
break;
case OBJECT_TSDICTIONARY:
! CommentTSDictionary(stmt->objname, stmt->comment);
break;
case OBJECT_TSTEMPLATE:
! CommentTSTemplate(stmt->objname, stmt->comment);
break;
case OBJECT_TSCONFIGURATION:
! CommentTSConfiguration(stmt->objname, stmt->comment);
break;
default:
elog(ERROR, "unrecognized object type: %d",
(int) stmt->objtype);
}
}
/*
--- 52,225 ----
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.
+ */
+ 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:
! CheckRelationComment(stmt->objtype, relation);
break;
case OBJECT_COLUMN:
! CheckAttributeComment(relation);
break;
case OBJECT_DATABASE:
! if (!pg_database_ownercheck(address.objectId, GetUserId()))
! aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_DATABASE,
! strVal(linitial(stmt->objname)));
break;
case OBJECT_TYPE:
! if (!pg_type_ownercheck(address.objectId, GetUserId()))
! aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TYPE,
! format_type_be(address.objectId));
break;
case OBJECT_AGGREGATE:
case OBJECT_FUNCTION:
! if (!pg_proc_ownercheck(address.objectId, GetUserId()))
! aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_PROC,
! NameListToString(stmt->objname));
break;
case OBJECT_OPERATOR:
! 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:
! case OBJECT_CONSTRAINT:
! if (!pg_class_ownercheck(RelationGetRelid(relation), GetUserId()))
! aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS,
! RelationGetRelationName(relation));
break;
case OBJECT_SCHEMA:
! if (!pg_namespace_ownercheck(address.objectId, GetUserId()))
! aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_NAMESPACE,
! strVal(linitial(stmt->objname)));
break;
case OBJECT_CONVERSION:
! if (!pg_conversion_ownercheck(address.objectId, GetUserId()))
! aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CONVERSION,
! NameListToString(stmt->objname));
break;
case OBJECT_LANGUAGE:
! if (!superuser())
! ereport(ERROR,
! (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
! errmsg("must be superuser to comment on procedural language")));
break;
case OBJECT_OPCLASS:
! if (!pg_opclass_ownercheck(address.objectId, GetUserId()))
! aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_OPCLASS,
! NameListToString(stmt->objname));
break;
case OBJECT_OPFAMILY:
! if (!pg_opfamily_ownercheck(address.objectId, GetUserId()))
! aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_OPFAMILY,
! NameListToString(stmt->objname));
break;
case OBJECT_LARGEOBJECT:
! 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:
! CheckCastComment(stmt->objname, stmt->objargs);
break;
case OBJECT_TABLESPACE:
! if (!pg_tablespace_ownercheck(address.objectId, GetUserId()))
! aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TABLESPACE,
! strVal(linitial(stmt->objname)));
break;
case OBJECT_ROLE:
! 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:
! if (!superuser())
! ereport(ERROR,
! (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
! errmsg("must be superuser to comment on text search parser")));
break;
case OBJECT_TSDICTIONARY:
! if (!pg_ts_dict_ownercheck(address.objectId, GetUserId()))
! aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TSDICTIONARY,
! NameListToString(stmt->objname));
break;
case OBJECT_TSTEMPLATE:
! if (!superuser())
! ereport(ERROR,
! (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
! errmsg("must be superuser to comment on text search template")));
break;
case OBJECT_TSCONFIGURATION:
! 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,559 **** 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.
*/
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:
--- 561,577 ----
}
/*
! * Check whether the user is allowed to comment on this relation.
*/
static void
! CheckRelationComment(int objtype, Relation relation)
{
/* 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,632 **** 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
*/
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));
--- 603,617 ----
RelationGetRelationName(relation))));
break;
}
}
/*
! * Check whether the user is allowed to comment on an attribute of the
! * specified relation.
*/
static void
! CheckAttributeComment(Relation relation)
{
if (!pg_class_ownercheck(RelationGetRelid(relation), GetUserId()))
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS,
RelationGetRelationName(relation));
***************
*** 645,1257 **** 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.
- */
- 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)
{
TypeName *sourcetype;
TypeName *targettype;
Oid sourcetypeid;
Oid targettypeid;
- Oid castOid;
Assert(list_length(qualname) == 1);
sourcetype = (TypeName *) linitial(qualname);
--- 630,647 ----
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table, view, or composite type",
RelationGetRelationName(relation))));
}
/*
! * Check whether the user is allowed to comment on the specified cast.
*/
static void
! CheckCastComment(List *qualname, List *arguments)
{
TypeName *sourcetype;
TypeName *targettype;
Oid sourcetypeid;
Oid targettypeid;
Assert(list_length(qualname) == 1);
sourcetype = (TypeName *) linitial(qualname);
***************
*** 1263,1271 **** 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()))
--- 653,658 ----
***************
*** 1274,1338 **** 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);
}
--- 661,664 ----
*** a/src/backend/parser/Makefile
--- b/src/backend/parser/Makefile
***************
*** 14,21 **** override CPPFLAGS := -I. -I$(srcdir) $(CPPFLAGS)
OBJS= analyze.o gram.o keywords.o kwlookup.o parser.o \
parse_agg.o parse_clause.o parse_coerce.o parse_cte.o parse_expr.o \
! parse_func.o parse_node.o parse_oper.o parse_param.o parse_relation.o \
! parse_target.o parse_type.o parse_utilcmd.o scansup.o
FLEXFLAGS = -CF
--- 14,21 ----
OBJS= analyze.o gram.o keywords.o kwlookup.o parser.o \
parse_agg.o parse_clause.o parse_coerce.o parse_cte.o parse_expr.o \
! parse_func.o parse_node.o parse_object.o parse_oper.o parse_param.o \
! parse_relation.o parse_target.o parse_type.o parse_utilcmd.o scansup.o
FLEXFLAGS = -CF
*** /dev/null
--- b/src/backend/parser/parse_object.c
***************
*** 0 ****
--- 1,644 ----
+ /*-------------------------------------------------------------------------
+ *
+ * parse_object.c
+ * transform parser representations of arbitrary objects into
+ * object addresses
+ *
+ * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * $PostgreSQL$
+ *
+ *-------------------------------------------------------------------------
+ */
+
+ #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/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_object.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);
+
+ /*
+ * 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;
+ }
+
+ /*
+ * 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.
+ *
+ * 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;
+ }
*** /dev/null
--- b/src/include/parser/parse_object.h
***************
*** 0 ****
--- 1,24 ----
+ /*-------------------------------------------------------------------------
+ *
+ * parse_object.h
+ * transform parser representations of arbitrary objects into object
+ * addresses
+ *
+ * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * $PostgreSQL$
+ *
+ *-------------------------------------------------------------------------
+ */
+ #ifndef PARSE_OBJECT_H
+ #define PARSE_OBJECT_H
+
+ #include "catalog/dependency.h"
+ #include "nodes/pg_list.h"
+ #include "storage/lock.h"
+
+ ObjectAddress get_object_address(ObjectType objtype, List *objname,
+ List *objargs, Relation *relp, LOCKMODE lockmode);
+
+ #endif /* PARSE_OBJECT_H */
(2010/08/16 11:50), Robert Haas wrote:
On Fri, Aug 6, 2010 at 11:15 PM, KaiGai Kohei<kaigai@kaigai.gr.jp> wrote:
[brief review]
OK, here's an updated patch:
1. I fixed the typo Alvaro spotted.
2. I haven't done anything about moving the definition of
ObjectAddress elsewhere, as Alvaro suggested, because I'm not sure
quite where it ought to go. I still think it's a good idea, though
I'm not dead set on it, either. Suggestions?3. I fixed the issue Kaigai Kohei spotted, regarding
LargeObjectRelationId vs. LargeObjectMetadataRelationId, by adding a
grotty hack. However, I feel that I'm not so much adding a new grotty
hack as working around an existing grotty hack which was added for
reasons I'm unclear on. Is there a pg_upgrade-related reason not to
revert the original hack instead?
When we were developing largeobject access controls, Tom Lane commented
as follows:
* Re: [HACKERS] [PATCH] Largeobject access controls
http://marc.info/?l=pgsql-hackers&m=125548822906571&w=2
| I notice that the patch decides to change the pg_description classoid for
| LO comments from pg_largeobject's OID to pg_largeobject_metadata's. This
| will break existing clients that look at pg_description (eg, pg_dump and
| psql, plus any other clients that have any intelligence about comments,
| for instance it probably breaks pgAdmin). And there's just not a lot of
| return that I can see. I agree that using pg_largeobject_metadata would
| be more consistent given the new catalog layout, but I'm inclined to think
| we should stick to the old convention on compatibility grounds. Given
| that choice, for consistency we'd better also use pg_largeobject's OID not
| pg_largeobject_metadata's in pg_shdepend entries for LOs.
He concerned about existing applications which have knowledge about internal
layout of system catalogs, then I fixed up the patch according to the suggestion.
4. In response to Kaigai Kohei's complaint about lockmode possibly
being NoLock, I've just added an Assert() that it isn't, in lieu of
trying to do something sensible in that case. I can't at present
think of a situation in which being able to call it that way would be
useful, and the Assert() seems like it ought to be enough warning to
anyone coming along later that they'd better think twice before
thinking that will work.5. Since I'm hoping Tom will read this, I ran it through filterdiff. :-)
--
KaiGai Kohei <kaigai@ak.jp.nec.com>
Excerpts from KaiGai Kohei's message of lun ago 16 00:19:54 -0400 2010:
(2010/08/16 11:50), Robert Haas wrote:
When we were developing largeobject access controls, Tom Lane commented
as follows:* Re: [HACKERS] [PATCH] Largeobject access controls
http://marc.info/?l=pgsql-hackers&m=125548822906571&w=2
| I notice that the patch decides to change the pg_description classoid for
| LO comments from pg_largeobject's OID to pg_largeobject_metadata's. This
| will break existing clients that look at pg_description (eg, pg_dump and
| psql, plus any other clients that have any intelligence about comments,
| for instance it probably breaks pgAdmin). And there's just not a lot of
| return that I can see. I agree that using pg_largeobject_metadata would
| be more consistent given the new catalog layout, but I'm inclined to think
| we should stick to the old convention on compatibility grounds. Given
| that choice, for consistency we'd better also use pg_largeobject's OID not
| pg_largeobject_metadata's in pg_shdepend entries for LOs.He concerned about existing applications which have knowledge about internal
layout of system catalogs, then I fixed up the patch according to the suggestion.
I think that with this patch we have the return for the change that we
didn't have previously. A patch that changes it should also fix pg_dump
and psql at the same time, but IMHO it doesn't make sense to keep adding
grotty hacks on top of it.
Maybe we could do with a grotty hack in obj_description() instead?
(...checks...)
Oh, it's defined as a SQL function directly in pg_proc.h :-(
--
Álvaro Herrera <alvherre@commandprompt.com>
The PostgreSQL Company - Command Prompt, Inc.
PostgreSQL Replication, Consulting, Custom Development, 24x7 support
Robert Haas <robertmhaas@gmail.com> writes:
5. Since I'm hoping Tom will read this, I ran it through filterdiff. :-)
OK, I looked ;-). It mostly looks good, but of course I've got some
opinions...
2. I haven't done anything about moving the definition of
ObjectAddress elsewhere, as Alvaro suggested, because I'm not sure
quite where it ought to go. I still think it's a good idea, though
I'm not dead set on it, either. Suggestions?
I think the problem is you're trying to put this into backend/parser
which is not really the right place for it. It's an execution-time
animal not a parse-time animal. I would put it into backend/catalog,
perhaps named objectaddress.c, and similarly the header file would be
objectaddress.h. Then it would be reasonable to move struct
ObjectAddress into this header and have dependency.h #include it.
There might be some other stuff in dependency.c that more naturally
belongs here, too.
3. I fixed the issue Kaigai Kohei spotted, regarding
LargeObjectRelationId vs. LargeObjectMetadataRelationId, by adding a
grotty hack. However, I feel that I'm not so much adding a new grotty
hack as working around an existing grotty hack which was added for
reasons I'm unclear on. Is there a pg_upgrade-related reason not to
revert the original hack instead?
It's not pg_upgrade (nor psql, nor pg_dump) we are protecting here.
It's third-party applications that might understand the contents of
pg_description, pg_depend, etc. I think that hack is quite small
and localized enough to live with, rather than causing a flag day
for an unknown number of clients by changing the representation.
+ /*
+ * Translate the parser representation which identifies this object into
+ * an ObjectAddress. get_object_address() will throw an error if the
+ * object does not exist.
+ */
+ address = get_object_address(stmt->objtype, stmt->objname, stmt->objargs,
+ &relation, ShareUpdateExclusiveLock);
I think this comment should also explicitly mention that we're getting a
lock to protect against concurrent DROPs.
+ /*
+ * 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.
+ *
+ * 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)
This comment's a bit shy of a load too, since it totally fails to
mention the relp argument, or specify what the caller is required to
do with it, or explain how the locking on the relation works.
As a matter of style, I'd suggest putting the single externally callable
function (ie get_object_address) at the top of the file not the bottom.
People shouldn't have to read through the entire file before finding out
what API it is supposed to provide to the outside world.
regards, tom lane
On Mon, Aug 16, 2010 at 3:48 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
2. I haven't done anything about moving the definition of
ObjectAddress elsewhere, as Alvaro suggested, because I'm not sure
quite where it ought to go. I still think it's a good idea, though
I'm not dead set on it, either. Suggestions?I think the problem is you're trying to put this into backend/parser
which is not really the right place for it. It's an execution-time
animal not a parse-time animal. I would put it into backend/catalog,
perhaps named objectaddress.c, and similarly the header file would be
objectaddress.h. Then it would be reasonable to move struct
ObjectAddress into this header and have dependency.h #include it.
There might be some other stuff in dependency.c that more naturally
belongs here, too.
If this isn't parse analysis, then you and I have very different ideas
of what parse analysis is. And under this theory, what are routines
like LookupAggNameTypeNames() doing in src/backend/parser?
I'll make the rest of the changes you suggest...
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise Postgres Company
Robert Haas <robertmhaas@gmail.com> writes:
On Mon, Aug 16, 2010 at 3:48 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
I think the problem is you're trying to put this into backend/parser
which is not really the right place for it.
If this isn't parse analysis, then you and I have very different ideas
of what parse analysis is.
Maybe so, but the parser is expected to put out a representation that
will still be valid when the command is executed some time later.
That is exactly why utility statements have the barely-more-than-source
parsetree representation they do: because we do not hold locks on the
objects from parsing to execution, we could not expect an OID-level
representation to remain good. This is a lot different from what we
do with DML statements, but there are good reasons for it.
I repeat my observation that this code doesn't belong in /parser.
The code you're replacing was not in /parser, and that was because
it didn't belong there, not because somebody didn't understand the
system structure.
regards, tom lane
I wrote:
Maybe so, but the parser is expected to put out a representation that
will still be valid when the command is executed some time later.
Rereading this, I see I didn't make my point very clearly. The reason
this code doesn't belong in parser/ is that there's no prospect the
parser itself would ever use it. ObjectAddress is an execution-time
creature because we don't want utility statement representations to be
resolved to OID-level detail before they execute.
regards, tom lane
On Tue, Aug 17, 2010 at 11:30 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
I wrote:
Maybe so, but the parser is expected to put out a representation that
will still be valid when the command is executed some time later.Rereading this, I see I didn't make my point very clearly. The reason
this code doesn't belong in parser/ is that there's no prospect the
parser itself would ever use it. ObjectAddress is an execution-time
creature because we don't want utility statement representations to be
resolved to OID-level detail before they execute.
Well, that is a good reason for doing it your way, but I'm slightly
fuzzy on why we need a crisp separation between parse-time and
execution-time.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise Postgres Company
Robert Haas <robertmhaas@gmail.com> writes:
On Tue, Aug 17, 2010 at 11:30 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Rereading this, I see I didn't make my point very clearly. �The reason
this code doesn't belong in parser/ is that there's no prospect the
parser itself would ever use it. �ObjectAddress is an execution-time
creature because we don't want utility statement representations to be
resolved to OID-level detail before they execute.
Well, that is a good reason for doing it your way, but I'm slightly
fuzzy on why we need a crisp separation between parse-time and
execution-time.
I don't insist that the separation has to be crisp. I'm merely saying
that putting a large chunk of useful-only-at-execution-time code into
backend/parser is the Wrong Thing.
regards, tom lane
On Tue, Aug 17, 2010 at 2:24 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Robert Haas <robertmhaas@gmail.com> writes:
On Tue, Aug 17, 2010 at 11:30 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Rereading this, I see I didn't make my point very clearly. The reason
this code doesn't belong in parser/ is that there's no prospect the
parser itself would ever use it. ObjectAddress is an execution-time
creature because we don't want utility statement representations to be
resolved to OID-level detail before they execute.Well, that is a good reason for doing it your way, but I'm slightly
fuzzy on why we need a crisp separation between parse-time and
execution-time.I don't insist that the separation has to be crisp. I'm merely saying
that putting a large chunk of useful-only-at-execution-time code into
backend/parser is the Wrong Thing.
OK, but there should be a reason for that. For example, if there are
circumstances when we parse a statement, and then time passes, and
then we execute it later, that's a good argument for what you're
saying here. But otherwise, the fact that these bits of barely-parsed
stuff get passed all over the backend seems like an inconvenient wart.
I was actually thinking of proposing that we make more things happen
during the parsing process and postpone less to the execution phase,
and I need to make sure that I understand why you don't want to do
that.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise Postgres Company
Robert Haas <robertmhaas@gmail.com> writes:
On Tue, Aug 17, 2010 at 2:24 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
I don't insist that the separation has to be crisp. �I'm merely saying
that putting a large chunk of useful-only-at-execution-time code into
backend/parser is the Wrong Thing.
OK, but there should be a reason for that. For example, if there are
circumstances when we parse a statement, and then time passes, and
then we execute it later, that's a good argument for what you're
saying here.
Yeah, and that's exactly what happens with utility statements that (for
example) get into the plan cache.
I was actually thinking of proposing that we make more things happen
during the parsing process and postpone less to the execution phase,
and I need to make sure that I understand why you don't want to do
that.
The reason to not do that is that you'd have to hold more locks,
and do more management of those locks, in order to protect the
more-thoroughly-analyzed statements from parsing to execution. In the
case of utility statements, particularly ones that affect a lot of
objects, I think that would be a seriously bad idea. In fact it would
fly in the face of lessons we learned the hard way. The whole of
parser/parse_utilcmd.c is code that we used to execute "at parse time",
and had to rearrange to postpone to execution time, in order to avoid
nasty bugs. It's still in backend/parser because it fits well there
and uses a lot of parser infrastructure that's shared with parse-time
analysis of DML statements. But neither of those statements hold for
the ObjectAddress resolution code.
regards, tom lane
On Tue, Aug 17, 2010 at 2:49 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Robert Haas <robertmhaas@gmail.com> writes:
On Tue, Aug 17, 2010 at 2:24 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
I don't insist that the separation has to be crisp. I'm merely saying
that putting a large chunk of useful-only-at-execution-time code into
backend/parser is the Wrong Thing.OK, but there should be a reason for that. For example, if there are
circumstances when we parse a statement, and then time passes, and
then we execute it later, that's a good argument for what you're
saying here.Yeah, and that's exactly what happens with utility statements that (for
example) get into the plan cache.
OK. I'll have to look through that code some time.
Here's v3.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise Postgres Company
Attachments:
refactor_comment-v3.patchapplication/octet-stream; name=refactor_comment-v3.patchDownload
*** a/src/backend/catalog/Makefile
--- b/src/backend/catalog/Makefile
***************
*** 11,19 **** 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
BKIFILES = postgres.bki postgres.description postgres.shdescription
--- 11,20 ----
include $(top_builddir)/src/Makefile.global
OBJS = catalog.o dependency.o heap.o index.o indexing.o namespace.o aclchk.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
*** /dev/null
--- b/src/backend/catalog/objectaddress.c
***************
*** 0 ****
--- 1,650 ----
+ /*-------------------------------------------------------------------------
+ *
+ * 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$
+ *
+ *-------------------------------------------------------------------------
+ */
+
+ #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, *relp will set to point to that relation). This
+ * is a bit grotty but it makes life simpler, since the caller will
+ * typically need it. (For example, contraints have no permissions of their
+ * own, so any permission checks the caller wishes to do will need to be
+ * based on the relation's permissions.)
+ *
+ * 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;
+ }
*** a/src/backend/commands/comment.c
--- b/src/backend/commands/comment.c
***************
*** 17,99 ****
#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/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.
*/
!
! 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);
/*
--- 17,46 ----
#include "access/genam.h"
#include "access/heapam.h"
#include "catalog/indexing.h"
! #include "catalog/objectaddress.h"
#include "catalog/pg_description.h"
#include "catalog/pg_shdescription.h"
#include "commands/comment.h"
#include "commands/dbcommands.h"
#include "libpq/be-fsstubs.h"
#include "miscadmin.h"
#include "parser/parse_func.h"
#include "parser/parse_type.h"
#include "utils/acl.h"
#include "utils/builtins.h"
#include "utils/fmgroids.h"
#include "utils/rel.h"
#include "utils/tqual.h"
/*
! * 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 CheckRelationComment(int objtype, Relation relation);
! static void CheckAttributeComment(Relation relation);
! static void CheckCastComment(List *qualname, List *arguments);
/*
***************
*** 105,188 **** static void CommentTSConfiguration(List *qualname, char *comment);
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_OPFAMILY:
! CommentOpFamily(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;
case OBJECT_TABLESPACE:
! CommentTablespace(stmt->objname, stmt->comment);
break;
case OBJECT_ROLE:
! CommentRole(stmt->objname, stmt->comment);
break;
case OBJECT_TSPARSER:
! CommentTSParser(stmt->objname, stmt->comment);
break;
case OBJECT_TSDICTIONARY:
! CommentTSDictionary(stmt->objname, stmt->comment);
break;
case OBJECT_TSTEMPLATE:
! CommentTSTemplate(stmt->objname, stmt->comment);
break;
case OBJECT_TSCONFIGURATION:
! CommentTSConfiguration(stmt->objname, stmt->comment);
break;
default:
elog(ERROR, "unrecognized object type: %d",
(int) stmt->objtype);
}
}
/*
--- 52,226 ----
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:
! CheckRelationComment(stmt->objtype, relation);
break;
case OBJECT_COLUMN:
! CheckAttributeComment(relation);
break;
case OBJECT_DATABASE:
! if (!pg_database_ownercheck(address.objectId, GetUserId()))
! aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_DATABASE,
! strVal(linitial(stmt->objname)));
break;
case OBJECT_TYPE:
! if (!pg_type_ownercheck(address.objectId, GetUserId()))
! aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TYPE,
! format_type_be(address.objectId));
break;
case OBJECT_AGGREGATE:
case OBJECT_FUNCTION:
! if (!pg_proc_ownercheck(address.objectId, GetUserId()))
! aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_PROC,
! NameListToString(stmt->objname));
break;
case OBJECT_OPERATOR:
! 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:
! case OBJECT_CONSTRAINT:
! if (!pg_class_ownercheck(RelationGetRelid(relation), GetUserId()))
! aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS,
! RelationGetRelationName(relation));
break;
case OBJECT_SCHEMA:
! if (!pg_namespace_ownercheck(address.objectId, GetUserId()))
! aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_NAMESPACE,
! strVal(linitial(stmt->objname)));
break;
case OBJECT_CONVERSION:
! if (!pg_conversion_ownercheck(address.objectId, GetUserId()))
! aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CONVERSION,
! NameListToString(stmt->objname));
break;
case OBJECT_LANGUAGE:
! if (!superuser())
! ereport(ERROR,
! (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
! errmsg("must be superuser to comment on procedural language")));
break;
case OBJECT_OPCLASS:
! if (!pg_opclass_ownercheck(address.objectId, GetUserId()))
! aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_OPCLASS,
! NameListToString(stmt->objname));
break;
case OBJECT_OPFAMILY:
! if (!pg_opfamily_ownercheck(address.objectId, GetUserId()))
! aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_OPFAMILY,
! NameListToString(stmt->objname));
break;
case OBJECT_LARGEOBJECT:
! 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:
! CheckCastComment(stmt->objname, stmt->objargs);
break;
case OBJECT_TABLESPACE:
! if (!pg_tablespace_ownercheck(address.objectId, GetUserId()))
! aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TABLESPACE,
! strVal(linitial(stmt->objname)));
break;
case OBJECT_ROLE:
! 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:
! if (!superuser())
! ereport(ERROR,
! (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
! errmsg("must be superuser to comment on text search parser")));
break;
case OBJECT_TSDICTIONARY:
! if (!pg_ts_dict_ownercheck(address.objectId, GetUserId()))
! aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TSDICTIONARY,
! NameListToString(stmt->objname));
break;
case OBJECT_TSTEMPLATE:
! if (!superuser())
! ereport(ERROR,
! (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
! errmsg("must be superuser to comment on text search template")));
break;
case OBJECT_TSCONFIGURATION:
! 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,559 **** 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.
*/
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:
--- 562,578 ----
}
/*
! * Check whether the user is allowed to comment on this relation.
*/
static void
! CheckRelationComment(int objtype, Relation relation)
{
/* 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,632 **** 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
*/
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));
--- 604,618 ----
RelationGetRelationName(relation))));
break;
}
}
/*
! * Check whether the user is allowed to comment on an attribute of the
! * specified relation.
*/
static void
! CheckAttributeComment(Relation relation)
{
if (!pg_class_ownercheck(RelationGetRelid(relation), GetUserId()))
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS,
RelationGetRelationName(relation));
***************
*** 645,1257 **** 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.
- */
- 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)
{
TypeName *sourcetype;
TypeName *targettype;
Oid sourcetypeid;
Oid targettypeid;
- Oid castOid;
Assert(list_length(qualname) == 1);
sourcetype = (TypeName *) linitial(qualname);
--- 631,648 ----
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table, view, or composite type",
RelationGetRelationName(relation))));
}
/*
! * Check whether the user is allowed to comment on the specified cast.
*/
static void
! CheckCastComment(List *qualname, List *arguments)
{
TypeName *sourcetype;
TypeName *targettype;
Oid sourcetypeid;
Oid targettypeid;
Assert(list_length(qualname) == 1);
sourcetype = (TypeName *) linitial(qualname);
***************
*** 1263,1271 **** 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()))
--- 654,659 ----
***************
*** 1274,1338 **** 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);
}
--- 662,665 ----
*** a/src/backend/parser/Makefile
--- b/src/backend/parser/Makefile
***************
*** 14,21 **** override CPPFLAGS := -I. -I$(srcdir) $(CPPFLAGS)
OBJS= analyze.o gram.o keywords.o kwlookup.o parser.o \
parse_agg.o parse_clause.o parse_coerce.o parse_cte.o parse_expr.o \
! parse_func.o parse_node.o parse_oper.o parse_param.o parse_relation.o \
! parse_target.o parse_type.o parse_utilcmd.o scansup.o
FLEXFLAGS = -CF
--- 14,21 ----
OBJS= analyze.o gram.o keywords.o kwlookup.o parser.o \
parse_agg.o parse_clause.o parse_coerce.o parse_cte.o parse_expr.o \
! parse_func.o parse_node.o parse_oper.o parse_param.o \
! parse_relation.o parse_target.o parse_type.o parse_utilcmd.o scansup.o
FLEXFLAGS = -CF
*** a/src/include/catalog/dependency.h
--- b/src/include/catalog/dependency.h
***************
*** 15,20 ****
--- 15,21 ----
#define DEPENDENCY_H
#include "nodes/parsenodes.h" /* for DropBehavior */
+ #include "catalog/objectaddress.h"
/*
***************
*** 100,116 **** 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;
--- 101,106 ----
*** /dev/null
--- b/src/include/catalog/objectaddress.h
***************
*** 0 ****
--- 1,34 ----
+ /*-------------------------------------------------------------------------
+ *
+ * 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$
+ *
+ *-------------------------------------------------------------------------
+ */
+ #ifndef OBJECTADDRESS_H
+ #define OBJECTADDRESS_H
+
+ #include "nodes/parsenodes.h"
+ #include "nodes/pg_list.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 */
Excerpts from Robert Haas's message of mié ago 18 21:32:48 -0400 2010:
Here's v3.
The header comment in objectaddress.c contains a funny mistake: it says
it works with ObjectAddresses. However, ObjectAddresses is a different
type altogether, so I recommend not using that as plural for
ObjectAddress. Maybe "ObjectAddress objects"? :-D
--
Álvaro Herrera <alvherre@commandprompt.com>
The PostgreSQL Company - Command Prompt, Inc.
PostgreSQL Replication, Consulting, Custom Development, 24x7 support
Alvaro Herrera <alvherre@commandprompt.com> writes:
Excerpts from Robert Haas's message of mié ago 18 21:32:48 -0400 2010:
Here's v3.
The header comment in objectaddress.c contains a funny mistake: it says
it works with ObjectAddresses. However, ObjectAddresses is a different
type altogether, so I recommend not using that as plural for
ObjectAddress. Maybe "ObjectAddress objects"? :-D
Alternatively, maybe ObjectAddresses was a bad choice of type name,
and it should be ObjectAddressList or ObjectAddressArray or some such.
But changing that might be more trouble than it's worth.
regards, tom lane
On Thu, Aug 19, 2010 at 11:57 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Alvaro Herrera <alvherre@commandprompt.com> writes:
Excerpts from Robert Haas's message of mié ago 18 21:32:48 -0400 2010:
Here's v3.
The header comment in objectaddress.c contains a funny mistake: it says
it works with ObjectAddresses. However, ObjectAddresses is a different
type altogether, so I recommend not using that as plural for
ObjectAddress. Maybe "ObjectAddress objects"? :-DAlternatively, maybe ObjectAddresses was a bad choice of type name,
and it should be ObjectAddressList or ObjectAddressArray or some such.
But changing that might be more trouble than it's worth.
Yeah, I think it was a bad choice of type name. If I were otherwise
touching that code, I'd probably advocate for changing it, but since
I'm not, I'm inclined to just reword the comment. It might be
something to keep in mind if we ever overhaul that part of the system,
though, since at that point anything that must be back-patched will
have merge conflicts anyway.
Any other kibitzing before I commit this?
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise Postgres Company
Robert Haas <robertmhaas@gmail.com> writes:
Any other kibitzing before I commit this?
Sure ...
+ * If the object is a relation or a child object of a relation (e.g. an
+ * attribute or contraint, *relp will set to point to that relation). This
Parenthesis in the wrong place here, grammar and spelling not much better.
Also, I still feel that this comment could do better about explaining the
behavior, particularly with respect to locking. Perhaps say
+ * If the target object is a relation or a child object of a relation
+ * (e.g. an attribute or constraint), 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.
+ ScanKeyInit(&skey[0], ObjectIdAttributeNumber, BTEqualStrategyNumber,
+ F_OIDEQ, ObjectIdGetDatum(address.objectId));
There's a standard convention for the layout of ScanKeyInit calls, and
this isn't it. Trivial, I know, but it's better to make similar code
look similar.
There's no longer any need for a diff in src/backend/parser/Makefile.
+ #define OBJECTADDRESS_H
+
+ #include "nodes/parsenodes.h"
+ #include "nodes/pg_list.h"
+ #include "storage/lock.h"
+ #include "utils/rel.h"
You shouldn't need pg_list.h here, as parsenodes.h surely includes it.
regards, tom lane
On Thu, Aug 19, 2010 at 3:34 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Robert Haas <robertmhaas@gmail.com> writes:
Any other kibitzing before I commit this?
Sure ...
[kibitzing]
v4.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise Postgres Company
Attachments:
refactor_comment-v4.patchapplication/octet-stream; name=refactor_comment-v4.patchDownload
*** a/src/backend/catalog/Makefile
--- b/src/backend/catalog/Makefile
***************
*** 11,19 **** 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
BKIFILES = postgres.bki postgres.description postgres.shdescription
--- 11,20 ----
include $(top_builddir)/src/Makefile.global
OBJS = catalog.o dependency.o heap.o index.o indexing.o namespace.o aclchk.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
*** /dev/null
--- b/src/backend/catalog/objectaddress.c
***************
*** 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$
+ *
+ *-------------------------------------------------------------------------
+ */
+
+ #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;
+ }
*** a/src/backend/commands/comment.c
--- b/src/backend/commands/comment.c
***************
*** 17,99 ****
#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/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.
*/
!
! 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);
/*
--- 17,46 ----
#include "access/genam.h"
#include "access/heapam.h"
#include "catalog/indexing.h"
! #include "catalog/objectaddress.h"
#include "catalog/pg_description.h"
#include "catalog/pg_shdescription.h"
#include "commands/comment.h"
#include "commands/dbcommands.h"
#include "libpq/be-fsstubs.h"
#include "miscadmin.h"
#include "parser/parse_func.h"
#include "parser/parse_type.h"
#include "utils/acl.h"
#include "utils/builtins.h"
#include "utils/fmgroids.h"
#include "utils/rel.h"
#include "utils/tqual.h"
/*
! * 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 CheckRelationComment(int objtype, Relation relation);
! static void CheckAttributeComment(Relation relation);
! static void CheckCastComment(List *qualname, List *arguments);
/*
***************
*** 105,188 **** static void CommentTSConfiguration(List *qualname, char *comment);
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_OPFAMILY:
! CommentOpFamily(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;
case OBJECT_TABLESPACE:
! CommentTablespace(stmt->objname, stmt->comment);
break;
case OBJECT_ROLE:
! CommentRole(stmt->objname, stmt->comment);
break;
case OBJECT_TSPARSER:
! CommentTSParser(stmt->objname, stmt->comment);
break;
case OBJECT_TSDICTIONARY:
! CommentTSDictionary(stmt->objname, stmt->comment);
break;
case OBJECT_TSTEMPLATE:
! CommentTSTemplate(stmt->objname, stmt->comment);
break;
case OBJECT_TSCONFIGURATION:
! CommentTSConfiguration(stmt->objname, stmt->comment);
break;
default:
elog(ERROR, "unrecognized object type: %d",
(int) stmt->objtype);
}
}
/*
--- 52,226 ----
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:
! CheckRelationComment(stmt->objtype, relation);
break;
case OBJECT_COLUMN:
! CheckAttributeComment(relation);
break;
case OBJECT_DATABASE:
! if (!pg_database_ownercheck(address.objectId, GetUserId()))
! aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_DATABASE,
! strVal(linitial(stmt->objname)));
break;
case OBJECT_TYPE:
! if (!pg_type_ownercheck(address.objectId, GetUserId()))
! aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TYPE,
! format_type_be(address.objectId));
break;
case OBJECT_AGGREGATE:
case OBJECT_FUNCTION:
! if (!pg_proc_ownercheck(address.objectId, GetUserId()))
! aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_PROC,
! NameListToString(stmt->objname));
break;
case OBJECT_OPERATOR:
! 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:
! case OBJECT_CONSTRAINT:
! if (!pg_class_ownercheck(RelationGetRelid(relation), GetUserId()))
! aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS,
! RelationGetRelationName(relation));
break;
case OBJECT_SCHEMA:
! if (!pg_namespace_ownercheck(address.objectId, GetUserId()))
! aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_NAMESPACE,
! strVal(linitial(stmt->objname)));
break;
case OBJECT_CONVERSION:
! if (!pg_conversion_ownercheck(address.objectId, GetUserId()))
! aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CONVERSION,
! NameListToString(stmt->objname));
break;
case OBJECT_LANGUAGE:
! if (!superuser())
! ereport(ERROR,
! (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
! errmsg("must be superuser to comment on procedural language")));
break;
case OBJECT_OPCLASS:
! if (!pg_opclass_ownercheck(address.objectId, GetUserId()))
! aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_OPCLASS,
! NameListToString(stmt->objname));
break;
case OBJECT_OPFAMILY:
! if (!pg_opfamily_ownercheck(address.objectId, GetUserId()))
! aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_OPFAMILY,
! NameListToString(stmt->objname));
break;
case OBJECT_LARGEOBJECT:
! 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:
! CheckCastComment(stmt->objname, stmt->objargs);
break;
case OBJECT_TABLESPACE:
! if (!pg_tablespace_ownercheck(address.objectId, GetUserId()))
! aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TABLESPACE,
! strVal(linitial(stmt->objname)));
break;
case OBJECT_ROLE:
! 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:
! if (!superuser())
! ereport(ERROR,
! (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
! errmsg("must be superuser to comment on text search parser")));
break;
case OBJECT_TSDICTIONARY:
! if (!pg_ts_dict_ownercheck(address.objectId, GetUserId()))
! aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TSDICTIONARY,
! NameListToString(stmt->objname));
break;
case OBJECT_TSTEMPLATE:
! if (!superuser())
! ereport(ERROR,
! (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
! errmsg("must be superuser to comment on text search template")));
break;
case OBJECT_TSCONFIGURATION:
! 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,559 **** 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.
*/
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:
--- 562,578 ----
}
/*
! * Check whether the user is allowed to comment on this relation.
*/
static void
! CheckRelationComment(int objtype, Relation relation)
{
/* 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,632 **** 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
*/
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));
--- 604,618 ----
RelationGetRelationName(relation))));
break;
}
}
/*
! * Check whether the user is allowed to comment on an attribute of the
! * specified relation.
*/
static void
! CheckAttributeComment(Relation relation)
{
if (!pg_class_ownercheck(RelationGetRelid(relation), GetUserId()))
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS,
RelationGetRelationName(relation));
***************
*** 645,1257 **** 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.
- */
- 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)
{
TypeName *sourcetype;
TypeName *targettype;
Oid sourcetypeid;
Oid targettypeid;
- Oid castOid;
Assert(list_length(qualname) == 1);
sourcetype = (TypeName *) linitial(qualname);
--- 631,648 ----
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table, view, or composite type",
RelationGetRelationName(relation))));
}
/*
! * Check whether the user is allowed to comment on the specified cast.
*/
static void
! CheckCastComment(List *qualname, List *arguments)
{
TypeName *sourcetype;
TypeName *targettype;
Oid sourcetypeid;
Oid targettypeid;
Assert(list_length(qualname) == 1);
sourcetype = (TypeName *) linitial(qualname);
***************
*** 1263,1271 **** 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()))
--- 654,659 ----
***************
*** 1274,1338 **** 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);
}
--- 662,665 ----
*** a/src/include/catalog/dependency.h
--- b/src/include/catalog/dependency.h
***************
*** 15,20 ****
--- 15,21 ----
#define DEPENDENCY_H
#include "nodes/parsenodes.h" /* for DropBehavior */
+ #include "catalog/objectaddress.h"
/*
***************
*** 100,116 **** 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;
--- 101,106 ----
*** /dev/null
--- b/src/include/catalog/objectaddress.h
***************
*** 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$
+ *
+ *-------------------------------------------------------------------------
+ */
+ #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 */
Robert Haas <robertmhaas@gmail.com> writes:
On Thu, Aug 19, 2010 at 3:34 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Robert Haas <robertmhaas@gmail.com> writes:
Any other kibitzing before I commit this?
Sure ...
[kibitzing]
v4.
For my money, you could just have said "Applied with suggested changes".
They were pretty darn trivial.
regards, tom lane
On Fri, Aug 27, 2010 at 12:52 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Robert Haas <robertmhaas@gmail.com> writes:
On Thu, Aug 19, 2010 at 3:34 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Robert Haas <robertmhaas@gmail.com> writes:
Any other kibitzing before I commit this?
Sure ...
[kibitzing]
v4.
For my money, you could just have said "Applied with suggested changes".
They were pretty darn trivial.
Boy, tough to please. Complaints if you commit it too soon,
complaints if you don't commit it soon enough.
Anyway, committed.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise Postgres Company
On fre, 2010-08-27 at 07:49 -0400, Robert Haas wrote:
Anyway, committed.
I suppose this is responsible for this:
gcc -O2 -Wall -Wmissing-prototypes -Wpointer-arith
-Wdeclaration-after-statement -Wendif-labels -fno-strict-aliasing
-fwrapv -g -Werror -Wno-inline -I../../../src/include -D_GNU_SOURCE
-I/usr/include/libxml2 -c -o objectaddress.o objectaddress.c -MMD -MP
-MF .deps/objectaddress.Po
cc1: warnings being treated as errors
objectaddress.c: In function ‘get_object_address’:
objectaddress.c:102: error: ‘address.objectId’ may be used uninitialized
in this function
objectaddress.c:102: error: ‘address.classId’ may be used uninitialized
in this function
make[1]: *** [objectaddress.o] Error 1
On Fri, Aug 27, 2010 at 4:16 PM, Peter Eisentraut <peter_e@gmx.net> wrote:
On fre, 2010-08-27 at 07:49 -0400, Robert Haas wrote:
Anyway, committed.
I suppose this is responsible for this:
gcc -O2 -Wall -Wmissing-prototypes -Wpointer-arith
-Wdeclaration-after-statement -Wendif-labels -fno-strict-aliasing
-fwrapv -g -Werror -Wno-inline -I../../../src/include -D_GNU_SOURCE
-I/usr/include/libxml2 -c -o objectaddress.o objectaddress.c -MMD -MP
-MF .deps/objectaddress.Po
cc1: warnings being treated as errors
objectaddress.c: In function ‘get_object_address’:
objectaddress.c:102: error: ‘address.objectId’ may be used uninitialized
in this function
objectaddress.c:102: error: ‘address.classId’ may be used uninitialized
in this function
make[1]: *** [objectaddress.o] Error 1
I suppose this is unhappy because it things elog(ERROR) might return?
For whatever reason, I don't get this on my machine. But I'll try
plugging that hole and perhaps that will fix it.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise Postgres Company
Robert Haas <robertmhaas@gmail.com> wrote:
I suppose this is unhappy because it things elog(ERROR) might
return?
It looks more like this code uses it without initialization:
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;
-Kevin
On Fri, Aug 27, 2010 at 5:35 PM, Kevin Grittner
<Kevin.Grittner@wicourts.gov> wrote:
Robert Haas <robertmhaas@gmail.com> wrote:
I suppose this is unhappy because it things elog(ERROR) might
return?It looks more like this code uses it without initialization:
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;
OK, I must be missing something. Go through that a little slower?
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise Postgres Company
Robert Haas <robertmhaas@gmail.com> wrote:
OK, I must be missing something. Go through that a little slower?
No, my mistake. Please ignore.
I am getting the same warnings, but I think your take on the cause
must be right.
-Kevin
Robert Haas <robertmhaas@gmail.com> wrote:
I suppose this is unhappy because it things elog(ERROR) might
return?
If I set these fields right in front of the elog(ERROR) the warnings
go away for me. (Hopefully this observation will make up, to some
extent, for my earlier brain fart.)
-Kevin
On Fri, Aug 27, 2010 at 5:57 PM, Kevin Grittner
<Kevin.Grittner@wicourts.gov> wrote:
Robert Haas <robertmhaas@gmail.com> wrote:
I suppose this is unhappy because it things elog(ERROR) might
return?If I set these fields right in front of the elog(ERROR) the warnings
go away for me. (Hopefully this observation will make up, to some
extent, for my earlier brain fart.)
I set them right after the ERROR so that those statements needn't
actually be executed.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise Postgres Company
Robert Haas <robertmhaas@gmail.com> wrote:
I set them right after the ERROR so that those statements needn't
actually be executed.
Good thinking.
I checked and that also eliminates the warnings for me. (No
surprise there, of course.)
-Kevin
Excerpts from Robert Haas's message of vie ago 27 18:07:55 -0400 2010:
On Fri, Aug 27, 2010 at 5:57 PM, Kevin Grittner
<Kevin.Grittner@wicourts.gov> wrote:Robert Haas <robertmhaas@gmail.com> wrote:
I suppose this is unhappy because it things elog(ERROR) might
return?If I set these fields right in front of the elog(ERROR) the warnings
go away for me. (Hopefully this observation will make up, to some
extent, for my earlier brain fart.)I set them right after the ERROR so that those statements needn't
actually be executed.
Didn't we inject some smarts so that the compiler would notice that
elog(ERROR) doesn't return? Why isn't it working here?
--
Álvaro Herrera <alvherre@commandprompt.com>
The PostgreSQL Company - Command Prompt, Inc.
PostgreSQL Replication, Consulting, Custom Development, 24x7 support
Alvaro Herrera <alvherre@commandprompt.com> writes:
Didn't we inject some smarts so that the compiler would notice that
elog(ERROR) doesn't return?
No. If you know a portable (as in "works on every compiler") way
to do that, we could talk. If only some compilers understand it,
we'll probably end up worse off --- the ones that don't understand it
will still need things like these unreachable assignments, while the
ones that do understand will start warning about unreachable code.
regards, tom lane
On Fri, Aug 27, 2010 at 9:35 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Alvaro Herrera <alvherre@commandprompt.com> writes:
Didn't we inject some smarts so that the compiler would notice that
elog(ERROR) doesn't return?No. If you know a portable (as in "works on every compiler") way
to do that, we could talk. If only some compilers understand it,
we'll probably end up worse off --- the ones that don't understand it
will still need things like these unreachable assignments, while the
ones that do understand will start warning about unreachable code.
What's a bit odd about this is that I do get warnings for this kind of
thing in general, which get turned into errors since I compile with
-Werror; and in fact the version of the patch that I committed has no
fewer than four places where there is a comment that says "placate
compiler". But for some reason the compiler I used to develop this
patch (gcc-4.2.1 i686-apple-darwin10) did not complain about this
case, for reasons that are not quite clear to me.
Which I guess also goes to your point.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise Postgres Company
Robert Haas <robertmhaas@gmail.com> writes:
What's a bit odd about this is that I do get warnings for this kind of
thing in general, which get turned into errors since I compile with
-Werror; and in fact the version of the patch that I committed has no
fewer than four places where there is a comment that says "placate
compiler". But for some reason the compiler I used to develop this
patch (gcc-4.2.1 i686-apple-darwin10) did not complain about this
case, for reasons that are not quite clear to me.
gcc has been able to detect possibly-uninitialized scalar variables
for many years, but only fairly-recent versions seem to apply the
same type of logic to fields of local structs. I've also noticed
that sometimes it can only spot the potential problem after inlining
a function that sets the local variable, and so a more recent version
and/or a more aggressive -O setting can also affect whether you get
a warning. In short: this warning is a lot more context sensitive
than you might guess.
regards, tom lane
On Fri, Aug 27, 2010 at 9:49 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Robert Haas <robertmhaas@gmail.com> writes:
What's a bit odd about this is that I do get warnings for this kind of
thing in general, which get turned into errors since I compile with
-Werror; and in fact the version of the patch that I committed has no
fewer than four places where there is a comment that says "placate
compiler". But for some reason the compiler I used to develop this
patch (gcc-4.2.1 i686-apple-darwin10) did not complain about this
case, for reasons that are not quite clear to me.gcc has been able to detect possibly-uninitialized scalar variables
for many years, but only fairly-recent versions seem to apply the
same type of logic to fields of local structs.
I thought it might be something like that.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise Postgres Company
On Fri, Aug 27, 2010 at 09:35:55PM -0400, Tom Lane wrote:
Alvaro Herrera <alvherre@commandprompt.com> writes:
Didn't we inject some smarts so that the compiler would notice that
elog(ERROR) doesn't return?No. If you know a portable (as in "works on every compiler") way
to do that, we could talk. If only some compilers understand it,
we'll probably end up worse off --- the ones that don't understand it
will still need things like these unreachable assignments, while the
ones that do understand will start warning about unreachable code.
We've been here before:
http://www.mail-archive.com/pgsql-hackers@postgresql.org/msg72113.html
The problem appears to be mainly avoiding double evaluation, as
otherwise it's just some macro tweaking. On gcc you can avoid the
double evaluation also, but it's other compilers which we also need to
support.
If we really wanted to we could arrange for GCC to throw an error if
the first argument to elog was non-constant, which would prevent bugs
creeping in due to double evaluation. That still won't help users of
other compilers though.
Have a nice day,
--
Martijn van Oosterhout <kleptog@svana.org> http://svana.org/kleptog/
Show quoted text
Patriotism is when love of your own people comes first; nationalism,
when hate for people other than your own comes first.
- Charles de Gaulle