From 6b5428489be17d6d1e281e9a720769af163d3470 Mon Sep 17 00:00:00 2001 From: Richard Guo Date: Sun, 30 Aug 2026 08:21:21 +0900 Subject: [PATCH v3] Fix stale copies of PHVs in subqueries When a subquery references an output of another subquery that gets pulled up, and that output must be wrapped in a PlaceHolderVar because of an intermediate outer join, the PHV expression is pushed down into the subquery. That copy is not preprocessed along with the outer query's expressions, so the two copies can diverge. This used to be harmless, but since commit 2ebf25e7d join removal edits the whole query tree, walking into subqueries, and can trip an assert in ChangeVarNodes if it removes a rel whose Var survives only in such a copy. To fix, preprocess these copies at their owning query level, early in subquery_planner, before anything can consume them (in particular before SubLinks are turned into SubPlans). This covers copies pushed into both LATERAL subquery RTEs and SubLink subselects, and handles nested copies innermost-first. extract_lateral_references no longer preprocesses the copies it pulls out. Correspondingly, the subquery's own processing must leave the contents of an upper-level PHV alone, since the owning level has already preprocessed them: eval_const_expressions returns such a PHV unchanged, and flatten_join_alias_vars no longer recurses into it. --- src/backend/optimizer/plan/initsplan.c | 15 +- src/backend/optimizer/plan/planner.c | 96 +++++++++++-- src/backend/optimizer/plan/subselect.c | 13 +- src/backend/optimizer/util/clauses.c | 35 +++-- src/backend/optimizer/util/paramassign.c | 23 ++- src/backend/optimizer/util/var.c | 28 +++- src/include/optimizer/planner.h | 1 - src/test/regress/expected/groupingsets.out | 4 +- src/test/regress/expected/join.out | 160 ++++++++++++++++++++- src/test/regress/sql/join.sql | 47 ++++++ src/tools/pgindent/typedefs.list | 1 + 11 files changed, 355 insertions(+), 68 deletions(-) diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c index fb6f81453ea..8893e37c8f7 100644 --- a/src/backend/optimizer/plan/initsplan.c +++ b/src/backend/optimizer/plan/initsplan.c @@ -29,7 +29,6 @@ #include "optimizer/paths.h" #include "optimizer/placeholder.h" #include "optimizer/planmain.h" -#include "optimizer/planner.h" #include "optimizer/restrictinfo.h" #include "parser/analyze.h" #include "rewrite/rewriteManip.h" @@ -1132,17 +1131,13 @@ extract_lateral_references(PlannerInfo *root, RelOptInfo *brel, Index rtindex) PlaceHolderVar *phv = (PlaceHolderVar *) node; int levelsup = phv->phlevelsup; - /* Have to work harder to adjust the contained expression too */ - if (levelsup != 0) - IncrementVarSublevelsUp(node, -levelsup, 0); - /* - * If we pulled the PHV out of a subquery RTE, its expression - * needs to be preprocessed. subquery_planner() already did this - * for level-zero PHVs in function and values RTEs, though. + * Have to work harder to adjust the contained expression too. + * (Its expression has already been preprocessed by + * subquery_planner(), so we must not do that again here.) */ - if (levelsup > 0) - phv->phexpr = preprocess_phv_expression(root, phv->phexpr); + if (levelsup != 0) + IncrementVarSublevelsUp(node, -levelsup, 0); } else Assert(false); diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index c3c158a253d..8cd2e8a71eb 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -144,8 +144,18 @@ typedef struct Index group_rtindex; } having_grouping_ctx; +/* Context for preprocess_subquery_phvs_walker */ +typedef struct +{ + PlannerInfo *root; + int sublevels_up; +} preprocess_subquery_phvs_context; + /* Local functions */ static Node *preprocess_expression(PlannerInfo *root, Node *expr, int kind); +static void preprocess_subquery_phvs(PlannerInfo *root); +static bool preprocess_subquery_phvs_walker(Node *node, + preprocess_subquery_phvs_context *context); static void preprocess_qual_conditions(PlannerInfo *root, Node *jtnode); static Bitmapset *find_having_conflicts(Query *parse, Index group_rtindex); static Oid having_var_grouping_eqop(Var *var, void *context); @@ -1022,6 +1032,14 @@ subquery_planner(PlannerGlobal *glob, Query *parse, char *plan_name, */ root->hasHavingQual = (parse->havingQual != NULL); + /* + * Preprocess any PlaceHolderVars of our level that were pushed down into + * subqueries. This must happen before anything can consume those copies, + * in particular before SubLinks below are turned into SubPlans. + */ + if (root->glob->lastPHId != 0) + preprocess_subquery_phvs(root); + /* * Do expression preprocessing on targetlist and quals, as well as other * random expressions in the querytree. Note that we do not need to @@ -1645,20 +1663,74 @@ group_var_eqop(Query *parse, Var *var) } /* - * preprocess_phv_expression - * Do preprocessing on a PlaceHolderVar expression that's been pulled up. - * - * If a LATERAL subquery references an output of another subquery, and that - * output must be wrapped in a PlaceHolderVar because of an intermediate outer - * join, then we'll push the PlaceHolderVar expression down into the subquery - * and later pull it back up during find_lateral_references, which runs after - * subquery_planner has preprocessed all the expressions that were in the - * current query level to start with. So we need to preprocess it then. + * preprocess_subquery_phvs + * Preprocess copies of this level's PlaceHolderVars that were pushed + * down into subqueries. + * + * When a subquery (a LATERAL RTE or a SubLink's subselect) references a + * pulled-up output that must be wrapped in a PlaceHolderVar, the PHV + * expression is pushed down into the subquery. The subquery's own planning + * leaves that copy alone, since it belongs to our level, so we need to + * preprocess it. We modify the PHVs in place, temporarily adjusting each to + * our level, and handle nested copies innermost-first. */ -Expr * -preprocess_phv_expression(PlannerInfo *root, Expr *expr) +static void +preprocess_subquery_phvs(PlannerInfo *root) +{ + preprocess_subquery_phvs_context context; + + context.root = root; + context.sublevels_up = 0; + (void) query_tree_walker(root->parse, preprocess_subquery_phvs_walker, + &context, 0); +} + +static bool +preprocess_subquery_phvs_walker(Node *node, + preprocess_subquery_phvs_context *context) { - return (Expr *) preprocess_expression(root, (Node *) expr, EXPRKIND_PHV); + if (node == NULL) + return false; + if (IsA(node, Query)) + { + bool result; + + context->sublevels_up++; + result = query_tree_walker((Query *) node, + preprocess_subquery_phvs_walker, + context, 0); + context->sublevels_up--; + return result; + } + if (IsA(node, PlaceHolderVar)) + { + PlaceHolderVar *phv = (PlaceHolderVar *) node; + + /* Handle any nested copies within the expression first */ + (void) expression_tree_walker(node, preprocess_subquery_phvs_walker, + context); + + /* + * Is this a copy of one of our PHVs that is pushed down into a + * subquery? + */ + if (context->sublevels_up > 0 && + phv->phlevelsup == context->sublevels_up) + { + int levelsup = phv->phlevelsup; + Node *expr; + + /* Adjust the expression to our level, preprocess, adjust back */ + expr = copyObject((Node *) phv->phexpr); + IncrementVarSublevelsUp(expr, -levelsup, 0); + expr = preprocess_expression(context->root, expr, EXPRKIND_PHV); + IncrementVarSublevelsUp(expr, levelsup, 0); + phv->phexpr = (Expr *) expr; + } + return false; + } + return expression_tree_walker(node, preprocess_subquery_phvs_walker, + context); } /*-------------------- diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c index 2cf5c15a309..ae7c489b432 100644 --- a/src/backend/optimizer/plan/subselect.c +++ b/src/backend/optimizer/plan/subselect.c @@ -371,13 +371,14 @@ build_subplan(PlannerInfo *root, Plan *plan, Path *path, * already been adjusted to have the correct varlevelsup, phlevelsup, * agglevelsup, or retlevelsup. * - * If it's a PlaceHolderVar, Aggref, GroupingFunc, or ReturningExpr, - * its arguments might contain SubLinks, which have not yet been - * processed (see the comments for SS_replace_correlation_vars). Do - * that now. + * If it's an Aggref, GroupingFunc, or ReturningExpr, its arguments + * might contain SubLinks, which have not yet been processed (see the + * comments for SS_replace_correlation_vars). Do that now. A + * PlaceHolderVar needs no such treatment: subquery_planner already + * preprocessed the PHVs of its owning level, so its expression is + * fully processed and may already contain SubPlans. */ - if (IsA(arg, PlaceHolderVar) || - IsA(arg, Aggref) || + if (IsA(arg, Aggref) || IsA(arg, GroupingFunc) || IsA(arg, ReturningExpr)) arg = SS_process_sublinks(root, arg, false); diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c index 55cebe4a74b..a83b8bdd0ac 100644 --- a/src/backend/optimizer/util/clauses.c +++ b/src/backend/optimizer/util/clauses.c @@ -3407,9 +3407,8 @@ eval_const_expressions_mutator(Node *node, /* * Return a SubPlan unchanged --- too late to do anything with it. - * - * XXX should we ereport() here instead? Probably this routine - * should never be invoked after SubPlan creation. + * This can happen in estimation mode, which runs after SubPlans + * have been created. */ return node; case T_RelabelType: @@ -4215,20 +4214,28 @@ eval_const_expressions_mutator(Node *node, return (Node *) newcdomain; } case T_PlaceHolderVar: - - /* - * In estimation mode, just strip the PlaceHolderVar node - * altogether; this amounts to estimating that the contained value - * won't be forced to null by an outer join. In regular mode we - * just use the default behavior (ie, simplify the expression but - * leave the PlaceHolderVar node intact). - */ - if (context->estimate) { PlaceHolderVar *phv = (PlaceHolderVar *) node; - return eval_const_expressions_mutator((Node *) phv->phexpr, - context); + /* + * Leave a PHV of an upper query level alone: its expression + * belongs to that level, which has already preprocessed it. + * But we do copy the subtree, just to conform to this + * function's API spec. + */ + if (phv->phlevelsup > 0) + return copyObject(node); + + /* + * In estimation mode, just strip the PlaceHolderVar node + * altogether; this amounts to estimating that the contained + * value won't be forced to null by an outer join. In regular + * mode we just use the default behavior (ie, simplify the + * expression but leave the PlaceHolderVar node intact). + */ + if (context->estimate) + return eval_const_expressions_mutator((Node *) phv->phexpr, + context); } break; case T_ConvertRowtypeExpr: diff --git a/src/backend/optimizer/util/paramassign.c b/src/backend/optimizer/util/paramassign.c index 222de1450b2..6a1be4551bd 100644 --- a/src/backend/optimizer/util/paramassign.c +++ b/src/backend/optimizer/util/paramassign.c @@ -677,19 +677,18 @@ identify_current_nestloop_params(PlannerInfo *root, /* * Deal with an edge case: if the PHV was pulled up out of a * subquery and it contains a subquery that was originally - * pushed down from this query level, then that will still be - * represented as a SubLink, because SS_process_sublinks won't - * recurse into outer PHVs, so it didn't get transformed - * during expression preprocessing in the subquery. We need a - * version of the PHV that has a SubPlan, which we can get - * from the current query level's placeholder_list. This is - * quite grotty of course, but dealing with it earlier in the - * handling of subplan params would be just as grotty, and it - * might end up being a waste of cycles if we don't decide to - * treat the PHV as a NestLoopParam. (Perhaps that whole - * mechanism should be redesigned someday, but today is not - * that day.) + * pushed down from this query level, then the copy of the PHV + * we got from the subquery has its own SubPlan for that, + * distinct from the one in the current query level's + * placeholder_list. We'd rather use the placeholder_list's + * version of the PHV, so that we don't end up evaluating + * duplicate SubPlans. + * + * Note that the subquery's copy cannot contain any SubLinks: + * subquery_planner preprocessed the PHVs of this query level + * within the subquery before the subquery was planned. */ + Assert(!checkExprHasSubLink((Node *) phv)); if (root->parse->hasSubLinks) { phv = copyObject(phinfo->ph_var); diff --git a/src/backend/optimizer/util/var.c b/src/backend/optimizer/util/var.c index 907a255c36f..c061bb5c4ab 100644 --- a/src/backend/optimizer/util/var.c +++ b/src/backend/optimizer/util/var.c @@ -932,12 +932,30 @@ flatten_join_alias_vars_mutator(Node *node, } if (IsA(node, PlaceHolderVar)) { - /* Copy the PlaceHolderVar node with correct mutation of subnodes */ - PlaceHolderVar *phv; + PlaceHolderVar *phv = (PlaceHolderVar *) node; + + /* + * Don't recurse into a PHV of an outer query level: its expression is + * already preprocessed by that level and may contain SubPlans, and it + * holds no join aliases of the target level anyway. A PHV above the + * target level needs nothing fixed, so return it as-is; a pushed-down + * copy still needs its relid sets fixed below, so shallow-copy it + * instead of recursing. + */ + if (phv->phlevelsup > context->sublevels_up) + return node; /* no need to copy, really */ - phv = (PlaceHolderVar *) expression_tree_mutator(node, - flatten_join_alias_vars_mutator, - context); + if (phv->phlevelsup > 0) + { + PlaceHolderVar *newphv = makeNode(PlaceHolderVar); + + memcpy(newphv, phv, sizeof(PlaceHolderVar)); + phv = newphv; + } + else + phv = (PlaceHolderVar *) expression_tree_mutator(node, + flatten_join_alias_vars_mutator, + context); /* now fix PlaceHolderVar's relid sets */ if (phv->phlevelsup == context->sublevels_up) { diff --git a/src/include/optimizer/planner.h b/src/include/optimizer/planner.h index 9c4950b340f..d181c2f143c 100644 --- a/src/include/optimizer/planner.h +++ b/src/include/optimizer/planner.h @@ -77,7 +77,6 @@ extern void mark_partial_aggref(Aggref *agg, AggSplit aggsplit); extern Path *get_cheapest_fractional_path(RelOptInfo *rel, double tuple_fraction); -extern Expr *preprocess_phv_expression(PlannerInfo *root, Expr *expr); extern RelOptInfo *create_unique_paths(PlannerInfo *root, RelOptInfo *rel, SpecialJoinInfo *sjinfo); diff --git a/src/test/regress/expected/groupingsets.out b/src/test/regress/expected/groupingsets.out index c3f00771f6e..08d5ce3156d 100644 --- a/src/test/regress/expected/groupingsets.out +++ b/src/test/regress/expected/groupingsets.out @@ -2387,12 +2387,12 @@ select (select grouping(v1)) from (values ((select 1))) v(v1) group by cube(v1); MixedAggregate Hash Key: (InitPlan expr_3).col1 Group Key: () - InitPlan expr_2 + InitPlan expr_1 -> Result InitPlan expr_3 -> Result -> Result - SubPlan expr_1 + SubPlan expr_2 -> Result (10 rows) diff --git a/src/test/regress/expected/join.out b/src/test/regress/expected/join.out index 051c3539930..75544fe6aa3 100644 --- a/src/test/regress/expected/join.out +++ b/src/test/regress/expected/join.out @@ -5182,7 +5182,7 @@ where i8.q2 = 123; QUERY PLAN --------------------------------------------------------------------- Nested Loop - Output: i8.q1, i8.q2, (InitPlan expr_1).col1, false, (i8.q2) + Output: i8.q1, i8.q2, (InitPlan expr_2).col1, false, (i8.q2) InitPlan expr_1 -> Result Output: true @@ -5195,7 +5195,7 @@ where i8.q2 = 123; -> Nested Loop Output: i8.q1, i8.q2, (i8.q2) -> Subquery Scan on ss1 - Output: ss1.y, (InitPlan expr_1).col1 + Output: ss1.y, (InitPlan expr_2).col1 -> Limit Output: NULL::integer -> Result @@ -5209,7 +5209,7 @@ where i8.q2 = 123; Output: (i8.q2) -> Result Output: i8.q2 - One-Time Filter: ((InitPlan expr_1).col1) + One-Time Filter: ((InitPlan expr_2).col1) (29 rows) explain (verbose, costs off) @@ -5225,7 +5225,7 @@ where i8.q2 = 123; QUERY PLAN --------------------------------------------------------------------- Nested Loop - Output: i8.q1, i8.q2, (InitPlan expr_1).col1, false, (i8.q2) + Output: i8.q1, i8.q2, (InitPlan expr_2).col1, false, (i8.q2) InitPlan expr_1 -> Result Output: true @@ -5239,7 +5239,7 @@ where i8.q2 = 123; -> Nested Loop Output: i8.q1, i8.q2, (i8.q2) -> Seq Scan on public.int4_tbl i4 - Output: i4.f1, (InitPlan expr_1).col1 + Output: i4.f1, (InitPlan expr_2).col1 Filter: (i4.f1 = 0) -> Nested Loop Output: i8.q1, i8.q2, (i8.q2) @@ -5250,7 +5250,7 @@ where i8.q2 = 123; Output: (i8.q2) -> Result Output: i8.q2 - One-Time Filter: ((InitPlan expr_1).col1) + One-Time Filter: ((InitPlan expr_2).col1) (27 rows) -- Test proper handling of appendrel PHVs during useless-RTE removal @@ -7282,6 +7282,154 @@ on lhs.id = rhs.id; -> Result (5 rows) +-- check handling of a removed Var that's pushed down into a subquery +-- (fallout from the fix for bug #19560) +explain (verbose, costs off) +select c1, c2 +from (select case when false then remov.id end as c1 + from int4_tbl i41 left join a remov on i41.f1 = remov.id) ss1 + right join int4_tbl i42 on false, + lateral (select ss1.c1 as c2 from int4_tbl i43 offset 0) ss2; + QUERY PLAN +---------------------------------------------- + Nested Loop + Output: (NULL::integer), ((NULL::integer)) + -> Nested Loop Left Join + Output: (NULL::integer) + Join Filter: false + -> Seq Scan on public.int4_tbl i42 + Output: i42.f1 + -> Result + Output: NULL::integer + Replaces: Scan on i41 + One-Time Filter: false + -> Memoize + Output: ((NULL::integer)) + Cache Key: (NULL::integer) + Cache Mode: binary + -> Seq Scan on public.int4_tbl i43 + Output: (NULL::integer) +(17 rows) + +-- likewise, where the subquery is a UNION ALL whose arms are appendrel +-- children that are not simple enough to be pulled up +explain (verbose, costs off) +select c1, c2 +from (select case when false then remov.id end as c1 + from int4_tbl i41 left join a remov on i41.f1 = remov.id) ss1 + right join int4_tbl i42 on false, + lateral ((select ss1.c1 as c2 from int4_tbl i43 offset 0) + union all + (select ss1.c1 from int4_tbl i44 offset 0)) ss2; + QUERY PLAN +--------------------------------------------------- + Nested Loop + Output: (NULL::integer), ((NULL::integer)) + -> Nested Loop Left Join + Output: (NULL::integer) + Join Filter: false + -> Seq Scan on public.int4_tbl i42 + Output: i42.f1 + -> Result + Output: NULL::integer + Replaces: Scan on i41 + One-Time Filter: false + -> Memoize + Output: ((NULL::integer)) + Cache Key: (NULL::integer) + Cache Mode: binary + -> Append + -> Seq Scan on public.int4_tbl i43 + Output: (NULL::integer) + -> Seq Scan on public.int4_tbl i44 + Output: (NULL::integer) +(20 rows) + +-- likewise, where the PHV contains a SubPlan and the subquery has a join, so +-- that its planner runs flatten_join_alias_vars over the outer-level PHV +explain (verbose, costs off) +select c1, c2 +from (select case when false then remov.id else (select i41.f1) end as c1 + from int4_tbl i41 left join a remov on i41.f1 = remov.id) ss1 + right join int4_tbl i42 on false, + lateral (select ss1.c1 as c2 from int4_tbl i43 join int4_tbl i44 on true + offset 0) ss2; + QUERY PLAN +--------------------------------------------------------- + Nested Loop + Output: ((SubPlan expr_2)), (((SubPlan expr_2))) + -> Nested Loop Left Join + Output: ((SubPlan expr_2)) + Join Filter: false + -> Seq Scan on public.int4_tbl i42 + Output: i42.f1 + -> Result + Output: (SubPlan expr_2) + Replaces: Scan on i41 + One-Time Filter: false + SubPlan expr_2 + -> Result + Output: i41.f1 + -> Memoize + Output: (((SubPlan expr_2))) + Cache Key: ((SubPlan expr_2)) + Cache Mode: binary + -> Nested Loop + Output: ((SubPlan expr_2)) + -> Seq Scan on public.int4_tbl i43 + Output: i43.f1 + -> Materialize + -> Seq Scan on public.int4_tbl i44 +(24 rows) + +-- likewise, where the PHV copy is pushed into a SubLink's subselect rather +-- than a LATERAL subquery +explain (verbose, costs off) +select (select ss1.c1 from int4_tbl i43 offset 0) as c2 +from (select case when false then remov.id end as c1 + from int4_tbl i41 left join a remov on i41.f1 = remov.id) ss1 + right join int4_tbl i42 on true; + QUERY PLAN +--------------------------------------------- + Nested Loop Left Join + Output: (SubPlan expr_1) + -> Seq Scan on public.int4_tbl i42 + Output: i42.f1 + -> Materialize + Output: (NULL::integer) + -> Seq Scan on public.int4_tbl i41 + Output: NULL::integer + SubPlan expr_1 + -> Seq Scan on public.int4_tbl i43 + Output: (NULL::integer) +(11 rows) + +-- likewise, where the pushed-down PHV's expression itself contains a SubLink, +-- so it must be preprocessed once at the outer level rather than again while +-- building the SubPlan that references it +explain (verbose, costs off) +select (select ss1.c1 from int4_tbl i43 offset 0) as c2 +from (select case when false then remov.id else (select i41.f1) end as c1 + from int4_tbl i41 left join a remov on i41.f1 = remov.id) ss1 + right join int4_tbl i42 on true; + QUERY PLAN +--------------------------------------------- + Nested Loop Left Join + Output: (SubPlan expr_2) + -> Seq Scan on public.int4_tbl i42 + Output: i42.f1 + -> Materialize + Output: ((SubPlan expr_1)) + -> Seq Scan on public.int4_tbl i41 + Output: (SubPlan expr_1) + SubPlan expr_1 + -> Result + Output: i41.f1 + SubPlan expr_2 + -> Seq Scan on public.int4_tbl i43 + Output: ((SubPlan expr_1)) +(14 rows) + -- More tests of correct placement of pseudoconstant quals -- simple constant-false condition explain (costs off) diff --git a/src/test/regress/sql/join.sql b/src/test/regress/sql/join.sql index c30fd339f13..fb83a96e939 100644 --- a/src/test/regress/sql/join.sql +++ b/src/test/regress/sql/join.sql @@ -2699,6 +2699,53 @@ full join ) as rhs on lhs.id = rhs.id; +-- check handling of a removed Var that's pushed down into a subquery +-- (fallout from the fix for bug #19560) +explain (verbose, costs off) +select c1, c2 +from (select case when false then remov.id end as c1 + from int4_tbl i41 left join a remov on i41.f1 = remov.id) ss1 + right join int4_tbl i42 on false, + lateral (select ss1.c1 as c2 from int4_tbl i43 offset 0) ss2; + +-- likewise, where the subquery is a UNION ALL whose arms are appendrel +-- children that are not simple enough to be pulled up +explain (verbose, costs off) +select c1, c2 +from (select case when false then remov.id end as c1 + from int4_tbl i41 left join a remov on i41.f1 = remov.id) ss1 + right join int4_tbl i42 on false, + lateral ((select ss1.c1 as c2 from int4_tbl i43 offset 0) + union all + (select ss1.c1 from int4_tbl i44 offset 0)) ss2; + +-- likewise, where the PHV contains a SubPlan and the subquery has a join, so +-- that its planner runs flatten_join_alias_vars over the outer-level PHV +explain (verbose, costs off) +select c1, c2 +from (select case when false then remov.id else (select i41.f1) end as c1 + from int4_tbl i41 left join a remov on i41.f1 = remov.id) ss1 + right join int4_tbl i42 on false, + lateral (select ss1.c1 as c2 from int4_tbl i43 join int4_tbl i44 on true + offset 0) ss2; + +-- likewise, where the PHV copy is pushed into a SubLink's subselect rather +-- than a LATERAL subquery +explain (verbose, costs off) +select (select ss1.c1 from int4_tbl i43 offset 0) as c2 +from (select case when false then remov.id end as c1 + from int4_tbl i41 left join a remov on i41.f1 = remov.id) ss1 + right join int4_tbl i42 on true; + +-- likewise, where the pushed-down PHV's expression itself contains a SubLink, +-- so it must be preprocessed once at the outer level rather than again while +-- building the SubPlan that references it +explain (verbose, costs off) +select (select ss1.c1 from int4_tbl i43 offset 0) as c2 +from (select case when false then remov.id else (select i41.f1) end as c1 + from int4_tbl i41 left join a remov on i41.f1 = remov.id) ss1 + right join int4_tbl i42 on true; + -- More tests of correct placement of pseudoconstant quals -- simple constant-false condition diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 7aedaafab90..2fb1502da3b 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -4167,6 +4167,7 @@ postprocess_result_function pqbool pqsigfunc pqsigfunc_legacy +preprocess_subquery_phvs_context printQueryOpt printTableContent printTableFooter -- 2.37.1 (Apple Git-137.1)