]> git.ipfire.org Git - thirdparty/postgresql.git/commitdiff
Cope with lateral references in the quals of a subquery RTE.
authorTom Lane <tgl@sss.pgh.pa.us>
Tue, 14 Jul 2020 00:38:21 +0000 (20:38 -0400)
committerTom Lane <tgl@sss.pgh.pa.us>
Tue, 14 Jul 2020 00:38:21 +0000 (20:38 -0400)
The qual pushdown logic assumed that all Vars in a restriction clause
must be Vars referencing subquery outputs; but since we introduced
LATERAL, it's possible for such a Var to be a lateral reference instead.
This led to an assertion failure in debug builds.  In a non-debug
build, there might be no ill effects (if qual_is_pushdown_safe decided
the qual was unsafe anyway), or we could get failures later due to
construction of an invalid plan.  I've not gone to much length to
characterize the possible failures, but at least segfaults in the
executor have been observed.

Given that this has been busted since 9.3 and it took this long for
anybody to notice, I judge that the case isn't worth going to great
lengths to optimize.  Hence, fix by just teaching qual_is_pushdown_safe
that such quals are unsafe to push down, matching the previous behavior
when it accidentally didn't fail.

Per report from Tom Ellis.  Back-patch to all supported branches.

Discussion: https://postgr.es/m/20200713175124.GQ8220@cloudinit-builder

src/backend/optimizer/path/allpaths.c
src/test/regress/expected/subselect.out
src/test/regress/sql/subselect.sql

index 0513bb2ab8189bd561cd8da55adb3406eab34af9..62fa2c8b0da79e47884b0ac041dea95ea94822df 100644 (file)
@@ -2599,8 +2599,10 @@ qual_is_pushdown_safe(Query *subquery, Index rti, Node *qual,
        Assert(!contain_window_function(qual));
 
        /*
-        * Examine all Vars used in clause; since it's a restriction clause, all
-        * such Vars must refer to subselect output columns.
+        * Examine all Vars used in clause.  Since it's a restriction clause, all
+        * such Vars must refer to subselect output columns ... unless this is
+        * part of a LATERAL subquery, in which case there could be lateral
+        * references.
         */
        vars = pull_var_clause(qual, PVC_INCLUDE_PLACEHOLDERS);
        foreach(vl, vars)
@@ -2620,7 +2622,19 @@ qual_is_pushdown_safe(Query *subquery, Index rti, Node *qual,
                        break;
                }
 
-               Assert(var->varno == rti);
+               /*
+                * Punt if we find any lateral references.  It would be safe to push
+                * these down, but we'd have to convert them into outer references,
+                * which subquery_push_qual lacks the infrastructure to do.  The case
+                * arises so seldom that it doesn't seem worth working hard on.
+                */
+               if (var->varno != rti)
+               {
+                       safe = false;
+                       break;
+               }
+
+               /* Subqueries have no system columns */
                Assert(var->varattno >= 0);
 
                /* Check point 4 */
index 52a498ca7fbb8a884543c1bf0dce43d8c3130c9c..363755edad745926672ab4f782df18391c5023e9 100644 (file)
@@ -931,6 +931,59 @@ from int4_tbl;
  (4,5,6.0)
 (5 rows)
 
+--
+-- Check for sane handling of a lateral reference in a subquery's quals
+-- (most of the complication here is to prevent the test case from being
+-- flattened too much)
+--
+explain (verbose, costs off)
+select * from
+    int4_tbl i4,
+    lateral (
+        select i4.f1 > 1 as b, 1 as id
+        from (select random() order by 1) as t1
+      union all
+        select true as b, 2 as id
+    ) as t2
+where b and f1 >= 0;
+                 QUERY PLAN                 
+--------------------------------------------
+ Nested Loop
+   Output: i4.f1, ((i4.f1 > 1)), (1)
+   ->  Seq Scan on public.int4_tbl i4
+         Output: i4.f1
+         Filter: (i4.f1 >= 0)
+   ->  Append
+         ->  Subquery Scan on t1
+               Output: (i4.f1 > 1), 1
+               Filter: (i4.f1 > 1)
+               ->  Sort
+                     Output: (random())
+                     Sort Key: (random())
+                     ->  Result
+                           Output: random()
+         ->  Result
+               Output: true, 2
+(16 rows)
+
+select * from
+    int4_tbl i4,
+    lateral (
+        select i4.f1 > 1 as b, 1 as id
+        from (select random() order by 1) as t1
+      union all
+        select true as b, 2 as id
+    ) as t2
+where b and f1 >= 0;
+     f1     | b | id 
+------------+---+----
+          0 | t |  2
+     123456 | t |  1
+     123456 | t |  2
+ 2147483647 | t |  1
+ 2147483647 | t |  2
+(5 rows)
+
 --
 -- Check that volatile quals aren't pushed down past a DISTINCT:
 -- nextval() should not be called more than the nominal number of times
index 3685f2849ae3e02a88dd5ef78e2f5888cf344944..fd74fbc6fa426b3a06564cd68c6ae176c830aecb 100644 (file)
@@ -498,6 +498,32 @@ select (select q from
          ) q )
 from int4_tbl;
 
+--
+-- Check for sane handling of a lateral reference in a subquery's quals
+-- (most of the complication here is to prevent the test case from being
+-- flattened too much)
+--
+explain (verbose, costs off)
+select * from
+    int4_tbl i4,
+    lateral (
+        select i4.f1 > 1 as b, 1 as id
+        from (select random() order by 1) as t1
+      union all
+        select true as b, 2 as id
+    ) as t2
+where b and f1 >= 0;
+
+select * from
+    int4_tbl i4,
+    lateral (
+        select i4.f1 > 1 as b, 1 as id
+        from (select random() order by 1) as t1
+      union all
+        select true as b, 2 as id
+    ) as t2
+where b and f1 >= 0;
+
 --
 -- Check that volatile quals aren't pushed down past a DISTINCT:
 -- nextval() should not be called more than the nominal number of times