Improve error messages about mismatching relkind
authorPeter Eisentraut <peter@eisentraut.org>
Thu, 8 Jul 2021 07:38:52 +0000 (09:38 +0200)
committerPeter Eisentraut <peter@eisentraut.org>
Thu, 8 Jul 2021 07:44:51 +0000 (09:44 +0200)
Most error messages about a relkind that was not supported or
appropriate for the command was of the pattern

    "relation \"%s\" is not a table, foreign table, or materialized view"

This style can become verbose and tedious to maintain.  Moreover, it's
not very helpful: If I'm trying to create a comment on a TOAST table,
which is not supported, then the information that I could have created
a comment on a materialized view is pointless.

Instead, write the primary error message shorter and saying more
directly that what was attempted is not possible.  Then, in the detail
message, explain that the operation is not supported for the relkind
the object was.  To simplify that, add a new function
errdetail_relkind_not_supported() that does this.

In passing, make use of RELKIND_HAS_STORAGE() where appropriate,
instead of listing out the relkinds individually.

Reviewed-by: Michael Paquier <michael@paquier.xyz>
Reviewed-by: Alvaro Herrera <alvherre@alvh.no-ip.org>
Discussion: https://www.postgresql.org/message-id/flat/dc35a398-37d0-75ce-07ea-1dd71d98f8ec@2ndquadrant.com

32 files changed:
contrib/amcheck/expected/check_heap.out
contrib/amcheck/verify_heapam.c
contrib/pageinspect/expected/page.out
contrib/pageinspect/rawpage.c
contrib/pg_surgery/expected/heap_surgery.out
contrib/pg_surgery/heap_surgery.c
contrib/pg_visibility/expected/pg_visibility.out
contrib/pg_visibility/pg_visibility.c
contrib/pgstattuple/expected/pgstattuple.out
contrib/pgstattuple/pgstatapprox.c
contrib/pgstattuple/pgstatindex.c
contrib/pgstattuple/pgstattuple.c
src/backend/catalog/Makefile
src/backend/catalog/pg_class.c [new file with mode: 0644]
src/backend/catalog/toasting.c
src/backend/commands/comment.c
src/backend/commands/indexcmds.c
src/backend/commands/lockcmds.c
src/backend/commands/seclabel.c
src/backend/commands/sequence.c
src/backend/commands/statscmds.c
src/backend/commands/tablecmds.c
src/backend/commands/trigger.c
src/backend/parser/parse_utilcmd.c
src/backend/rewrite/rewriteDefine.c
src/include/catalog/pg_class.h
src/test/regress/expected/alter_table.out
src/test/regress/expected/create_table_like.out
src/test/regress/expected/foreign_data.out
src/test/regress/expected/indexing.out
src/test/regress/expected/sequence.out
src/test/regress/expected/stats_ext.out

index 1fb382314290221abfcd4bcab8d922a5b46be9b3..ad3086d2aacdc65e0b00e5577bba3bbca9f8295b 100644 (file)
@@ -139,7 +139,8 @@ CREATE TABLE test_partitioned (a int, b text default repeat('x', 5000))
 SELECT * FROM verify_heapam('test_partitioned',
                            startblock := NULL,
                            endblock := NULL);
-ERROR:  "test_partitioned" is not a table, materialized view, or TOAST table
+ERROR:  cannot check relation "test_partitioned"
+DETAIL:  This operation is not supported for partitioned tables.
 -- Check that valid options are not rejected nor corruption reported
 -- for an empty partition table (the child one)
 CREATE TABLE test_partition partition OF test_partitioned FOR VALUES IN (1);
@@ -165,19 +166,22 @@ CREATE INDEX test_index ON test_partition (a);
 SELECT * FROM verify_heapam('test_index',
                            startblock := NULL,
                            endblock := NULL);
-ERROR:  "test_index" is not a table, materialized view, or TOAST table
+ERROR:  cannot check relation "test_index"
+DETAIL:  This operation is not supported for indexes.
 -- Check that views are rejected
 CREATE VIEW test_view AS SELECT 1;
 SELECT * FROM verify_heapam('test_view',
                            startblock := NULL,
                            endblock := NULL);
-ERROR:  "test_view" is not a table, materialized view, or TOAST table
+ERROR:  cannot check relation "test_view"
+DETAIL:  This operation is not supported for views.
 -- Check that sequences are rejected
 CREATE SEQUENCE test_sequence;
 SELECT * FROM verify_heapam('test_sequence',
                            startblock := NULL,
                            endblock := NULL);
-ERROR:  "test_sequence" is not a table, materialized view, or TOAST table
+ERROR:  cannot check relation "test_sequence"
+DETAIL:  This operation is not supported for sequences.
 -- Check that foreign tables are rejected
 CREATE FOREIGN DATA WRAPPER dummy;
 CREATE SERVER dummy_server FOREIGN DATA WRAPPER dummy;
@@ -185,7 +189,8 @@ CREATE FOREIGN TABLE test_foreign_table () SERVER dummy_server;
 SELECT * FROM verify_heapam('test_foreign_table',
                            startblock := NULL,
                            endblock := NULL);
-ERROR:  "test_foreign_table" is not a table, materialized view, or TOAST table
+ERROR:  cannot check relation "test_foreign_table"
+DETAIL:  This operation is not supported for foreign tables.
 -- cleanup
 DROP TABLE heaptest;
 DROP TABLE test_partition;
index a3caee7cdd38c4436d7cda9c80cfecf6198e9516..226271923a89b41970e8d8f1c7e14916ef2fb34d 100644 (file)
@@ -147,7 +147,6 @@ typedef struct HeapCheckContext
 } HeapCheckContext;
 
 /* Internal implementation */
-static void sanity_check_relation(Relation rel);
 static void check_tuple(HeapCheckContext *ctx);
 static void check_toast_tuple(HeapTuple toasttup, HeapCheckContext *ctx,
                              ToastedAttribute *ta, int32 *expected_chunk_seq,
@@ -300,7 +299,23 @@ verify_heapam(PG_FUNCTION_ARGS)
 
    /* Open relation, check relkind and access method */
    ctx.rel = relation_open(relid, AccessShareLock);
-   sanity_check_relation(ctx.rel);
+
+   /*
+    * Check that a relation's relkind and access method are both supported.
+    */
+   if (ctx.rel->rd_rel->relkind != RELKIND_RELATION &&
+       ctx.rel->rd_rel->relkind != RELKIND_MATVIEW &&
+       ctx.rel->rd_rel->relkind != RELKIND_TOASTVALUE)
+       ereport(ERROR,
+               (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+                errmsg("cannot check relation \"%s\"",
+                       RelationGetRelationName(ctx.rel)),
+                errdetail_relkind_not_supported(ctx.rel->rd_rel->relkind)));
+
+   if (ctx.rel->rd_rel->relam != HEAP_TABLE_AM_OID)
+       ereport(ERROR,
+               (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                errmsg("only heap AM is supported")));
 
    /* Early exit if the relation is empty */
    nblocks = RelationGetNumberOfBlocks(ctx.rel);
@@ -523,25 +538,6 @@ verify_heapam(PG_FUNCTION_ARGS)
    PG_RETURN_NULL();
 }
 
-/*
- * Check that a relation's relkind and access method are both supported.
- */
-static void
-sanity_check_relation(Relation rel)
-{
-   if (rel->rd_rel->relkind != RELKIND_RELATION &&
-       rel->rd_rel->relkind != RELKIND_MATVIEW &&
-       rel->rd_rel->relkind != RELKIND_TOASTVALUE)
-       ereport(ERROR,
-               (errcode(ERRCODE_WRONG_OBJECT_TYPE),
-                errmsg("\"%s\" is not a table, materialized view, or TOAST table",
-                       RelationGetRelationName(rel))));
-   if (rel->rd_rel->relam != HEAP_TABLE_AM_OID)
-       ereport(ERROR,
-               (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-                errmsg("only heap AM is supported")));
-}
-
 /*
  * Shared internal implementation for report_corruption and
  * report_toast_corruption.
index 4da28f0a1db58bc19abcc4943860b97849f101bf..4e325ae56dd8471b31b80f396a63696cbdd42470 100644 (file)
@@ -180,9 +180,11 @@ DROP TABLE test1;
 create table test_partitioned (a int) partition by range (a);
 create index test_partitioned_index on test_partitioned (a);
 select get_raw_page('test_partitioned', 0); -- error about partitioned table
-ERROR:  cannot get raw page from partitioned table "test_partitioned"
+ERROR:  cannot get raw page from relation "test_partitioned"
+DETAIL:  This operation is not supported for partitioned tables.
 select get_raw_page('test_partitioned_index', 0); -- error about partitioned index
-ERROR:  cannot get raw page from partitioned index "test_partitioned_index"
+ERROR:  cannot get raw page from relation "test_partitioned_index"
+DETAIL:  This operation is not supported for partitioned indexes.
 -- a regular table which is a member of a partition set should work though
 create table test_part1 partition of test_partitioned for values from ( 1 ) to (100);
 select get_raw_page('test_part1', 0); -- get farther and error about empty table
index 7272b2101686927b406d82ca30c0dc3122d2170a..e9fee73bc436f7588ea1fccf9c6275b3de0d1b89 100644 (file)
@@ -155,32 +155,12 @@ get_raw_page_internal(text *relname, ForkNumber forknum, BlockNumber blkno)
    relrv = makeRangeVarFromNameList(textToQualifiedNameList(relname));
    rel = relation_openrv(relrv, AccessShareLock);
 
-   /* Check that this relation has storage */
-   if (rel->rd_rel->relkind == RELKIND_VIEW)
+   if (!RELKIND_HAS_STORAGE(rel->rd_rel->relkind))
        ereport(ERROR,
                (errcode(ERRCODE_WRONG_OBJECT_TYPE),
-                errmsg("cannot get raw page from view \"%s\"",
-                       RelationGetRelationName(rel))));
-   if (rel->rd_rel->relkind == RELKIND_COMPOSITE_TYPE)
-       ereport(ERROR,
-               (errcode(ERRCODE_WRONG_OBJECT_TYPE),
-                errmsg("cannot get raw page from composite type \"%s\"",
-                       RelationGetRelationName(rel))));
-   if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
-       ereport(ERROR,
-               (errcode(ERRCODE_WRONG_OBJECT_TYPE),
-                errmsg("cannot get raw page from foreign table \"%s\"",
-                       RelationGetRelationName(rel))));
-   if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
-       ereport(ERROR,
-               (errcode(ERRCODE_WRONG_OBJECT_TYPE),
-                errmsg("cannot get raw page from partitioned table \"%s\"",
-                       RelationGetRelationName(rel))));
-   if (rel->rd_rel->relkind == RELKIND_PARTITIONED_INDEX)
-       ereport(ERROR,
-               (errcode(ERRCODE_WRONG_OBJECT_TYPE),
-                errmsg("cannot get raw page from partitioned index \"%s\"",
-                       RelationGetRelationName(rel))));
+                errmsg("cannot get raw page from relation \"%s\"",
+                       RelationGetRelationName(rel)),
+                errdetail_relkind_not_supported(rel->rd_rel->relkind)));
 
    /*
     * Reject attempts to read non-local temporary relations; we would be
index d4a757ffa0145b85350ceadd1c2c0c67c5b8b6a1..df7d13b09086f4aa1dac042210cca6b0bde46950 100644 (file)
@@ -170,9 +170,11 @@ rollback;
 -- check that it fails on an unsupported relkind
 create view vw as select 1;
 select heap_force_kill('vw'::regclass, ARRAY['(0, 1)']::tid[]);
-ERROR:  "vw" is not a table, materialized view, or TOAST table
+ERROR:  cannot operate on relation "vw"
+DETAIL:  This operation is not supported for views.
 select heap_force_freeze('vw'::regclass, ARRAY['(0, 1)']::tid[]);
-ERROR:  "vw" is not a table, materialized view, or TOAST table
+ERROR:  cannot operate on relation "vw"
+DETAIL:  This operation is not supported for views.
 -- cleanup.
 drop view vw;
 drop extension pg_surgery;
index d31e5f31fd42a84bb47356826c301ce4a2fc293b..7edfe4f326f5bc5a1cb843f5737ee1b85da11007 100644 (file)
@@ -37,7 +37,6 @@ static int32 tidcmp(const void *a, const void *b);
 static Datum heap_force_common(FunctionCallInfo fcinfo,
                               HeapTupleForceOption heap_force_opt);
 static void sanity_check_tid_array(ArrayType *ta, int *ntids);
-static void sanity_check_relation(Relation rel);
 static BlockNumber find_tids_one_page(ItemPointer tids, int ntids,
                                      OffsetNumber *next_start_ptr);
 
@@ -101,8 +100,28 @@ heap_force_common(FunctionCallInfo fcinfo, HeapTupleForceOption heap_force_opt)
 
    rel = relation_open(relid, RowExclusiveLock);
 
-   /* Check target relation. */
-   sanity_check_relation(rel);
+   /*
+    * Check target relation.
+    */
+   if (rel->rd_rel->relkind != RELKIND_RELATION &&
+       rel->rd_rel->relkind != RELKIND_MATVIEW &&
+       rel->rd_rel->relkind != RELKIND_TOASTVALUE)
+       ereport(ERROR,
+               (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+                errmsg("cannot operate on relation \"%s\"",
+                       RelationGetRelationName(rel)),
+                errdetail_relkind_not_supported(rel->rd_rel->relkind)));
+
+   if (rel->rd_rel->relam != HEAP_TABLE_AM_OID)
+       ereport(ERROR,
+               (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                errmsg("only heap AM is supported")));
+
+   /* Must be owner of the table or superuser. */
+   if (!pg_class_ownercheck(RelationGetRelid(rel), GetUserId()))
+       aclcheck_error(ACLCHECK_NOT_OWNER,
+                      get_relkind_objtype(rel->rd_rel->relkind),
+                      RelationGetRelationName(rel));
 
    tids = ((ItemPointer) ARR_DATA_PTR(ta));
 
