Skip to content

Commit 0a0ca1c

Browse files
committed
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.
1 parent 543e5ab commit 0a0ca1c

File tree

5 files changed

+156
-32
lines changed

5 files changed

+156
-32
lines changed

src/backend/optimizer/plan/subselect.c

Lines changed: 98 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,65 @@ replace_outer_var(PlannerInfo *root, Var *var)
145145
return retval;
146146
}
147147

148+
/*
149+
* Generate a Param node to replace the given PlaceHolderVar,
150+
* which is expected to have phlevelsup > 0 (ie, it is not local).
151+
*
152+
* This is just like replace_outer_var, except for PlaceHolderVars.
153+
*/
154+
static Param *
155+
replace_outer_placeholdervar(PlannerInfo *root, PlaceHolderVar *phv)
156+
{
157+
Param *retval;
158+
ListCell *ppl;
159+
PlannerParamItem *pitem;
160+
Index abslevel;
161+
int i;
162+
163+
Assert(phv->phlevelsup > 0 && phv->phlevelsup < root->query_level);
164+
abslevel = root->query_level - phv->phlevelsup;
165+
166+
/* If there's already a paramlist entry for this same PHV, just use it */
167+
i = 0;
168+
foreach(ppl, root->glob->paramlist)
169+
{
170+
pitem = (PlannerParamItem *) lfirst(ppl);
171+
if (pitem->abslevel == abslevel && IsA(pitem->item, PlaceHolderVar))
172+
{
173+
PlaceHolderVar *pphv = (PlaceHolderVar *) pitem->item;
174+
175+
/* We assume comparing the PHIDs is sufficient */
176+
if (pphv->phid == phv->phid)
177+
break;
178+
}
179+
i++;
180+
}
181+
182+
if (!ppl)
183+
{
184+
/* Nope, so make a new one */
185+
phv = (PlaceHolderVar *) copyObject(phv);
186+
IncrementVarSublevelsUp((Node *) phv, -((int) phv->phlevelsup), 0);
187+
Assert(phv->phlevelsup == 0);
188+
189+
pitem = makeNode(PlannerParamItem);
190+
pitem->item = (Node *) phv;
191+
pitem->abslevel = abslevel;
192+
193+
root->glob->paramlist = lappend(root->glob->paramlist, pitem);
194+
/* i is already the correct index for the new item */
195+
}
196+
197+
retval = makeNode(Param);
198+
retval->paramkind = PARAM_EXEC;
199+
retval->paramid = i;
200+
retval->paramtype = exprType((Node *) phv->phexpr);
201+
retval->paramtypmod = exprTypmod((Node *) phv->phexpr);
202+
retval->location = -1;
203+
204+
return retval;
205+
}
206+
148207
/*
149208
* Generate a Param node to replace the given Aggref
150209
* which is expected to have agglevelsup > 0 (ie, it is not local).
@@ -440,17 +499,19 @@ build_subplan(PlannerInfo *root, Plan *plan, List *rtable,
440499
Node *arg;
441500

442501
/*
443-
* The Var or Aggref has already been adjusted to have the correct
444-
* varlevelsup or agglevelsup. We probably don't even need to
445-
* copy it again, but be safe.
502+
* The Var, PlaceHolderVar, or Aggref has already been adjusted to
503+
* have the correct varlevelsup, phlevelsup, or agglevelsup. We
504+
* probably don't even need to copy it again, but be safe.
446505
*/
447506
arg = copyObject(pitem->item);
448507

449508
/*
450-
* If it's an Aggref, its arguments might contain SubLinks, which
451-
* have not yet been processed. Do that now.
509+
* If it's a PlaceHolderVar or Aggref, its arguments might contain
510+
* SubLinks, which have not yet been processed (see the comments
511+
* for SS_replace_correlation_vars). Do that now.
452512
*/
453-
if (IsA(arg, Aggref))
513+
if (IsA(arg, PlaceHolderVar) ||
514+
IsA(arg, Aggref))
454515
arg = SS_process_sublinks(root, arg, false);
455516

