]> git.ipfire.org Git - thirdparty/postgresql.git/commitdiff
Disallow converting an inheritance child table to a view.
authorTom Lane <tgl@sss.pgh.pa.us>
Sat, 6 Feb 2021 20:17:01 +0000 (15:17 -0500)
committerTom Lane <tgl@sss.pgh.pa.us>
Sat, 6 Feb 2021 20:17:01 +0000 (15:17 -0500)
Generally, members of inheritance trees must be plain tables (or,
in more recent versions, foreign tables).  ALTER TABLE INHERIT
rejects creating an inheritance relationship that has a view at
either end.  When DefineQueryRewrite attempts to convert a relation
to a view, it already had checks prohibiting doing so for partitioning
parents or children as well as traditional-inheritance parents ...
but it neglected to check that a traditional-inheritance child wasn't
being converted.  Since the planner assumes that any inheritance
child is a table, this led to making plans that tried to do a physical
scan on a view, causing failures (or even crashes, in recent versions).

One could imagine trying to support such a case by expanding the view
normally, but since the rewriter runs before the planner does
inheritance expansion, it would take some very fundamental refactoring
to make that possible.  There are probably a lot of other parts of the
system that don't cope well with such a situation, too.  For now,
just forbid it.

Per bug #16856 from Yang Lin.  Back-patch to all supported branches.
(In versions before v10, this includes back-patching the portion of
commit 501ed02cf that added has_superclass().  Perhaps the lack of
that infrastructure partially explains the missing check.)

Discussion: https://postgr.es/m/16856-0363e05c6e1612fd@postgresql.org

src/backend/catalog/pg_inherits.c
src/backend/rewrite/rewriteDefine.c
src/test/regress/expected/rules.out
src/test/regress/sql/rules.sql

index 00f7957b3da2431116408c5a3a8186d1f06da38b..42ef6466cd9b783984fc989cfb96d13d42a779c4 100644 (file)
@@ -3,8 +3,8 @@
  * pg_inherits.c
  *       routines to support manipulation of the pg_inherits relation
  *
- * Note: currently, this module only contains inquiry functions; the actual
- * creation and deletion of pg_inherits entries is done in tablecmds.c.
+ * Note: currently, this module mostly contains inquiry functions; actual
+ * creation and deletion of pg_inherits entries is mostly done in tablecmds.c.
  * Perhaps someday that code should be moved here, but it'd have to be
  * disentangled from other stuff such as pg_depend updates.
  *
@@ -272,9 +272,11 @@ has_subclass(Oid relationId)
 }
 
 /*
- * has_superclass - does this relation inherit from another?  The caller
- * should hold a lock on the given relation so that it can't be concurrently
- * added to or removed from an inheritance hierarchy.
+ * has_superclass - does this relation inherit from another?
+ *
+ * Unlike has_subclass, this can be relied on to give an accurate answer.
+ * However, the caller must hold a lock on the given relation so that it
+ * can't be concurrently added to or removed from an inheritance hierarchy.
  */
 bool
 has_superclass(Oid relationId)
index 7df2b6154cb1ca1db18eaf43cff156af231bcd37..c9788e538d2f4477c910d181b8e3ad893aca0c6a 100644 (file)
@@ -26,6 +26,7 @@
 #include "catalog/indexing.h"
 #include "catalog/namespace.h"
 #include "catalog/objectaccess.h"
+#include "catalog/pg_inherits.h"
 #include "catalog/pg_rewrite.h"
 #include "catalog/storage.h"
 #include "commands/policy.h"
@@ -413,13 +414,14 @@ DefineQueryRewrite(const char *rulename,
                 * Are we converting a relation to a view?
                 *
                 * If so, check that the relation is empty because the storage for the
-                * relation is going to be deleted.  Also insist that the rel not have
-                * any triggers, indexes, child tables, policies, or RLS enabled.
-                * (Note: these tests are too strict, because they will reject
-                * relations that once had such but don't anymore.  But we don't
-                * really care, because this whole business of converting relations to
-                * views is just a kluge to allow dump/reload of views that
-                * participate in circular dependencies.)
+                * relation is going to be deleted.  Also insist that the rel not be
+                * involved in partitioning, nor have any triggers, indexes, child or
+                * parent tables, RLS policies, or RLS enabled.  (Note: some of these
+                * tests are too strict, because they will reject relations that once
+                * had such but don't anymore.  But we don't really care, because this
+                * whole business of converting relations to views is just an obsolete
+                * kluge to allow dump/reload of views that participate in circular
+                * dependencies.)
                 */
                if (event_relation->rd_rel->relkind != RELKIND_VIEW &&
                        event_relation->rd_rel->relkind != RELKIND_MATVIEW)
@@ -434,6 +436,9 @@ DefineQueryRewrite(const char *rulename,
                                                 errmsg("cannot convert partitioned table \"%s\" to a view",
                                                                RelationGetRelationName(event_relation))));
 
