Skip to content

Commit 66e9df9

Browse files
committed
Fix some new issues with planning of PlaceHolderVars.
In the wake of commit a16ef31, we need to deal with more cases involving PlaceHolderVars in NestLoopParams than we did before. For one thing, a16ef31 was incorrect to suppose that we could rely on the required-outer relids of the lefthand path to decide placement of nestloop-parameter PHVs. As Richard Guo argued at the time, we must look at the required-outer relids of the join path itself. For another, we have to apply replace_nestloop_params() to such a PHV's expression, in case it contains references to values that will be supplied from NestLoopParams of higher-level nestloops. For another, we need to be more careful about the phnullingrels of the PHV than we were being. identify_current_nestloop_params only bothered to ensure that the phnullingrels didn't contain "too many" relids, but now it has to be exact, because setrefs.c will apply both NRM_SUBSET and NRM_SUPERSET checks in different places. We can compute the correct relids by determining the set of outer joins that should be able to null the PHV and then subtracting whatever's been applied at or below this join. Do the same for plain Vars, too. (This should make it possible to use NRM_EQUAL to process nestloop params in setrefs.c, but I won't risk making such a change in v18 now.) Lastly, if a nestloop parameter 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 can substitute the version of the PHV's expression appearing in its PlaceHolderInfo to ensure that that preprocessing has happened. (Seems like this processing sequence could stand to be redesigned, but again, late in v18 development is not the time for that.) It's not very clear to me why the old have_dangerous_phv join-order restriction prevented us from seeing the last three of these problems. But given the lack of field complaints, it must have done so. Reported-by: Alexander Lakhin <exclusion@gmail.com> Author: Tom Lane <tgl@sss.pgh.pa.us> Discussion: https://postgr.es/m/18953-1c9883a9d4afeb30@postgresql.org
1 parent 8319e5c commit 66e9df9

File tree

7 files changed

+330
-37
lines changed

7 files changed

+330
-37
lines changed

