diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c index f30f02f266..fb6c704548 100644 --- a/src/backend/optimizer/plan/subselect.c +++ b/src/backend/optimizer/plan/subselect.c @@ -190,6 +190,87 @@ assign_nestloop_param_var(PlannerInfo *root, Var *var) return retval; } +/* + * Select a PARAM_EXEC number to identify the given PlaceHolderVar. + * If the PlaceHolderVar already has a param slot, return that one. + * + * This is just like assign_param_for_var, except for PlaceHolderVars. + */ +static int +assign_param_for_placeholdervar(PlannerInfo *root, PlaceHolderVar *phv) +{ + ListCell *ppl; + PlannerParamItem *pitem; + Index abslevel; + int i; + + abslevel = root->query_level - phv->phlevelsup; + + /* If there's already a paramlist entry for this same PHV, just use it */ + i = 0; + foreach(ppl, root->glob->paramlist) + { + pitem = (PlannerParamItem *) lfirst(ppl); + if (pitem->abslevel == abslevel && IsA(pitem->item, PlaceHolderVar)) + { + PlaceHolderVar *pphv = (PlaceHolderVar *) pitem->item; + + /* We assume comparing the PHIDs is sufficient */ + if (pphv->phid == phv->phid) + return i; + } + i++; + } + + /* Nope, so make a new one */ + phv = (PlaceHolderVar *) copyObject(phv); + if (phv->phlevelsup != 0) + { + IncrementVarSublevelsUp((Node *) phv, -((int) phv->phlevelsup), 0); + Assert(phv->phlevelsup == 0); + } + + pitem = makeNode(PlannerParamItem); + pitem->item = (Node *) phv; + pitem->abslevel = abslevel; + + root->glob->paramlist = lappend(root->glob->paramlist, pitem); + + /* i is already the correct list index for the new item */ + return i; +} + +/* + * Generate a Param node to replace the given PlaceHolderVar, + * which is expected to have phlevelsup > 0 (ie, it is not local). + * + * This is just like replace_outer_var, except for PlaceHolderVars. + */ +static Param * +replace_outer_placeholdervar(PlannerInfo *root, PlaceHolderVar *phv) +{ + Param *retval; + int i; + + Assert(phv->phlevelsup > 0 && phv->phlevelsup < root->query_level); + + /* + * Find the PlaceHolderVar in root->glob->paramlist, or add it if not + * present. + */ + i = assign_param_for_placeholdervar(root, phv); + + retval = makeNode(Param); + retval->paramkind = PARAM_EXEC; + retval->paramid = i; + retval->paramtype = exprType((Node *) phv->phexpr); + retval->paramtypmod = exprTypmod((Node *) phv->phexpr); + retval->paramcollid = exprCollation((Node *) phv->phexpr); + retval->location = -1; + + return retval; +} + /* * Generate a Param node to replace the given PlaceHolderVar, which will be * supplied from an upper NestLoop join node. @@ -200,43 +281,11 @@ Param * assign_nestloop_param_placeholdervar(PlannerInfo *root, PlaceHolderVar *phv) { Param *retval; - ListCell *ppl; - PlannerParamItem *pitem; - Index abslevel; int i; Assert(phv->phlevelsup == 0); - abslevel = root->query_level; - /* If there's already a paramlist entry for this same PHV, just use it */ - /* We assume comparing the PHIDs is sufficient */ - i = 0; - foreach(ppl, root->glob->paramlist) - { - pitem = (PlannerParamItem *) lfirst(ppl); - if (pitem->abslevel == abslevel && IsA(pitem->item, PlaceHolderVar)) - { - PlaceHolderVar *pphv = (PlaceHolderVar *) pitem->item; - - if (pphv->phid == phv->phid) - break; - } - i++; - } - - if (ppl == NULL) - { - /* Nope, so make a new one */ - phv = (PlaceHolderVar *) copyObject(phv); - - pitem = makeNode(PlannerParamItem); - pitem->item = (Node *) phv; - pitem->abslevel = abslevel; - - root->glob->paramlist = lappend(root->glob->paramlist, pitem); - - /* i is already the correct list index for the new item */ - } + i = assign_param_for_placeholdervar(root, phv); retval = makeNode(Param); retval->paramkind = PARAM_EXEC; @@ -555,17 +604,19 @@ build_subplan(PlannerInfo *root, Plan *plan, PlannerInfo *subroot, Node *arg; /* - * The Var or Aggref has already been adjusted to have the correct - * varlevelsup or agglevelsup. We probably don't even need to - * copy it again, but be safe. + * The Var, PlaceHolderVar, or Aggref has already been adjusted to + * have the correct varlevelsup, phlevelsup, or agglevelsup. We + * probably don't even need to copy it again, but be safe. */ arg = copyObject(pitem->item); /* - * If it's an Aggref, its arguments might contain SubLinks, which - * have not yet been processed. Do that now. + * If it's a PlaceHolderVar or Aggref, its arguments might contain + * SubLinks, which have not yet been processed (see the comments + * for SS_replace_correlation_vars). Do that now. */ - if (IsA(arg, Aggref)) + if (IsA(arg, PlaceHolderVar) || + IsA(arg, Aggref)) arg = SS_process_sublinks(root, arg, false); splan->parParam = lappend_int(splan->parParam, paramid); @@ -1668,24 +1719,25 @@ convert_EXISTS_to_ANY(PlannerInfo *root, Query *subselect, /* * Replace correlation vars (uplevel vars) with Params. * - * Uplevel aggregates are replaced, too. + * Uplevel PlaceHolderVars and aggregates are replaced, too. * * Note: it is critical that this runs immediately after SS_process_sublinks. - * Since we do not recurse into the arguments of uplevel aggregates, they will - * get copied to the appropriate subplan args list in the parent query with - * uplevel vars not replaced by Params, but only adjusted in level (see - * replace_outer_agg). That's exactly what we want for the vars of the parent - * level --- but if an aggregate's argument contains any further-up variables, - * they have to be replaced with Params in their turn. That will happen when - * the parent level runs SS_replace_correlation_vars. Therefore it must do - * so after expanding its sublinks to subplans. And we don't want any steps - * in between, else those steps would never get applied to the aggregate - * argument expressions, either in the parent or the child level. + * Since we do not recurse into the arguments of uplevel PHVs and aggregates, + * they will get copied to the appropriate subplan args list in the parent + * query with uplevel vars not replaced by Params, but only adjusted in level + * (see replace_outer_placeholdervar and replace_outer_agg). That's exactly + * what we want for the vars of the parent level --- but if a PHV's or + * aggregate's argument contains any further-up variables, they have to be + * replaced with Params in their turn. That will happen when the parent level + * runs SS_replace_correlation_vars. Therefore it must do so after expanding + * its sublinks to subplans. And we don't want any steps in between, else + * those steps would never get applied to the argument expressions, either in + * the parent or the child level. * * Another fairly tricky thing going on here is the handling of SubLinks in - * the arguments of uplevel aggregates. Those are not touched inside the - * intermediate query level, either. Instead, SS_process_sublinks recurses - * on them after copying the Aggref expression into the parent plan level + * the arguments of uplevel PHVs/aggregates. Those are not touched inside the + * intermediate query level, either. Instead, SS_process_sublinks recurses on + * them after copying the PHV or Aggref expression into the parent plan level * (this is actually taken care of in build_subplan). */ Node * @@ -1705,6 +1757,12 @@ replace_correlation_vars_mutator(Node *node, PlannerInfo *root) if (((Var *) node)->varlevelsup > 0) return (Node *) replace_outer_var(root, (Var *) node); } + if (IsA(node, PlaceHolderVar)) + { + if (((PlaceHolderVar *) node)->phlevelsup > 0) + return (Node *) replace_outer_placeholdervar(root, + (PlaceHolderVar *) node); + } if (IsA(node, Aggref)) { if (((Aggref *) node)->agglevelsup > 0) @@ -1764,12 +1822,17 @@ process_sublinks_mutator(Node *node, process_sublinks_context *context) } /* - * Don't recurse into the arguments of an outer aggregate here. Any - * SubLinks in the arguments have to be dealt with at the outer query - * level; they'll be handled when build_subplan collects the Aggref into - * the arguments to be passed down to the current subplan. + * Don't recurse into the arguments of an outer PHV or aggregate here. + * Any SubLinks in the arguments have to be dealt with at the outer query + * level; they'll be handled when build_subplan collects the PHV or Aggref + * into the arguments to be passed down to the current subplan. */ - if (IsA(node, Aggref)) + if (IsA(node, PlaceHolderVar)) + { + if (((PlaceHolderVar *) node)->phlevelsup > 0) + return node; + } + else if (IsA(node, Aggref)) { if (((Aggref *) node)->agglevelsup > 0) return node; diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c index c1b2f1db11..47ddae6992 100644 --- a/src/backend/optimizer/prep/prepjointree.c +++ b/src/backend/optimizer/prep/prepjointree.c @@ -2069,8 +2069,6 @@ reduce_outer_joins_pass2(Node *jtnode, * * Find any PlaceHolderVar nodes in the given tree that reference the * pulled-up relid, and change them to reference the replacement relid(s). - * We do not need to recurse into subqueries, since no subquery of the current - * top query could (yet) contain such a reference. * * NOTE: although this has the form of a walker, we cheat and modify the * nodes in-place. This should be OK since the tree was copied by @@ -2081,6 +2079,7 @@ reduce_outer_joins_pass2(Node *jtnode, typedef struct { int varno; + int sublevels_up; Relids subrelids; } substitute_multiple_relids_context; @@ -2094,7 +2093,8 @@ substitute_multiple_relids_walker(Node *node, { PlaceHolderVar *phv = (PlaceHolderVar *) node; - if (bms_is_member(context->varno, phv->phrels)) + if (phv->phlevelsup == context->sublevels_up && + bms_is_member(context->varno, phv->phrels)) { phv->phrels = bms_union(phv->phrels, context->subrelids); @@ -2103,6 +2103,18 @@ substitute_multiple_relids_walker(Node *node, } /* fall through to examine children */ } + if (IsA(node, Query)) + { + /* Recurse into subselects */ + bool result; + + context->sublevels_up++; + result = query_tree_walker((Query *) node, + substitute_multiple_relids_walker, + (void *) context, 0); + context->sublevels_up--; + return result; + } /* Shouldn't need to handle planner auxiliary nodes here */ Assert(!IsA(node, SpecialJoinInfo)); Assert(!IsA(node, AppendRelInfo)); @@ -2119,6 +2131,7 @@ substitute_multiple_relids(Node *node, int varno, Relids subrelids) substitute_multiple_relids_context context; context.varno = varno; + context.sublevels_up = 0; context.subrelids = subrelids; /* diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h index d901a85e7a..6606e67055 100644 --- a/src/include/nodes/relation.h +++ b/src/include/nodes/relation.h @@ -1445,8 +1445,10 @@ typedef struct MinMaxAggInfo * from a NestLoop node of that level to its inner scan. The varlevelsup * value in the Var will always be zero. * - * A PlaceHolderVar: this works much like the Var case. It is currently - * only needed for NestLoop parameters, not outer references. + * A PlaceHolderVar: this works much like the Var case, except that the + * entry is a PlaceHolderVar node with a contained expression. The PHV + * will have phlevelsup = 0, and the contained expression is adjusted + * to match in level. * * An Aggref (with an expression tree representing its argument): the slot * represents an aggregate expression that is an outer reference for some diff --git a/src/test/regress/expected/join.out b/src/test/regress/expected/join.out index 5447a26f44..c0c7283333 100644 --- a/src/test/regress/expected/join.out +++ b/src/test/regress/expected/join.out @@ -2571,6 +2571,53 @@ SELECT qq, unique1 456 | 7318 (3 rows) +-- +-- test case where a PlaceHolderVar is propagated into a subquery +-- +explain (costs off) +select * from + int8_tbl t1 left join + (select q1 as x, 42 as y from int8_tbl t2) ss + on t1.q2 = ss.x +where + 1 = (select 1 from int8_tbl t3 where ss.y is not null limit 1) +order by 1,2; + QUERY PLAN +----------------------------------------------------------- + Sort + Sort Key: t1.q1, t1.q2 + -> Hash Left Join + Hash Cond: (t1.q2 = t2.q1) + Filter: (1 = (SubPlan 1)) + -> Seq Scan on int8_tbl t1 + -> Hash + -> Seq Scan on int8_tbl t2 + SubPlan 1 + -> Limit + -> Result + One-Time Filter: ((42) IS NOT NULL) + -> Seq Scan on int8_tbl t3 +(13 rows) + +select * from + int8_tbl t1 left join + (select q1 as x, 42 as y from int8_tbl t2) ss + on t1.q2 = ss.x +where + 1 = (select 1 from int8_tbl t3 where ss.y is not null limit 1) +order by 1,2; + q1 | q2 | x | y +------------------+------------------+------------------+---- + 123 | 4567890123456789 | 4567890123456789 | 42 + 123 | 4567890123456789 | 4567890123456789 | 42 + 123 | 4567890123456789 | 4567890123456789 | 42 + 4567890123456789 | 123 | 123 | 42 + 4567890123456789 | 123 | 123 | 42 + 4567890123456789 | 4567890123456789 | 4567890123456789 | 42 + 4567890123456789 | 4567890123456789 | 4567890123456789 | 42 + 4567890123456789 | 4567890123456789 | 4567890123456789 | 42 +(8 rows) + -- -- test the corner cases FULL JOIN ON TRUE and FULL JOIN ON FALSE -- diff --git a/src/test/regress/sql/join.sql b/src/test/regress/sql/join.sql index 247fae11f9..2d53cf1725 100644 --- a/src/test/regress/sql/join.sql +++ b/src/test/regress/sql/join.sql @@ -662,6 +662,27 @@ SELECT qq, unique1 USING (qq) INNER JOIN tenk1 c ON qq = unique2; +-- +-- test case where a PlaceHolderVar is propagated into a subquery +-- + +explain (costs off) +select * from + int8_tbl t1 left join + (select q1 as x, 42 as y from int8_tbl t2) ss + on t1.q2 = ss.x +where + 1 = (select 1 from int8_tbl t3 where ss.y is not null limit 1) +order by 1,2; + +select * from + int8_tbl t1 left join + (select q1 as x, 42 as y from int8_tbl t2) ss + on t1.q2 = ss.x +where + 1 = (select 1 from int8_tbl t3 where ss.y is not null limit 1) +order by 1,2; + -- -- test the corner cases FULL JOIN ON TRUE and FULL JOIN ON FALSE --