Disallow converting an inheritance child table to a view.
authorTom Lane <[email protected]>
Sat, 6 Feb 2021 20:17:02 +0000 (15:17 -0500)
committerTom Lane <[email protected]>
Sat, 6 Feb 2021 20:17:02 +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/include/catalog/pg_inherits_fn.h
src/test/regress/expected/rules.out
src/test/regress/sql/rules.sql

index 04687c1a3e14dadd1be1cd14db861a4f8ece8677..d0fd1a4170d51aac3740ee7975be2afd7134d8f5 100644 (file)
@@ -254,6 +254,32 @@ has_subclass(Oid relationId)
    return result;
 }
 
+/*
+ * 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)
+{
+   Relation    catalog;
+   SysScanDesc scan;
+   ScanKeyData skey;
+   bool        result;
+
+   catalog = heap_open(InheritsRelationId, AccessShareLock);
+   ScanKeyInit(&skey, Anum_pg_inherits_inhrelid, BTEqualStrategyNumber,
+               F_OIDEQ, ObjectIdGetDatum(relationId));
+   scan = systable_beginscan(catalog, InheritsRelidSeqnoIndexId, true,
+                             NULL, 1, &skey);
+   result = HeapTupleIsValid(systable_getnext(scan));
+   systable_endscan(scan);
+   heap_close(catalog, AccessShareLock);
+
+   return result;
+}
 
 /*
  * Given two type OIDs, determine whether the first is a complex type
index 357847f1f01fcf5bd89cd533d05b7dfec5ac274e..d2dea4e696b68a350967cf9f7c8352eb214aa735 100644 (file)
@@ -25,6 +25,7 @@
 #include "catalog/indexing.h"
 #include "catalog/namespace.h"
 #include "catalog/objectaccess.h"
+#include "catalog/pg_inherits_fn.h"
 #include "catalog/pg_rewrite.h"
 #include "catalog/storage.h"
 #include "commands/policy.h"
@@ -411,12 +412,12 @@ DefineQueryRewrite(char *rulename,
         *
         * 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.)
+        * 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 a 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)
@@ -453,6 +454,12 @@ DefineQueryRewrite(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 3ff1947f3e2426d4198ead905d152e40175a117b..2a8ced5f67da5dccb657e8422e5cb2ef38737efd 100644 (file)
@@ -21,6 +21,7 @@ extern List *find_inheritance_children(Oid parentrelId, LOCKMODE lockmode);
 extern List *find_all_inheritors(Oid parentrelId, LOCKMODE lockmode,
                    List **parents);
 extern bool has_subclass(Oid relationId);
+extern bool has_superclass(Oid relationId);
 extern bool typeInheritsFrom(Oid subclassTypeId, Oid superclassTypeId);
 
 #endif   /* PG_INHERITS_FN_H */
index 615e14fbe484006f5d2f13f165f608b47ab3c648..1d094b995ae1fc110834fa061d4d529e4a22f6cd 100644 (file)
@@ -2457,6 +2457,17 @@ select reltoastrelid, relkind, relfrozenxid
 (1 row)
 
 drop view fooview;
+-- cannot convert an inheritance parent or child to a view, though
+create table fooview (x int, y text);
+create table fooview_child () inherits (fooview);
+create rule "_RETURN" as on select to fooview do instead
+  select 1 as x, 'aaa'::text as y;
+ERROR:  could not convert table "fooview" to a view because it has child tables
+create rule "_RETURN" as on select to fooview_child do instead
+  select 1 as x, 'aaa'::text as y;
+ERROR:  could not convert table "fooview_child" to a view because it has parent tables
+drop table fooview cascade;
+NOTICE:  drop cascades to table fooview_child
 --
 -- check for planner problems with complex inherited UPDATES
 --
index 12cd4a012bc839383ea304f6b4b6f91bf22e78ca..347545063f08b0f06ae5fd406c047f25dbccbe7e 100644 (file)
@@ -898,6 +898,17 @@ select reltoastrelid, relkind, relfrozenxid
 
 drop view fooview;
 
+-- cannot convert an inheritance parent or child to a view, though
+create table fooview (x int, y text);
+create table fooview_child () inherits (fooview);
+
+create rule "_RETURN" as on select to fooview do instead
+  select 1 as x, 'aaa'::text as y;
+create rule "_RETURN" as on select to fooview_child do instead
+  select 1 as x, 'aaa'::text as y;
+
+drop table fooview cascade;
+
 --
 -- check for planner problems with complex inherited UPDATES
 --