Commit 8279eb41 authored by Tom Lane's avatar Tom Lane

Fix planner's handling of outer PlaceHolderVars within subqueries.

For some reason, in the original coding of the PlaceHolderVar mechanism
I had supposed that PlaceHolderVars couldn't propagate into subqueries.
That is of course entirely possible.  When it happens, we need to treat
an outer-level PlaceHolderVar much like an outer Var or Aggref, that is
SS_replace_correlation_vars() needs to replace the PlaceHolderVar with
a Param, and then when building the finished SubPlan we have to provide
the PlaceHolderVar expression as an actual parameter for the SubPlan.
The handling of the contained expression is a bit delicate but it can be
treated exactly like an Aggref's expression.

In addition to the missing logic in subselect.c, prepjointree.c was failing
to search subqueries for PlaceHolderVars that need their relids adjusted
during subquery pullup.  It looks like everyplace else that touches
PlaceHolderVars got it right, though.

Per report from Mark Murawski.  In 9.1 and HEAD, queries affected by this
oversight would fail with "ERROR: Upper-level PlaceHolderVar found where
not expected".  But in 9.0 and 8.4, you'd silently get possibly-wrong
answers, since the value transmitted into the subquery wouldn't go to null
when it should.
parent ed61127b
...@@ -191,25 +191,22 @@ assign_nestloop_param_var(PlannerInfo *root, Var *var) ...@@ -191,25 +191,22 @@ assign_nestloop_param_var(PlannerInfo *root, Var *var)
} }
/* /*
* Generate a Param node to replace the given PlaceHolderVar, which will be * Select a PARAM_EXEC number to identify the given PlaceHolderVar.
* supplied from an upper NestLoop join node. * If the PlaceHolderVar already has a param slot, return that one.
* *
* This is just like assign_nestloop_param_var, except for PlaceHolderVars. * This is just like assign_param_for_var, except for PlaceHolderVars.
*/ */
Param * static int
assign_nestloop_param_placeholdervar(PlannerInfo *root, PlaceHolderVar *phv) assign_param_for_placeholdervar(PlannerInfo *root, PlaceHolderVar *phv)
{ {
Param *retval;
ListCell *ppl; ListCell *ppl;
PlannerParamItem *pitem; PlannerParamItem *pitem;
Index abslevel; Index abslevel;
int i; int i;
Assert(phv->phlevelsup == 0); abslevel = root->query_level - phv->phlevelsup;
abslevel = root->query_level;
/* If there's already a paramlist entry for this same PHV, just use it */ /* If there's already a paramlist entry for this same PHV, just use it */
/* We assume comparing the PHIDs is sufficient */
i = 0; i = 0;
foreach(ppl, root->glob->paramlist) foreach(ppl, root->glob->paramlist)
{ {
...@@ -218,25 +215,77 @@ assign_nestloop_param_placeholdervar(PlannerInfo *root, PlaceHolderVar *phv) ...@@ -218,25 +215,77 @@ assign_nestloop_param_placeholdervar(PlannerInfo *root, PlaceHolderVar *phv)
{ {
PlaceHolderVar *pphv = (PlaceHolderVar *) pitem->item; PlaceHolderVar *pphv = (PlaceHolderVar *) pitem->item;
/* We assume comparing the PHIDs is sufficient */
if (pphv->phid == phv->phid) if (pphv->phid == phv->phid)
break; return i;
} }
i++; i++;
} }
if (ppl == NULL) /* Nope, so make a new one */
phv = (PlaceHolderVar *) copyObject(phv);
if (phv->phlevelsup != 0)
{ {
/* Nope, so make a new one */ IncrementVarSublevelsUp((Node *) phv, -((int) phv->phlevelsup), 0);
phv = (PlaceHolderVar *) copyObject(phv); Assert(phv->phlevelsup == 0);
}
pitem = makeNode(PlannerParamItem); pitem = makeNode(PlannerParamItem);
pitem->item = (Node *) phv; pitem->item = (Node *) phv;
pitem->abslevel = abslevel; pitem->abslevel = abslevel;
root->glob->paramlist = lappend(root->glob->paramlist, pitem); root->glob->paramlist = lappend(root->glob->paramlist, pitem);
/* i is already the correct list index for the new item */ /* 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.
*
* This is just like assign_nestloop_param_var, except for PlaceHolderVars.
*/
Param *
assign_nestloop_param_placeholdervar(PlannerInfo *root, PlaceHolderVar *phv)
{
Param *retval;
int i;
Assert(phv->phlevelsup == 0);
i = assign_param_for_placeholdervar(root, phv);
retval = makeNode(Param); retval = makeNode(Param);
retval->paramkind = PARAM_EXEC; retval->paramkind = PARAM_EXEC;
...@@ -555,17 +604,19 @@ build_subplan(PlannerInfo *root, Plan *plan, PlannerInfo *subroot, ...@@ -555,17 +604,19 @@ build_subplan(PlannerInfo *root, Plan *plan, PlannerInfo *subroot,
Node *arg; Node *arg;
/* /*
* The Var or Aggref has already been adjusted to have the correct * The Var, PlaceHolderVar, or Aggref has already been adjusted to
* varlevelsup or agglevelsup. We probably don't even need to * have the correct varlevelsup, phlevelsup, or agglevelsup. We
* copy it again, but be safe. * probably don't even need to copy it again, but be safe.
*/ */
arg = copyObject(pitem->item); arg = copyObject(pitem->item);
/* /*
* If it's an Aggref, its arguments might contain SubLinks, which * If it's a PlaceHolderVar or Aggref, its arguments might contain
* have not yet been processed. Do that now. * 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); arg = SS_process_sublinks(root, arg, false);
splan->parParam = lappend_int(splan->parParam, paramid); splan->parParam = lappend_int(splan->parParam, paramid);
...@@ -1668,24 +1719,25 @@ convert_EXISTS_to_ANY(PlannerInfo *root, Query *subselect, ...@@ -1668,24 +1719,25 @@ convert_EXISTS_to_ANY(PlannerInfo *root, Query *subselect,
/* /*
* Replace correlation vars (uplevel vars) with Params. * 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. * 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 * Since we do not recurse into the arguments of uplevel PHVs and aggregates,
* get copied to the appropriate subplan args list in the parent query with * they will get copied to the appropriate subplan args list in the parent
* uplevel vars not replaced by Params, but only adjusted in level (see * query with uplevel vars not replaced by Params, but only adjusted in level
* replace_outer_agg). That's exactly what we want for the vars of the parent * (see replace_outer_placeholdervar and replace_outer_agg). That's exactly
* level --- but if an aggregate's argument contains any further-up variables, * what we want for the vars of the parent level --- but if a PHV's or
* they have to be replaced with Params in their turn. That will happen when * aggregate's argument contains any further-up variables, they have to be
* the parent level runs SS_replace_correlation_vars. Therefore it must do * replaced with Params in their turn. That will happen when the parent level
* so after expanding its sublinks to subplans. And we don't want any steps * runs SS_replace_correlation_vars. Therefore it must do so after expanding
* in between, else those steps would never get applied to the aggregate * its sublinks to subplans. And we don't want any steps in between, else
* argument expressions, either in the parent or the child level. * 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 * Another fairly tricky thing going on here is the handling of SubLinks in
* the arguments of uplevel aggregates. Those are not touched inside the * the arguments of uplevel PHVs/aggregates. Those are not touched inside the
* intermediate query level, either. Instead, SS_process_sublinks recurses * intermediate query level, either. Instead, SS_process_sublinks recurses on
* on them after copying the Aggref expression into the parent plan level * them after copying the PHV or Aggref expression into the parent plan level
* (this is actually taken care of in build_subplan). * (this is actually taken care of in build_subplan).
*/ */
Node * Node *
...@@ -1705,6 +1757,12 @@ replace_correlation_vars_mutator(Node *node, PlannerInfo *root) ...@@ -1705,6 +1757,12 @@ replace_correlation_vars_mutator(Node *node, PlannerInfo *root)
if (((Var *) node)->varlevelsup > 0) if (((Var *) node)->varlevelsup > 0)
return (Node *) replace_outer_var(root, (Var *) node); 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 (IsA(node, Aggref))
{ {
if (((Aggref *) node)->agglevelsup > 0) if (((Aggref *) node)->agglevelsup > 0)
...@@ -1764,12 +1822,17 @@ process_sublinks_mutator(Node *node, process_sublinks_context *context) ...@@ -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 * Don't recurse into the arguments of an outer PHV or aggregate here.
* SubLinks in the arguments have to be dealt with at the outer query * 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 * level; they'll be handled when build_subplan collects the PHV or Aggref
* the arguments to be passed down to the current subplan. * 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) if (((Aggref *) node)->agglevelsup > 0)
return node; return node;
......
...@@ -2069,8 +2069,6 @@ reduce_outer_joins_pass2(Node *jtnode, ...@@ -2069,8 +2069,6 @@ reduce_outer_joins_pass2(Node *jtnode,
* *
* Find any PlaceHolderVar nodes in the given tree that reference the * Find any PlaceHolderVar nodes in the given tree that reference the
* pulled-up relid, and change them to reference the replacement relid(s). * 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 * 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 * nodes in-place. This should be OK since the tree was copied by
...@@ -2081,6 +2079,7 @@ reduce_outer_joins_pass2(Node *jtnode, ...@@ -2081,6 +2079,7 @@ reduce_outer_joins_pass2(Node *jtnode,
typedef struct typedef struct
{ {
int varno; int varno;
int sublevels_up;
Relids subrelids; Relids subrelids;
} substitute_multiple_relids_context; } substitute_multiple_relids_context;
...@@ -2094,7 +2093,8 @@ substitute_multiple_relids_walker(Node *node, ...@@ -2094,7 +2093,8 @@ substitute_multiple_relids_walker(Node *node,
{ {
PlaceHolderVar *phv = (PlaceHolderVar *) 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, phv->phrels = bms_union(phv->phrels,
context->subrelids); context->subrelids);
...@@ -2103,6 +2103,18 @@ substitute_multiple_relids_walker(Node *node, ...@@ -2103,6 +2103,18 @@ substitute_multiple_relids_walker(Node *node,
} }
/* fall through to examine children */ /* 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 */ /* Shouldn't need to handle planner auxiliary nodes here */
Assert(!IsA(node, SpecialJoinInfo)); Assert(!IsA(node, SpecialJoinInfo));
Assert(!IsA(node, AppendRelInfo)); Assert(!IsA(node, AppendRelInfo));
...@@ -2119,6 +2131,7 @@ substitute_multiple_relids(Node *node, int varno, Relids subrelids) ...@@ -2119,6 +2131,7 @@ substitute_multiple_relids(Node *node, int varno, Relids subrelids)
substitute_multiple_relids_context context; substitute_multiple_relids_context context;
context.varno = varno; context.varno = varno;
context.sublevels_up = 0;
context.subrelids = subrelids; context.subrelids = subrelids;
/* /*
......
...@@ -1445,8 +1445,10 @@ typedef struct MinMaxAggInfo ...@@ -1445,8 +1445,10 @@ typedef struct MinMaxAggInfo
* from a NestLoop node of that level to its inner scan. The varlevelsup * from a NestLoop node of that level to its inner scan. The varlevelsup
* value in the Var will always be zero. * value in the Var will always be zero.
* *
* A PlaceHolderVar: this works much like the Var case. It is currently * A PlaceHolderVar: this works much like the Var case, except that the
* only needed for NestLoop parameters, not outer references. * 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 * An Aggref (with an expression tree representing its argument): the slot
* represents an aggregate expression that is an outer reference for some * represents an aggregate expression that is an outer reference for some
......
...@@ -2571,6 +2571,53 @@ SELECT qq, unique1 ...@@ -2571,6 +2571,53 @@ SELECT qq, unique1
456 | 7318 456 | 7318
(3 rows) (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 -- test the corner cases FULL JOIN ON TRUE and FULL JOIN ON FALSE
-- --
......
...@@ -662,6 +662,27 @@ SELECT qq, unique1 ...@@ -662,6 +662,27 @@ SELECT qq, unique1
USING (qq) USING (qq)
INNER JOIN tenk1 c ON qq = unique2; 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 -- test the corner cases FULL JOIN ON TRUE and FULL JOIN ON FALSE
-- --
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment