On Mon, Dec 27, 2010 at 2:06 PM, Robert Haas <robertmh...@gmail.com> wrote:
> The problem is that alter table actions AT_AddIndex and
> AT_AddConstraint don't tie neatly back to a particular piece of
> syntax.  The message as written isn't incomprehensible (especially if
> you're reading it in English) but it definitely leaves something to be
> desired.
>
> Ideas?

Here's a somewhat more complete patch implementing this concept, plus
adding additional messages for non-support of constraints, rules, and
triggers.  More could be done in this vein, but this picks a decent
fraction of the low-hanging fruit.

I've had to change some of the heap_open(rv) calls to
relation_open(rv) to avoid having the former throw the wrong error
message before the latter kicks in.  I think there might be stylistic
objections to that, but I'm not sure what else to propose.  I'm
actually pretty suspicious that many of the heap_open(rv) calls I
*didn't* change are either already a little iffy or likely to become
so once the SQL/MED stuff for foreign tables goes in.  They make it
easy to forget that we've got a whole pile of relkinds and you
actually need to really think about which ones you can handle.

For example, on unpatched head:

rhaas=# create view v as select 1 as a;
CREATE VIEW
rhaas=# cluster v;
ERROR:  there is no previously clustered index for table "v"

The error message is demonstrably correct in the sense that, first,
there isn't any table v, only a view v, so surely table v has no
clustered index - or anything else; and second, even if we construe
table "v" to mean view "v", it is certainly right to say it has no
clustered index because it does not - and can not - have any indexes
at all.  But as undeniably true as that error message is, it's a bad
error message.  With the patch:

rhaas=# cluster v;
ERROR:  views do not support CLUSTER

That's more like it.

-- 
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 249067f..1555b61 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,7 +113,9 @@ cluster(ClusterStmt *stmt, bool isTopLevel)
 		Relation	rel;
 
 		/* Find and lock the table */
-		rel = heap_openrv(stmt->relation, AccessExclusiveLock);
+		rel = relation_openrv(stmt->relation, AccessExclusiveLock);
+		if (rel->rd_rel->relkind != RELKIND_RELATION)
+			ErrorWrongRelkind(rel, WRONG_RELKIND_FOR_COMMAND, "CLUSTER");
 
 		tableOid = RelationGetRelid(rel);
 
diff --git a/src/backend/commands/comment.c b/src/backend/commands/comment.c
index b578818..66df9f8 100644
--- a/src/backend/commands/comment.c
+++ b/src/backend/commands/comment.c
@@ -22,6 +22,7 @@
 #include "catalog/pg_shdescription.h"
 #include "commands/comment.h"
 #include "commands/dbcommands.h"
+#include "commands/tablecmds.h"
 #include "libpq/be-fsstubs.h"
 #include "miscadmin.h"
 #include "parser/parse_func.h"
@@ -583,10 +584,8 @@ CheckAttributeComment(Relation relation)
 	if (relation->rd_rel->relkind != RELKIND_RELATION &&
 		relation->rd_rel->relkind != RELKIND_VIEW &&
 		relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE)
-		ereport(ERROR,
-				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-				 errmsg("\"%s\" is not a table, view, or composite type",
-						RelationGetRelationName(relation))));
+		ErrorWrongRelkind(relation, WRONG_RELKIND_FOR_COMMAND,
+							   "COMMENT ON COLUMN");
 }
 
 /*
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 7b8bee8..488cc80 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -27,6 +27,7 @@
 #include "catalog/pg_type.h"
 #include "commands/copy.h"
 #include "commands/defrem.h"
+#include "commands/tablecmds.h"
 #include "commands/trigger.h"
 #include "executor/executor.h"
 #include "libpq/libpq.h"
@@ -998,8 +999,19 @@ DoCopy(const CopyStmt *stmt, const char *queryString)
 		cstate->queryDesc = NULL;
 
 		/* Open and lock the relation, using the appropriate lock type. */