@@ -363,35 +382,6 @@ sanity_check_tid_array(ArrayType *ta, int *ntids)
    *ntids = ArrayGetNItems(ARR_NDIM(ta), ARR_DIMS(ta));
 }
 
-/*-------------------------------------------------------------------------
- * sanity_check_relation()
- *
- * Perform sanity checks on the given relation.
- * ------------------------------------------------------------------------
- */
-static void
-sanity_check_relation(Relation rel)
-{
-   if (rel->rd_rel->relkind != RELKIND_RELATION &&
-       rel->rd_rel->relkind != RELKIND_MATVIEW &&
-       rel->rd_rel->relkind != RELKIND_TOASTVALUE)
-       ereport(ERROR,
-               (errcode(ERRCODE_WRONG_OBJECT_TYPE),
-                errmsg("\"%s\" is not a table, materialized view, or TOAST table",
-                       RelationGetRelationName(rel))));
-
-   if (rel->rd_rel->relam != HEAP_TABLE_AM_OID)
-       ereport(ERROR,
-               (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-                errmsg("only heap AM is supported")));
-
-   /* Must be owner of the table or superuser. */
-   if (!pg_class_ownercheck(RelationGetRelid(rel), GetUserId()))
-       aclcheck_error(ACLCHECK_NOT_OWNER,
-                      get_relkind_objtype(rel->rd_rel->relkind),
-                      RelationGetRelationName(rel));
-}
-
 /*-------------------------------------------------------------------------
  * find_tids_one_page()
  *
index 315633bfea66c1c17e55897fe03a554a83fd3f3e..9de54db2a29247dd67bb8570279a458f06c4c4e4 100644 (file)
@@ -41,66 +41,91 @@ ROLLBACK;
 create table test_partitioned (a int) partition by list (a);
 -- these should all fail
 select pg_visibility('test_partitioned', 0);
-ERROR:  "test_partitioned" is not a table, materialized view, or TOAST table
+ERROR:  relation "test_partitioned" is of wrong relation kind
+DETAIL:  This operation is not supported for partitioned tables.
 select pg_visibility_map('test_partitioned');
-ERROR:  "test_partitioned" is not a table, materialized view, or TOAST table
+ERROR:  relation "test_partitioned" is of wrong relation kind
+DETAIL:  This operation is not supported for partitioned tables.
 select pg_visibility_map_summary('test_partitioned');
-ERROR:  "test_partitioned" is not a table, materialized view, or TOAST table
+ERROR:  relation "test_partitioned" is of wrong relation kind
+DETAIL:  This operation is not supported for partitioned tables.
 select pg_check_frozen('test_partitioned');
-ERROR:  "test_partitioned" is not a table, materialized view, or TOAST table
+ERROR:  relation "test_partitioned" is of wrong relation kind
+DETAIL:  This operation is not supported for partitioned tables.
 select pg_truncate_visibility_map('test_partitioned');
-ERROR:  "test_partitioned" is not a table, materialized view, or TOAST table
+ERROR:  relation "test_partitioned" is of wrong relation kind
+DETAIL:  This operation is not supported for partitioned tables.
 create table test_partition partition of test_partitioned for values in (1);
 create index test_index on test_partition (a);
 -- indexes do not, so these all fail
 select pg_visibility('test_index', 0);
-ERROR:  "test_index" is not a table, materialized view, or TOAST table
+ERROR:  relation "test_index" is of wrong relation kind
+DETAIL:  This operation is not supported for indexes.
 select pg_visibility_map('test_index');
-ERROR:  "test_index" is not a table, materialized view, or TOAST table
+ERROR:  relation "test_index" is of wrong relation kind
+DETAIL:  This operation is not supported for indexes.
 select pg_visibility_map_summary('test_index');
-ERROR:  "test_index" is not a table, materialized view, or TOAST table
+ERROR:  relation "test_index" is of wrong relation kind
+DETAIL:  This operation is not supported for indexes.
 select pg_check_frozen('test_index');
-ERROR:  "test_index" is not a table, materialized view, or TOAST table
+ERROR:  relation "test_index" is of wrong relation kind
+DETAIL:  This operation is not supported for indexes.
 select pg_truncate_visibility_map('test_index');
-ERROR:  "test_index" is not a table, materialized view, or TOAST table
+ERROR:  relation "test_index" is of wrong relation kind
+DETAIL:  This operation is not supported for indexes.
 create view test_view as select 1;
 -- views do not have VMs, so these all fail
 select pg_visibility('test_view', 0);
-ERROR:  "test_view" is not a table, materialized view, or TOAST table
+ERROR:  relation "test_view" is of wrong relation kind
+DETAIL:  This operation is not supported for views.
 select pg_visibility_map('test_view');
-ERROR:  "test_view" is not a table, materialized view, or TOAST table
+ERROR:  relation "test_view" is of wrong relation kind
+DETAIL:  This operation is not supported for views.
 select pg_visibility_map_summary('test_view');
-ERROR:  "test_view" is not a table, materialized view, or TOAST table
+ERROR:  relation "test_view" is of wrong relation kind
+DETAIL:  This operation is not supported for views.
 select pg_check_frozen('test_view');
-ERROR:  "test_view" is not a table, materialized view, or TOAST table
+ERROR:  relation "test_view" is of wrong relation kind
+DETAIL:  This operation is not supported for views.
 select pg_truncate_visibility_map('test_view');
-ERROR:  "test_view" is not a table, materialized view, or TOAST table
+ERROR:  relation "test_view" is of wrong relation kind
+DETAIL:  This operation is not supported for views.
 create sequence test_sequence;
 -- sequences do not have VMs, so these all fail
 select pg_visibility('test_sequence', 0);
-ERROR:  "test_sequence" is not a table, materialized view, or TOAST table
+ERROR:  relation "test_sequence" is of wrong relation kind
+DETAIL:  This operation is not supported for sequences.
 select pg_visibility_map('test_sequence');
-ERROR:  "test_sequence" is not a table, materialized view, or TOAST table
+ERROR:  relation "test_sequence" is of wrong relation kind
+DETAIL:  This operation is not supported for sequences.
 select pg_visibility_map_summary('test_sequence');
-ERROR:  "test_sequence" is not a table, materialized view, or TOAST table
+ERROR:  relation "test_sequence" is of wrong relation kind
+DETAIL:  This operation is not supported for sequences.
 select pg_check_frozen('test_sequence');
-ERROR:  "test_sequence" is not a table, materialized view, or TOAST table
+ERROR:  relation "test_sequence" is of wrong relation kind
+DETAIL:  This operation is not supported for sequences.
 select pg_truncate_visibility_map('test_sequence');
-ERROR:  "test_sequence" is not a table, materialized view, or TOAST table
+ERROR:  relation "test_sequence" is of wrong relation kind
+DETAIL:  This operation is not supported for sequences.
 create foreign data wrapper dummy;
 create server dummy_server foreign data wrapper dummy;
 create foreign table test_foreign_table () server dummy_server;
 -- foreign tables do not have VMs, so these all fail
 select pg_visibility('test_foreign_table', 0);
-ERROR:  "test_foreign_table" is not a table, materialized view, or TOAST table
+ERROR:  relation "test_foreign_table" is of wrong relation kind
+DETAIL:  This operation is not supported for foreign tables.
 select pg_visibility_map('test_foreign_table');
-ERROR:  "test_foreign_table" is not a table, materialized view, or TOAST table
+ERROR:  relation "test_foreign_table" is of wrong relation kind
+DETAIL:  This operation is not supported for foreign tables.
 select pg_visibility_map_summary('test_foreign_table');
-ERROR:  "test_foreign_table" is not a table, materialized view, or TOAST table
+ERROR:  relation "test_foreign_table" is of wrong relation kind
+DETAIL:  This operation is not supported for foreign tables.
 select pg_check_frozen('test_foreign_table');
-ERROR:  "test_foreign_table" is not a table, materialized view, or TOAST table
+ERROR:  relation "test_foreign_table" is of wrong relation kind
+DETAIL:  This operation is not supported for foreign tables.
 select pg_truncate_visibility_map('test_foreign_table');
-ERROR:  "test_foreign_table" is not a table, materialized view, or TOAST table
+ERROR:  relation "test_foreign_table" is of wrong relation kind
+DETAIL:  This operation is not supported for foreign tables.
 -- check some of the allowed relkinds
 create table regular_table (a int, b text);
 alter table regular_table alter column b set storage external;
index dd0c124e6255ed5fedec93d51f5469d8fad5695d..c6d983183dbc1b91ffcac2144766ed0a219ade9e 100644 (file)
@@ -781,6 +781,7 @@ check_relation_relkind(Relation rel)
        rel->rd_rel->relkind != RELKIND_TOASTVALUE)
        ereport(ERROR,
                (errcode(ERRCODE_WRONG_OBJECT_TYPE),
-                errmsg("\"%s\" is not a table, materialized view, or TOAST table",
-                       RelationGetRelationName(rel))));
+                errmsg("relation \"%s\" is of wrong relation kind",
+                       RelationGetRelationName(rel)),
+                errdetail_relkind_not_supported(rel->rd_rel->relkind)));
 }
index 40f7825ddb4bf985d637b322e7e8e18ec8dd7e90..e4ac86f9e30f8059a289eb2b2fd6767cd7aa9727 100644 (file)
@@ -155,13 +155,17 @@ create table test_partitioned (a int) partition by range (a);
 create index test_partitioned_index on test_partitioned(a);
 -- these should all fail
 select pgstattuple('test_partitioned');
-ERROR:  "test_partitioned" (partitioned table) is not supported
+ERROR:  cannot get tuple-level statistics for relation "test_partitioned"
+DETAIL:  This operation is not supported for partitioned tables.
 select pgstattuple('test_partitioned_index');
-ERROR:  "test_partitioned_index" (partitioned index) is not supported
+ERROR:  cannot get tuple-level statistics for relation "test_partitioned_index"
+DETAIL:  This operation is not supported for partitioned indexes.
 select pgstattuple_approx('test_partitioned');
-ERROR:  "test_partitioned" is not a table, materialized view, or TOAST table
+ERROR:  relation "test_partitioned" is of wrong relation kind
+DETAIL:  This operation is not supported for partitioned tables.
 select pg_relpages('test_partitioned');
-ERROR:  "test_partitioned" is not a table, index, materialized view, sequence, or TOAST table
+ERROR:  cannot get page count of relation "test_partitioned"
+DETAIL:  This operation is not supported for partitioned tables.
 select pgstatindex('test_partitioned');
 ERROR:  relation "test_partitioned" is not a btree index
 select pgstatginindex('test_partitioned');
@@ -171,11 +175,14 @@ ERROR:  "test_partitioned" is not an index
 create view test_view as select 1;
 -- these should all fail
 select pgstattuple('test_view');
-ERROR:  "test_view" (view) is not supported
+ERROR:  cannot get tuple-level statistics for relation "test_view"
+DETAIL:  This operation is not supported for views.
 select pgstattuple_approx('test_view');
-ERROR:  "test_view" is not a table, materialized view, or TOAST table
+ERROR:  relation "test_view" is of wrong relation kind
+DETAIL:  This operation is not supported for views.
 select pg_relpages('test_view');
-ERROR:  "test_view" is not a table, index, materialized view, sequence, or TOAST table
+ERROR:  cannot get page count of relation "test_view"
+DETAIL:  This operation is not supported for views.
 select pgstatindex('test_view');
 ERROR:  relation "test_view" is not a btree index
 select pgstatginindex('test_view');
@@ -187,11 +194,14 @@ create server dummy_server foreign data wrapper dummy;
 create foreign table test_foreign_table () server dummy_server;
 -- these should all fail
 select pgstattuple('test_foreign_table');
-ERROR:  "test_foreign_table" (foreign table) is not supported
+ERROR:  cannot get tuple-level statistics for relation "test_foreign_table"
+DETAIL:  This operation is not supported for foreign tables.
 select pgstattuple_approx('test_foreign_table');
-ERROR:  "test_foreign_table" is not a table, materialized view, or TOAST table
+ERROR:  relation "test_foreign_table" is of wrong relation kind
+DETAIL:  This operation is not supported for foreign tables.
 select pg_relpages('test_foreign_table');
-ERROR:  "test_foreign_table" is not a table, index, materialized view, sequence, or TOAST table
+ERROR:  cannot get page count of relation "test_foreign_table"
+DETAIL:  This operation is not supported for foreign tables.
 select pgstatindex('test_foreign_table');
 ERROR:  relation "test_foreign_table" is not a btree index
 select pgstatginindex('test_foreign_table');
index 1fe193bb256f24a40e54a69929042b86d873358d..3b836f370e20baa9eface250d7f418ed4287164b 100644 (file)
@@ -289,8 +289,9 @@ pgstattuple_approx_internal(Oid relid, FunctionCallInfo fcinfo)
          rel->rd_rel->relkind == RELKIND_TOASTVALUE))
        ereport(ERROR,
                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-                errmsg("\"%s\" is not a table, materialized view, or TOAST table",
-                       RelationGetRelationName(rel))));
+                errmsg("relation \"%s\" is of wrong relation kind",
+                       RelationGetRelationName(rel)),
+                errdetail_relkind_not_supported(rel->rd_rel->relkind)));
 
    if (rel->rd_rel->relam != HEAP_TABLE_AM_OID)
        ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
index 5368bb30f0c556593cbb14e56d3543763086b4f7..6c4b053dd0777a215b23da50d575b91df0602faf 100644 (file)
@@ -128,8 +128,8 @@ typedef struct HashIndexStat
 } HashIndexStat;
 
 static Datum pgstatindex_impl(Relation rel, FunctionCallInfo fcinfo);
+static int64 pg_relpages_impl(Relation rel);
 static void GetHashPageStats(Page page, HashIndexStat *stats);
-static void check_relation_relkind(Relation rel);
 
 /* ------------------------------------------------------
  * pgstatindex()
@@ -383,7 +383,6 @@ Datum
 pg_relpages(PG_FUNCTION_ARGS)
 {
    text       *relname = PG_GETARG_TEXT_PP(0);
-   int64       relpages;
    Relation    rel;
    RangeVar   *relrv;
 
@@ -395,16 +394,7 @@ pg_relpages(PG_FUNCTION_ARGS)
    relrv = makeRangeVarFromNameList(textToQualifiedNameList(relname));
    rel = relation_openrv(relrv, AccessShareLock);
 
-   /* only some relkinds have storage */
-   check_relation_relkind(rel);
-
-   /* note: this will work OK on non-local temp tables */
-
-   relpages = RelationGetNumberOfBlocks(rel);
-
-   relation_close(rel, AccessShareLock);
-
-   PG_RETURN_INT64(relpages);
+   PG_RETURN_INT64(pg_relpages_impl(rel));
 }
 
 /* No need for superuser checks in v1.5, see above */