456517
splan->parParam = lappend_int(splan->parParam, paramid);
@@ -1544,24 +1605,25 @@ convert_EXISTS_to_ANY(PlannerInfo *root, Query *subselect,
15441605
/*
15451606
* Replace correlation vars (uplevel vars) with Params.
15461607
*
1547-
* Uplevel aggregates are replaced, too.
1608+
* Uplevel PlaceHolderVars and aggregates are replaced, too.
15481609
*
15491610
* Note: it is critical that this runs immediately after SS_process_sublinks.
1550-
* Since we do not recurse into the arguments of uplevel aggregates, they will
1551-
* get copied to the appropriate subplan args list in the parent query with
1552-
* uplevel vars not replaced by Params, but only adjusted in level (see
1553-
* replace_outer_agg). That's exactly what we want for the vars of the parent
1554-
* level --- but if an aggregate's argument contains any further-up variables,
1555-
* they have to be replaced with Params in their turn. That will happen when
1556-
* the parent level runs SS_replace_correlation_vars. Therefore it must do
1557-
* so after expanding its sublinks to subplans. And we don't want any steps
1558-
* in between, else those steps would never get applied to the aggregate
1559-
* argument expressions, either in the parent or the child level.
1611+
* Since we do not recurse into the arguments of uplevel PHVs and aggregates,
1612+
* they will get copied to the appropriate subplan args list in the parent
1613+
* query with uplevel vars not replaced by Params, but only adjusted in level
1614+
* (see replace_outer_placeholdervar and replace_outer_agg). That's exactly
1615+
* what we want for the vars of the parent level --- but if a PHV's or
1616+
* aggregate's argument contains any further-up variables, they have to be
1617+
* replaced with Params in their turn. That will happen when the parent level
1618+
* runs SS_replace_correlation_vars. Therefore it must do so after expanding
1619+
* its sublinks to subplans. And we don't want any steps in between, else
1620+
* those steps would never get applied to the argument expressions, either in
1621+
* the parent or the child level.
15601622
*
15611623
* Another fairly tricky thing going on here is the handling of SubLinks in
1562-
* the arguments of uplevel aggregates. Those are not touched inside the
1563-
* intermediate query level, either. Instead, SS_process_sublinks recurses
1564-
* on them after copying the Aggref expression into the parent plan level
1624+
* the arguments of uplevel PHVs/aggregates. Those are not touched inside the
1625+
* intermediate query level, either. Instead, SS_process_sublinks recurses on
1626+
* them after copying the PHV or Aggref expression into the parent plan level
15651627
* (this is actually taken care of in build_subplan).
15661628
*/
15671629
Node *
@@ -1581,6 +1643,12 @@ replace_correlation_vars_mutator(Node *node, PlannerInfo *root)
15811643
if (((Var *) node)->varlevelsup > 0)
15821644
return (Node *) replace_outer_var(root, (Var *) node);
15831645
}
1646+
if (IsA(node, PlaceHolderVar))
1647+
{
1648+
if (((PlaceHolderVar *) node)->phlevelsup > 0)
1649+
return (Node *) replace_outer_placeholdervar(root,
1650+
(PlaceHolderVar *) node);
1651+
}
15841652
if (IsA(node, Aggref))
15851653
{
15861654
if (((Aggref *) node)->agglevelsup > 0)
@@ -1640,12 +1708,17 @@ process_sublinks_mutator(Node *node, process_sublinks_context *context)
16401708
}
16411709