-		cstate->rel = heap_openrv(stmt->relation,
+		cstate->rel = relation_openrv(stmt->relation,
 							 (is_from ? RowExclusiveLock : AccessShareLock));
+		if (cstate->rel->rd_rel->relkind != RELKIND_RELATION)
+		{
+			if (!is_from && cstate->rel->rd_rel->relkind == RELKIND_VIEW)
+				ereport(ERROR,
+						(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+						 errmsg("views do not support COPY TO"),
+						 errhint("Try the COPY (SELECT ...) TO variant.")));
+			else
+				ErrorWrongRelkind(cstate->rel, WRONG_RELKIND_FOR_COMMAND,
+								  is_from ? "COPY FROM" : "COPY TO");
+		}
 
 		tupDesc = RelationGetDescr(cstate->rel);
 
@@ -1225,29 +1237,6 @@ DoCopyTo(CopyState cstate)
 {
 	bool		pipe = (cstate->filename == NULL);
 
-	if (cstate->rel)
-	{
-		if (cstate->rel->rd_rel->relkind != RELKIND_RELATION)
-		{
-			if (cstate->rel->rd_rel->relkind == RELKIND_VIEW)
-				ereport(ERROR,
-						(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-						 errmsg("cannot copy from view \"%s\"",
-								RelationGetRelationName(cstate->rel)),
-						 errhint("Try the COPY (SELECT ...) TO variant.")));
-			else if (cstate->rel->rd_rel->relkind == RELKIND_SEQUENCE)
-				ereport(ERROR,
-						(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-						 errmsg("cannot copy from sequence \"%s\"",
-								RelationGetRelationName(cstate->rel))));
-			else
-				ereport(ERROR,
-						(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-						 errmsg("cannot copy from non-table relation \"%s\"",
-								RelationGetRelationName(cstate->rel))));
-		}
-	}
-
 	if (pipe)
 	{
 		if (whereToSendOutput == DestRemote)
@@ -1701,25 +1690,6 @@ CopyFrom(CopyState cstate)
 
 	Assert(cstate->rel);
 
-	if (cstate->rel->rd_rel->relkind != RELKIND_RELATION)
-	{
-		if (cstate->rel->rd_rel->relkind == RELKIND_VIEW)
-			ereport(ERROR,
-					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-					 errmsg("cannot copy to view \"%s\"",
-							RelationGetRelationName(cstate->rel))));
-		else if (cstate->rel->rd_rel->relkind == RELKIND_SEQUENCE)
-			ereport(ERROR,
-					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-					 errmsg("cannot copy to sequence \"%s\"",
-							RelationGetRelationName(cstate->rel))));
-		else
-			ereport(ERROR,
-					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-					 errmsg("cannot copy to non-table relation \"%s\"",
-							RelationGetRelationName(cstate->rel))));
-	}
-
 	/*----------
 	 * Check to see if we can avoid writing WAL
 	 *
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index 762bbae..5c7bf29 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -366,10 +366,7 @@ CheckAttributeSecLabel(Relation relation)
 	if (relation->rd_rel->relkind != RELKIND_RELATION &&
 		relation->rd_rel->relkind != RELKIND_VIEW &&
 		relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE)
-		ereport(ERROR,
-				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-				 errmsg("\"%s\" is not a table, view, or composite type",
-						RelationGetRelationName(relation))));
+		ErrorWrongRelkind(relation, "SECURITY LABEL ON COLUMN");
 }
 
 void
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 6729d83..80017e8 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -222,6 +222,65 @@ static const struct dropmsgstrings dropmsgstringarray[] = {
 	{'\0', 0, NULL, NULL, NULL, NULL}
 };
 
+/*
+ * Error-reporting support for wrong-object type errors
+ */
+struct wrongtypestrings
+{
+	char		kind;
+	const char *command_message;
+	const char *constraint_message;
+	const char *trigger_message;
+	const char *rule_message;
+};
+
+static const struct wrongtypestrings wrongtypestringarray[] = {
+	{RELKIND_RELATION,
+		/* translator: %s is an SQL command */
+		gettext_noop("tables do not support %s"),
+		NULL,		/* constraints are supported, so no message */
+		NULL,		/* rules are supported, so no message */
+		NULL},		/* triggers are supported, so no message */
+	{RELKIND_SEQUENCE,
+		/* translator: %s is an SQL command */
+		gettext_noop("sequences do not support %s"),
+		gettext_noop("sequences do not support constraints"),
+		gettext_noop("sequences do not support rules"),
+		gettext_noop("sequences do not support triggers")},
+	{RELKIND_VIEW,
+		/* translator: %s is an SQL command */
+		gettext_noop("views do not support %s"),
+		gettext_noop("views do not support constraints"),
+		NULL,		/* rules are supported, so no message */
+		NULL},		/* triggers are supported, so no message */
+	{RELKIND_INDEX,
+		/* translator: %s is an SQL command */
+		gettext_noop("indexes do not support %s"),
+		gettext_noop("indexes do not support constraints"),
+		gettext_noop("indexes do not support rules"),
+		gettext_noop("indexes do not support triggers")},
+	{RELKIND_COMPOSITE_TYPE,
+		/* translator: %s is an SQL command */
+		gettext_noop("composite types do not support %s"),
+		gettext_noop("composite types do not support constraints"),
+		gettext_noop("composite types do not support rules"),
+		gettext_noop("composite types do not support triggers")},
+	{RELKIND_TOASTVALUE,
+		/* translator: %s is an SQL command */
+		gettext_noop("TOAST tables do not support %s"),
+		gettext_noop("TOAST tables do not support constraints"),
+		gettext_noop("TOAST tables do not support rules"),
+		gettext_noop("TOAST tables do not support triggers")},
+	{'\0', NULL, NULL, NULL}
+};
+
+/* Convenience strings for ATSimplePermissions */
+const char rk_relation[2] = { RELKIND_RELATION, '\0' };
+const char rk_view[2] = { RELKIND_VIEW, '\0' };
+const char rk_relation_view[3] = { RELKIND_RELATION, RELKIND_VIEW, '\0' };
+const char rk_relation_index[3] = { RELKIND_RELATION, RELKIND_INDEX, '\0' };
+const char rk_relation_type[3] =
+	{ RELKIND_RELATION, RELKIND_COMPOSITE_TYPE, '\0' };
 
 static void truncate_check_rel(Relation rel);
 static List *MergeAttributes(List *schema, List *supers, char relpersistence,
@@ -264,8 +323,8 @@ static void ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
 static void ATRewriteTables(List **wqueue, LOCKMODE lockmode);
 static void ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode);
 static AlteredTableInfo *ATGetQueueEntry(List **wqueue, Relation rel);