@@ -412,23 +402,13 @@ Datum
 pg_relpages_v1_5(PG_FUNCTION_ARGS)
 {
    text       *relname = PG_GETARG_TEXT_PP(0);
-   int64       relpages;
    Relation    rel;
    RangeVar   *relrv;
 
    relrv = makeRangeVarFromNameList(textToQualifiedNameList(relname));
    rel = relation_openrv(relrv, AccessShareLock);
 
-   /* only some relkinds have storage */
-   check_relation_relkind(rel);
-
-   /* note: this will work OK on non-local temp tables */
-
-   relpages = RelationGetNumberOfBlocks(rel);
-
-   relation_close(rel, AccessShareLock);
-
-   PG_RETURN_INT64(relpages);
+   PG_RETURN_INT64(pg_relpages_impl(rel));
 }
 
 /* Must keep superuser() check, see above. */
@@ -436,7 +416,6 @@ Datum
 pg_relpagesbyid(PG_FUNCTION_ARGS)
 {
    Oid         relid = PG_GETARG_OID(0);
-   int64       relpages;
    Relation    rel;
 
    if (!superuser())
@@ -446,16 +425,7 @@ pg_relpagesbyid(PG_FUNCTION_ARGS)
 
    rel = relation_open(relid, AccessShareLock);
 
-   /* only some relkinds have storage */
-   check_relation_relkind(rel);
-
-   /* note: this will work OK on non-local temp tables */
-
-   relpages = RelationGetNumberOfBlocks(rel);
-
-   relation_close(rel, AccessShareLock);
-
-   PG_RETURN_INT64(relpages);
+   PG_RETURN_INT64(pg_relpages_impl(rel));
 }
 
 /* No need for superuser checks in v1.5, see above */
@@ -463,13 +433,24 @@ Datum
 pg_relpagesbyid_v1_5(PG_FUNCTION_ARGS)
 {
    Oid         relid = PG_GETARG_OID(0);
-   int64       relpages;
    Relation    rel;
 
    rel = relation_open(relid, AccessShareLock);
 
-   /* only some relkinds have storage */
-   check_relation_relkind(rel);
+   PG_RETURN_INT64(pg_relpages_impl(rel));
+}
+
+static int64
+pg_relpages_impl(Relation rel)
+{
+   int64       relpages;
+
+   if (!RELKIND_HAS_STORAGE(rel->rd_rel->relkind))
+       ereport(ERROR,
+               (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+                errmsg("cannot get page count of relation \"%s\"",
+                       RelationGetRelationName(rel)),
+                errdetail_relkind_not_supported(rel->rd_rel->relkind)));
 
    /* note: this will work OK on non-local temp tables */
 
@@ -477,7 +458,7 @@ pg_relpagesbyid_v1_5(PG_FUNCTION_ARGS)
 
    relation_close(rel, AccessShareLock);
 
-   PG_RETURN_INT64(relpages);
+   return relpages;
 }
 
 /* ------------------------------------------------------
@@ -754,21 +735,3 @@ GetHashPageStats(Page page, HashIndexStat *stats)
    }
    stats->free_space += PageGetExactFreeSpace(page);
 }
-
-/*
- * check_relation_relkind - convenience routine to check that relation
- * is of the relkind supported by the callers
- */
-static void
-check_relation_relkind(Relation rel)
-{
-   if (rel->rd_rel->relkind != RELKIND_RELATION &&
-       rel->rd_rel->relkind != RELKIND_INDEX &&
-       rel->rd_rel->relkind != RELKIND_MATVIEW &&
-       rel->rd_rel->relkind != RELKIND_SEQUENCE &&
-       rel->rd_rel->relkind != RELKIND_TOASTVALUE)
-       ereport(ERROR,
-               (errcode(ERRCODE_WRONG_OBJECT_TYPE),
-                errmsg("\"%s\" is not a table, index, materialized view, sequence, or TOAST table",
-                       RelationGetRelationName(rel))));
-}
index 21fdeff8afd54cb8d5442b4ca48166288eb58a5e..f408e6d84dbc38a799faa09c349fc42ced945bda 100644 (file)
@@ -284,31 +284,20 @@ pgstat_relation(Relation rel, FunctionCallInfo fcinfo)
                    err = "unknown index";
                    break;
            }