+                       /* only case left: */
+                       Assert(event_relation->rd_rel->relkind == RELKIND_RELATION);
+
                        if (event_relation->rd_rel->relispartition)
                                ereport(ERROR,
                                                (errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -471,6 +476,12 @@ DefineQueryRewrite(const char *rulename,
                                                 errmsg("could not convert table \"%s\" to a view because it has child tables",
                                                                RelationGetRelationName(event_relation))));
 
+                       if (has_superclass(RelationGetRelid(event_relation)))
+                               ereport(ERROR,
+                                               (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+                                                errmsg("could not convert table \"%s\" to a view because it has parent tables",
+                                                               RelationGetRelationName(event_relation))));
+
                        if (event_relation->rd_rel->relrowsecurity)
                                ereport(ERROR,
                                                (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
index 6b0b8cbe3e0109d3cdaa3f0f7375949d711c860d..030d253ed324c4b101a4b15f628ffba14f9d69c0 100644 (file)
@@ -2693,16 +2693,27 @@ select reltoastrelid, relkind, relfrozenxid
 (1 row)
 
 drop view rules_fooview;
--- trying to convert a partitioned table to view is not allowed
+-- cannot convert an inheritance parent or child to a view, though
+create table rules_fooview (x int, y text);
+create table rules_fooview_child () inherits (rules_fooview);
+create rule "_RETURN" as on select to rules_fooview do instead
+  select 1 as x, 'aaa'::text as y;
+ERROR:  could not convert table "rules_fooview" to a view because it has child tables
+create rule "_RETURN" as on select to rules_fooview_child do instead
+  select 1 as x, 'aaa'::text as y;
+ERROR:  could not convert table "rules_fooview_child" to a view because it has parent tables
+drop table rules_fooview cascade;
+NOTICE:  drop cascades to table rules_fooview_child
+-- likewise, converting a partitioned table or partition to view is not allowed
 create table rules_fooview (x int, y text) partition by list (x);
 create rule "_RETURN" as on select to rules_fooview do instead
   select 1 as x, 'aaa'::text as y;
 ERROR:  cannot convert partitioned table "rules_fooview" to a view
--- nor can one convert a partition to view
 create table rules_fooview_part partition of rules_fooview for values in (1);
 create rule "_RETURN" as on select to rules_fooview_part do instead
   select 1 as x, 'aaa'::text as y;
 ERROR:  cannot convert partition "rules_fooview_part" to a view
+drop table rules_fooview;
 --
 -- check for planner problems with complex inherited UPDATES
 --
index b7d7f434b6a5b8d15f5226f57f305bc705ee819e..f641e303c09ccd040380fe41126dd6e0f23899d7 100644 (file)
@@ -901,16 +901,28 @@ select reltoastrelid, relkind, relfrozenxid
 
 drop view rules_fooview;
 
--- trying to convert a partitioned table to view is not allowed
+-- cannot convert an inheritance parent or child to a view, though
+create table rules_fooview (x int, y text);
+create table rules_fooview_child () inherits (rules_fooview);
+
+create rule "_RETURN" as on select to rules_fooview do instead
+  select 1 as x, 'aaa'::text as y;
+create rule "_RETURN" as on select to rules_fooview_child do instead
+  select 1 as x, 'aaa'::text as y;
+
+drop table rules_fooview cascade;
+
+-- likewise, converting a partitioned table or partition to view is not allowed
 create table rules_fooview (x int, y text) partition by list (x);
 create rule "_RETURN" as on select to rules_fooview do instead
   select 1 as x, 'aaa'::text as y;
 
--- nor can one convert a partition to view
 create table rules_fooview_part partition of rules_fooview for values in (1);
 create rule "_RETURN" as on select to rules_fooview_part do instead
   select 1 as x, 'aaa'::text as y;
 
+drop table rules_fooview;
+
 --
 -- check for planner problems with complex inherited UPDATES
 --