-static void ATSimplePermissions(Relation rel, bool allowView, bool allowType);
-static void ATSimplePermissionsRelationOrIndex(Relation rel);
+static void ATSimplePermissions(Relation rel, const char *relkinds,
+					WrongRelkindDetail detail, const char *command);
 static void ATSimpleRecursion(List **wqueue, Relation rel,
 				  AlterTableCmd *cmd, bool recurse, LOCKMODE lockmode);
 static void ATOneLevelRecursion(List **wqueue, Relation rel,
@@ -841,7 +900,7 @@ ExecuteTruncate(TruncateStmt *stmt)
 		bool		recurse = interpretInhOption(rv->inhOpt);
 		Oid			myrelid;
 
-		rel = heap_openrv(rv, AccessExclusiveLock);
+		rel = relation_openrv(rv, AccessExclusiveLock);
 		myrelid = RelationGetRelid(rel);
 		/* don't throw error for "TRUNCATE foo, foo" */
 		if (list_member_oid(relids, myrelid))
@@ -868,7 +927,7 @@ ExecuteTruncate(TruncateStmt *stmt)
 					continue;
 
 				/* find_all_inheritors already got lock */
-				rel = heap_open(childrelid, NoLock);
+				rel = relation_open(childrelid, NoLock);
 				truncate_check_rel(rel);
 				rels = lappend(rels, rel);
 				relids = lappend_oid(relids, childrelid);
@@ -899,7 +958,7 @@ ExecuteTruncate(TruncateStmt *stmt)
 				Oid			relid = lfirst_oid(cell);
 				Relation	rel;
 
-				rel = heap_open(relid, AccessExclusiveLock);
+				rel = relation_open(relid, AccessExclusiveLock);
 				ereport(NOTICE,
 						(errmsg("truncate cascades to table \"%s\"",
 								RelationGetRelationName(rel))));
@@ -1096,10 +1155,7 @@ truncate_check_rel(Relation rel)
 
 	/* Only allow truncate on regular tables */
 	if (rel->rd_rel->relkind != RELKIND_RELATION)
-		ereport(ERROR,
-				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-				 errmsg("\"%s\" is not a table",
-						RelationGetRelationName(rel))));
+		ErrorWrongRelkind(rel, WRONG_RELKIND_FOR_COMMAND, "TRUNCATE");
 
 	/* Permissions checks */
 	aclresult = pg_class_aclcheck(RelationGetRelid(rel), GetUserId(),
@@ -1994,8 +2050,7 @@ renameatt_internal(Oid myrelid,
 		relkind != RELKIND_INDEX)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-			   errmsg("\"%s\" is not a table, view, composite type or index",
-					  RelationGetRelationName(targetrelation))));
+				 errmsg("system-generated column names may not be altered")));
 
 	/*
 	 * permissions checking.  only the owner of a class can change its schema.
@@ -2684,14 +2739,17 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
 	switch (cmd->subtype)
 	{
 		case AT_AddColumn:		/* ADD COLUMN */
-			ATSimplePermissions(rel, false, true);
+			ATSimplePermissions(rel, rk_relation_type,
+								WRONG_RELKIND_FOR_COMMAND, "ADD COLUMN");
 			/* Performs own recursion */
 			ATPrepAddColumn(wqueue, rel, recurse, recursing, cmd, lockmode);
 			pass = AT_PASS_ADD_COL;
 			break;
 		case AT_AddColumnToView:		/* add column via CREATE OR REPLACE
 										 * VIEW */
-			ATSimplePermissions(rel, true, false);
+			ATSimplePermissions(rel, rk_view,
+								WRONG_RELKIND_FOR_COMMAND,
+								"ADD COLUMN");
 			/* Performs own recursion */
 			ATPrepAddColumn(wqueue, rel, recurse, recursing, cmd, lockmode);
 			pass = AT_PASS_ADD_COL;