+           ereport(ERROR,
+                   (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                    errmsg("index \"%s\" (%s) is not supported",
+                           RelationGetRelationName(rel), err)));
            break;
-       case RELKIND_VIEW:
-           err = "view";
-           break;
-       case RELKIND_COMPOSITE_TYPE:
-           err = "composite type";
-           break;
-       case RELKIND_FOREIGN_TABLE:
-           err = "foreign table";
-           break;
-       case RELKIND_PARTITIONED_TABLE:
-           err = "partitioned table";
-           break;
-       case RELKIND_PARTITIONED_INDEX:
-           err = "partitioned index";
-           break;
+
        default:
-           err = "unknown";
-           break;
+           ereport(ERROR,
+                   (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                    errmsg("cannot get tuple-level statistics for relation \"%s\"",
+                           RelationGetRelationName(rel)),
+                    errdetail_relkind_not_supported(rel->rd_rel->relkind)));
    }
 
-   ereport(ERROR,
-           (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-            errmsg("\"%s\" (%s) is not supported",
-                   RelationGetRelationName(rel), err)));
    return 0;                   /* should not happen */
 }
 
index 69f9dd51a7a2e5dec0f3c5fd0118174f09f22dba..d297e773612f64d87a13849e1dd0d24c6f0ff597 100644 (file)
@@ -26,6 +26,7 @@ OBJS = \
    partition.o \
    pg_aggregate.o \
    pg_cast.o \
+   pg_class.o \
    pg_collation.o \
    pg_constraint.o \
    pg_conversion.o \
diff --git a/src/backend/catalog/pg_class.c b/src/backend/catalog/pg_class.c
new file mode 100644 (file)
index 0000000..304c0af
--- /dev/null
@@ -0,0 +1,52 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_class.c
+ *   routines to support manipulation of the pg_class relation
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *   src/backend/catalog/pg_class.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "catalog/pg_class.h"
+
+/*
+ * Issue an errdetail() informing that the relkind is not supported for this
+ * operation.
+ */
+int
+errdetail_relkind_not_supported(char relkind)
+{
+   switch (relkind)
+   {
+       case RELKIND_RELATION:
+           return errdetail("This operation is not supported for tables.");
+       case RELKIND_INDEX:
+           return errdetail("This operation is not supported for indexes.");
+       case RELKIND_SEQUENCE:
+           return errdetail("This operation is not supported for sequences.");
+       case RELKIND_TOASTVALUE:
+           return errdetail("This operation is not supported for TOAST tables.");
+       case RELKIND_VIEW:
+           return errdetail("This operation is not supported for views.");
+       case RELKIND_MATVIEW:
+           return errdetail("This operation is not supported for materialized views.");
+       case RELKIND_COMPOSITE_TYPE:
+           return errdetail("This operation is not supported for composite types.");
+       case RELKIND_FOREIGN_TABLE:
+           return errdetail("This operation is not supported for foreign tables.");
+       case RELKIND_PARTITIONED_TABLE:
+           return errdetail("This operation is not supported for partitioned tables.");
+       case RELKIND_PARTITIONED_INDEX:
+           return errdetail("This operation is not supported for partitioned indexes.");
+       default:
+           elog(ERROR, "unrecognized relkind: '%c'", relkind);
+           return 0;
+   }
+}
index bf81f6ccc5552030d7f94726c6aa2bcfd8f52c9d..147b5abc190fbe70f882d0059911e323467f74f2 100644 (file)
@@ -99,10 +99,8 @@ BootstrapToastTable(char *relName, Oid toastOid, Oid toastIndexOid)
 
    if (rel->rd_rel->relkind != RELKIND_RELATION &&
        rel->rd_rel->relkind != RELKIND_MATVIEW)
-       ereport(ERROR,
-               (errcode(ERRCODE_WRONG_OBJECT_TYPE),
-                errmsg("\"%s\" is not a table or materialized view",
-                       relName)));
+       elog(ERROR, "\"%s\" is not a table or materialized view",
+            relName);
 
    /* create_toast_table does all the work */
    if (!create_toast_table(rel, toastOid, toastIndexOid, (Datum) 0,
index 216b8d3068825ea1b98afb57b791b750c1af1325..834f1a2a3f5627735397acda7f623ffe19d1936a 100644 (file)
@@ -98,8 +98,9 @@ CommentObject(CommentStmt *stmt)
                relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
                ereport(ERROR,
                        (errcode(ERRCODE_WRONG_OBJECT_TYPE),
-                        errmsg("\"%s\" is not a table, view, materialized view, composite type, or foreign table",
-                               RelationGetRelationName(relation))));
+                        errmsg("cannot set comment on relation \"%s\"",
+                               RelationGetRelationName(relation)),
+                        errdetail_relkind_not_supported(relation->rd_rel->relkind)));
            break;
        default:
            break;
index 76774dce064460ae4497a358d833ba15db7191a9..c14ca27c5edf20d092bf98a9bf33d98ce14e0971 100644 (file)
@@ -650,22 +650,12 @@ DefineIndex(Oid relationId,
        case RELKIND_PARTITIONED_TABLE:
            /* OK */
            break;
-       case RELKIND_FOREIGN_TABLE:
-
-           /*
-            * Custom error message for FOREIGN TABLE since the term is close
-            * to a regular table and can confuse the user.
-            */
-           ereport(ERROR,
-                   (errcode(ERRCODE_WRONG_OBJECT_TYPE),
-                    errmsg("cannot create index on foreign table \"%s\"",
-                           RelationGetRelationName(rel))));
-           break;
        default:
            ereport(ERROR,
                    (errcode(ERRCODE_WRONG_OBJECT_TYPE),
-                    errmsg("\"%s\" is not a table or materialized view",
-                           RelationGetRelationName(rel))));
+                    errmsg("cannot create index on relation \"%s\"",
+                           RelationGetRelationName(rel)),
+                    errdetail_relkind_not_supported(rel->rd_rel->relkind)));
            break;
    }
 
index 34f2270cedffc9360394ef97c11b9178fcedfaa7..62465bacd8116a5a3a08f2f1357a040501860140 100644 (file)
@@ -89,8 +89,9 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
        relkind != RELKIND_VIEW)
        ereport(ERROR,
                (errcode(ERRCODE_WRONG_OBJECT_TYPE),
-                errmsg("\"%s\" is not a table or view",
-                       rv->relname)));
+                errmsg("cannot lock relation \"%s\"",
+                       rv->relname),
+                errdetail_relkind_not_supported(relkind)));
 
    /*
     * Make note if a temporary relation has been accessed in this
index 69067142986949ca551090aba5076fce4c9f8d33..ddc019cb39a597b672d39dafa5f30f1814df0d2c 100644 (file)
@@ -188,8 +188,9 @@ ExecSecLabelStmt(SecLabelStmt *stmt)
                relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
                ereport(ERROR,
                        (errcode(ERRCODE_WRONG_OBJECT_TYPE),
-                        errmsg("\"%s\" is not a table, view, materialized view, composite type, or foreign table",
-                               RelationGetRelationName(relation))));
+                        errmsg("cannot set security label on relation \"%s\"",
+                               RelationGetRelationName(relation)),
+                        errdetail_relkind_not_supported(relation->rd_rel->relkind)));
            break;
        default:
            break;
index 0415df9ccb7eb17c885232995f78d208dcbdd78b..e3f9f6d53d04b05862615f2a219698c7e1595977 100644 (file)
@@ -1680,8 +1680,9 @@ process_owned_by(Relation seqrel, List *owned_by, bool for_identity)
              tablerel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE))
            ereport(ERROR,
                    (errcode(ERRCODE_WRONG_OBJECT_TYPE),
-                    errmsg("referenced relation \"%s\" is not a table or foreign table",
-                           RelationGetRelationName(tablerel))));
+                    errmsg("sequence cannot be owned by relation \"%s\"",
+                           RelationGetRelationName(tablerel)),
+                    errdetail_relkind_not_supported(tablerel->rd_rel->relkind)));
 
        /* We insist on same owner and schema */
        if (seqrel->rd_rel->relowner != tablerel->rd_rel->relowner)
index b244a0fbd7bde610ed12215d115641e77c9de4e3..4856f4b41d6998dad52115f33edf82a50b738eb8 100644 (file)
@@ -135,8 +135,9 @@ CreateStatistics(CreateStatsStmt *stmt)
            rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
            ereport(ERROR,
                    (errcode(ERRCODE_WRONG_OBJECT_TYPE),
-                    errmsg("relation \"%s\" is not a table, foreign table, or materialized view",
-                           RelationGetRelationName(rel))));
+                    errmsg("cannot define statistics for relation \"%s\"",
+                           RelationGetRelationName(rel)),
+                    errdetail_relkind_not_supported(rel->rd_rel->relkind)));
 
        /* You must own the relation to create stats on it */
        if (!pg_class_ownercheck(RelationGetRelid(rel), stxowner))
index 97a9725df75c8db0ddca00db362ce40ed0e5f458..03dfd2e7fa667e221f2ed4c264af90cb3d49f231 100644 (file)
@@ -398,8 +398,7 @@ static void ATRewriteTables(AlterTableStmt *parsetree,
                            AlterTableUtilityContext *context);
 static void ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode);
 static AlteredTableInfo *ATGetQueueEntry(List **wqueue, Relation rel);
-static void ATSimplePermissions(Relation rel, int allowed_targets);
-static void ATWrongRelkindError(Relation rel, int allowed_targets);
+static void ATSimplePermissions(AlterTableType cmdtype, Relation rel, int allowed_targets);
 static void ATSimpleRecursion(List **wqueue, Relation rel,
                              AlterTableCmd *cmd, bool recurse, LOCKMODE lockmode,
                              AlterTableUtilityContext *context);
