diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c index 6248f9caaaa..c12f3e6665d 100644 --- a/src/backend/optimizer/prep/prepjointree.c +++ b/src/backend/optimizer/prep/prepjointree.c @@ -3459,14 +3459,17 @@ remove_result_refs(PlannerInfo *root, int varno, Node *newjtloc) * * find_dependent_phvs should be used when we want to see if there are * any such PHVs anywhere in the Query. Another use-case is to see if - * a subtree of the join tree contains such PHVs; but for that, we have + * a subtree of the join tree needs such PHVs; but for that, we have * to look not only at the join tree nodes themselves but at the - * referenced RTEs. For that, use find_dependent_phvs_in_jointree. + * referenced RTEs, and also inside any PHVs elsewhere in the Query that + * are evaluated within the subtree. For that, use + * find_dependent_phvs_in_jointree. */ typedef struct { Relids relids; + Relids siblingrels; /* base RT indexes of target's sibling rels */ int sublevels_up; } find_dependent_phvs_context; @@ -3507,6 +3510,50 @@ find_dependent_phvs_walker(Node *node, (void *) context); } +/* + * Look for PlaceHolderVars that are to be evaluated at some but not all of + * context->siblingrels and contain a dependent PHV in their expressions. + */ +static bool +find_nested_dependent_phvs_walker(Node *node, + find_dependent_phvs_context *context) +{ + if (node == NULL) + return false; + if (IsA(node, PlaceHolderVar)) + { + PlaceHolderVar *phv = (PlaceHolderVar *) node; + + if (phv->phlevelsup == context->sublevels_up) + { + bool match; + + match = (!bms_is_subset(context->relids, phv->phrels) && + bms_overlap(phv->phrels, context->siblingrels) && + !bms_is_subset(context->siblingrels, phv->phrels)); + if (match && + find_dependent_phvs_walker((Node *) phv->phexpr, context)) + return true; + } + /* fall through to examine children */ + } + if (IsA(node, Query)) + { + /* Recurse into subselects */ + bool result; + + context->sublevels_up++; + result = query_tree_walker((Query *) node, + find_nested_dependent_phvs_walker, + context, 0); + context->sublevels_up--; + return result; + } + + return expression_tree_walker(node, find_nested_dependent_phvs_walker, + context); +} + static bool find_dependent_phvs(PlannerInfo *root, int varno) { @@ -3517,6 +3564,7 @@ find_dependent_phvs(PlannerInfo *root, int varno) return false; context.relids = bms_make_singleton(varno); + context.siblingrels = NULL; context.sublevels_up = 0; return query_tree_walker(root->parse, @@ -3537,6 +3585,7 @@ find_dependent_phvs_in_jointree(PlannerInfo *root, Node *node, int varno) return false; context.relids = bms_make_singleton(varno); + context.siblingrels = NULL; context.sublevels_up = 0; /* @@ -3566,6 +3615,19 @@ find_dependent_phvs_in_jointree(PlannerInfo *root, Node *node, int varno) return true; } + /* + * Lastly, check for PHVs that are evaluated at some but not all of the + * sibling rels and contain a dependent PHV. Removing the RTE_RESULT + * would move the dependent PHV up to the join of all the sibling rels, so + * it would not yet be available where the outer PHV is evaluated. The + * outer PHV can appear anywhere in the query (e.g., in the targetlist), + * so search the whole query. + */ + context.siblingrels = bms_del_member(subrelids, varno); + if (query_tree_walker(root->parse, find_nested_dependent_phvs_walker, + &context, 0)) + return true; + return false; } diff --git a/src/test/regress/expected/join.out b/src/test/regress/expected/join.out index 117605221a3..01334e3e224 100644 --- a/src/test/regress/expected/join.out +++ b/src/test/regress/expected/join.out @@ -3420,6 +3420,89 @@ select * from 1 | 2 | 2 (1 row) +-- Nor an RTE_RESULT whose PHV is nested in a PHV evaluated at some but not +-- all of the sibling rels +explain (verbose, costs off) +select ss.z +from (select 1) s1 left join + ((select 1) s2 left join (select 1 as x) v on true + cross join (lateral (select coalesce(v.x, 0) as z, f1 from int4_tbl) ss + cross join lateral (select ss.f1 offset 0) g)) + on true; + QUERY PLAN +----------------------------------------------------------- + Nested Loop Left Join + Output: (COALESCE((1), 0)) + -> Result + -> Nested Loop + Output: (COALESCE((1), 0)) + -> Nested Loop + Output: int4_tbl.f1, (COALESCE((1), 0)) + -> Result + Output: 1 + -> Seq Scan on public.int4_tbl + Output: int4_tbl.f1, COALESCE((1), 0) + -> Result + Output: NULL::integer +(13 rows) + +select ss.z +from (select 1) s1 left join + ((select 1) s2 left join (select 1 as x) v on true + cross join (lateral (select coalesce(v.x, 0) as z, f1 from int4_tbl) ss + cross join lateral (select ss.f1 offset 0) g)) + on true; + z +--- + 1 + 1 + 1 + 1 + 1 +(5 rows) + +-- Same, with the PHVs coming from grouping sets +explain (costs off) +select s1.x +from (values (1)) as v(x), + lateral (select v.x + 1, 2) as s1(x, y) + cross join generate_series(s1.y, s1.y) as g +group by grouping sets (1, 1); + QUERY PLAN +------------------------------------------------ + HashAggregate + Hash Key: (((1) + 1)) + Hash Key: (((1) + 1)) + -> Nested Loop + -> Nested Loop + -> Result + -> Result + -> Function Scan on generate_series g +(8 rows) + +select s1.x +from (values (1)) as v(x), + lateral (select v.x + 1, 2) as s1(x, y) + cross join generate_series(s1.y, s1.y) as g +group by grouping sets (1, 1); + x +--- + 2 + 2 +(2 rows) + +-- But we can remove it if the outer PHV covers all of the sibling rels +explain (costs off) +select s.y from (values (1)) v(x), lateral (select v.x + f1 as y from int4_tbl) s +group by grouping sets (1, ()); + QUERY PLAN +------------------------------- + MixedAggregate + Hash Key: (1 + int4_tbl.f1) + Group Key: () + -> Seq Scan on int4_tbl +(4 rows) + -- Test proper handling of appendrel PHVs during useless-RTE removal explain (costs off) select * from diff --git a/src/test/regress/sql/join.sql b/src/test/regress/sql/join.sql index 66bc8456596..b19219b5bf2 100644 --- a/src/test/regress/sql/join.sql +++ b/src/test/regress/sql/join.sql @@ -1091,6 +1091,40 @@ select * from (select 1 as x) ss1 left join (select 2 as y) ss2 on (true), lateral (select ss2.y as z limit 1) ss3; +-- Nor an RTE_RESULT whose PHV is nested in a PHV evaluated at some but not +-- all of the sibling rels +explain (verbose, costs off) +select ss.z +from (select 1) s1 left join + ((select 1) s2 left join (select 1 as x) v on true + cross join (lateral (select coalesce(v.x, 0) as z, f1 from int4_tbl) ss + cross join lateral (select ss.f1 offset 0) g)) + on true; +select ss.z +from (select 1) s1 left join + ((select 1) s2 left join (select 1 as x) v on true + cross join (lateral (select coalesce(v.x, 0) as z, f1 from int4_tbl) ss + cross join lateral (select ss.f1 offset 0) g)) + on true; + +-- Same, with the PHVs coming from grouping sets +explain (costs off) +select s1.x +from (values (1)) as v(x), + lateral (select v.x + 1, 2) as s1(x, y) + cross join generate_series(s1.y, s1.y) as g +group by grouping sets (1, 1); +select s1.x +from (values (1)) as v(x), + lateral (select v.x + 1, 2) as s1(x, y) + cross join generate_series(s1.y, s1.y) as g +group by grouping sets (1, 1); + +-- But we can remove it if the outer PHV covers all of the sibling rels +explain (costs off) +select s.y from (values (1)) v(x), lateral (select v.x + f1 as y from int4_tbl) s +group by grouping sets (1, ()); + -- Test proper handling of appendrel PHVs during useless-RTE removal explain (costs off) select * from