@@ -2704,19 +2762,25 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
 			 * substitutes default values into INSERTs before it expands
 			 * rules.
 			 */
-			ATSimplePermissions(rel, true, false);
+			ATSimplePermissions(rel, rk_relation_view,
+								WRONG_RELKIND_FOR_COMMAND,
+								"ALTER COLUMN DEFAULT");
 			ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
 			/* No command-specific prep needed */
 			pass = cmd->def ? AT_PASS_ADD_CONSTR : AT_PASS_DROP;
 			break;
 		case AT_DropNotNull:	/* ALTER COLUMN DROP NOT NULL */
-			ATSimplePermissions(rel, false, false);
+			ATSimplePermissions(rel, rk_relation,
+								WRONG_RELKIND_FOR_COMMAND,
+								"DROP NOT NULL");
 			ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
 			/* No command-specific prep needed */
 			pass = AT_PASS_DROP;
 			break;
 		case AT_SetNotNull:		/* ALTER COLUMN SET NOT NULL */
-			ATSimplePermissions(rel, false, false);
+			ATSimplePermissions(rel, rk_relation,
+								WRONG_RELKIND_FOR_COMMAND,
+								"SET NOT NULL");
 			ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
 			/* No command-specific prep needed */
 			pass = AT_PASS_ADD_CONSTR;
@@ -2728,31 +2792,47 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
 			pass = AT_PASS_MISC;
 			break;
 		case AT_SetOptions:		/* ALTER COLUMN SET ( options ) */
+			/* This command never recurses */
+			ATSimplePermissions(rel, rk_relation_index,
+								WRONG_RELKIND_FOR_COMMAND,
+								"ALTER COLUMN SET (...)");
+			pass = AT_PASS_MISC;
+			break;
 		case AT_ResetOptions:	/* ALTER COLUMN RESET ( options ) */
-			ATSimplePermissionsRelationOrIndex(rel);
 			/* This command never recurses */
+			ATSimplePermissions(rel, rk_relation_index,
+								WRONG_RELKIND_FOR_COMMAND,
+								"ALTER COLUMN RESET (...)");
 			pass = AT_PASS_MISC;
 			break;
 		case AT_SetStorage:		/* ALTER COLUMN SET STORAGE */
-			ATSimplePermissions(rel, false, false);
+			ATSimplePermissions(rel, rk_relation,
+								WRONG_RELKIND_FOR_COMMAND,
+								"ALTER COLUMN SET STORAGE");
 			ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
 			/* No command-specific prep needed */
 			pass = AT_PASS_MISC;
 			break;
 		case AT_DropColumn:		/* DROP COLUMN */
-			ATSimplePermissions(rel, false, true);
+			ATSimplePermissions(rel, rk_relation_type,
+								WRONG_RELKIND_FOR_COMMAND,
+								"DROP COLUMN");
 			ATPrepDropColumn(wqueue, rel, recurse, recursing, cmd, lockmode);
 			/* Recursion occurs during execution phase */
 			pass = AT_PASS_DROP;
 			break;
 		case AT_AddIndex:		/* ADD INDEX */
-			ATSimplePermissions(rel, false, false);
+			ATSimplePermissions(rel, rk_relation,
+								WRONG_RELKIND_FOR_CONSTRAINTS,
+								NULL);
 			/* This command never recurses */
 			/* No command-specific prep needed */
 			pass = AT_PASS_ADD_INDEX;
 			break;
 		case AT_AddConstraint:	/* ADD CONSTRAINT */
-			ATSimplePermissions(rel, false, false);
+			ATSimplePermissions(rel, rk_relation,
+								WRONG_RELKIND_FOR_CONSTRAINTS,
+								NULL);
 			/* Recursion occurs during execution phase */
 			/* No command-specific prep needed except saving recurse flag */
 			if (recurse)
@@ -2760,7 +2840,9 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
 			pass = AT_PASS_ADD_CONSTR;
 			break;
 		case AT_DropConstraint:	/* DROP CONSTRAINT */
-			ATSimplePermissions(rel, false, false);
+			ATSimplePermissions(rel, rk_relation,
+								WRONG_RELKIND_FOR_CONSTRAINTS,
+								NULL);
 			/* Recursion occurs during execution phase */
 			/* No command-specific prep needed except saving recurse flag */
 			if (recurse)
@@ -2768,7 +2850,9 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
 			pass = AT_PASS_DROP;
 			break;
 		case AT_AlterColumnType:		/* ALTER COLUMN TYPE */
-			ATSimplePermissions(rel, false, true);
+			ATSimplePermissions(rel, rk_relation_type,
+								WRONG_RELKIND_FOR_COMMAND,
+								"ALTER COLUMN TYPE");
 			/* Performs own recursion */
 			ATPrepAlterColumnType(wqueue, tab, rel, recurse, recursing, cmd, lockmode);
 			pass = AT_PASS_ALTER_TYPE;
