Skip to content

Commit 4e54685

Browse files
committed
Fix mishandling of FieldSelect-on-whole-row-Var in nested lateral queries.
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.
1 parent 7804f35 commit 4e54685

File tree

3 files changed

+87
-5
lines changed

3 files changed

+87
-5
lines changed

src/backend/optimizer/util/clauses.c

+15-5
Original file line numberDiff line numberDiff line change
@@ -3115,10 +3115,19 @@ eval_const_expressions_mutator(Node *node,
31153115
* But it can arise while simplifying functions.) Also, we
31163116
* can optimize field selection from a RowExpr construct.
31173117
*
3118-
* We must however check that the declared type of the field
3119-
* is still the same as when the FieldSelect was created ---
3120-
* this can change if someone did ALTER COLUMN TYPE on the
3121-
* rowtype.
3118+
* However, replacing a whole-row Var in this way has a
3119+
* pitfall: if we've already built the reltargetlist for the
3120+
* source relation, then the whole-row Var is scheduled to be
3121+
* produced by the relation scan, but the simple Var probably
3122+
* isn't, which will lead to a failure in setrefs.c. This is
3123+
* not a problem when handling simple single-level queries, in
3124+
* which expression simplification always happens first. It
3125+
* is a risk for lateral references from subqueries, though.
3126+
* To avoid such failures, don't optimize uplevel references.
3127+
*
3128+
* We must also check that the declared type of the field is
3129+
* still the same as when the FieldSelect was created --- this
3130+
* can change if someone did ALTER COLUMN TYPE on the rowtype.
31223131
*/
31233132
FieldSelect *fselect = (FieldSelect *) node;
31243133
FieldSelect *newfselect;
@@ -3127,7 +3136,8 @@ eval_const_expressions_mutator(Node *node,
31273136
arg = eval_const_expressions_mutator((Node *) fselect->arg,
31283137
context);
31293138
if (arg && IsA(arg, Var) &&
3130-
((Var *) arg)->varattno == InvalidAttrNumber)
3139+
((Var *) arg)->varattno == InvalidAttrNumber &&
3140+
((Var *) arg)->varlevelsup == 0)
31313141
{
31323142
if (rowtype_field_matches(((Var *) arg)->vartype,
31333143
fselect->fieldnum,

src/test/regress/expected/rangefuncs.out

+52
Original file line numberDiff line numberDiff line change
@@ -944,3 +944,55 @@ FROM
944944
3 | FROM 10000000876 | from 10000000876
945945
(3 rows)
946946

947+
-- check whole-row-Var handling in nested lateral functions (bug #11703)
948+
create function extractq2(t int8_tbl) returns int8 as $$
949+
select t.q2
950+
$$ language sql immutable;
951+
explain (verbose, costs off)
952+
select x from int8_tbl, extractq2(int8_tbl) f(x);
953+
QUERY PLAN
954+
------------------------------------------
955+
Nested Loop
956+
Output: f.x
957+
-> Seq Scan on public.int8_tbl
958+
Output: int8_tbl.q1, int8_tbl.q2
959+
-> Function Scan on f
960+
Output: f.x
961+
Function Call: int8_tbl.q2
962+
(7 rows)
963+
964+
select x from int8_tbl, extractq2(int8_tbl) f(x);
965+
x
966+
-------------------
967+
456
968+
4567890123456789
969+
123
970+
4567890123456789
971+
-4567890123456789
972+
(5 rows)
973+
974+
create function extractq2_2(t int8_tbl) returns table(ret1 int8) as $$
975+
select extractq2(t)
976+
$$ language sql immutable;
977+
explain (verbose, costs off)
978+
select x from int8_tbl, extractq2_2(int8_tbl) f(x);
979+
QUERY PLAN
980+
-----------------------------------
981+
Nested Loop
982+
Output: ((int8_tbl.*).q2)
983+
-> Seq Scan on public.int8_tbl
984+
Output: int8_tbl.*
985+
-> Result
986+
Output: (int8_tbl.*).q2
987+
(6 rows)
988+
989+
select x from int8_tbl, extractq2_2(int8_tbl) f(x);
990+
x
991+
-------------------
992+
456
993+
4567890123456789
994+
123
995+
4567890123456789
996+
-4567890123456789
997+
(5 rows)
998+

src/test/regress/sql/rangefuncs.sql

+20
Original file line numberDiff line numberDiff line change
@@ -464,3 +464,23 @@ SELECT *,
464464
END)
465465
FROM
466466
(VALUES (1,''), (2,'0000000049404'), (3,'FROM 10000000876')) v(id, str);
467+
468+
-- check whole-row-Var handling in nested lateral functions (bug #11703)
469+
470+
create function extractq2(t int8_tbl) returns int8 as $$
471+
select t.q2
472+
$$ language sql immutable;
473+
474+
explain (verbose, costs off)
475+
select x from int8_tbl, extractq2(int8_tbl) f(x);
476+
477+
select x from int8_tbl, extractq2(int8_tbl) f(x);
478+
479+
create function extractq2_2(t int8_tbl) returns table(ret1 int8) as $$
480+
select extractq2(t)
481+
$$ language sql immutable;
482+
483+
explain (verbose, costs off)
484+
select x from int8_tbl, extractq2_2(int8_tbl) f(x);
485+
486+
select x from int8_tbl, extractq2_2(int8_tbl) f(x);

0 commit comments

Comments
 (0)