Fix mishandling of FieldSelect-on-whole-row-Var in nested lateral queries.
authorTom Lane <tgl@sss.pgh.pa.us>
Mon, 20 Oct 2014 16:23:48 +0000 (12:23 -0400)
committerTom Lane <tgl@sss.pgh.pa.us>
Mon, 20 Oct 2014 16:23:48 +0000 (12:23 -0400)
If an inline-able SQL function taking a composite argument is used in a
LATERAL subselect, and the composite argument is a lateral reference,
the planner could fail with "variable not found in subplan target list",
as seen in bug #11703 from Karl Bartel.  (The outer function call used in
the bug report and in the committed regression test is not really necessary
to provoke the bug --- you can get it if you manually expand the outer
function into "LATERAL (SELECT inner_function(outer_relation))", too.)

The cause of this is that we generate the reltargetlist for the referenced
relation before doing eval_const_expressions() on the lateral sub-select's
expressions (cf find_lateral_references()), so what's scheduled to be
emitted by the referenced relation is a whole-row Var, not the simplified
single-column Var produced by optimizing the function's FieldSelect on the
whole-row Var.  Then setrefs.c fails to match up that lateral reference to
what's available from the outer scan.

Preserving the FieldSelect optimization in such cases would require either
major planner restructuring (to recursively do expression simplification
on sub-selects much earlier) or some amazingly ugly kluge to change the
reltargetlist of a possibly-already-planned relation.  It seems better
just to skip the optimization when the Var is from an upper query level;
the case is not so common that it's likely anyone will notice a few
wasted cycles.

AFAICT this problem only occurs for uplevel LATERAL references, so
back-patch to 9.3 where LATERAL was added.

src/backend/optimizer/util/clauses.c
src/test/regress/expected/rangefuncs.out
src/test/regress/sql/rangefuncs.sql

index dfef0778afae4ed7eaf6206992ca711f3d555927..11bb3946aa8844696fabb231d60d4f0075d5e669 100644 (file)
@@ -3115,10 +3115,19 @@ eval_const_expressions_mutator(Node *node,
                 * But it can arise while simplifying functions.)  Also, we
                 * can optimize field selection from a RowExpr construct.
                 *
-                * We must however check that the declared type of the field
-                * is still the same as when the FieldSelect was created ---
-                * this can change if someone did ALTER COLUMN TYPE on the
-                * rowtype.
+                * However, replacing a whole-row Var in this way has a
+                * pitfall: if we've already built the reltargetlist for the
+                * source relation, then the whole-row Var is scheduled to be
+                * produced by the relation scan, but the simple Var probably
+                * isn't, which will lead to a failure in setrefs.c.  This is
+                * not a problem when handling simple single-level queries, in
+                * which expression simplification always happens first.  It
+                * is a risk for lateral references from subqueries, though.
+                * To avoid such failures, don't optimize uplevel references.
+                *
+                * We must also check that the declared type of the field is
+                * still the same as when the FieldSelect was created --- this
+                * can change if someone did ALTER COLUMN TYPE on the rowtype.
                 */
                FieldSelect *fselect = (FieldSelect *) node;
                FieldSelect *newfselect;
@@ -3127,7 +3136,8 @@ eval_const_expressions_mutator(Node *node,
                arg = eval_const_expressions_mutator((Node *) fselect->arg,
                                                     context);
                if (arg && IsA(arg, Var) &&
-                   ((Var *) arg)->varattno == InvalidAttrNumber)
+                   ((Var *) arg)->varattno == InvalidAttrNumber &&
+                   ((Var *) arg)->varlevelsup == 0)
                {
                    if (rowtype_field_matches(((Var *) arg)->vartype,
                                              fselect->fieldnum,
index 8c61c0c15e097487c40392b424b7eb47b8466e93..91acd52062d0f4045c14dc986e5aacdcfc72b0d0 100644 (file)
@@ -944,3 +944,55 @@ FROM
   3 | FROM 10000000876 | from 10000000876
 (3 rows)
 
+-- check whole-row-Var handling in nested lateral functions (bug #11703)
+create function extractq2(t int8_tbl) returns int8 as $$
+  select t.q2
+$$ language sql immutable;
+explain (verbose, costs off)
+select x from int8_tbl, extractq2(int8_tbl) f(x);
+                QUERY PLAN                
+------------------------------------------
+ Nested Loop
+   Output: f.x
+   ->  Seq Scan on public.int8_tbl
+         Output: int8_tbl.q1, int8_tbl.q2
+   ->  Function Scan on f
+         Output: f.x
+         Function Call: int8_tbl.q2
+(7 rows)
+
+select x from int8_tbl, extractq2(int8_tbl) f(x);
+         x         
+-------------------
+               456
+  4567890123456789
+               123
+  4567890123456789
+ -4567890123456789
+(5 rows)
+
+create function extractq2_2(t int8_tbl) returns table(ret1 int8) as $$
+  select extractq2(t)
+$$ language sql immutable;
+explain (verbose, costs off)
+select x from int8_tbl, extractq2_2(int8_tbl) f(x);
+            QUERY PLAN             
+-----------------------------------
+ Nested Loop
+   Output: ((int8_tbl.*).q2)
+   ->  Seq Scan on public.int8_tbl
+         Output: int8_tbl.*
+   ->  Result
+         Output: (int8_tbl.*).q2
+(6 rows)
+
+select x from int8_tbl, extractq2_2(int8_tbl) f(x);
+         x         
+-------------------
+               456
+  4567890123456789
+               123
+  4567890123456789
+ -4567890123456789
+(5 rows)
+
index 1d0b0c82d4e3a2e6ca9a274ede054389b739ef21..b1367ebaa0cf82b77770703cd45311200778425b 100644 (file)
@@ -464,3 +464,23 @@ SELECT *,
         END)
 FROM
   (VALUES (1,''), (2,'0000000049404'), (3,'FROM 10000000876')) v(id, str);
+
+-- check whole-row-Var handling in nested lateral functions (bug #11703)
+
+create function extractq2(t int8_tbl) returns int8 as $$
+  select t.q2
+$$ language sql immutable;
+
+explain (verbose, costs off)
+select x from int8_tbl, extractq2(int8_tbl) f(x);
+
+select x from int8_tbl, extractq2(int8_tbl) f(x);
+
+create function extractq2_2(t int8_tbl) returns table(ret1 int8) as $$
+  select extractq2(t)
+$$ language sql immutable;
+
+explain (verbose, costs off)
+select x from int8_tbl, extractq2_2(int8_tbl) f(x);
+
+select x from int8_tbl, extractq2_2(int8_tbl) f(x);