@@ -2779,21 +2863,34 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
 			pass = AT_PASS_MISC;
 			break;
 		case AT_ClusterOn:		/* CLUSTER ON */
+			ATSimplePermissions(rel, rk_relation,
+								WRONG_RELKIND_FOR_COMMAND,
+								"CLUSTER ON");
+			/* These commands never recurse */
+			/* No command-specific prep needed */
+			pass = AT_PASS_MISC;
+			break;
 		case AT_DropCluster:	/* SET WITHOUT CLUSTER */
-			ATSimplePermissions(rel, false, false);
+			ATSimplePermissions(rel, rk_relation,
+								WRONG_RELKIND_FOR_COMMAND,
+								"SET WITHOUT CLUSTER");
 			/* These commands never recurse */
 			/* No command-specific prep needed */
 			pass = AT_PASS_MISC;
 			break;
 		case AT_AddOids:		/* SET WITH OIDS */
-			ATSimplePermissions(rel, false, false);
+			ATSimplePermissions(rel, rk_relation,
+								WRONG_RELKIND_FOR_COMMAND,
+								"SET WITH OIDS");
 			/* Performs own recursion */
 			if (!rel->rd_rel->relhasoids || recursing)
 				ATPrepAddOids(wqueue, rel, recurse, cmd, lockmode);
 			pass = AT_PASS_ADD_COL;
 			break;
 		case AT_DropOids:		/* SET WITHOUT OIDS */
-			ATSimplePermissions(rel, false, false);
+			ATSimplePermissions(rel, rk_relation,
+								WRONG_RELKIND_FOR_COMMAND,
+								"SET WITHOUT OIDS");
 			/* Performs own recursion */
 			if (rel->rd_rel->relhasoids)
 			{
@@ -2807,20 +2904,32 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
 			pass = AT_PASS_DROP;
 			break;
 		case AT_SetTableSpace:	/* SET TABLESPACE */
-			ATSimplePermissionsRelationOrIndex(rel);
+			ATSimplePermissions(rel, rk_relation_index,
+								WRONG_RELKIND_FOR_COMMAND,
+								"SET TABLESPACE");
 			/* This command never recurses */
 			ATPrepSetTableSpace(tab, rel, cmd->name, lockmode);
 			pass = AT_PASS_MISC;	/* doesn't actually matter */
 			break;
 		case AT_SetRelOptions:	/* SET (...) */
+			ATSimplePermissions(rel, rk_relation_index,
+								WRONG_RELKIND_FOR_COMMAND,
+								"SET (...)");
+			/* This command never recurses */
+			/* No command-specific prep needed */
+			pass = AT_PASS_MISC;
 		case AT_ResetRelOptions:		/* RESET (...) */
-			ATSimplePermissionsRelationOrIndex(rel);
+			ATSimplePermissions(rel, rk_relation_index,
+								WRONG_RELKIND_FOR_COMMAND,
+								"RESET (...)");
 			/* This command never recurses */
 			/* No command-specific prep needed */
 			pass = AT_PASS_MISC;
 			break;
 		case AT_AddInherit:		/* INHERIT */
-			ATSimplePermissions(rel, false, false);
+			ATSimplePermissions(rel, rk_relation,
+								WRONG_RELKIND_FOR_COMMAND,
+								"INHERIT");
 			/* This command never recurses */
 			ATPrepAddInherit(rel);
 			pass = AT_PASS_MISC;
@@ -2833,12 +2942,28 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
 		case AT_DisableTrig:	/* DISABLE TRIGGER variants */
 		case AT_DisableTrigAll:
 		case AT_DisableTrigUser:
+			ATSimplePermissions(rel, rk_relation,
+								WRONG_RELKIND_FOR_TRIGGERS,
+								NULL);
+			/* These commands never recurse */
+			/* No command-specific prep needed */
+			pass = AT_PASS_MISC;
+			break;
 		case AT_EnableRule:		/* ENABLE/DISABLE RULE variants */
 		case AT_EnableAlwaysRule:
 		case AT_EnableReplicaRule:
 		case AT_DisableRule:
+			ATSimplePermissions(rel, rk_relation,
+								WRONG_RELKIND_FOR_RULES,
+								NULL);
+			/* These commands never recurse */
+			/* No command-specific prep needed */
+			pass = AT_PASS_MISC;
+			break;
 		case AT_DropInherit:	/* NO INHERIT */
-			ATSimplePermissions(rel, false, false);
+			ATSimplePermissions(rel, rk_relation,
+								WRONG_RELKIND_FOR_COMMAND,
+								"NO INHERIT");
 			/* These commands never recurse */
 			/* No command-specific prep needed */
 			pass = AT_PASS_MISC;
@@ -3559,66 +3684,16 @@ ATGetQueueEntry(List **wqueue, Relation rel)
 /*
  * ATSimplePermissions
  *
- * - Ensure that it is a relation (or possibly a view)
- * - Ensure this user is the owner
- * - Ensure that it is not a system table
- */
-static void
-ATSimplePermissions(Relation rel, bool allowView, bool allowType)
-{
-	if (rel->rd_rel->relkind != RELKIND_RELATION)
-	{
-		if (allowView)
-		{
-			if (rel->rd_rel->relkind != RELKIND_VIEW)
-				ereport(ERROR,
-						(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-						 errmsg("\"%s\" is not a table or view",
-								RelationGetRelationName(rel))));
-		}
-		else if (allowType)
-		{
-			if (rel->rd_rel->relkind != RELKIND_COMPOSITE_TYPE)
-				ereport(ERROR,
-						(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-						 errmsg("\"%s\" is not a table or composite type",
-								RelationGetRelationName(rel))));
-		}
-		else
-			ereport(ERROR,
-					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-					 errmsg("\"%s\" is not a table",
-							RelationGetRelationName(rel))));
-	}
-
-	/* Permissions checks */
-	if (!pg_class_ownercheck(RelationGetRelid(rel), GetUserId()))
-		aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS,
-					   RelationGetRelationName(rel));
-
-	if (!allowSystemTableMods && IsSystemRelation(rel))
-		ereport(ERROR,
-				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
-				 errmsg("permission denied: \"%s\" is a system catalog",
-						RelationGetRelationName(rel))));
-}
-
-/*
- * ATSimplePermissionsRelationOrIndex
- *
- * - Ensure that it is a relation or an index
+ * - Check the relkind against the provided list
  * - Ensure this user is the owner
  * - Ensure that it is not a system table
  */
 static void
