Skip to content

Commit ddc3cb3

Browse files
hlinnakareshke
authored andcommitted
Push down join quals into lateral subqueries.
We don't normally push down join quals into subqueries, because that would require creating parameterized plans. However, if the plan is already parameterized because it's LATERAL, we might as well push down any additional join quals, that refer the same relations that are already referenced within the subquery. This changes the behavior of the sublevels_up parameters to ReplaceVarsFromTargetList(). The targetlist entries used to replace vars are no longer offset by that amount. I'm not sure what the original thinking on it was, but all the existing callers passed sublevels_up = 0, so I hope this is OK. Rebase-by: reshke <reshke@double.cloud>
1 parent 997e5c0 commit ddc3cb3

10 files changed

Lines changed: 335 additions & 61 deletions

File tree

src/backend/optimizer/path/allpaths.c

Lines changed: 143 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -163,13 +163,12 @@ static pushdown_safe_type qual_is_pushdown_safe(Query *subquery, Index rti,
163163
RestrictInfo *rinfo,
164164
pushdown_safety_info *safetyInfo);
165165
static void subquery_push_qual(Query *subquery,
166-
RangeTblEntry *rte, Index rti, Node *qual);
166+
RangeTblEntry *rte, Index rti, Node *qual, int sublevels_up);
167167
static void recurse_push_qual(Node *setOp, Query *topquery,
168-
RangeTblEntry *rte, Index rti, Node *qual);
168+
RangeTblEntry *rte, Index rti, Node *qual, int sublevels_up);
169169
static void remove_unused_subquery_outputs(Query *subquery, RelOptInfo *rel,
170170
Bitmapset *extra_used_attrs);
171171

172-
173172
/*
174173
* make_one_rel
175174
* Finds all possible access paths for executing a query, returning a
@@ -2674,6 +2673,11 @@ check_and_push_window_quals(Query *subquery, Node *clause,
26742673
* So the paths made here will be parameterized if the subquery contains
26752674
* LATERAL references, otherwise not. As long as that's true, there's no need
26762675
* for a separate set_subquery_size phase: just make the paths right away.
2676+
*
2677+
* (If a subquery is LATERAL, though, we do push down join clauses that refer
2678+
* to relations that the subquery already references laterally. Pushing down
2679+
* such quals won't make the subquery any more lateral, so there's no reason
2680+
* not to.)
26772681
*/
26782682
static void
26792683
set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
@@ -2689,6 +2693,7 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
26892693
Bitmapset *run_cond_attrs = NULL;
26902694
ListCell *lc;
26912695
char *plan_name;
2696+
List *pushed_down_ec_joins = NIL;
26922697

26932698
/*
26942699
* Must copy the Query so that planning doesn't mess up the RTE contents
@@ -2699,8 +2704,7 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
26992704

27002705
/*
27012706
* If it's a LATERAL subquery, it might contain some Vars of the current
2702-
* query level, requiring it to be treated as parameterized, even though
2703-
* we don't support pushing down join quals into subqueries.
2707+
* query level, requiring it to be treated as parameterized.
27042708
*/
27052709
required_outer = rel->lateral_relids;
27062710