@@ -3394,8 +3393,9 @@ renameatt_check(Oid myrelid, Form_pg_class classform, bool recursing)
        relkind != RELKIND_PARTITIONED_TABLE)
        ereport(ERROR,
                (errcode(ERRCODE_WRONG_OBJECT_TYPE),
-                errmsg("\"%s\" is not a table, view, materialized view, composite type, index, or foreign table",
-                       NameStr(classform->relname))));
+                errmsg("cannot rename columns of relation \"%s\"",
+                       NameStr(classform->relname)),
+                errdetail_relkind_not_supported(relkind)));
 
    /*
     * permissions checking.  only the owner of a class can change its schema.
@@ -4422,7 +4422,7 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
    switch (cmd->subtype)
    {
        case AT_AddColumn:      /* ADD COLUMN */
-           ATSimplePermissions(rel,
+           ATSimplePermissions(cmd->subtype, rel,
                                ATT_TABLE | ATT_COMPOSITE_TYPE | ATT_FOREIGN_TABLE);
            ATPrepAddColumn(wqueue, rel, recurse, recursing, false, cmd,
                            lockmode, context);
@@ -4430,7 +4430,7 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
            pass = AT_PASS_ADD_COL;
            break;
        case AT_AddColumnToView:    /* add column via CREATE OR REPLACE VIEW */
-           ATSimplePermissions(rel, ATT_VIEW);
+           ATSimplePermissions(cmd->subtype, rel, ATT_VIEW);
            ATPrepAddColumn(wqueue, rel, recurse, recursing, true, cmd,
                            lockmode, context);
            /* Recursion occurs during execution phase */
@@ -4444,7 +4444,7 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
             * substitutes default values into INSERTs before it expands
             * rules.
             */
-           ATSimplePermissions(rel, ATT_TABLE | ATT_VIEW | ATT_FOREIGN_TABLE);
+           ATSimplePermissions(cmd->subtype, rel, ATT_TABLE | ATT_VIEW | ATT_FOREIGN_TABLE);
            ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode, context);
            /* No command-specific prep needed */
            pass = cmd->def ? AT_PASS_ADD_OTHERCONSTR : AT_PASS_DROP;
@@ -4452,77 +4452,77 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
        case AT_CookedColumnDefault:    /* add a pre-cooked default */
            /* This is currently used only in CREATE TABLE */
            /* (so the permission check really isn't necessary) */
-           ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
+           ATSimplePermissions(cmd->subtype, rel, ATT_TABLE | ATT_FOREIGN_TABLE);
            /* This command never recurses */
            pass = AT_PASS_ADD_OTHERCONSTR;
            break;
        case AT_AddIdentity:
-           ATSimplePermissions(rel, ATT_TABLE | ATT_VIEW | ATT_FOREIGN_TABLE);
+           ATSimplePermissions(cmd->subtype, rel, ATT_TABLE | ATT_VIEW | ATT_FOREIGN_TABLE);
            /* This command never recurses */
            pass = AT_PASS_ADD_OTHERCONSTR;
            break;
        case AT_SetIdentity:
-           ATSimplePermissions(rel, ATT_TABLE | ATT_VIEW | ATT_FOREIGN_TABLE);
+           ATSimplePermissions(cmd->subtype, rel, ATT_TABLE | ATT_VIEW | ATT_FOREIGN_TABLE);
            /* This command never recurses */
            /* This should run after AddIdentity, so do it in MISC pass */
            pass = AT_PASS_MISC;
            break;
        case AT_DropIdentity:
-           ATSimplePermissions(rel, ATT_TABLE | ATT_VIEW | ATT_FOREIGN_TABLE);
+           ATSimplePermissions(cmd->subtype, rel, ATT_TABLE | ATT_VIEW | ATT_FOREIGN_TABLE);
            /* This command never recurses */
            pass = AT_PASS_DROP;
            break;
        case AT_DropNotNull:    /* ALTER COLUMN DROP NOT NULL */
-           ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
+           ATSimplePermissions(cmd->subtype, rel, ATT_TABLE | ATT_FOREIGN_TABLE);
            ATPrepDropNotNull(rel, recurse, recursing);
            ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode, context);
            pass = AT_PASS_DROP;
            break;
        case AT_SetNotNull:     /* ALTER COLUMN SET NOT NULL */
-           ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
+           ATSimplePermissions(cmd->subtype, rel, ATT_TABLE | ATT_FOREIGN_TABLE);
            /* Need command-specific recursion decision */
            ATPrepSetNotNull(wqueue, rel, cmd, recurse, recursing,
                             lockmode, context);
            pass = AT_PASS_COL_ATTRS;
            break;
        case AT_CheckNotNull:   /* check column is already marked NOT NULL */
-           ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
+           ATSimplePermissions(cmd->subtype, rel, ATT_TABLE | ATT_FOREIGN_TABLE);
            ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode, context);
            /* No command-specific prep needed */
            pass = AT_PASS_COL_ATTRS;
            break;
        case AT_DropExpression: /* ALTER COLUMN DROP EXPRESSION */
-           ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
+           ATSimplePermissions(cmd->subtype, rel, ATT_TABLE | ATT_FOREIGN_TABLE);
            ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode, context);
            ATPrepDropExpression(rel, cmd, recurse, recursing, lockmode);
            pass = AT_PASS_DROP;
            break;
        case AT_SetStatistics:  /* ALTER COLUMN SET STATISTICS */
-           ATSimplePermissions(rel, ATT_TABLE | ATT_MATVIEW | ATT_INDEX | ATT_PARTITIONED_INDEX | ATT_FOREIGN_TABLE);
+           ATSimplePermissions(cmd->subtype, rel, ATT_TABLE | ATT_MATVIEW | ATT_INDEX | ATT_PARTITIONED_INDEX | ATT_FOREIGN_TABLE);
            ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode, context);
            /* No command-specific prep needed */
            pass = AT_PASS_MISC;
            break;
        case AT_SetOptions:     /* ALTER COLUMN SET ( options ) */
        case AT_ResetOptions:   /* ALTER COLUMN RESET ( options ) */
-           ATSimplePermissions(rel, ATT_TABLE | ATT_MATVIEW | ATT_INDEX | ATT_FOREIGN_TABLE);
+           ATSimplePermissions(cmd->subtype, rel, ATT_TABLE | ATT_MATVIEW | ATT_INDEX | ATT_FOREIGN_TABLE);
            /* This command never recurses */
            pass = AT_PASS_MISC;
            break;
        case AT_SetStorage:     /* ALTER COLUMN SET STORAGE */
-           ATSimplePermissions(rel, ATT_TABLE | ATT_MATVIEW | ATT_FOREIGN_TABLE);
+           ATSimplePermissions(cmd->subtype, rel, ATT_TABLE | ATT_MATVIEW | ATT_FOREIGN_TABLE);
            ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode, context);
            /* No command-specific prep needed */
            pass = AT_PASS_MISC;
            break;
        case AT_SetCompression: /* ALTER COLUMN SET COMPRESSION */
-           ATSimplePermissions(rel, ATT_TABLE | ATT_MATVIEW);
+           ATSimplePermissions(cmd->subtype, rel, ATT_TABLE | ATT_MATVIEW);
            /* This command never recurses */
            /* No command-specific prep needed */
            pass = AT_PASS_MISC;
            break;
        case AT_DropColumn:     /* DROP COLUMN */