-ATSimplePermissionsRelationOrIndex(Relation rel)
+ATSimplePermissions(Relation rel, const char *relkinds,
+					WrongRelkindDetail detail, const char *command)
 {
-	if (rel->rd_rel->relkind != RELKIND_RELATION &&
-		rel->rd_rel->relkind != RELKIND_INDEX)
-		ereport(ERROR,
-				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-				 errmsg("\"%s\" is not a table or index",
-						RelationGetRelationName(rel))));
+	if (!strchr(relkinds, rel->rd_rel->relkind))
+		ErrorWrongRelkind(rel, detail, command);
 
 	/* Permissions checks */
 	if (!pg_class_ownercheck(RelationGetRelid(rel), GetUserId()))
@@ -4449,10 +4524,8 @@ ATPrepSetStatistics(Relation rel, const char *colName, Node *newValue, LOCKMODE
 	 */
 	if (rel->rd_rel->relkind != RELKIND_RELATION &&
 		rel->rd_rel->relkind != RELKIND_INDEX)
-		ereport(ERROR,
-				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-				 errmsg("\"%s\" is not a table or index",
-						RelationGetRelationName(rel))));
+		ErrorWrongRelkind(rel, WRONG_RELKIND_FOR_COMMAND,
+							   "ALTER COLUMN .. SET STATISTICS");
 
 	/* Permissions checks */
 	if (!pg_class_ownercheck(RelationGetRelid(rel), GetUserId()))
@@ -4692,7 +4765,9 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 
 	/* At top level, permission check was done in ATPrepCmd, else do it */
 	if (recursing)
-		ATSimplePermissions(rel, false, true);
+		ATSimplePermissions(rel, rk_relation_type,
+							WRONG_RELKIND_FOR_COMMAND,
+							"DROP COLUMN");
 
 	/*
 	 * get the number of the attribute
@@ -4996,7 +5071,9 @@ ATAddCheckConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 
 	/* At top level, permission check was done in ATPrepCmd, else do it */
 	if (recursing)
-		ATSimplePermissions(rel, false, false);
+		ATSimplePermissions(rel, rk_relation,
+							WRONG_RELKIND_FOR_CONSTRAINTS,
+							NULL);
 
 	/*
 	 * Call AddRelationNewConstraints to do the work, making sure it works on
@@ -5940,7 +6017,9 @@ ATExecDropConstraint(Relation rel, const char *constrName,
 
 	/* At top level, permission check was done in ATPrepCmd, else do it */
 	if (recursing)
-		ATSimplePermissions(rel, false, false);
+		ATSimplePermissions(rel, rk_relation,
+							WRONG_RELKIND_FOR_CONSTRAINTS,
+							NULL);
 
 	conrel = heap_open(ConstraintRelationId, RowExclusiveLock);
 