16421710
/*
1643-
* Don't recurse into the arguments of an outer aggregate here. Any
1644-
* SubLinks in the arguments have to be dealt with at the outer query
1645-
* level; they'll be handled when build_subplan collects the Aggref into
1646-
* the arguments to be passed down to the current subplan.
1711+
* Don't recurse into the arguments of an outer PHV or aggregate here.
1712+
* Any SubLinks in the arguments have to be dealt with at the outer query
1713+
* level; they'll be handled when build_subplan collects the PHV or Aggref
1714+
* into the arguments to be passed down to the current subplan.
16471715
*/
1648-
if (IsA(node, Aggref))
1716+
if (IsA(node, PlaceHolderVar))
1717+
{
1718+
if (((PlaceHolderVar *) node)->phlevelsup > 0)
1719+
return node;
1720+
}
1721+
else if (IsA(node, Aggref))
16491722
{
16501723
if (((Aggref *) node)->agglevelsup > 0)
16511724
return node;

src/backend/optimizer/prep/prepjointree.c

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1875,8 +1875,6 @@ reduce_outer_joins_pass2(Node *jtnode,
18751875
*
18761876
* Find any PlaceHolderVar nodes in the given tree that reference the
18771877
* pulled-up relid, and change them to reference the replacement relid(s).
1878-
* We do not need to recurse into subqueries, since no subquery of the current
1879-
* top query could (yet) contain such a reference.
18801878
*
18811879
* NOTE: although this has the form of a walker, we cheat and modify the
18821880
* nodes in-place. This should be OK since the tree was copied by
@@ -1887,6 +1885,7 @@ reduce_outer_joins_pass2(Node *jtnode,
18871885
typedef struct
18881886
{
18891887
int varno;
1888+
int sublevels_up;
18901889
Relids subrelids;
18911890
} substitute_multiple_relids_context;
18921891

@@ -1900,7 +1899,8 @@ substitute_multiple_relids_walker(Node *node,
19001899
{
19011900
PlaceHolderVar *phv = (PlaceHolderVar *) node;
19021901

1903-
if (bms_is_member(context->varno, phv->phrels))
1902+
if (phv->phlevelsup == context->sublevels_up &&
1903+
bms_is_member(context->varno, phv->phrels))
19041904
{
19051905
phv->phrels = bms_union(phv->phrels,
19061906
context->subrelids);
@@ -1909,6 +1909,18 @@ substitute_multiple_relids_walker(Node *node,
19091909
}
19101910
/* fall through to examine children */
19111911
}
1912+
if (IsA(node, Query))
1913+
{
1914+
/* Recurse into subselects */
1915+
bool result;
1916+
1917+
context->sublevels_up++;
1918+
result = query_tree_walker((Query *) node,
1919+
substitute_multiple_relids_walker,
1920+
(void *) context, 0);
1921+
context->sublevels_up--;
1922+
return result;
1923+
}
19121924
/* Shouldn't need to handle planner auxiliary nodes here */
19131925
Assert(!IsA(node, SpecialJoinInfo));
19141926
Assert(!IsA(node, AppendRelInfo));
@@ -1924,6 +1936,7 @@ substitute_multiple_relids(Node *node, int varno, Relids subrelids)
19241936
substitute_multiple_relids_context context;
19251937

19261938
context.varno = varno;
1939+
context.sublevels_up = 0;
19271940
context.subrelids = subrelids;
19281941

19291942
/*

src/include/nodes/relation.h

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1318,12 +1318,17 @@ typedef struct PlaceHolderInfo
13181318
*
13191319
* Each paramlist item shows the absolute query level it is associated with,
13201320
* where the outermost query is level 1 and nested subqueries have higher
1321-
* numbers. The item the parameter slot represents can be one of three kinds:
1321+
* numbers. The item the parameter slot represents can be one of four kinds:
13221322
*
13231323
* A Var: the slot represents a variable of that level that must be passed
13241324
* down because subqueries have outer references to it. The varlevelsup
13251325
* value in the Var will always be zero.
13261326
*
1327+
* A PlaceHolderVar: this works much like the Var case, except that the
1328+
* entry is a PlaceHolderVar node with a contained expression. The PHV
1329+
* will have phlevelsup = 0, and the contained expression is adjusted
1330+
* to match in level.
1331+
*
13271332
* An Aggref (with an expression tree representing its argument): the slot
13281333
* represents an aggregate expression that is an outer reference for some
13291334
* subquery. The Aggref itself has agglevelsup = 0, and its argument tree
@@ -1333,14 +1338,14 @@ typedef struct PlaceHolderInfo
13331338
* for that subplan). The absolute level shown for such items corresponds
13341339
* to the parent query of the subplan.
13351340
*
1336-
* Note: we detect duplicate Var parameters and coalesce them into one slot,
1337-
* but we do not do this for Aggref or Param slots.
1341+
* Note: we detect duplicate Var and PlaceHolderVar parameters and coalesce
1342+
* them into one slot, but we do not do this for Aggref or Param slots.
13381343
*/
13391344
typedef struct PlannerParamItem
13401345
{
13411346
NodeTag type;
13421347

1343-
Node *item; /* the Var, Aggref, or Param */
1348+
Node *item; /* the Var, PlaceHolderVar, Aggref, or Param */
13441349
Index abslevel; /* its absolute query level */
13451350
} PlannerParamItem;
13461351

src/test/regress/expected/join.out

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2506,6 +2506,28 @@ ON sub1.key1 = sub2.key3;
25062506
1 | 1 | 1 | 1
25072507
(1 row)
25082508

2509+
--
2510+
-- test case where a PlaceHolderVar is propagated into a subquery
2511+
--
2512+
select * from
2513+
int8_tbl t1 left join
2514+
(select q1 as x, 42 as y from int8_tbl t2) ss
2515+
on t1.q2 = ss.x
2516+
where
2517+
1 = (select 1 from int8_tbl t3 where ss.y is not null limit 1)
2518+
order by 1,2;
2519+
q1 | q2 | x | y
2520+
------------------+------------------+------------------+----
2521+
123 | 4567890123456789 | 4567890123456789 | 42
2522+
123 | 4567890123456789 | 4567890123456789 | 42
2523+
123 | 4567890123456789 | 4567890123456789 | 42
2524+
4567890123456789 | 123 | 123 | 42
2525+
4567890123456789 | 123 | 123 | 42
2526+
4567890123456789 | 4567890123456789 | 4567890123456789 | 42
2527+
4567890123456789 | 4567890123456789 | 4567890123456789 | 42
2528+
4567890123456789 | 4567890123456789 | 4567890123456789 | 42
2529+
(8 rows)
2530+
25092531
--
25102532
-- test the corner cases FULL JOIN ON TRUE and FULL JOIN ON FALSE
25112533
--

src/test/regress/sql/join.sql

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -621,6 +621,17 @@ LEFT JOIN
621621
) sub2
622622
ON sub1.key1 = sub2.key3;
623623

624+
--
625+
-- test case where a PlaceHolderVar is propagated into a subquery
626+
--
627+
select * from
628+
int8_tbl t1 left join
629+
(select q1 as x, 42 as y from int8_tbl t2) ss
630+
on t1.q2 = ss.x
631+
where
632+
1 = (select 1 from int8_tbl t3 where ss.y is not null limit 1)
633+
order by 1,2;
634+
624635
--
625636
-- test the corner cases FULL JOIN ON TRUE and FULL JOIN ON FALSE
626637
--

0 commit comments

Comments
 (0)