@@ -2739,65 +2743,140 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
27392743
* pseudoconstant clauses; better to have the gating node above the
27402744
* subquery.
27412745
*
2746+
* Join clauses are only pushed down, if the subquery is LATERAL, and
2747+
* the join clause only refers to relations that the subquery already
2748+
* depends on. It might be useful to push down other join clauses, too,
2749+
* but then we would need to plan the subquery multiple times, to create
2750+
* parameterized paths, which seems too expensive.
2751+
*
27422752
* Non-pushed-down clauses will get evaluated as qpquals of the
27432753
* SubqueryScan node.
27442754
*
27452755
* XXX Are there any cases where we want to make a policy decision not to
27462756
* push down a pushable qual, because it'd result in a worse plan?
27472757
*/
2748-
if (rel->baserestrictinfo != NIL &&
2758+
if ((rel->baserestrictinfo != NIL ||
2759+
(!bms_is_empty(required_outer) && (rel->joininfo || rel->has_eclass_joins))) &&
27492760
subquery_is_pushdown_safe(subquery, subquery, &safetyInfo))
27502761
{
27512762
/* OK to consider pushing down individual quals */
2752-
List *upperrestrictlist = NIL;
27532763
ListCell *l;
2764+
Bitmapset *available_relids;
27542765

2755-
foreach(l, rel->baserestrictinfo)
2766+
if (rel->baserestrictinfo)
27562767
{
2757-
RestrictInfo *rinfo = (RestrictInfo *) lfirst(l);
2758-
Node *clause = (Node *) rinfo->clause;
2768+
List *upperrestrictlist = NIL;
27592769

2760-
if (rinfo->pseudoconstant)
2761-
{
2762-
upperrestrictlist = lappend(upperrestrictlist, rinfo);
2763-
continue;
2764-
}
27652770

2766-
switch (qual_is_pushdown_safe(subquery, rti, rinfo, &safetyInfo))
2771+
foreach(l, rel->baserestrictinfo)
27672772
{
2768-
case PUSHDOWN_SAFE:
2769-
/* Push it down */
2770-
subquery_push_qual(subquery, rte, rti, clause);
2771-
break;
2773+
RestrictInfo *rinfo = (RestrictInfo *) lfirst(l);
2774+
Node *clause = (Node *) rinfo->clause;
27722775

2773-
case PUSHDOWN_WINDOWCLAUSE_RUNCOND:
2776+
if (rinfo->pseudoconstant)
2777+
{
2778+
upperrestrictlist = lappend(upperrestrictlist, rinfo);
2779+
continue;
2780+
}
2781+
2782+
switch (qual_is_pushdown_safe(subquery, rti, rinfo, &safetyInfo))
2783+
{
2784+
case PUSHDOWN_SAFE:
2785+
/* Push it down */
2786+
subquery_push_qual(subquery, rte, rti, clause, 0);
2787+
break;
2788+
2789+
case PUSHDOWN_WINDOWCLAUSE_RUNCOND:
27742790

2775-
/*
2776-
* Since we can't push the qual down into the subquery,
2777-
* check if it happens to reference a window function. If
2778-
* so then it might be useful to use for the WindowAgg's
2779-
* runCondition.
2780-
*/
2781-
if (!subquery->hasWindowFuncs ||
2782-
check_and_push_window_quals(subquery, clause,
2783-
&run_cond_attrs))
2784-
{
27852791
/*
2786-
* subquery has no window funcs or the clause is not a
2787-
* suitable window run condition qual or it is, but
2788-
* the original must also be kept in the upper query.
2789-
*/
2792+
* Since we can't push the qual down into the subquery,
2793+
* check if it happens to reference a window function. If
2794+
* so then it might be useful to use for the WindowAgg's
2795+
* runCondition.
2796+
*/
2797+
if (!subquery->hasWindowFuncs ||
2798+
check_and_push_window_quals(subquery, clause,
2799+
&run_cond_attrs))
2800+
{
2801+
/*
2802+
* subquery has no window funcs or the clause is not a
2803+
* suitable window run condition qual or it is, but
2804+
* the original must also be kept in the upper query.
2805+
*/
2806+
upperrestrictlist = lappend(upperrestrictlist, rinfo);
2807+
}
2808+
break;
2809+
2810+
case PUSHDOWN_UNSAFE:
2811+
/* Keep it in the upper query */
27902812
upperrestrictlist = lappend(upperrestrictlist, rinfo);
2813+
break;
2814+
}
2815+
}
2816+
}
2817+
2818+
/*
2819+
* Push down join quals, as well. But only for LATERAL, and only for those
2820+
* relations that are "required" anyway.
2821+
*/
2822+
if (!bms_is_empty(required_outer))
2823+
{
2824+
available_relids = bms_copy(required_outer);
2825+
available_relids = bms_add_member(available_relids, rti);
2826+
2827+
if (rel->joininfo)
2828+
{
2829+
ListCell *lc;
2830+
List *upperjoinlist = NIL;
2831+
2832+
foreach(lc, rel->joininfo)
2833+
{
2834+
RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
2835+
Node *clause = (Node *) rinfo->clause;
2836+
2837+
if (!rinfo->pseudoconstant &&
2838+
bms_is_subset(rinfo->required_relids, available_relids) &&
2839+
qual_is_pushdown_safe(subquery, rti, clause, &safetyInfo))
2840+
{
2841+
/* Push it down */
2842+
subquery_push_qual(subquery, rte, rti, clause, 0);
2843+
}
2844+
else
2845+
{
2846+
/* Keep it in the upper query */
2847+
upperjoinlist = lappend(upperjoinlist, rinfo);
27912848
}
2792-
break;
2849+
}
2850+
rel->joininfo = upperjoinlist;
2851+
}
27932852

2794-
case PUSHDOWN_UNSAFE:
2795-
upperrestrictlist = lappend(upperrestrictlist, rinfo);
2796-
break;
2853+
if (rel->has_eclass_joins)
2854+
{
2855+
List *clauses;
2856+
2857+
clauses = generate_join_implied_equalities(root,
2858+
available_relids,
2859+
required_outer,
2860+
rel,
2861+
NULL);
2862+
2863+
foreach(lc, clauses)
2864+
{
2865+
RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
2866+
Node *clause = (Node *) rinfo->clause;
2867+
2868+
if (!rinfo->pseudoconstant &&
2869+
qual_is_pushdown_safe(subquery, rti, clause, &safetyInfo))
2870+
{
2871+
/* Push it down */
2872+
Assert(bms_is_subset(rinfo->required_relids, available_relids));
2873+
subquery_push_qual(subquery, rte, rti, clause, 0);
2874+
2875+
pushed_down_ec_joins = lappend(pushed_down_ec_joins, clause);
2876+
}
2877+
}
27972878
}
27982879
}
2799-
rel->baserestrictinfo = upperrestrictlist;
2800-
/* We don't bother recomputing baserestrict_min_security */
28012880
}
28022881

28032882
pfree(safetyInfo.unsafeFlags);
@@ -2908,7 +2987,7 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
29082987
add_path(rel, (Path *)
29092988
create_subqueryscan_path(root, rel, subpath,
29102989
trivial_pathtarget,
2911-
pathkeys, required_outer));
2990+
pathkeys, required_outer, pushed_down_ec_joins));
29122991
}
29132992