@@ -6881,10 +6960,8 @@ ATExecChangeOwner(Oid relationOid, Oid newOwnerId, bool recursing, LOCKMODE lock
 				break;
 			/* FALL THRU */
 		default:
-			ereport(ERROR,
-					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-					 errmsg("\"%s\" is not a table, view, or sequence",
-							NameStr(tuple_class->relname))));
+			ErrorWrongRelkind(target_rel, WRONG_RELKIND_FOR_COMMAND,
+								   "OWNER TO");
 	}
 
 	/*
@@ -7188,10 +7265,8 @@ ATExecSetRelOptions(Relation rel, List *defList, bool isReset, LOCKMODE lockmode
 			(void) index_reloptions(rel->rd_am->amoptions, newOptions, true);
 			break;
 		default:
-			ereport(ERROR,
-					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-					 errmsg("\"%s\" is not a table, index, or TOAST table",
-							RelationGetRelationName(rel))));
+			ErrorWrongRelkind(rel, WRONG_RELKIND_FOR_COMMAND, isReset ?
+				"RESET (...)" : "SET (...)");
 			break;
 	}
 
@@ -7543,7 +7618,9 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
 	 * Must be owner of both parent and child -- child was checked by
 	 * ATSimplePermissions call in ATPrepCmd
 	 */
-	ATSimplePermissions(parent_rel, false, false);
+	ATSimplePermissions(parent_rel, rk_relation,
+						WRONG_RELKIND_FOR_COMMAND,
+						"INHERIT");
 
 	/* Permanent rels cannot inherit from temporary ones */
 	if (RelationUsesTempNamespace(parent_rel)
@@ -8186,10 +8263,8 @@ AlterTableNamespace(RangeVar *relation, const char *newschema,
 		case RELKIND_TOASTVALUE:
 			/* FALL THRU */
 		default:
-			ereport(ERROR,
-					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-					 errmsg("\"%s\" is not a table, view, or sequence",
-							RelationGetRelationName(rel))));
+			ErrorWrongRelkind(rel, WRONG_RELKIND_FOR_COMMAND,
+								   "SET SCHEMA");
 	}
 
 	/* get schema OID and check its permissions */
@@ -8376,6 +8451,50 @@ AlterSeqNamespaces(Relation classRel, Relation rel,
 	relation_close(depRel, AccessShareLock);
 }
 
+/*
+ * Emit the right error message for a SQL command issue on a relation of
+ * the wrong type.
+ */
+void
+ErrorWrongRelkind(Relation rel, WrongRelkindDetail detail,
+				  const char *command)
+{
+	const struct wrongtypestrings *entry;
+
+	for (entry = wrongtypestringarray; entry->kind != '\0'; entry++)
+		if (entry->kind == rel->rd_rel->relkind)
+			break;
+	if (entry->kind == '\0')
+		elog(ERROR, "unexpected relkind: %c", rel->rd_rel->relkind);
+	switch (detail)
+	{
+		case WRONG_RELKIND_FOR_COMMAND:
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg(entry->command_message, command)));
+			break;
+		case WRONG_RELKIND_FOR_CONSTRAINTS:
+			Assert(entry->constraint_message != NULL);
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("%s", _(entry->constraint_message))));
+			break;
+		case WRONG_RELKIND_FOR_TRIGGERS:
+			Assert(entry->trigger_message != NULL);
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("%s", _(entry->trigger_message))));
+			break;
+		case WRONG_RELKIND_FOR_RULES:
+			Assert(entry->rule_message != NULL);
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("%s", _(entry->rule_message))));
+			break;
+		default:
+			elog(ERROR, "unexpected WrongRelkindDetail");
+	}
+}
 
 /*
  * This code supports
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 8195392..3c2d352 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -27,6 +27,7 @@
 #include "catalog/pg_type.h"
 #include "commands/dbcommands.h"
 #include "commands/defrem.h"
+#include "commands/tablecmds.h"
 #include "commands/trigger.h"
 #include "executor/executor.h"
 #include "executor/instrument.h"
@@ -149,7 +150,7 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	 * need to take an AccessExclusiveLock to add one of those, just as we do
 	 * with ON SELECT rules.
 	 */