-           ATSimplePermissions(rel,
+           ATSimplePermissions(cmd->subtype, rel,
                                ATT_TABLE | ATT_COMPOSITE_TYPE | ATT_FOREIGN_TABLE);
            ATPrepDropColumn(wqueue, rel, recurse, recursing, cmd,
                             lockmode, context);
@@ -4530,13 +4530,13 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
            pass = AT_PASS_DROP;
            break;
        case AT_AddIndex:       /* ADD INDEX */
-           ATSimplePermissions(rel, ATT_TABLE);
+           ATSimplePermissions(cmd->subtype, rel, ATT_TABLE);
            /* This command never recurses */
            /* No command-specific prep needed */
            pass = AT_PASS_ADD_INDEX;
            break;
        case AT_AddConstraint:  /* ADD CONSTRAINT */
-           ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
+           ATSimplePermissions(cmd->subtype, rel, ATT_TABLE | ATT_FOREIGN_TABLE);
            /* Recursion occurs during execution phase */
            /* No command-specific prep needed except saving recurse flag */
            if (recurse)
@@ -4544,13 +4544,13 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
            pass = AT_PASS_ADD_CONSTR;
            break;
        case AT_AddIndexConstraint: /* ADD CONSTRAINT USING INDEX */
-           ATSimplePermissions(rel, ATT_TABLE);
+           ATSimplePermissions(cmd->subtype, rel, ATT_TABLE);
            /* This command never recurses */
            /* No command-specific prep needed */
            pass = AT_PASS_ADD_INDEXCONSTR;
            break;
        case AT_DropConstraint: /* DROP CONSTRAINT */
-           ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
+           ATSimplePermissions(cmd->subtype, rel, ATT_TABLE | ATT_FOREIGN_TABLE);
            ATCheckPartitionsNotInUse(rel, lockmode);
            /* Other recursion occurs during execution phase */
            /* No command-specific prep needed except saving recurse flag */
@@ -4559,7 +4559,7 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
            pass = AT_PASS_DROP;
            break;
        case AT_AlterColumnType:    /* ALTER COLUMN TYPE */
-           ATSimplePermissions(rel,
+           ATSimplePermissions(cmd->subtype, rel,
                                ATT_TABLE | ATT_COMPOSITE_TYPE | ATT_FOREIGN_TABLE);
            /* See comments for ATPrepAlterColumnType */
            cmd = ATParseTransformCmd(wqueue, tab, rel, cmd, recurse, lockmode,
@@ -4571,7 +4571,7 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
            pass = AT_PASS_ALTER_TYPE;
            break;
        case AT_AlterColumnGenericOptions:
-           ATSimplePermissions(rel, ATT_FOREIGN_TABLE);
+           ATSimplePermissions(cmd->subtype, rel, ATT_FOREIGN_TABLE);
            /* This command never recurses */
            /* No command-specific prep needed */
            pass = AT_PASS_MISC;
@@ -4583,13 +4583,13 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
            break;
        case AT_ClusterOn:      /* CLUSTER ON */
        case AT_DropCluster:    /* SET WITHOUT CLUSTER */
-           ATSimplePermissions(rel, ATT_TABLE | ATT_MATVIEW);
+           ATSimplePermissions(cmd->subtype, rel, ATT_TABLE | ATT_MATVIEW);
            /* These commands never recurse */
            /* No command-specific prep needed */
            pass = AT_PASS_MISC;
            break;
        case AT_SetLogged:      /* SET LOGGED */
-           ATSimplePermissions(rel, ATT_TABLE);
+           ATSimplePermissions(cmd->subtype, rel, ATT_TABLE);
            if (tab->chgPersistence)
                ereport(ERROR,
                        (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -4604,7 +4604,7 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
            pass = AT_PASS_MISC;
            break;
        case AT_SetUnLogged:    /* SET UNLOGGED */
-           ATSimplePermissions(rel, ATT_TABLE);
+           ATSimplePermissions(cmd->subtype, rel, ATT_TABLE);
            if (tab->chgPersistence)
                ereport(ERROR,
                        (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -4619,11 +4619,11 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
            pass = AT_PASS_MISC;
            break;
        case AT_DropOids:       /* SET WITHOUT OIDS */
-           ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
+           ATSimplePermissions(cmd->subtype, rel, ATT_TABLE | ATT_FOREIGN_TABLE);
            pass = AT_PASS_DROP;
            break;
        case AT_SetTableSpace:  /* SET TABLESPACE */
-           ATSimplePermissions(rel, ATT_TABLE | ATT_MATVIEW | ATT_INDEX |
+           ATSimplePermissions(cmd->subtype, rel, ATT_TABLE | ATT_MATVIEW | ATT_INDEX |
                                ATT_PARTITIONED_INDEX);
            /* This command never recurses */
            ATPrepSetTableSpace(tab, rel, cmd->name, lockmode);
@@ -4632,30 +4632,30 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
        case AT_SetRelOptions:  /* SET (...) */
        case AT_ResetRelOptions:    /* RESET (...) */
        case AT_ReplaceRelOptions:  /* reset them all, then set just these */
-           ATSimplePermissions(rel, ATT_TABLE | ATT_VIEW | ATT_MATVIEW | ATT_INDEX);
+           ATSimplePermissions(cmd->subtype, rel, ATT_TABLE | ATT_VIEW | ATT_MATVIEW | ATT_INDEX);
            /* This command never recurses */
            /* No command-specific prep needed */
            pass = AT_PASS_MISC;
            break;
        case AT_AddInherit:     /* INHERIT */
-           ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
+           ATSimplePermissions(cmd->subtype, rel, ATT_TABLE | ATT_FOREIGN_TABLE);
            /* This command never recurses */
            ATPrepAddInherit(rel);
            pass = AT_PASS_MISC;
            break;
        case AT_DropInherit:    /* NO INHERIT */
-           ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
+           ATSimplePermissions(cmd->subtype, rel, ATT_TABLE | ATT_FOREIGN_TABLE);
            /* This command never recurses */
            /* No command-specific prep needed */
            pass = AT_PASS_MISC;
            break;
        case AT_AlterConstraint:    /* ALTER CONSTRAINT */
-           ATSimplePermissions(rel, ATT_TABLE);
+           ATSimplePermissions(cmd->subtype, rel, ATT_TABLE);
            /* Recursion occurs during execution phase */
            pass = AT_PASS_MISC;
            break;
        case AT_ValidateConstraint: /* VALIDATE CONSTRAINT */
-           ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
+           ATSimplePermissions(cmd->subtype, rel, ATT_TABLE | ATT_FOREIGN_TABLE);
            /* Recursion occurs during execution phase */
            /* No command-specific prep needed except saving recurse flag */
            if (recurse)
@@ -4663,7 +4663,7 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
            pass = AT_PASS_MISC;
            break;
        case AT_ReplicaIdentity:    /* REPLICA IDENTITY ... */
-           ATSimplePermissions(rel, ATT_TABLE | ATT_MATVIEW);
+           ATSimplePermissions(cmd->subtype, rel, ATT_TABLE | ATT_MATVIEW);
            pass = AT_PASS_MISC;
            /* This command never recurses */
            /* No command-specific prep needed */
@@ -4676,7 +4676,7 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
        case AT_DisableTrig:    /* DISABLE TRIGGER variants */
        case AT_DisableTrigAll:
        case AT_DisableTrigUser:
-           ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
+           ATSimplePermissions(cmd->subtype, rel, ATT_TABLE | ATT_FOREIGN_TABLE);
            if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
                ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode, context);
            pass = AT_PASS_MISC;
@@ -4691,28 +4691,28 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
        case AT_DisableRowSecurity:
        case AT_ForceRowSecurity:
        case AT_NoForceRowSecurity:
-           ATSimplePermissions(rel, ATT_TABLE);
+           ATSimplePermissions(cmd->subtype, rel, ATT_TABLE);
            /* These commands never recurse */
            /* No command-specific prep needed */
            pass = AT_PASS_MISC;
            break;
        case AT_GenericOptions:
-           ATSimplePermissions(rel, ATT_FOREIGN_TABLE);
+           ATSimplePermissions(cmd->subtype, rel, ATT_FOREIGN_TABLE);
            /* No command-specific prep needed */
            pass = AT_PASS_MISC;
            break;
        case AT_AttachPartition:
-           ATSimplePermissions(rel, ATT_TABLE | ATT_PARTITIONED_INDEX);
+           ATSimplePermissions(cmd->subtype, rel, ATT_TABLE | ATT_PARTITIONED_INDEX);
            /* No command-specific prep needed */
            pass = AT_PASS_MISC;
            break;
        case AT_DetachPartition:
-           ATSimplePermissions(rel, ATT_TABLE);
+           ATSimplePermissions(cmd->subtype, rel, ATT_TABLE);
            /* No command-specific prep needed */
            pass = AT_PASS_MISC;
            break;
        case AT_DetachPartitionFinalize:
-           ATSimplePermissions(rel, ATT_TABLE);
+           ATSimplePermissions(cmd->subtype, rel, ATT_TABLE);
            /* No command-specific prep needed */
            pass = AT_PASS_MISC;
            break;
@@ -5941,6 +5941,145 @@ ATGetQueueEntry(List **wqueue, Relation rel)
    return tab;
 }
 
+static const char *
+alter_table_type_to_string(AlterTableType cmdtype)
+{
+   switch (cmdtype)
+   {
+       case AT_AddColumn:
+       case AT_AddColumnRecurse:
+       case AT_AddColumnToView:
+           return "ADD COLUMN";
+       case AT_ColumnDefault:
+       case AT_CookedColumnDefault:
+           return "ALTER COLUMN ... SET DEFAULT";
+       case AT_DropNotNull:
+           return "ALTER COLUMN ... DROP NOT NULL";
+       case AT_SetNotNull:
+           return "ALTER COLUMN ... SET NOT NULL";
+       case AT_DropExpression:
+           return "ALTER COLUMN ... DROP EXPRESSION";
+       case AT_CheckNotNull:
+           return NULL;            /* not real grammar */
+       case AT_SetStatistics:
+           return "ALTER COLUMN ... SET STATISTICS";
+       case AT_SetOptions:
+           return "ALTER COLUMN ... SET";
+       case AT_ResetOptions:
+           return "ALTER COLUMN ... RESET";
+       case AT_SetStorage:
+           return "ALTER COLUMN ... SET STORAGE";
+       case AT_SetCompression:
+           return "ALTER COLUMN ... SET COMPRESSION";
+       case AT_DropColumn:
+       case AT_DropColumnRecurse:
+           return "DROP COLUMN";
+       case AT_AddIndex:
+       case AT_ReAddIndex:
+           return NULL;            /* not real grammar */
+       case AT_AddConstraint:
+       case AT_AddConstraintRecurse:
+       case AT_ReAddConstraint:
+       case AT_ReAddDomainConstraint:
+       case AT_AddIndexConstraint:
+           return "ADD CONSTRAINT";
+       case AT_AlterConstraint:
+           return "ALTER CONSTRAINT";
+       case AT_ValidateConstraint:
+       case AT_ValidateConstraintRecurse:
+           return "VALIDATE CONSTRAINT";
+       case AT_DropConstraint:
+       case AT_DropConstraintRecurse:
+           return "DROP CONSTRAINT";
+       case AT_ReAddComment:
+           return NULL;            /* not real grammar */
+       case AT_AlterColumnType:
+           return "ALTER COLUMN ... SET DATA TYPE";
+       case AT_AlterColumnGenericOptions:
+           return "ALTER COLUMN ... OPTIONS";
+       case AT_ChangeOwner:
+           return "OWNER TO";
+       case AT_ClusterOn:
+           return "CLUSTER ON";
+       case AT_DropCluster:
+           return "SET WITHOUT CLUSTER";
+       case AT_SetLogged:
+           return "SET LOGGED";
+       case AT_SetUnLogged:
+           return "SET UNLOGGED";
+       case AT_DropOids:
+           return "SET WITHOUT OIDS";
+       case AT_SetTableSpace:
+           return "SET TABLESPACE";
+       case AT_SetRelOptions:
+           return "SET";
+       case AT_ResetRelOptions:
+           return "RESET";
+       case AT_ReplaceRelOptions:
+           return NULL;            /* not real grammar */
+       case AT_EnableTrig:
+           return "ENABLE TRIGGER";
+       case AT_EnableAlwaysTrig:
+           return "ENABLE ALWAYS TRIGGER";
+       case AT_EnableReplicaTrig:
+           return "ENABLE REPLICA TRIGGER";
+       case AT_DisableTrig:
+           return "DISABLE TRIGGER";
+       case AT_EnableTrigAll:
+           return "ENABLE TRIGGER ALL";
+       case AT_DisableTrigAll:
+           return "DISABLE TRIGGER ALL";
+       case AT_EnableTrigUser:
+           return "ENABLE TRIGGER USER";
+       case AT_DisableTrigUser:
+           return "DISABLE TRIGGER USER";
+       case AT_EnableRule:
+           return "ENABLE RULE";
+       case AT_EnableAlwaysRule:
+           return "ENABLE ALWAYS RULE";
+       case AT_EnableReplicaRule:
+           return "ENABLE REPLICA RULE";
+       case AT_DisableRule:
+           return "DISABLE RULE";
+       case AT_AddInherit:
+           return "INHERIT";
+       case AT_DropInherit:
+           return "NO INHERIT";
+       case AT_AddOf:
+           return "OF";
+       case AT_DropOf:
+           return "NOT OF";
+       case AT_ReplicaIdentity:
+           return "REPLICA IDENTITY";
+       case AT_EnableRowSecurity:
+           return "ENABLE ROW SECURITY";
+       case AT_DisableRowSecurity:
+           return "DISABLE ROW SECURITY";
+       case AT_ForceRowSecurity:
+           return "FORCE ROW SECURITY";
+       case AT_NoForceRowSecurity:
+           return "NO FORCE ROW SECURITY";
+       case AT_GenericOptions:
+           return "OPTIONS";
+       case AT_AttachPartition:
+           return "ATTACH PARTITION";
+       case AT_DetachPartition:
+           return "DETACH PARTITION";
+       case AT_DetachPartitionFinalize:
+           return "DETACH PARTITION ... FINALIZE";
+       case AT_AddIdentity:
+           return "ALTER COLUMN ... ADD IDENTITY";
+       case AT_SetIdentity:
+           return "ALTER COLUMN ... SET";
+       case AT_DropIdentity:
+           return "ALTER COLUMN ... DROP IDENTITY";
+       case AT_ReAddStatistics:
+           return NULL;            /* not real grammar */
+   }
+
+   return NULL;
+}
+
 /*
  * ATSimplePermissions
  *
@@ -5949,7 +6088,7 @@ ATGetQueueEntry(List **wqueue, Relation rel)
  * - Ensure that it is not a system table
  */
 static void
-ATSimplePermissions(Relation rel, int allowed_targets)
+ATSimplePermissions(AlterTableType cmdtype, Relation rel, int allowed_targets)
 {
    int         actual_target;
 
@@ -5984,7 +6123,21 @@ ATSimplePermissions(Relation rel, int allowed_targets)
 
    /* Wrong target type? */
    if ((actual_target & allowed_targets) == 0)
-       ATWrongRelkindError(rel, allowed_targets);
+   {
+       const char *action_str = alter_table_type_to_string(cmdtype);
+
+       if (action_str)
+           ereport(ERROR,
+                   (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+                    /* translator: %s is a group of some SQL keywords */
+                    errmsg("ALTER action %s cannot be performed on relation \"%s\"",
+                           action_str, RelationGetRelationName(rel)),
+                    errdetail_relkind_not_supported(rel->rd_rel->relkind)));
+       else
+           /* internal error? */
+           elog(ERROR, "invalid ALTER action attempted on relation \"%s\"",
+                RelationGetRelationName(rel));
+   }
 
    /* Permissions checks */
    if (!pg_class_ownercheck(RelationGetRelid(rel), GetUserId()))
@@ -5998,66 +6151,6 @@ ATSimplePermissions(Relation rel, int allowed_targets)
                        RelationGetRelationName(rel))));
 }
 
-/*
- * ATWrongRelkindError
- *
- * Throw an error when a relation has been determined to be of the wrong
- * type.
- */
-static void
-ATWrongRelkindError(Relation rel, int allowed_targets)
-{
-   char       *msg;
-
-   switch (allowed_targets)
-   {
-       case ATT_TABLE:
-           msg = _("\"%s\" is not a table");
-           break;
-       case ATT_TABLE | ATT_VIEW:
-           msg = _("\"%s\" is not a table or view");
-           break;
-       case ATT_TABLE | ATT_VIEW | ATT_FOREIGN_TABLE:
-           msg = _("\"%s\" is not a table, view, or foreign table");
-           break;
-       case ATT_TABLE | ATT_VIEW | ATT_MATVIEW | ATT_INDEX:
-           msg = _("\"%s\" is not a table, view, materialized view, or index");
-           break;
-       case ATT_TABLE | ATT_MATVIEW:
-           msg = _("\"%s\" is not a table or materialized view");
-           break;
-       case ATT_TABLE | ATT_MATVIEW | ATT_INDEX:
-           msg = _("\"%s\" is not a table, materialized view, or index");
-           break;
-       case ATT_TABLE | ATT_MATVIEW | ATT_FOREIGN_TABLE:
-           msg = _("\"%s\" is not a table, materialized view, or foreign table");
-           break;
-       case ATT_TABLE | ATT_FOREIGN_TABLE:
-           msg = _("\"%s\" is not a table or foreign table");
-           break;
-       case ATT_TABLE | ATT_COMPOSITE_TYPE | ATT_FOREIGN_TABLE:
-           msg = _("\"%s\" is not a table, composite type, or foreign table");
-           break;
-       case ATT_TABLE | ATT_MATVIEW | ATT_INDEX | ATT_FOREIGN_TABLE:
-           msg = _("\"%s\" is not a table, materialized view, index, or foreign table");
-           break;
-       case ATT_VIEW:
-           msg = _("\"%s\" is not a view");
-           break;
-       case ATT_FOREIGN_TABLE:
-           msg = _("\"%s\" is not a foreign table");
-           break;
-       default:
-           /* shouldn't get here, add all necessary cases above */
-           msg = _("\"%s\" is of the wrong type");
-           break;
-   }
-
-   ereport(ERROR,
-           (errcode(ERRCODE_WRONG_OBJECT_TYPE),
-            errmsg(msg, RelationGetRelationName(rel))));
-}
-
 /*
  * ATSimpleRecursion
  *
@@ -6452,7 +6545,7 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 
    /* At top level, permission check was done in ATPrepCmd, else do it */
    if (recursing)
-       ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
+       ATSimplePermissions((*cmd)->subtype, rel, ATT_TABLE | ATT_FOREIGN_TABLE);
 
    if (rel->rd_rel->relispartition && !recursing)
        ereport(ERROR,
@@ -8186,7 +8279,7 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 
    /* At top level, permission check was done in ATPrepCmd, else do it */
    if (recursing)
-       ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
+       ATSimplePermissions(AT_DropColumn, rel, ATT_TABLE | ATT_FOREIGN_TABLE);
 
    /* Initialize addrs on the first invocation */
    Assert(!recursing || addrs != NULL);
@@ -8670,7 +8763,7 @@ ATAddCheckConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 
    /* At top level, permission check was done in ATPrepCmd, else do it */
    if (recursing)
-       ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
+       ATSimplePermissions(AT_AddConstraint, rel, ATT_TABLE | ATT_FOREIGN_TABLE);
 
    /*
     * Call AddRelationNewConstraints to do the work, making sure it works on
@@ -11286,7 +11379,7 @@ ATExecDropConstraint(Relation rel, const char *constrName,
 
    /* At top level, permission check was done in ATPrepCmd, else do it */
    if (recursing)
-       ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
+       ATSimplePermissions(AT_DropConstraint, rel, ATT_TABLE | ATT_FOREIGN_TABLE);
 
    conrel = table_open(ConstraintRelationId, RowExclusiveLock);
 
@@ -13205,8 +13298,9 @@ ATExecChangeOwner(Oid relationOid, Oid newOwnerId, bool recursing, LOCKMODE lock
        default:
            ereport(ERROR,
                    (errcode(ERRCODE_WRONG_OBJECT_TYPE),
-                    errmsg("\"%s\" is not a table, view, sequence, or foreign table",
-                           NameStr(tuple_class->relname))));
+                    errmsg("cannot change owner of relation \"%s\"",
+                           NameStr(tuple_class->relname)),
+                    errdetail_relkind_not_supported(tuple_class->relkind)));
    }
 
    /*
@@ -13621,8 +13715,9 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
        default:
            ereport(ERROR,
                    (errcode(ERRCODE_WRONG_OBJECT_TYPE),
-                    errmsg("\"%s\" is not a table, view, materialized view, index, or TOAST table",
-                           RelationGetRelationName(rel))));
+                    errmsg("cannot set options for relation \"%s\"",
+                           RelationGetRelationName(rel)),
+                    errdetail_relkind_not_supported(rel->rd_rel->relkind)));
            break;
    }
 
@@ -14176,7 +14271,7 @@ 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, ATT_TABLE | ATT_FOREIGN_TABLE);
+   ATSimplePermissions(AT_AddInherit, parent_rel, ATT_TABLE | ATT_FOREIGN_TABLE);
 
    /* Permanent rels cannot inherit from temporary ones */
    if (parent_rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP &&
@@ -16505,17 +16600,27 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
     * Don't allow ALTER TABLE .. SET SCHEMA on relations that can't be moved
     * to a different schema, such as indexes and TOAST tables.
     */
-   if (IsA(stmt, AlterObjectSchemaStmt) &&
-       relkind != RELKIND_RELATION &&
-       relkind != RELKIND_VIEW &&
-       relkind != RELKIND_MATVIEW &&
-       relkind != RELKIND_SEQUENCE &&
-       relkind != RELKIND_FOREIGN_TABLE &&
-       relkind != RELKIND_PARTITIONED_TABLE)
-       ereport(ERROR,
-               (errcode(ERRCODE_WRONG_OBJECT_TYPE),
-                errmsg("\"%s\" is not a table, view, materialized view, sequence, or foreign table",
-                       rv->relname)));
+   if (IsA(stmt, AlterObjectSchemaStmt))
+   {
+       if (relkind == RELKIND_INDEX || relkind == RELKIND_PARTITIONED_INDEX)
+           ereport(ERROR,
+                   (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+                    errmsg("cannot change schema of index \"%s\"",
+                           rv->relname),
+                    errhint("Change the schema of the table instead.")));
+       else if (relkind == RELKIND_COMPOSITE_TYPE)
+           ereport(ERROR,
+                   (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+                    errmsg("cannot change schema of composite type \"%s\"",
+                           rv->relname),
+                    errhint("Use ALTER TYPE instead.")));
+       else if (relkind == RELKIND_TOASTVALUE)
+           ereport(ERROR,
+                   (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+                    errmsg("cannot change schema of TOAST table \"%s\"",
+                           rv->relname),
+                    errhint("Change the schema of the table instead.")));
+   }
 
    ReleaseSysCache(tuple);
 }
@@ -17077,7 +17182,7 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd,
     * Must be owner of both parent and source table -- parent was checked by
     * ATSimplePermissions call in ATPrepCmd
     */
-   ATSimplePermissions(attachrel, ATT_TABLE | ATT_FOREIGN_TABLE);
+   ATSimplePermissions(AT_AttachPartition, attachrel, ATT_TABLE | ATT_FOREIGN_TABLE);
 
    /* A partition can only have one parent */
    if (attachrel->rd_rel->relispartition)
index 07c73f39de84ac98c44ec4251a990d6c6e9b4568..952c8d582a11434e23204f9e2d760707ec2605b8 100644 (file)
@@ -286,8 +286,9 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
    else
        ereport(ERROR,
                (errcode(ERRCODE_WRONG_OBJECT_TYPE),
-                errmsg("\"%s\" is not a table or view",
-                       RelationGetRelationName(rel))));
+                errmsg("relation \"%s\" cannot have triggers",
+                       RelationGetRelationName(rel)),
+                errdetail_relkind_not_supported(rel->rd_rel->relkind)));
 
    if (!allowSystemTableMods && IsSystemRelation(rel))
        ereport(ERROR,
@@ -1262,8 +1263,9 @@ RemoveTriggerById(Oid trigOid)
        rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
        ereport(ERROR,
                (errcode(ERRCODE_WRONG_OBJECT_TYPE),
-                errmsg("\"%s\" is not a table, view, or foreign table",
-                       RelationGetRelationName(rel))));
+                errmsg("relation \"%s\" cannot have triggers",
+                       RelationGetRelationName(rel)),
+                errdetail_relkind_not_supported(rel->rd_rel->relkind)));
 
    if (!allowSystemTableMods && IsSystemRelation(rel))
        ereport(ERROR,
@@ -1368,8 +1370,9 @@ RangeVarCallbackForRenameTrigger(const RangeVar *rv, Oid relid, Oid oldrelid,
        form->relkind != RELKIND_PARTITIONED_TABLE)
        ereport(ERROR,
                (errcode(ERRCODE_WRONG_OBJECT_TYPE),
-                errmsg("\"%s\" is not a table, view, or foreign table",
-                       rv->relname)));
+                errmsg("relation \"%s\" cannot have triggers",
+                       rv->relname),
+                errdetail_relkind_not_supported(form->relkind)));
 
    /* you must own the table to rename one of its triggers */
    if (!pg_class_ownercheck(relid, GetUserId()))