src/backend/optimizer/plan/createplan.c

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4344,6 +4344,7 @@ create_nestloop_plan(PlannerInfo *root,
43444344
NestLoop *join_plan;
43454345
Plan *outer_plan;
43464346
Plan *inner_plan;
4347+
Relids outerrelids;
43474348
List *tlist = build_path_tlist(root, &best_path->jpath.path);
43484349
List *joinrestrictclauses = best_path->jpath.joinrestrictinfo;
43494350
List *joinclauses;
@@ -4374,8 +4375,8 @@ create_nestloop_plan(PlannerInfo *root,
43744375
outer_plan = create_plan_recurse(root, best_path->jpath.outerjoinpath, 0);
43754376

43764377
/* For a nestloop, include outer relids in curOuterRels for inner side */
4377-
root->curOuterRels = bms_union(root->curOuterRels,
4378-
best_path->jpath.outerjoinpath->parent->relids);
4378+
outerrelids = best_path->jpath.outerjoinpath->parent->relids;
4379+
root->curOuterRels = bms_union(root->curOuterRels, outerrelids);
43794380

43804381
inner_plan = create_plan_recurse(root, best_path->jpath.innerjoinpath, 0);
43814382

@@ -4415,40 +4416,59 @@ create_nestloop_plan(PlannerInfo *root,
44154416
* node, and remove them from root->curOuterParams.
44164417
*/
44174418
nestParams = identify_current_nestloop_params(root,
4418-
best_path->jpath.outerjoinpath);
4419+
outerrelids,
4420+
PATH_REQ_OUTER((Path *) best_path));
44194421

44204422
/*
44214423
* While nestloop parameters that are Vars had better be available from
44224424
* the outer_plan already, there are edge cases where nestloop parameters
44234425
* that are PHVs won't be. In such cases we must add them to the
44244426
* outer_plan's tlist, since the executor's NestLoopParam machinery
44254427
* requires the params to be simple outer-Var references to that tlist.
4428+
* (This is cheating a little bit, because the outer path's required-outer
4429+
* relids might not be enough to allow evaluating such a PHV. But in
4430+
* practice, if we could have evaluated the PHV at the nestloop node, we
4431+
* can do so in the outer plan too.)
44264432
*/
44274433
outer_tlist = outer_plan->targetlist;
44284434
outer_parallel_safe = outer_plan->parallel_safe;
44294435
foreach(lc, nestParams)
44304436
{
44314437
NestLoopParam *nlp = (NestLoopParam *) lfirst(lc);
4438+
PlaceHolderVar *phv;
44324439
TargetEntry *tle;
44334440

44344441
if (IsA(nlp->paramval, Var))
44354442
continue; /* nothing to do for simple Vars */
4436-
if (tlist_member((Expr *) nlp->paramval, outer_tlist))
4443+
/* Otherwise it must be a PHV */
4444+
phv = castNode(PlaceHolderVar, nlp->paramval);
4445+
4446+
if (tlist_member((Expr *) phv, outer_tlist))
44374447
continue; /* already available */
44384448

4449+
/*
4450+
* It's possible that nestloop parameter PHVs selected to evaluate
4451+
* here contain references to surviving root->curOuterParams items
4452+
* (that is, they reference values that will be supplied by some
4453+
* higher-level nestloop). Those need to be converted to Params now.
4454+
* Note: it's safe to do this after the tlist_member() check, because
4455+
* equal() won't pay attention to phv->phexpr.
4456+
*/
4457+
phv->phexpr = (Expr *) replace_nestloop_params(root,
4458+
(Node *) phv->phexpr);
4459+
44394460
/* Make a shallow copy of outer_tlist, if we didn't already */
44404461
if (outer_tlist == outer_plan->targetlist)
44414462
outer_tlist = list_copy(outer_tlist);
44424463
/* ... and add the needed expression */
4443-
tle = makeTargetEntry((Expr *) copyObject(nlp->paramval),
4464+
tle = makeTargetEntry((Expr *) copyObject(phv),
44444465
list_length(outer_tlist) + 1,
44454466
NULL,
44464467
true);
44474468
outer_tlist = lappend(outer_tlist, tle);
44484469
/* ... and track whether tlist is (still) parallel-safe */
44494470
if (outer_parallel_safe)
4450-
outer_parallel_safe = is_parallel_safe(root,
4451-
(Node *) nlp->paramval);
4471+
outer_parallel_safe = is_parallel_safe(root, (Node *) phv);
44524472
}
44534473
if (outer_tlist != outer_plan->targetlist)
44544474
outer_plan = change_plan_targetlist(outer_plan, outer_tlist,

src/backend/optimizer/util/paramassign.c

Lines changed: 55 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -599,38 +599,31 @@ process_subquery_nestloop_params(PlannerInfo *root, List *subplan_params)
599599
}
600600

601601
/*
602-
* Identify any NestLoopParams that should be supplied by a NestLoop plan
603-
* node with the specified lefthand input path. Remove them from the active
604-
* root->curOuterParams list and return them as the result list.
602+
* Identify any NestLoopParams that should be supplied by a NestLoop
603+
* plan node with the specified lefthand rels and required-outer rels.
604+
* Remove them from the active root->curOuterParams list and return
605+
* them as the result list.
605606
*
606-
* XXX Here we also hack up the returned Vars and PHVs so that they do not
607-
* contain nullingrel sets exceeding what is available from the outer side.
608-
* This is needed if we have applied outer join identity 3,
609-
* (A leftjoin B on (Pab)) leftjoin C on (Pb*c)
610-
* = A leftjoin (B leftjoin C on (Pbc)) on (Pab)
611-
* and C contains lateral references to B. It's still safe to apply the
612-
* identity, but the parser will have created those references in the form
613-
* "b*" (i.e., with varnullingrels listing the A/B join), while what we will
614-
* have available from the nestloop's outer side is just "b". We deal with
615-
* that here by stripping the nullingrels down to what is available from the
616-
* outer side according to leftrelids.
617-
*
618-
* That fixes matters for the case of forward application of identity 3.
619-
* If the identity was applied in the reverse direction, we will have
620-
* parameter Vars containing too few nullingrel bits rather than too many.
621-
* Currently, that causes no problems because setrefs.c applies only a
622-
* subset check to nullingrels in NestLoopParams, but we'd have to work
623-
* harder if we ever want to tighten that check. This is all pretty annoying
624-
* because it greatly weakens setrefs.c's cross-check, but the alternative
607+
* Vars and PHVs appearing in the result list must have nullingrel sets
608+
* that could validly appear in the lefthand rel's output. Ordinarily that
609+
* would be true already, but if we have applied outer join identity 3,
610+
* there could be more or fewer nullingrel bits in the nodes appearing in
611+
* curOuterParams than are in the nominal leftrelids. We deal with that by
612+
* forcing their nullingrel sets to include exactly the outer-join relids
613+
* that appear in leftrelids and can null the respective Var or PHV.
614+
* This fix is a bit ad-hoc and intellectually unsatisfactory, because it's
615+
* essentially jumping to the conclusion that we've placed evaluation of
616+
* the nestloop parameters correctly, and thus it defeats the intent of the
617+
* subsequent nullingrel cross-checks in setrefs.c. But the alternative
625618
* seems to be to generate multiple versions of each laterally-parameterized
626619
* subquery, which'd be unduly expensive.
627620
*/
628621
List *
629-
identify_current_nestloop_params(PlannerInfo *root, Path *leftpath)
622+
identify_current_nestloop_params(PlannerInfo *root,
623+
Relids leftrelids,
624+
Relids outerrelids)
630625
{
631626
List *result;
632-
Relids leftrelids = leftpath->parent->relids;
633-
Relids outerrelids = PATH_REQ_OUTER(leftpath);
634627
Relids allleftrelids;
635628
ListCell *cell;
636629

@@ -661,25 +654,58 @@ identify_current_nestloop_params(PlannerInfo *root, Path *leftpath)
661654
bms_is_member(nlp->paramval->varno, leftrelids))
662655
{
663656
Var *var = (Var *) nlp->paramval;
657+
RelOptInfo *rel = root->simple_rel_array[var->varno];
664658

665659
root->curOuterParams = foreach_delete_current(root->curOuterParams,
666660
cell);
667-
var->varnullingrels = bms_intersect(var->varnullingrels,
661+
var->varnullingrels = bms_intersect(rel->nulling_relids,
668662
leftrelids);
669663
result = lappend(result, nlp);
670664
}
671665
else if (IsA(nlp->paramval, PlaceHolderVar))
672666
{
673667
PlaceHolderVar *phv = (PlaceHolderVar *) nlp->paramval;
674-
Relids eval_at = find_placeholder_info(root, phv)->ph_eval_at;
668+
PlaceHolderInfo *phinfo = find_placeholder_info(root, phv);
669+
Relids eval_at = phinfo->ph_eval_at;
675670

676671
if (bms_is_subset(eval_at, allleftrelids) &&
677672
bms_overlap(eval_at, leftrelids))
678673
{
679674
root->curOuterParams = foreach_delete_current(root->curOuterParams,
680675
cell);
681-
phv->phnullingrels = bms_intersect(phv->phnullingrels,
682-
leftrelids);
676+
677+
/*
678+
* Deal with an edge case: if the PHV was pulled up out of a
679+
* subquery and it contains a subquery that was originally
680+
* pushed down from this query level, then that will still be
681+
* represented as a SubLink, because SS_process_sublinks won't
682+
* recurse into outer PHVs, so it didn't get transformed
683+
* during expression preprocessing in the subquery. We need a
684+
* version of the PHV that has a SubPlan, which we can get
685+
* from the current query level's placeholder_list. This is
686+
* quite grotty of course, but dealing with it earlier in the
687+
* handling of subplan params would be just as grotty, and it
688+
* might end up being a waste of cycles if we don't decide to
689+
* treat the PHV as a NestLoopParam. (Perhaps that whole
690+
* mechanism should be redesigned someday, but today is not
691+
* that day.)
692+
*/
693+
if (root->parse->hasSubLinks)
694+
{
695+
phv = copyObject(phinfo->ph_var);
696+
697+
/*
698+
* The ph_var will have empty nullingrels, but that
699+
* doesn't matter since we're about to overwrite
700+
* phv->phnullingrels. Other fields should be OK already.
701+
*/
702+
nlp->paramval = (Var *) phv;
703+
}
704+
705+
phv->phnullingrels =
706+
bms_intersect(get_placeholder_nulling_relids(root, phinfo),
707+
leftrelids);
708+
683709
result = lappend(result, nlp);
684710
}
685711
}

src/backend/optimizer/util/placeholder.c

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -545,3 +545,43 @@ contain_placeholder_references_walker(Node *node,
545545
return expression_tree_walker(node, contain_placeholder_references_walker,
546546
context);
547547
}
548+
549+
/*
550+
* Compute the set of outer-join relids that can null a placeholder.
551+
*
552+
* This is analogous to RelOptInfo.nulling_relids for Vars, but we compute it
553+
* on-the-fly rather than saving it somewhere. Currently the value is needed
554+
* at most once per query, so there's little value in doing otherwise. If it
555+
* ever gains more widespread use, perhaps we should cache the result in
556+
* PlaceHolderInfo.
557+
*/
558+
Relids
559+
get_placeholder_nulling_relids(PlannerInfo *root, PlaceHolderInfo *phinfo)
560+
{
561+
Relids result = NULL;
562+
int relid = -1;
563+
564+
/*
565+
* Form the union of all potential nulling OJs for each baserel included
566+
* in ph_eval_at.
567+
*/
568+
while ((relid = bms_next_member(phinfo->ph_eval_at, relid)) > 0)
569+
{
570+
RelOptInfo *rel = root->simple_rel_array[relid];
571+
572+
/* ignore the RTE_GROUP RTE */
573+
if (relid == root->group_rtindex)
574+
continue;
575+
576+
if (rel == NULL) /* must be an outer join */
577+
{
578+
Assert(bms_is_member(relid, root->outer_join_rels));
579+
continue;
580+
}
581+
result = bms_add_members(result, rel->nulling_relids);
582+
}
583+
584+
/* Now remove any OJs already included in ph_eval_at, and we're done. */
585+
result = bms_del_members(result, phinfo->ph_eval_at);
586+
return result;
587+
}

src/include/optimizer/paramassign.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ extern Param *replace_nestloop_param_placeholdervar(PlannerInfo *root,
3030
extern void process_subquery_nestloop_params(PlannerInfo *root,
3131
List *subplan_params);
3232
extern List *identify_current_nestloop_params(PlannerInfo *root,
33-
Path *leftpath);
33+
Relids leftrelids,
34+
Relids outerrelids);
3435
extern Param *generate_new_exec_param(PlannerInfo *root, Oid paramtype,
3536
int32 paramtypmod, Oid paramcollation);
3637
extern int assign_special_exec_param(PlannerInfo *root);

src/include/optimizer/placeholder.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,5 +30,7 @@ extern void add_placeholders_to_joinrel(PlannerInfo *root, RelOptInfo *joinrel,
3030
SpecialJoinInfo *sjinfo);
3131
extern bool contain_placeholder_references_to(PlannerInfo *root, Node *clause,
3232
int relid);
33+
extern Relids get_placeholder_nulling_relids(PlannerInfo *root,
34+
PlaceHolderInfo *phinfo);
3335

3436
#endif /* PLACEHOLDER_H */

0 commit comments

Comments
 (0)