-	rel = heap_openrv(stmt->relation, ShareRowExclusiveLock);
+	rel = relation_openrv(stmt->relation, ShareRowExclusiveLock);
 
 	/*
 	 * Triggers must be on tables or views, and there are additional
@@ -187,10 +188,7 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 					 errdetail("Views cannot have TRUNCATE triggers.")));
 	}
 	else
-		ereport(ERROR,
-				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-				 errmsg("\"%s\" is not a table or view",
-						RelationGetRelationName(rel))));
+		ErrorWrongRelkind(rel, WRONG_RELKIND_FOR_TRIGGERS, NULL);
 
 	if (!allowSystemTableMods && IsSystemRelation(rel))
 		ereport(ERROR,
@@ -1089,10 +1087,7 @@ RemoveTriggerById(Oid trigOid)
 
 	if (rel->rd_rel->relkind != RELKIND_RELATION &&
 		rel->rd_rel->relkind != RELKIND_VIEW)
-		ereport(ERROR,
-				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-				 errmsg("\"%s\" is not a table or view",
-						RelationGetRelationName(rel))));
+		ErrorWrongRelkind(rel, WRONG_RELKIND_FOR_TRIGGERS, NULL);
 
 	if (!allowSystemTableMods && IsSystemRelation(rel))
 		ereport(ERROR,
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index aa7c144..90bf17e 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -1406,7 +1406,7 @@ transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt)
 				int			count;
 
 				Assert(IsA(inh, RangeVar));
-				rel = heap_openrv(inh, AccessShareLock);
+				rel = relation_openrv(inh, AccessShareLock);
 				if (rel->rd_rel->relkind != RELKIND_RELATION)
 					ereport(ERROR,
 							(errcode(ERRCODE_WRONG_OBJECT_TYPE),
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index 4354897..d81bce6 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -22,6 +22,7 @@
 #include "catalog/objectaccess.h"
 #include "catalog/pg_rewrite.h"
 #include "catalog/storage.h"
+#include "commands/tablecmds.h"
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "parser/parse_utilcmd.h"
@@ -255,10 +256,7 @@ DefineQueryRewrite(char *rulename,
 	 */
 	if (event_relation->rd_rel->relkind != RELKIND_RELATION &&
 		event_relation->rd_rel->relkind != RELKIND_VIEW)
-		ereport(ERROR,
-				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-				 errmsg("\"%s\" is not a table or view",
-						RelationGetRelationName(event_relation))));
+		ErrorWrongRelkind(event_relation, WRONG_RELKIND_FOR_RULES, NULL);
 
 	if (!allowSystemTableMods && IsSystemRelation(event_relation))
 		ereport(ERROR,
diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h
index ad932d3..9a16488 100644
--- a/src/include/commands/tablecmds.h
+++ b/src/include/commands/tablecmds.h
@@ -60,6 +60,16 @@ extern AttrNumber *varattnos_map(TupleDesc olddesc, TupleDesc newdesc);
 extern AttrNumber *varattnos_map_schema(TupleDesc old, List *schema);
 extern void change_varattnos_of_a_node(Node *node, const AttrNumber *newattno);
 
+typedef enum
+{
+	WRONG_RELKIND_FOR_COMMAND,
+	WRONG_RELKIND_FOR_CONSTRAINTS,
+	WRONG_RELKIND_FOR_TRIGGERS,
+	WRONG_RELKIND_FOR_RULES
+} WrongRelkindDetail;
+extern void ErrorWrongRelkind(Relation rel, WrongRelkindDetail detail,
+				  const char *command);
+
 extern void register_on_commit_action(Oid relid, OnCommitAction action);
 extern void remove_on_commit_action(Oid relid);
 
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index e415730..b7917ea 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -599,9 +599,9 @@ ERROR:  cannot alter system column "oid"
 -- try creating a view and altering that, should fail
 create view myview as select * from atacc1;
 alter table myview alter column test drop not null;
-ERROR:  "myview" is not a table
+ERROR:  views do not support DROP NOT NULL
 alter table myview alter column test set not null;
-ERROR:  "myview" is not a table
+ERROR:  views do not support SET NOT NULL
 drop view myview;
 drop table atacc1;
 -- test inheritance
@@ -854,7 +854,7 @@ select * from myview;
 (0 rows)
 
 alter table myview drop d;
-ERROR:  "myview" is not a table or composite type
+ERROR:  views do not support DROP COLUMN
 drop view myview;
 -- test some commands to make sure they fail on the dropped column
 analyze atacc1(a);
diff --git a/src/test/regress/expected/copyselect.out b/src/test/regress/expected/copyselect.out
index cbc140c..d7a04a0 100644
--- a/src/test/regress/expected/copyselect.out
+++ b/src/test/regress/expected/copyselect.out
@@ -30,7 +30,7 @@ copy test1 to stdout;
 -- This should fail
 --
 copy v_test1 to stdout;
-ERROR:  cannot copy from view "v_test1"
+ERROR:  views do not support COPY TO
 HINT:  Try the COPY (SELECT ...) TO variant.
 --
 -- Test COPY (select) TO
@@ -109,9 +109,9 @@ t
 -- This should fail
 --
 \copy v_test1 to stdout
-ERROR:  cannot copy from view "v_test1"
+ERROR:  views do not support COPY TO
 HINT:  Try the COPY (SELECT ...) TO variant.
-\copy: ERROR:  cannot copy from view "v_test1"
+\copy: ERROR:  views do not support COPY TO
 HINT:  Try the COPY (SELECT ...) TO variant.
 --
 -- Test \copy (select ...)
-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Reply via email to