29142993
/* If outer rel allows parallelism, do same for partial paths. */
@@ -2935,7 +3014,7 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
29353014
create_subqueryscan_path(root, rel, subpath,
29363015
trivial_pathtarget,
29373016
pathkeys,
2938-
required_outer));
3017+
required_outer, pushed_down_ec_joins));
29393018
}
29403019
}
29413020
}
@@ -4505,6 +4584,13 @@ qual_is_pushdown_safe(Query *subquery, Index rti, RestrictInfo *rinfo,
45054584
break;
45064585
}
45074586

4587+
/*
4588+
* In a restriction clause, all Vars must refer to subselect output
4589+
* columns, but join quals will contain Vars referring to other relations.
4590+
*/
4591+
if (var->varno != rti)
4592+
continue;
4593+
45084594
/* Subqueries have no system columns */
45094595
Assert(var->varattno >= 0);
45104596

@@ -4543,26 +4629,32 @@ qual_is_pushdown_safe(Query *subquery, Index rti, RestrictInfo *rinfo,
45434629
* subquery_push_qual - push down a qual that we have determined is safe
45444630
*/
45454631
static void
4546-
subquery_push_qual(Query *subquery, RangeTblEntry *rte, Index rti, Node *qual)
4632+
subquery_push_qual(Query *subquery, RangeTblEntry *rte, Index rti, Node *qual, int sublevels_up)
45474633
{
45484634
if (subquery->setOperations != NULL)
45494635
{
45504636
/* Recurse to push it separately to each component query */
45514637
recurse_push_qual(subquery->setOperations, subquery,
4552-
rte, rti, qual);
4638+
rte, rti, qual, sublevels_up);
45534639
}
45544640
else
45554641
{
45564642
/*
45574643
* We need to replace Vars in the qual (which must refer to outputs of
45584644
* the subquery) with copies of the subquery's targetlist expressions.
45594645
* Note that at this point, any uplevel Vars in the qual should have
4560-
* been replaced with Params, so they need no work.
4646+
* been replaced with Params, so they need no work. But in a join qual,
4647+
* there can be Vars referring to other relations at the same level.
4648+
* We need to increment varlevelsup of those, so that when the qual is
4649+
* pushed down, they refer to the parent query.
45614650
*
45624651
* This step also ensures that when we are pushing into a setop tree,
45634652
* each component query gets its own copy of the qual.
45644653
*/
4565-
qual = ReplaceVarsFromTargetList(qual, rti, 0, rte,
4654+
qual = copyObject(qual);
4655+
IncrementVarSublevelsUp(qual, sublevels_up + 1, 0);
4656+
4657+
qual = ReplaceVarsFromTargetList(qual, rti, sublevels_up + 1, rte,
45664658
subquery->targetList,
45674659
subquery->resultRelation,
45684660
REPLACEVARS_REPORT_ERROR, 0,
@@ -4592,7 +4684,7 @@ subquery_push_qual(Query *subquery, RangeTblEntry *rte, Index rti, Node *qual)
45924684
*/
45934685
static void
45944686
recurse_push_qual(Node *setOp, Query *topquery,
4595-
RangeTblEntry *rte, Index rti, Node *qual)
4687+
RangeTblEntry *rte, Index rti, Node *qual, int sublevels_up)
45964688
{
45974689
if (IsA(setOp, RangeTblRef))
45984690
{
@@ -4601,14 +4693,14 @@ recurse_push_qual(Node *setOp, Query *topquery,
46014693
Query *subquery = subrte->subquery;
46024694

46034695
Assert(subquery != NULL);
4604-
subquery_push_qual(subquery, rte, rti, qual);
4696+
subquery_push_qual(subquery, rte, rti, qual, sublevels_up + 1);
46054697
}
46064698
else if (IsA(setOp, SetOperationStmt))
46074699
{
46084700
SetOperationStmt *op = (SetOperationStmt *) setOp;
46094701

4610-
recurse_push_qual(op->larg, topquery, rte, rti, qual);
4611-
recurse_push_qual(op->rarg, topquery, rte, rti, qual);
4702+
recurse_push_qual(op->larg, topquery, rte, rti, qual, sublevels_up);
4703+
recurse_push_qual(op->rarg, topquery, rte, rti, qual, sublevels_up);
46124704
}
46134705
else
46144706
{

src/backend/optimizer/plan/createplan.c

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3544,6 +3544,9 @@ create_subqueryscan_plan(PlannerInfo *root, SubqueryScanPath *best_path,
35443544
RelOptInfo *rel = best_path->path.parent;
35453545
Index scan_relid = rel->relid;
35463546
Plan *subplan;
3547+
ListCell *l;
3548+
List *qpqual;
3549+
List *sq_quals = best_path->pushed_down_ec_joins;
35473550

35483551
/* it should be a subquery base rel... */
35493552
Assert(scan_relid > 0);
@@ -3556,6 +3559,33 @@ create_subqueryscan_plan(PlannerInfo *root, SubqueryScanPath *best_path,
35563559
*/
35573560
subplan = create_plan(rel->subroot, best_path->subpath);
35583561

3562+
/*
3563+
* If we had pushed down any join clauses to the subquery, we don't need
3564+
* to re-check them in the SubqueryScan node.
3565+
*
3566+
* This only applies to join clauses derived from equivalence classes.
3567+
* Non-join quals, and non-EC-derived join clauses are immediately removed
3568+
* from 'baserestrictinfo' and 'joininfo' when they're pushed down, so we
3569+
* won't need to worry about them here.
3570+
*/
3571+
qpqual = NIL;
3572+
foreach (l, scan_clauses)
3573+
{
3574+
RestrictInfo *rinfo = lfirst_node(RestrictInfo, l);
3575+
3576+
if (rinfo->pseudoconstant)
3577+
continue; /* we may drop pseudoconstants here */
3578+
if (list_member_ptr(sq_quals, rinfo))
3579+
continue; /* simple duplicate */
3580+
if (is_redundant_derived_clause(rinfo, sq_quals))
3581+
continue; /* derived from same EquivalenceClass */
3582+
if (!contain_mutable_functions((Node *) rinfo->clause) &&
3583+
predicate_implied_by(list_make1(rinfo->clause), sq_quals, false))
3584+
continue; /* provably implied by indexquals */
3585+
qpqual = lappend(qpqual, rinfo);
3586+
}
3587+
scan_clauses = qpqual;
3588+
35593589
/* Sort clauses into best execution order */
35603590
scan_clauses = order_qual_clauses(root, scan_clauses);
35613591

src/backend/optimizer/prep/prepunion.c

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -551,7 +551,7 @@ build_setop_child_paths(PlannerInfo *root, RelOptInfo *rel,
551551
subpath,
552552
trivial_tlist,
553553
pathkeys,
554-
NULL));
554+
NULL, NIL));
555555
}
556556

557557
/* skip dealing with sorted paths if the setop doesn't need them */
@@ -619,7 +619,7 @@ build_setop_child_paths(PlannerInfo *root, RelOptInfo *rel,
619619
subpath,
620620
trivial_tlist,
621621
pathkeys,
622-
NULL));
622+
NULL, NIL));
623623
}
624624
}
625625

@@ -642,7 +642,7 @@ build_setop_child_paths(PlannerInfo *root, RelOptInfo *rel,
642642
partial_path = (Path *)
643643
create_subqueryscan_path(root, rel, partial_subpath,
644644
trivial_tlist,
645-
NIL, NULL);
645+
NIL, NULL, NIL);
646646
add_partial_path(rel, partial_path);
647647
}
648648

0 commit comments

Comments
 (0)