index 81d3e7990c6b75510c6754ddeb6a075859c96288..3afcd6b5118f113a2e09e690cce3ce7110446636 100644 (file)
@@ -976,8 +976,9 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
        relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
        ereport(ERROR,
                (errcode(ERRCODE_WRONG_OBJECT_TYPE),
-                errmsg("\"%s\" is not a table, view, materialized view, composite type, or foreign table",
-                       RelationGetRelationName(relation))));
+                errmsg("relation \"%s\" is invalid in LIKE clause",
+                       RelationGetRelationName(relation)),
+                errdetail_relkind_not_supported(relation->rd_rel->relkind)));
 
    cancel_parser_errposition_callback(&pcbstate);
 
index 27e4ef911c8a730debc33dab834aa42653c7dd94..6589345ac4cbd7be1cec5d68c3b43c1618c19f36 100644 (file)
@@ -268,8 +268,9 @@ DefineQueryRewrite(const char *rulename,
        event_relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
        ereport(ERROR,
                (errcode(ERRCODE_WRONG_OBJECT_TYPE),
-                errmsg("\"%s\" is not a table or view",
-                       RelationGetRelationName(event_relation))));
+                errmsg("relation \"%s\" cannot have rules",
+                       RelationGetRelationName(event_relation)),
+                errdetail_relkind_not_supported(event_relation->rd_rel->relkind)));
 
    if (!allowSystemTableMods && IsSystemRelation(event_relation))
        ereport(ERROR,
@@ -935,7 +936,8 @@ RangeVarCallbackForRenameRule(const RangeVar *rv, Oid relid, Oid oldrelid,
        form->relkind != RELKIND_PARTITIONED_TABLE)
        ereport(ERROR,
                (errcode(ERRCODE_WRONG_OBJECT_TYPE),
-                errmsg("\"%s\" is not a table or view", rv->relname)));
+                errmsg("relation \"%s\" cannot have rules", rv->relname),
+                errdetail_relkind_not_supported(form->relkind)));
 
    if (!allowSystemTableMods && IsSystemClass(relid, form))
        ereport(ERROR,
index 3458d9560617c86bd39b31e0c4ef7553826c46e5..fef9945ed8fff5c9e3041207e8388b33a7506dcc 100644 (file)
@@ -198,6 +198,7 @@ DECLARE_INDEX(pg_class_tblspc_relfilenode_index, 3455, ClassTblspcRelfilenodeInd
     (relkind) == RELKIND_TOASTVALUE || \
     (relkind) == RELKIND_MATVIEW)
 
+extern int errdetail_relkind_not_supported(char relkind);
 
 #endif                         /* EXPOSE_TO_CLIENT_CODE */
 
index f81bdf513b6bf1e134ec77dec6a79220d99ff01d..8dcb00ac67a692d85e5211a109e0df51aa56539d 100644 (file)
@@ -1087,9 +1087,11 @@ ERROR:  column "bar" of relation "atacc1" does not exist
 -- 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 or foreign table
+ERROR:  ALTER action ALTER COLUMN ... DROP NOT NULL cannot be performed on relation "myview"
+DETAIL:  This operation is not supported for views.
 alter table myview alter column test set not null;
-ERROR:  "myview" is not a table or foreign table
+ERROR:  ALTER action ALTER COLUMN ... SET NOT NULL cannot be performed on relation "myview"
+DETAIL:  This operation is not supported for views.
 drop view myview;
 drop table atacc1;
 -- set not null verified by constraints
@@ -1387,7 +1389,8 @@ select * from myview;
 (0 rows)
 
 alter table myview drop d;
-ERROR:  "myview" is not a table, composite type, or foreign table
+ERROR:  ALTER action DROP COLUMN cannot be performed on relation "myview"
+DETAIL:  This operation is not supported for views.
 drop view myview;
 -- test some commands to make sure they fail on the dropped column
 analyze atacc1(a);
index 4dc5e6aa5fb899634da29771864225a562b6a791..7ad5fafe9368857e89edaa1ce2f31210ceae1830 100644 (file)
@@ -504,9 +504,10 @@ DROP TABLE noinh_con_copy, noinh_con_copy1;
 CREATE TABLE ctlt4 (a int, b text);
 CREATE SEQUENCE ctlseq1;
 CREATE TABLE ctlt10 (LIKE ctlseq1);  -- fail
-ERROR:  "ctlseq1" is not a table, view, materialized view, composite type, or foreign table
+ERROR:  relation "ctlseq1" is invalid in LIKE clause
 LINE 1: CREATE TABLE ctlt10 (LIKE ctlseq1);
                                   ^
+DETAIL:  This operation is not supported for sequences.
 CREATE VIEW ctlv1 AS SELECT * FROM ctlt4;
 CREATE TABLE ctlt11 (LIKE ctlv1);
 CREATE TABLE ctlt11a (LIKE ctlv1 INCLUDING ALL);
index 5385f98a0fe2706069c9c00af3174f7be3dfd3ef..809d40a79a99c4db8c7bb0405584678f93dc6fc7 100644 (file)
@@ -740,7 +740,8 @@ FDW options: (delimiter ',', quote '"', "be quoted" 'value')
 (1 row)
 
 CREATE INDEX id_ft1_c2 ON ft1 (c2);                             -- ERROR
-ERROR:  cannot create index on foreign table "ft1"
+ERROR:  cannot create index on relation "ft1"
+DETAIL:  This operation is not supported for foreign tables.
 SELECT * FROM ft1;                                              -- ERROR
 ERROR:  foreign-data wrapper "dummy" has no handler
 EXPLAIN SELECT * FROM ft1;                                      -- ERROR
@@ -864,7 +865,8 @@ LINE 1: ALTER FOREIGN TABLE ft1 ADD PRIMARY KEY (c7);
                                     ^
 ALTER FOREIGN TABLE ft1 ADD CONSTRAINT ft1_c9_check CHECK (c9 < 0) NOT VALID;
 ALTER FOREIGN TABLE ft1 ALTER CONSTRAINT ft1_c9_check DEFERRABLE; -- ERROR
-ERROR:  "ft1" is not a table
+ERROR:  ALTER action ALTER CONSTRAINT cannot be performed on relation "ft1"
+DETAIL:  This operation is not supported for foreign tables.
 ALTER FOREIGN TABLE ft1 DROP CONSTRAINT ft1_c9_check;
 ALTER FOREIGN TABLE ft1 DROP CONSTRAINT no_const;               -- ERROR
 ERROR:  constraint "no_const" of relation "ft1" does not exist
index c93f4470c92ca7d32001623921085f888584ea18..193f7801912dfddb122b013c43e69beb00e6718a 100644 (file)
@@ -137,7 +137,8 @@ select relname, relpartbound from pg_class
 (2 rows)
 
 alter table idxpart_c detach partition idxpart1_c;
-ERROR:  "idxpart_c" is not a table
+ERROR:  ALTER action DETACH PARTITION cannot be performed on relation "idxpart_c"
+DETAIL:  This operation is not supported for partitioned indexes.
 drop table idxpart;
 -- If a partition already has an index, don't create a duplicative one
 create table idxpart (a int, b int) partition by range (a, b);
index 8b928b28882dfff71842cd388910d6715bafd6f5..71c2b0f1dff42ab96854d3c0675e7661d081d2c9 100644 (file)
@@ -21,7 +21,8 @@ CREATE SEQUENCE sequence_testx OWNED BY nobody;  -- nonsense word
 ERROR:  invalid OWNED BY option
 HINT:  Specify OWNED BY table.column or OWNED BY NONE.
 CREATE SEQUENCE sequence_testx OWNED BY pg_class_oid_index.oid;  -- not a table
-ERROR:  referenced relation "pg_class_oid_index" is not a table or foreign table
+ERROR:  sequence cannot be owned by relation "pg_class_oid_index"
+DETAIL:  This operation is not supported for indexes.
 CREATE SEQUENCE sequence_testx OWNED BY pg_class.relname;  -- not same schema
 ERROR:  sequence must be in same schema as table it is linked to
 CREATE TABLE sequence_test_table (a int);
index 8c214d8dfc557a3c134cdfe14d12e021b622048e..62b05c79f9eb5329ed58e0660f5a92571d850c60 100644 (file)
@@ -211,14 +211,18 @@ CREATE TABLE tststats.pt (a int, b int, c text) PARTITION BY RANGE (a, b);
 CREATE TABLE tststats.pt1 PARTITION OF tststats.pt FOR VALUES FROM (-10, -10) TO (10, 10);
 CREATE STATISTICS tststats.s1 ON a, b FROM tststats.t;
 CREATE STATISTICS tststats.s2 ON a, b FROM tststats.ti;
-ERROR:  relation "ti" is not a table, foreign table, or materialized view
+ERROR:  cannot define statistics for relation "ti"
+DETAIL:  This operation is not supported for indexes.
 CREATE STATISTICS tststats.s3 ON a, b FROM tststats.s;
-ERROR:  relation "s" is not a table, foreign table, or materialized view
+ERROR:  cannot define statistics for relation "s"
+DETAIL:  This operation is not supported for sequences.
 CREATE STATISTICS tststats.s4 ON a, b FROM tststats.v;
-ERROR:  relation "v" is not a table, foreign table, or materialized view
+ERROR:  cannot define statistics for relation "v"
+DETAIL:  This operation is not supported for views.
 CREATE STATISTICS tststats.s5 ON a, b FROM tststats.mv;
 CREATE STATISTICS tststats.s6 ON a, b FROM tststats.ty;
-ERROR:  relation "ty" is not a table, foreign table, or materialized view
+ERROR:  cannot define statistics for relation "ty"
+DETAIL:  This operation is not supported for composite types.
 CREATE STATISTICS tststats.s7 ON a, b FROM tststats.f;
 CREATE STATISTICS tststats.s8 ON a, b FROM tststats.pt;
 CREATE STATISTICS tststats.s9 ON a, b FROM tststats.pt1;