Skip to content

Commit 6bc68af

Browse files
committed
Flatten join alias Vars before pulling up targetlist items from a subquery.
pullup_replace_vars()'s decisions about whether a pulled-up replacement expression needs to be wrapped in a PlaceHolderVar depend on the assumption that what looks like a Var behaves like a Var. However, if the Var is a join alias reference, later flattening of join aliases might replace the Var with something that's not a Var at all, and should have been wrapped. To fix, do a forcible pass of flatten_join_alias_vars() on the subquery targetlist before we start to copy items out of it. We'll re-run that processing on the pulled-up expressions later, but that's harmless. Per report from Ken Tanzer; the added regression test case is based on his example. This bug has been there since the PlaceHolderVar mechanism was invented, but has escaped detection because the circumstances that trigger it are fairly narrow. You need a flattenable query underneath an outer join, which contains another flattenable query inside a join of its own, with a dangerous expression (a constant or something else non-strict) in that one's targetlist. Having seen this, I'm wondering if it wouldn't be prudent to do all alias-variable flattening earlier, perhaps even in the rewriter. But that would probably not be a back-patchable change.
1 parent 72dcdc6 commit 6bc68af

File tree

3 files changed

+95
-0
lines changed

3 files changed

+95
-0
lines changed

src/backend/optimizer/prep/prepjointree.c

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -869,6 +869,18 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
869869
return jtnode;
870870
}
871871

872+
/*
873+
* We must flatten any join alias Vars in the subquery's targetlist,
874+
* because pulling up the subquery's subqueries might have changed their
875+
* expansions into arbitrary expressions, which could affect
876+
* pullup_replace_vars' decisions about whether PlaceHolderVar wrappers
877+
* are needed for tlist entries. (Likely it'd be better to do
878+
* flatten_join_alias_vars on the whole query tree at some earlier stage,
879+
* maybe even in the rewriter; but for now let's just fix this case here.)
880+
*/
881+
subquery->targetList = (List *)
882+
flatten_join_alias_vars(subroot, (Node *) subquery->targetList);
883+
872884
/*
873885
* Adjust level-0 varnos in subquery so that we can append its rangetable
874886
* to upper query's. We have to fix the subquery's append_rel_list as

src/test/regress/expected/join.out

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2928,6 +2928,58 @@ select a.unique1, b.unique1, c.unique1, coalesce(b.twothousand, a.twothousand)
29282928
---------+---------+---------+----------
29292929
(0 rows)
29302930

2931+
--
2932+
-- check handling of join aliases when flattening multiple levels of subquery
2933+
--
2934+
explain (verbose, costs off)
2935+
select foo1.join_key as foo1_id, foo3.join_key AS foo3_id, bug_field from
2936+
(values (0),(1)) foo1(join_key)
2937+
left join
2938+
(select join_key, bug_field from
2939+
(select ss1.join_key, ss1.bug_field from
2940+
(select f1 as join_key, 666 as bug_field from int4_tbl i1) ss1
2941+
) foo2
2942+
left join
2943+
(select unique2 as join_key from tenk1 i2) ss2
2944+
using (join_key)
2945+
) foo3
2946+
using (join_key);
2947+
QUERY PLAN
2948+
--------------------------------------------------------------------------
2949+
Nested Loop Left Join
2950+
Output: "*VALUES*".column1, i1.f1, (666)
2951+
Join Filter: ("*VALUES*".column1 = i1.f1)
2952+
-> Values Scan on "*VALUES*"
2953+
Output: "*VALUES*".column1
2954+
-> Materialize
2955+
Output: i1.f1, (666)
2956+
-> Nested Loop Left Join
2957+
Output: i1.f1, 666
2958+
-> Seq Scan on public.int4_tbl i1
2959+
Output: i1.f1
2960+
-> Index Only Scan using tenk1_unique2 on public.tenk1 i2
2961+
Output: i2.unique2
2962+
Index Cond: (i2.unique2 = i1.f1)
2963+
(14 rows)
2964+
2965+
select foo1.join_key as foo1_id, foo3.join_key AS foo3_id, bug_field from
2966+
(values (0),(1)) foo1(join_key)
2967+
left join
2968+
(select join_key, bug_field from
2969+
(select ss1.join_key, ss1.bug_field from
2970+
(select f1 as join_key, 666 as bug_field from int4_tbl i1) ss1
2971+
) foo2
2972+
left join
2973+
(select unique2 as join_key from tenk1 i2) ss2
2974+
using (join_key)
2975+
) foo3
2976+
using (join_key);
2977+
foo1_id | foo3_id | bug_field
2978+
---------+---------+-----------
2979+
0 | 0 | 666
2980+
1 | |
2981+
(2 rows)
2982+
29312983
--
29322984
-- test ability to push constants through outer join clauses
29332985
--

src/test/regress/sql/join.sql

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -801,6 +801,37 @@ select a.unique1, b.unique1, c.unique1, coalesce(b.twothousand, a.twothousand)
801801
from tenk1 a left join tenk1 b on b.thousand = a.unique1 left join tenk1 c on c.unique2 = coalesce(b.twothousand, a.twothousand)
802802
where a.unique2 = 5530 and coalesce(b.twothousand, a.twothousand) = 44;
803803

804+
--
805+
-- check handling of join aliases when flattening multiple levels of subquery
806+
--
807+
808+
explain (verbose, costs off)
809+
select foo1.join_key as foo1_id, foo3.join_key AS foo3_id, bug_field from
810+
(values (0),(1)) foo1(join_key)
811+
left join
812+
(select join_key, bug_field from
813+
(select ss1.join_key, ss1.bug_field from
814+
(select f1 as join_key, 666 as bug_field from int4_tbl i1) ss1
815+
) foo2
816+
left join
817+
(select unique2 as join_key from tenk1 i2) ss2
818+
using (join_key)
819+
) foo3
820+
using (join_key);
821+
822+
select foo1.join_key as foo1_id, foo3.join_key AS foo3_id, bug_field from
823+
(values (0),(1)) foo1(join_key)
824+
left join
825+
(select join_key, bug_field from
826+
(select ss1.join_key, ss1.bug_field from
827+
(select f1 as join_key, 666 as bug_field from int4_tbl i1) ss1
828+
) foo2
829+
left join
830+
(select unique2 as join_key from tenk1 i2) ss2
831+
using (join_key)
832+
) foo3
833+
using (join_key);
834+
804835
--
805836
-- test ability to push constants through outer join clauses
806837
--

0 commit comments

Comments
 (0)