{
PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) baserel->fdw_private;
ForeignPath *path;
- Relids lateral_referencers;
List *join_quals;
Relids required_outer;
double rows;
* consider combinations of clauses, probably.
*/
- /*
- * If there are any rels that have LATERAL references to this one, we
- * cannot use join quals referencing them as remote quals for this one,
- * since such rels would have to be on the inside not the outside of a
- * nestloop join relative to this one. Create a Relids set listing all
- * such rels, for use in checks of potential join clauses.
- */
- lateral_referencers = NULL;
- foreach(lc, root->lateral_info_list)
- {
- LateralJoinInfo *ljinfo = (LateralJoinInfo *) lfirst(lc);
-
- if (bms_is_member(baserel->relid, ljinfo->lateral_lhs))
- lateral_referencers = bms_add_member(lateral_referencers,
- ljinfo->lateral_rhs);
- }
-
/* Scan the rel's join clauses */
foreach(lc, baserel->joininfo)
{
RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
/* Check if clause can be moved to this rel */
- if (!join_clause_is_movable_to(rinfo, baserel->relid))
- continue;
-
- /* Not useful if it conflicts with any LATERAL references */
- if (bms_overlap(rinfo->clause_relids, lateral_referencers))
+ if (!join_clause_is_movable_to(rinfo, baserel))
continue;
/* See if it is safe to send to remote */
baserel,
ec_member_matches_foreign,
(void *) &arg,
- lateral_referencers);
+ baserel->lateral_referencers);
/* Done if there are no more expressions in the foreign rel */
if (arg.current == NULL)
RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
/* Check if clause can be moved to this rel */
- if (!join_clause_is_movable_to(rinfo, baserel->relid))
+ if (!join_clause_is_movable_to(rinfo, baserel))
continue;
- /* Shouldn't conflict with any LATERAL references */
- Assert(!bms_overlap(rinfo->clause_relids, lateral_referencers));
-
/* See if it is safe to send to remote */
if (!is_foreign_expr(root, baserel, rinfo->clause))
continue;
{
LateralJoinInfo *newnode = makeNode(LateralJoinInfo);
- COPY_SCALAR_FIELD(lateral_rhs);
COPY_BITMAPSET_FIELD(lateral_lhs);
+ COPY_BITMAPSET_FIELD(lateral_rhs);
return newnode;
}
COPY_SCALAR_FIELD(phid);
COPY_NODE_FIELD(ph_var);
COPY_BITMAPSET_FIELD(ph_eval_at);
+ COPY_BITMAPSET_FIELD(ph_lateral);
COPY_BITMAPSET_FIELD(ph_needed);
COPY_SCALAR_FIELD(ph_width);
/*
* We intentionally do not compare phexpr. Two PlaceHolderVars with the
* same ID and levelsup should be considered equal even if the contained
- * expressions have managed to mutate to different states. One way in
- * which that can happen is that initplan sublinks would get replaced by
- * differently-numbered Params when sublink folding is done. (The end
- * result of such a situation would be some unreferenced initplans, which
- * is annoying but not really a problem.)
+ * expressions have managed to mutate to different states. This will
+ * happen during final plan construction when there are nested PHVs, since
+ * the inner PHV will get replaced by a Param in some copies of the outer
+ * PHV. Another way in which it can happen is that initplan sublinks
+ * could get replaced by differently-numbered Params when sublink folding
+ * is done. (The end result of such a situation would be some
+ * unreferenced initplans, which is annoying but not really a problem.) On
+ * the same reasoning, there is no need to examine phrels.
*
* COMPARE_NODE_FIELD(phexpr);
+ *
+ * COMPARE_BITMAPSET_FIELD(phrels);
*/
- COMPARE_BITMAPSET_FIELD(phrels);
COMPARE_SCALAR_FIELD(phid);
COMPARE_SCALAR_FIELD(phlevelsup);
static bool
_equalLateralJoinInfo(const LateralJoinInfo *a, const LateralJoinInfo *b)
{
- COMPARE_SCALAR_FIELD(lateral_rhs);
COMPARE_BITMAPSET_FIELD(lateral_lhs);
+ COMPARE_BITMAPSET_FIELD(lateral_rhs);
return true;
}
_equalPlaceHolderInfo(const PlaceHolderInfo *a, const PlaceHolderInfo *b)
{
COMPARE_SCALAR_FIELD(phid);
- COMPARE_NODE_FIELD(ph_var);
+ COMPARE_NODE_FIELD(ph_var); /* should be redundant */
COMPARE_BITMAPSET_FIELD(ph_eval_at);
+ COMPARE_BITMAPSET_FIELD(ph_lateral);
COMPARE_BITMAPSET_FIELD(ph_needed);
COMPARE_SCALAR_FIELD(ph_width);
WRITE_INT_FIELD(max_attr);
WRITE_NODE_FIELD(lateral_vars);
WRITE_BITMAPSET_FIELD(lateral_relids);
+ WRITE_BITMAPSET_FIELD(lateral_referencers);
WRITE_NODE_FIELD(indexlist);
WRITE_UINT_FIELD(pages);
WRITE_FLOAT_FIELD(tuples, "%.0f");
{
WRITE_NODE_TYPE("LATERALJOININFO");
- WRITE_UINT_FIELD(lateral_rhs);
WRITE_BITMAPSET_FIELD(lateral_lhs);
+ WRITE_BITMAPSET_FIELD(lateral_rhs);
}
static void
WRITE_UINT_FIELD(phid);
WRITE_NODE_FIELD(ph_var);
WRITE_BITMAPSET_FIELD(ph_eval_at);
+ WRITE_BITMAPSET_FIELD(ph_lateral);
WRITE_BITMAPSET_FIELD(ph_needed);
WRITE_INT_FIELD(ph_width);
}
still expected to enforce any join clauses that can be pushed down to it,
so that all paths of the same parameterization have the same rowcount.
+We also allow LATERAL subqueries to be flattened (pulled up into the parent
+query) by the optimizer, but only when they don't contain any lateral
+references to relations outside the lowest outer join that can null the
+LATERAL subquery. This restriction prevents lateral references from being
+introduced into outer-join qualifications, which would create semantic
+confusion. Note that even with this restriction, pullup of a LATERAL
+subquery can result in creating PlaceHolderVars that contain lateral
+references to relations outside their syntactic scope. We still evaluate
+such PHVs at their syntactic location or lower, but the presence of such a
+PHV in the quals or targetlist of a plan node requires that node to appear
+on the inside of a nestloop join relative to the rel(s) supplying the
+lateral reference. (Perhaps now that that stuff works, we could relax the
+pullup restriction?)
+
-- bjm & tgl
/*
* We don't support pushing join clauses into the quals of a seqscan, but
* it could still have required parameterization due to LATERAL refs in
- * its tlist. (That can only happen if the seqscan is on a relation
- * pulled up out of a UNION ALL appendrel.)
+ * its tlist.
*/
required_outer = rel->lateral_relids;
* Note: the resulting childrel->reltargetlist may contain arbitrary
* expressions, which otherwise would not occur in a reltargetlist.
* Code that might be looking at an appendrel child must cope with
- * such. Note in particular that "arbitrary expression" can include
- * "Var belonging to another relation", due to LATERAL references.
+ * such. (Normally, a reltargetlist would only include Vars and
+ * PlaceHolderVars.)
*/
childrel->joininfo = (List *)
adjust_appendrel_attrs(root,
/*
* We don't support pushing join clauses into the quals of a CTE scan, but
* it could still have required parameterization due to LATERAL refs in
- * its tlist. (That can only happen if the CTE scan is on a relation
- * pulled up out of a UNION ALL appendrel.)
+ * its tlist.
*/
required_outer = rel->lateral_relids;
/*
* We don't support pushing join clauses into the quals of a worktable
* scan, but it could still have required parameterization due to LATERAL
- * refs in its tlist. (That can only happen if the worktable scan is on a
- * relation pulled up out of a UNION ALL appendrel. I'm not sure this is
- * actually possible given the restrictions on recursive references, but
- * it's easy enough to support.)
+ * refs in its tlist. (I'm not sure this is actually possible given the
+ * restrictions on recursive references, but it's easy enough to support.)
*/
required_outer = rel->lateral_relids;
/*
* Ordinarily, a Var in a rel's reltargetlist must belong to that rel;
- * but there are corner cases involving LATERAL references in
- * appendrel members where that isn't so (see set_append_rel_size()).
- * If the Var has the wrong varno, fall through to the generic case
- * (it doesn't seem worth the trouble to be any smarter).
+ * but there are corner cases involving LATERAL references where that
+ * isn't so. If the Var has the wrong varno, fall through to the
+ * generic case (it doesn't seem worth the trouble to be any smarter).
*/
if (IsA(node, Var) &&
((Var *) node)->varno == rel->relid)
IndexClauseSet *clauseset);
static void match_join_clauses_to_index(PlannerInfo *root,
RelOptInfo *rel, IndexOptInfo *index,
- Relids lateral_referencers,
IndexClauseSet *clauseset,
List **joinorclauses);
static void match_eclass_clauses_to_index(PlannerInfo *root,
IndexOptInfo *index,
- Relids lateral_referencers,
IndexClauseSet *clauseset);
static void match_clauses_to_index(IndexOptInfo *index,
List *clauses,
*
* Note: check_partial_indexes() must have been run previously for this rel.
*
- * Note: in corner cases involving LATERAL appendrel children, it's possible
- * that rel->lateral_relids is nonempty. Currently, we include lateral_relids
- * into the parameterization reported for each path, but don't take it into
- * account otherwise. The fact that any such rels *must* be available as
- * parameter sources perhaps should influence our choices of index quals ...
- * but for now, it doesn't seem worth troubling over. In particular, comments
- * below about "unparameterized" paths should be read as meaning
- * "unparameterized so far as the indexquals are concerned".
+ * Note: in cases involving LATERAL references in the relation's tlist, it's
+ * possible that rel->lateral_relids is nonempty. Currently, we include
+ * lateral_relids into the parameterization reported for each path, but don't
+ * take it into account otherwise. The fact that any such rels *must* be
+ * available as parameter sources perhaps should influence our choices of
+ * index quals ... but for now, it doesn't seem worth troubling over.
+ * In particular, comments below about "unparameterized" paths should be read
+ * as meaning "unparameterized so far as the indexquals are concerned".
*/
void
create_index_paths(PlannerInfo *root, RelOptInfo *rel)
List *bitindexpaths;
List *bitjoinpaths;
List *joinorclauses;
- Relids lateral_referencers;
IndexClauseSet rclauseset;
IndexClauseSet jclauseset;
IndexClauseSet eclauseset;
if (rel->indexlist == NIL)
return;
- /*
- * If there are any rels that have LATERAL references to this one, we
- * cannot use join quals referencing them as index quals for this one,
- * since such rels would have to be on the inside not the outside of a
- * nestloop join relative to this one. Create a Relids set listing all
- * such rels, for use in checks of potential join clauses.
- */
- lateral_referencers = NULL;
- foreach(lc, root->lateral_info_list)
- {
- LateralJoinInfo *ljinfo = (LateralJoinInfo *) lfirst(lc);
-
- if (bms_is_member(rel->relid, ljinfo->lateral_lhs))
- lateral_referencers = bms_add_member(lateral_referencers,
- ljinfo->lateral_rhs);
- }
-
/* Bitmap paths are collected and then dealt with at the end */
bitindexpaths = bitjoinpaths = joinorclauses = NIL;
* EquivalenceClasses. Also, collect join OR clauses for later.
*/
MemSet(&jclauseset, 0, sizeof(jclauseset));
- match_join_clauses_to_index(root, rel, index, lateral_referencers,
+ match_join_clauses_to_index(root, rel, index,
&jclauseset, &joinorclauses);
/*
* the index.
*/
MemSet(&eclauseset, 0, sizeof(eclauseset));
- match_eclass_clauses_to_index(root, index, lateral_referencers,
+ match_eclass_clauses_to_index(root, index,
&eclauseset);
/*
static void
match_join_clauses_to_index(PlannerInfo *root,
RelOptInfo *rel, IndexOptInfo *index,
- Relids lateral_referencers,
IndexClauseSet *clauseset,
List **joinorclauses)
{
RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
/* Check if clause can be moved to this rel */
- if (!join_clause_is_movable_to(rinfo, rel->relid))
- continue;
-
- /* Not useful if it conflicts with any LATERAL references */
- if (bms_overlap(rinfo->clause_relids, lateral_referencers))
+ if (!join_clause_is_movable_to(rinfo, rel))
continue;
/* Potentially usable, so see if it matches the index or is an OR */
*/
static void
match_eclass_clauses_to_index(PlannerInfo *root, IndexOptInfo *index,
- Relids lateral_referencers,
IndexClauseSet *clauseset)
{
int indexcol;
index->rel,
ec_member_matches_indexcol,
(void *) &arg,
- lateral_referencers);
+ index->rel->lateral_referencers);
/*
* We have to check whether the results actually do match the index,
RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
/* Check if clause can be moved to this rel */
- if (!join_clause_is_movable_to(rinfo, rel->relid))
+ if (!join_clause_is_movable_to(rinfo, rel))
continue;
clauselist = lappend(clauselist, rinfo);
RelOptInfo *outerrel, RelOptInfo *innerrel,
List *restrictlist, List *mergeclause_list,
JoinType jointype, SpecialJoinInfo *sjinfo,
- Relids param_source_rels);
+ Relids param_source_rels, Relids extra_lateral_rels);
static void match_unsorted_outer(PlannerInfo *root, RelOptInfo *joinrel,
RelOptInfo *outerrel, RelOptInfo *innerrel,
List *restrictlist, List *mergeclause_list,
JoinType jointype, SpecialJoinInfo *sjinfo,
SemiAntiJoinFactors *semifactors,
- Relids param_source_rels);
+ Relids param_source_rels, Relids extra_lateral_rels);
static void hash_inner_and_outer(PlannerInfo *root, RelOptInfo *joinrel,
RelOptInfo *outerrel, RelOptInfo *innerrel,
List *restrictlist,
JoinType jointype, SpecialJoinInfo *sjinfo,
SemiAntiJoinFactors *semifactors,
- Relids param_source_rels);
+ Relids param_source_rels, Relids extra_lateral_rels);
static List *select_mergejoin_clauses(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *outerrel,
bool mergejoin_allowed = true;
SemiAntiJoinFactors semifactors;
Relids param_source_rels = NULL;
+ Relids extra_lateral_rels = NULL;
ListCell *lc;
/*
{
LateralJoinInfo *ljinfo = (LateralJoinInfo *) lfirst(lc);
- if (bms_is_member(ljinfo->lateral_rhs, joinrel->relids))
+ if (bms_is_subset(ljinfo->lateral_rhs, joinrel->relids))
param_source_rels = bms_join(param_source_rels,
bms_difference(ljinfo->lateral_lhs,
joinrel->relids));
}
+ /*
+ * Another issue created by LATERAL references is that PlaceHolderVars
+ * that need to be computed at this join level might contain lateral
+ * references to rels not in the join, meaning that the paths for the join
+ * would need to be marked as parameterized by those rels, independently
+ * of all other considerations. Set extra_lateral_rels to the set of such
+ * rels. This will not affect our decisions as to which paths to
+ * generate; we merely add these rels to their required_outer sets.
+ */
+ foreach(lc, root->placeholder_list)
+ {
+ PlaceHolderInfo *phinfo = (PlaceHolderInfo *) lfirst(lc);
+
+ /* PHVs without lateral refs can be skipped over quickly */
+ if (phinfo->ph_lateral == NULL)
+ continue;
+ /* Is it due to be evaluated at this join, and not in either input? */
+ if (bms_is_subset(phinfo->ph_eval_at, joinrel->relids) &&
+ !bms_is_subset(phinfo->ph_eval_at, outerrel->relids) &&
+ !bms_is_subset(phinfo->ph_eval_at, innerrel->relids))
+ {
+ /* Yes, remember its lateral rels */
+ extra_lateral_rels = bms_add_members(extra_lateral_rels,
+ phinfo->ph_lateral);
+ }
+ }
+
+ /*
+ * Make sure extra_lateral_rels doesn't list anything within the join, and
+ * that it's NULL if empty. (This allows us to use bms_add_members to add
+ * it to required_outer below, while preserving the property that
+ * required_outer is exactly NULL if empty.)
+ */
+ extra_lateral_rels = bms_del_members(extra_lateral_rels, joinrel->relids);
+ if (bms_is_empty(extra_lateral_rels))
+ extra_lateral_rels = NULL;
+
/*
* 1. Consider mergejoin paths where both relations must be explicitly
* sorted. Skip this if we can't mergejoin.
if (mergejoin_allowed)
sort_inner_and_outer(root, joinrel, outerrel, innerrel,
restrictlist, mergeclause_list, jointype,
- sjinfo, param_source_rels);
+ sjinfo,
+ param_source_rels, extra_lateral_rels);
/*
* 2. Consider paths where the outer relation need not be explicitly
if (mergejoin_allowed)
match_unsorted_outer(root, joinrel, outerrel, innerrel,
restrictlist, mergeclause_list, jointype,
- sjinfo, &semifactors, param_source_rels);
+ sjinfo, &semifactors,
+ param_source_rels, extra_lateral_rels);
#ifdef NOT_USED
if (mergejoin_allowed)
match_unsorted_inner(root, joinrel, outerrel, innerrel,
restrictlist, mergeclause_list, jointype,
- sjinfo, &semifactors, param_source_rels);
+ sjinfo, &semifactors,
+ param_source_rels, extra_lateral_rels);
#endif
/*
if (enable_hashjoin || jointype == JOIN_FULL)
hash_inner_and_outer(root, joinrel, outerrel, innerrel,
restrictlist, jointype,
- sjinfo, &semifactors, param_source_rels);
+ sjinfo, &semifactors,
+ param_source_rels, extra_lateral_rels);
}
/*
SpecialJoinInfo *sjinfo,
SemiAntiJoinFactors *semifactors,
Relids param_source_rels,
+ Relids extra_lateral_rels,
Path *outer_path,
Path *inner_path,
List *restrict_clauses,
return;
}
+ /*
+ * Independently of that, add parameterization needed for any
+ * PlaceHolderVars that need to be computed at the join.
+ */
+ required_outer = bms_add_members(required_outer, extra_lateral_rels);
+
/*
* Do a precheck to quickly eliminate obviously-inferior paths. We
* calculate a cheap lower bound on the path's cost and then use
JoinType jointype,
SpecialJoinInfo *sjinfo,
Relids param_source_rels,
+ Relids extra_lateral_rels,
Path *outer_path,
Path *inner_path,
List *restrict_clauses,
return;
}
+ /*
+ * Independently of that, add parameterization needed for any
+ * PlaceHolderVars that need to be computed at the join.
+ */
+ required_outer = bms_add_members(required_outer, extra_lateral_rels);
+
/*
* If the given paths are already well enough ordered, we can skip doing
* an explicit sort.
SpecialJoinInfo *sjinfo,
SemiAntiJoinFactors *semifactors,
Relids param_source_rels,
+ Relids extra_lateral_rels,
Path *outer_path,
Path *inner_path,
List *restrict_clauses,
return;
}
+ /*
+ * Independently of that, add parameterization needed for any
+ * PlaceHolderVars that need to be computed at the join.
+ */
+ required_outer = bms_add_members(required_outer, extra_lateral_rels);
+
/*
* See comments in try_nestloop_path(). Also note that hashjoin paths
* never have any output pathkeys, per comments in create_hashjoin_path.
* 'jointype' is the type of join to do
* 'sjinfo' is extra info about the join for selectivity estimation
* 'param_source_rels' are OK targets for parameterization of result paths
+ * 'extra_lateral_rels' are additional parameterization for result paths
*/
static void
sort_inner_and_outer(PlannerInfo *root,
List *mergeclause_list,
JoinType jointype,
SpecialJoinInfo *sjinfo,
- Relids param_source_rels)
+ Relids param_source_rels,
+ Relids extra_lateral_rels)
{
Path *outer_path;
Path *inner_path;
jointype,
sjinfo,
param_source_rels,
+ extra_lateral_rels,
outer_path,
inner_path,
restrictlist,
* 'sjinfo' is extra info about the join for selectivity estimation
* 'semifactors' contains valid data if jointype is SEMI or ANTI
* 'param_source_rels' are OK targets for parameterization of result paths
+ * 'extra_lateral_rels' are additional parameterization for result paths
*/
static void
match_unsorted_outer(PlannerInfo *root,
JoinType jointype,
SpecialJoinInfo *sjinfo,
SemiAntiJoinFactors *semifactors,
- Relids param_source_rels)
+ Relids param_source_rels,
+ Relids extra_lateral_rels)
{
JoinType save_jointype = jointype;
bool nestjoinOK;
sjinfo,
semifactors,
param_source_rels,
+ extra_lateral_rels,
outerpath,
inner_cheapest_total,
restrictlist,
sjinfo,
semifactors,
param_source_rels,
+ extra_lateral_rels,
outerpath,
innerpath,
restrictlist,
sjinfo,
semifactors,
param_source_rels,
+ extra_lateral_rels,
outerpath,
matpath,
restrictlist,
jointype,
sjinfo,
param_source_rels,
+ extra_lateral_rels,
outerpath,
inner_cheapest_total,
restrictlist,
jointype,
sjinfo,
param_source_rels,
+ extra_lateral_rels,
outerpath,
innerpath,
restrictlist,
jointype,
sjinfo,
param_source_rels,
+ extra_lateral_rels,
outerpath,
innerpath,
restrictlist,
* 'sjinfo' is extra info about the join for selectivity estimation
* 'semifactors' contains valid data if jointype is SEMI or ANTI
* 'param_source_rels' are OK targets for parameterization of result paths
+ * 'extra_lateral_rels' are additional parameterization for result paths
*/
static void
hash_inner_and_outer(PlannerInfo *root,
JoinType jointype,
SpecialJoinInfo *sjinfo,
SemiAntiJoinFactors *semifactors,
- Relids param_source_rels)
+ Relids param_source_rels,
+ Relids extra_lateral_rels)
{
bool isouterjoin = IS_OUTER_JOIN(jointype);
List *hashclauses;
sjinfo,
semifactors,
param_source_rels,
+ extra_lateral_rels,
cheapest_total_outer,
cheapest_total_inner,
restrictlist,
sjinfo,
semifactors,
param_source_rels,
+ extra_lateral_rels,
cheapest_total_outer,
cheapest_total_inner,
restrictlist,
sjinfo,
semifactors,
param_source_rels,
+ extra_lateral_rels,
cheapest_startup_outer,
cheapest_total_inner,
restrictlist,
sjinfo,
semifactors,
param_source_rels,
+ extra_lateral_rels,
cheapest_startup_outer,
cheapest_total_inner,
restrictlist,
sjinfo,
semifactors,
param_source_rels,
+ extra_lateral_rels,
outerpath,
innerpath,
restrictlist,
{
LateralJoinInfo *ljinfo = (LateralJoinInfo *) lfirst(l);
- if (bms_is_member(ljinfo->lateral_rhs, rel2->relids) &&
+ if (bms_is_subset(ljinfo->lateral_rhs, rel2->relids) &&
bms_overlap(ljinfo->lateral_lhs, rel1->relids))
{
/* has to be implemented as nestloop with rel1 on left */
(reversed || match_sjinfo->jointype == JOIN_FULL))
return false; /* not implementable as nestloop */
}
- if (bms_is_member(ljinfo->lateral_rhs, rel1->relids) &&
+ if (bms_is_subset(ljinfo->lateral_rhs, rel1->relids) &&
bms_overlap(ljinfo->lateral_lhs, rel2->relids))
{
/* has to be implemented as nestloop with rel2 on left */
{
LateralJoinInfo *ljinfo = (LateralJoinInfo *) lfirst(l);
- if (bms_is_member(ljinfo->lateral_rhs, rel2->relids) &&
+ if (bms_is_subset(ljinfo->lateral_rhs, rel2->relids) &&
bms_overlap(ljinfo->lateral_lhs, rel1->relids))
return true;
- if (bms_is_member(ljinfo->lateral_rhs, rel1->relids) &&
+ if (bms_is_subset(ljinfo->lateral_rhs, rel1->relids) &&
bms_overlap(ljinfo->lateral_lhs, rel2->relids))
return true;
}
{
LateralJoinInfo *ljinfo = (LateralJoinInfo *) lfirst(l);
- if (bms_is_member(ljinfo->lateral_rhs, rel->relids) ||
+ if (bms_is_subset(ljinfo->lateral_rhs, rel->relids) ||
bms_overlap(ljinfo->lateral_lhs, rel->relids))
return true;
}
RestrictInfo *rinfo = (RestrictInfo *) lfirst(i);
if (restriction_is_or_clause(rinfo) &&
- join_clause_is_movable_to(rinfo, rel->relid))
+ join_clause_is_movable_to(rinfo, rel))
{
/*
* Use the generate_bitmap_or_paths() machinery to estimate the
/*
* We don't support pushing join clauses into the quals of a tidscan, but
* it could still have required parameterization due to LATERAL refs in
- * its tlist. (That can only happen if the tidscan is on a relation
- * pulled up out of a UNION ALL appendrel.)
+ * its tlist.
*/
required_outer = rel->lateral_relids;
* that will be used above the join. We only need to fail if such a PHV
* actually references some inner-rel attributes; but the correct check
* for that is relatively expensive, so we first check against ph_eval_at,
- * which must mention the inner rel if the PHV uses any inner-rel attrs.
+ * which must mention the inner rel if the PHV uses any inner-rel attrs as
+ * non-lateral references. Note that if the PHV's syntactic scope is just
+ * the inner rel, we can't drop the rel even if the PHV is variable-free.
*/
foreach(l, root->placeholder_list)
{
if (bms_is_subset(phinfo->ph_needed, joinrelids))
continue; /* PHV is not used above the join */
+ if (bms_overlap(phinfo->ph_lateral, innerrel->relids))
+ return false; /* it references innerrel laterally */
if (!bms_overlap(phinfo->ph_eval_at, innerrel->relids))
continue; /* it definitely doesn't reference innerrel */
- if (bms_overlap(pull_varnos((Node *) phinfo->ph_var),
+ if (bms_is_subset(phinfo->ph_eval_at, innerrel->relids))
+ return false; /* there isn't any other place to eval PHV */
+ if (bms_overlap(pull_varnos((Node *) phinfo->ph_var->phexpr),
innerrel->relids))
return false; /* it does reference innerrel */
}
* Likewise remove references from LateralJoinInfo data structures.
*
* If we are deleting a LATERAL subquery, we can forget its
- * LateralJoinInfo altogether. Otherwise, make sure the target is not
+ * LateralJoinInfos altogether. Otherwise, make sure the target is not
* included in any lateral_lhs set. (It probably can't be, since that
* should have precluded deciding to remove it; but let's cope anyway.)
*/
LateralJoinInfo *ljinfo = (LateralJoinInfo *) lfirst(l);
nextl = lnext(l);
- if (ljinfo->lateral_rhs == relid)
+ ljinfo->lateral_rhs = bms_del_member(ljinfo->lateral_rhs, relid);
+ if (bms_is_empty(ljinfo->lateral_rhs))
root->lateral_info_list = list_delete_ptr(root->lateral_info_list,
ljinfo);
else
+ {
ljinfo->lateral_lhs = bms_del_member(ljinfo->lateral_lhs, relid);
+ Assert(!bms_is_empty(ljinfo->lateral_lhs));
+ }
}
/*
* Likewise remove references from PlaceHolderVar data structures.
- *
- * Here we have a special case: if a PHV's eval_at set is just the target
- * relid, we want to leave it that way instead of reducing it to the empty
- * set. An empty eval_at set would confuse later processing since it
- * would match every possible eval placement.
*/
foreach(l, root->placeholder_list)
{
PlaceHolderInfo *phinfo = (PlaceHolderInfo *) lfirst(l);
phinfo->ph_eval_at = bms_del_member(phinfo->ph_eval_at, relid);
- if (bms_is_empty(phinfo->ph_eval_at)) /* oops, belay that */
- phinfo->ph_eval_at = bms_add_member(phinfo->ph_eval_at, relid);
-
+ Assert(!bms_is_empty(phinfo->ph_eval_at));
+ Assert(!bms_is_member(relid, phinfo->ph_lateral));
phinfo->ph_needed = bms_del_member(phinfo->ph_needed, relid);
}
static Plan *create_plan_recurse(PlannerInfo *root, Path *best_path);
static Plan *create_scan_plan(PlannerInfo *root, Path *best_path);
-static List *build_relation_tlist(RelOptInfo *rel);
+static List *build_path_tlist(PlannerInfo *root, Path *path);
static bool use_physical_tlist(PlannerInfo *root, RelOptInfo *rel);
-static void disuse_physical_tlist(Plan *plan, Path *path);
+static void disuse_physical_tlist(PlannerInfo *root, Plan *plan, Path *path);
static Plan *create_gating_plan(PlannerInfo *root, Plan *plan, List *quals);
static Plan *create_join_plan(PlannerInfo *root, JoinPath *best_path);
static Plan *create_append_plan(PlannerInfo *root, AppendPath *best_path);
tlist = build_physical_tlist(root, rel);
/* if fail because of dropped cols, use regular method */
if (tlist == NIL)
- tlist = build_relation_tlist(rel);
+ tlist = build_path_tlist(root, best_path);
}
}
else
{
- tlist = build_relation_tlist(rel);
-
- /*
- * If it's a parameterized otherrel, there might be lateral references
- * in the tlist, which need to be replaced with Params. This cannot
- * happen for regular baserels, though. Note use_physical_tlist()
- * always fails for otherrels, so we don't need to check this above.
- */
- if (rel->reloptkind != RELOPT_BASEREL && best_path->param_info)
- tlist = (List *) replace_nestloop_params(root, (Node *) tlist);
+ tlist = build_path_tlist(root, best_path);
}
/*
}
/*
- * Build a target list (ie, a list of TargetEntry) for a relation.
+ * Build a target list (ie, a list of TargetEntry) for the Path's output.
*/
static List *
-build_relation_tlist(RelOptInfo *rel)
+build_path_tlist(PlannerInfo *root, Path *path)
{
+ RelOptInfo *rel = path->parent;
List *tlist = NIL;
int resno = 1;
ListCell *v;
/* Do we really need to copy here? Not sure */
Node *node = (Node *) copyObject(lfirst(v));
+ /*
+ * If it's a parameterized path, there might be lateral references in
+ * the tlist, which need to be replaced with Params. There's no need
+ * to remake the TargetEntry nodes, so apply this to each list item
+ * separately.
+ */
+ if (path->param_info)
+ node = replace_nestloop_params(root, node);
+
tlist = lappend(tlist, makeTargetEntry((Expr *) node,
resno,
NULL,
* and Material nodes want this, so they don't have to store useless columns.
*/
static void
-disuse_physical_tlist(Plan *plan, Path *path)
+disuse_physical_tlist(PlannerInfo *root, Plan *plan, Path *path)
{
/* Only need to undo it for path types handled by create_scan_plan() */
switch (path->pathtype)
case T_CteScan:
case T_WorkTableScan:
case T_ForeignScan:
- plan->targetlist = build_relation_tlist(path->parent);
+ plan->targetlist = build_path_tlist(root, path);
break;
default:
break;
create_append_plan(PlannerInfo *root, AppendPath *best_path)
{
Append *plan;
- List *tlist = build_relation_tlist(best_path->path.parent);
+ List *tlist = build_path_tlist(root, &best_path->path);
List *subplans = NIL;
ListCell *subpaths;
{
MergeAppend *node = makeNode(MergeAppend);
Plan *plan = &node->plan;
- List *tlist = build_relation_tlist(best_path->path.parent);
+ List *tlist = build_path_tlist(root, &best_path->path);
List *pathkeys = best_path->path.pathkeys;
List *subplans = NIL;
ListCell *subpaths;
subplan = create_plan_recurse(root, best_path->subpath);
/* We don't want any excess columns in the materialized tuples */
- disuse_physical_tlist(subplan, best_path->subpath);
+ disuse_physical_tlist(root, subplan, best_path->subpath);
plan = make_material(subplan);
* should be left as-is if we don't need to add any expressions; but if we
* do have to add expressions, then a projection step will be needed at
* runtime anyway, so we may as well remove unneeded items. Therefore
- * newtlist starts from build_relation_tlist() not just a copy of the
+ * newtlist starts from build_path_tlist() not just a copy of the
* subplan's tlist; and we don't install it into the subplan unless we are
* sorting or stuff has to be added.
*/
uniq_exprs = best_path->uniq_exprs;
/* initialize modified subplan tlist as just the "required" vars */
- newtlist = build_relation_tlist(best_path->path.parent);
+ newtlist = build_path_tlist(root, &best_path->path);
nextresno = list_length(newtlist) + 1;
newitems = false;
* subplan tlist.
*/
plan = (Plan *) make_agg(root,
- build_relation_tlist(best_path->path.parent),
+ build_path_tlist(root, &best_path->path),
NIL,
AGG_HASHED,
NULL,
Plan *inner_plan)
{
NestLoop *join_plan;
- List *tlist = build_relation_tlist(best_path->path.parent);
+ List *tlist = build_path_tlist(root, &best_path->path);
List *joinrestrictclauses = best_path->joinrestrictinfo;
List *joinclauses;
List *otherclauses;
Plan *outer_plan,
Plan *inner_plan)
{
- List *tlist = build_relation_tlist(best_path->jpath.path.parent);
+ List *tlist = build_path_tlist(root, &best_path->jpath.path);
List *joinclauses;
List *otherclauses;
List *mergeclauses;
*/
if (best_path->outersortkeys)
{
- disuse_physical_tlist(outer_plan, best_path->jpath.outerjoinpath);
+ disuse_physical_tlist(root, outer_plan, best_path->jpath.outerjoinpath);
outer_plan = (Plan *)
make_sort_from_pathkeys(root,
outer_plan,
if (best_path->innersortkeys)
{
- disuse_physical_tlist(inner_plan, best_path->jpath.innerjoinpath);
+ disuse_physical_tlist(root, inner_plan, best_path->jpath.innerjoinpath);
inner_plan = (Plan *)
make_sort_from_pathkeys(root,
inner_plan,
Plan *outer_plan,
Plan *inner_plan)
{
- List *tlist = build_relation_tlist(best_path->jpath.path.parent);
+ List *tlist = build_path_tlist(root, &best_path->jpath.path);
List *joinclauses;
List *otherclauses;
List *hashclauses;
best_path->jpath.outerjoinpath->parent->relids);
/* We don't want any excess columns in the hashed tuples */
- disuse_physical_tlist(inner_plan, best_path->jpath.innerjoinpath);
+ disuse_physical_tlist(root, inner_plan, best_path->jpath.innerjoinpath);
/* If we expect batching, suppress excess columns in outer tuples too */
if (best_path->num_batches > 1)
- disuse_physical_tlist(outer_plan, best_path->jpath.outerjoinpath);
+ disuse_physical_tlist(root, outer_plan, best_path->jpath.outerjoinpath);
/*
* If there is a single join clause and we can identify the outer variable
Assert(phv->phlevelsup == 0);
/*
- * If not to be replaced, just return the PlaceHolderVar unmodified.
- * We use bms_overlap as a cheap/quick test to see if the PHV might be
- * evaluated in the outer rels, and then grab its PlaceHolderInfo to
- * tell for sure.
+ * Check whether we need to replace the PHV. We use bms_overlap as a
+ * cheap/quick test to see if the PHV might be evaluated in the outer
+ * rels, and then grab its PlaceHolderInfo to tell for sure.
*/
- if (!bms_overlap(phv->phrels, root->curOuterRels))
- return node;
- if (!bms_is_subset(find_placeholder_info(root, phv, false)->ph_eval_at,
- root->curOuterRels))
- return node;
+ if (!bms_overlap(phv->phrels, root->curOuterRels) ||
+ !bms_is_subset(find_placeholder_info(root, phv, false)->ph_eval_at,
+ root->curOuterRels))
+ {
+ /*
+ * We can't replace the whole PHV, but we might still need to
+ * replace Vars or PHVs within its expression, in case it ends up
+ * actually getting evaluated here. (It might get evaluated in
+ * this plan node, or some child node; in the latter case we don't
+ * really need to process the expression here, but we haven't got
+ * enough info to tell if that's the case.) Flat-copy the PHV
+ * node and then recurse on its expression.
+ *
+ * Note that after doing this, we might have different
+ * representations of the contents of the same PHV in different
+ * parts of the plan tree. This is OK because equal() will just
+ * match on phid/phlevelsup, so setrefs.c will still recognize an
+ * upper-level reference to a lower-level copy of the same PHV.
+ */
+ PlaceHolderVar *newphv = makeNode(PlaceHolderVar);
+
+ memcpy(newphv, phv, sizeof(PlaceHolderVar));
+ newphv->phexpr = (Expr *)
+ replace_nestloop_params_mutator((Node *) phv->phexpr,
+ root);
+ return (Node *) newphv;
+ }
/* Create a Param representing the PlaceHolderVar */
param = assign_nestloop_param_placeholdervar(root, phv);
/* Is this param already listed in root->curOuterParams? */
static void extract_lateral_references(PlannerInfo *root, RelOptInfo *brel,
Index rtindex);
-static void add_lateral_info(PlannerInfo *root, Index rhs, Relids lhs);
+static void add_lateral_info(PlannerInfo *root, Relids lhs, Relids rhs);
static List *deconstruct_recurse(PlannerInfo *root, Node *jtnode,
bool below_outer_join,
Relids *qualscope, Relids *inner_join_rels);
RelOptInfo *rel = find_base_rel(root, var->varno);
int attno = var->varattno;
+ if (bms_is_subset(where_needed, rel->relids))
+ continue;
Assert(attno >= rel->min_attr && attno <= rel->max_attr);
attno -= rel->min_attr;
if (rel->attr_needed[attno] == NULL)
* ensure that the Vars/PHVs propagate up to the nestloop join level; this
* means setting suitable where_needed values for them.
*
+ * Note that this only deals with lateral references in unflattened LATERAL
+ * subqueries. When we flatten a LATERAL subquery, its lateral references
+ * become plain Vars in the parent query, but they may have to be wrapped in
+ * PlaceHolderVars if they need to be forced NULL by outer joins that don't
+ * also null the LATERAL subquery. That's all handled elsewhere.
+ *
* This has to run before deconstruct_jointree, since it might result in
* creation of PlaceHolderInfos.
*/
* This bit is less obvious than it might look. We ignore appendrel
* otherrels and consider only their parent baserels. In a case where
* a LATERAL-containing UNION ALL subquery was pulled up, it is the
- * otherrels that are actually going to be in the plan. However, we
- * want to mark all their lateral references as needed by the parent,
+ * otherrel that is actually going to be in the plan. However, we
+ * want to mark all its lateral references as needed by the parent,
* because it is the parent's relid that will be used for join
* planning purposes. And the parent's RTE will contain all the
- * lateral references we need to know, since the pulled-up members are
- * nothing but copies of parts of the original RTE's subquery. We
- * could visit the children instead and transform their references
- * back to the parent's relid, but it would be much more complicated
- * for no real gain. (Important here is that the child members have
- * not yet received any processing beyond being pulled up.)
+ * lateral references we need to know, since the pulled-up member is
+ * nothing but a copy of parts of the original RTE's subquery. We
+ * could visit the parent's children instead and transform their
+ * references back to the parent's relid, but it would be much more
+ * complicated for no real gain. (Important here is that the child
+ * members have not yet received any processing beyond being pulled
+ * up.) Similarly, in appendrels created by inheritance expansion,
+ * it's sufficient to look at the parent relation.
*/
/* ignore RTEs that are "other rels" */
*/
where_needed = bms_make_singleton(rtindex);
- /* Push the Vars into their source relations' targetlists */
+ /*
+ * Push Vars into their source relations' targetlists, and PHVs into
+ * root->placeholder_list.
+ */
add_vars_to_targetlist(root, newvars, where_needed, true);
/* Remember the lateral references for create_lateral_join_info */
/*
* create_lateral_join_info
- * For each LATERAL subquery, create LateralJoinInfo(s) and add them to
- * root->lateral_info_list, and fill in the per-rel lateral_relids sets.
+ * For each unflattened LATERAL subquery, create LateralJoinInfo(s) and add
+ * them to root->lateral_info_list, and fill in the per-rel lateral_relids
+ * and lateral_referencers sets. Also generate LateralJoinInfo(s) to
+ * represent any lateral references within PlaceHolderVars (this part deals
+ * with the effects of flattened LATERAL subqueries).
*
* This has to run after deconstruct_jointree, because we need to know the
- * final ph_eval_at values for referenced PlaceHolderVars.
+ * final ph_eval_at values for PlaceHolderVars.
*/
void
create_lateral_join_info(PlannerInfo *root)
{
Index rti;
+ ListCell *lc;
/* We need do nothing if the query contains no LATERAL RTEs */
if (!root->hasLateralRTEs)
{
RelOptInfo *brel = root->simple_rel_array[rti];
Relids lateral_relids;
- ListCell *lc;
/* there may be empty slots corresponding to non-baserel RTEs */
if (brel == NULL)
{
Var *var = (Var *) node;
- add_lateral_info(root, rti, bms_make_singleton(var->varno));
+ add_lateral_info(root, bms_make_singleton(var->varno),
+ brel->relids);
lateral_relids = bms_add_member(lateral_relids,
var->varno);
}
PlaceHolderInfo *phinfo = find_placeholder_info(root, phv,
false);
- add_lateral_info(root, rti, bms_copy(phinfo->ph_eval_at));
+ add_lateral_info(root, phinfo->ph_eval_at, brel->relids);
lateral_relids = bms_add_members(lateral_relids,
phinfo->ph_eval_at);
}
if (bms_is_empty(lateral_relids))
continue; /* ensure lateral_relids is NULL if empty */
brel->lateral_relids = lateral_relids;
+ }
+
+ /*
+ * Now check for lateral references within PlaceHolderVars, and make
+ * LateralJoinInfos describing each such reference. Unlike references in
+ * unflattened LATERAL RTEs, the referencing location could be a join.
+ */
+ foreach(lc, root->placeholder_list)
+ {
+ PlaceHolderInfo *phinfo = (PlaceHolderInfo *) lfirst(lc);
+ Relids eval_at = phinfo->ph_eval_at;
+
+ if (phinfo->ph_lateral != NULL)
+ {
+ List *vars = pull_var_clause((Node *) phinfo->ph_var->phexpr,
+ PVC_RECURSE_AGGREGATES,
+ PVC_INCLUDE_PLACEHOLDERS);
+ ListCell *lc2;
+
+ foreach(lc2, vars)
+ {
+ Node *node = (Node *) lfirst(lc2);
+
+ if (IsA(node, Var))
+ {
+ Var *var = (Var *) node;
+
+ if (!bms_is_member(var->varno, eval_at))
+ add_lateral_info(root,
+ bms_make_singleton(var->varno),
+ eval_at);
+ }
+ else if (IsA(node, PlaceHolderVar))
+ {
+ PlaceHolderVar *other_phv = (PlaceHolderVar *) node;
+ PlaceHolderInfo *other_phi;
+
+ other_phi = find_placeholder_info(root, other_phv,
+ false);
+ if (!bms_is_subset(other_phi->ph_eval_at, eval_at))
+ add_lateral_info(root, other_phi->ph_eval_at, eval_at);
+ }
+ else
+ Assert(false);
+ }
+
+ list_free(vars);
+ }
+ }
+
+ /* If we found no lateral references, we're done. */
+ if (root->lateral_info_list == NIL)
+ return;
+
+ /*
+ * Now that we've identified all lateral references, make a second pass in
+ * which we mark each baserel with the set of relids of rels that
+ * reference it laterally (essentially, the inverse mapping of
+ * lateral_relids). We'll need this for join_clause_is_movable_to().
+ *
+ * Also, propagate lateral_relids and lateral_referencers from appendrel
+ * parent rels to their child rels. We intentionally give each child rel
+ * the same minimum parameterization, even though it's quite possible that
+ * some don't reference all the lateral rels. This is because any append
+ * path for the parent will have to have the same parameterization for
+ * every child anyway, and there's no value in forcing extra
+ * reparameterize_path() calls. Similarly, a lateral reference to the
+ * parent prevents use of otherwise-movable join rels for each child.
+ */
+ for (rti = 1; rti < root->simple_rel_array_size; rti++)
+ {
+ RelOptInfo *brel = root->simple_rel_array[rti];
+ Relids lateral_referencers;
+
+ if (brel == NULL)
+ continue;
+ if (brel->reloptkind != RELOPT_BASEREL)
+ continue;
+
+ /* Compute lateral_referencers using the finished lateral_info_list */
+ lateral_referencers = NULL;
+ foreach(lc, root->lateral_info_list)
+ {
+ LateralJoinInfo *ljinfo = (LateralJoinInfo *) lfirst(lc);
+
+ if (bms_is_member(brel->relid, ljinfo->lateral_lhs))
+ lateral_referencers = bms_add_members(lateral_referencers,
+ ljinfo->lateral_rhs);
+ }
+ brel->lateral_referencers = lateral_referencers;
/*
- * If it's an appendrel parent, copy its lateral_relids to each child
- * rel. We intentionally give each child rel the same minimum
- * parameterization, even though it's quite possible that some don't
- * reference all the lateral rels. This is because any append path
- * for the parent will have to have the same parameterization for
- * every child anyway, and there's no value in forcing extra
- * reparameterize_path() calls.
+ * If it's an appendrel parent, copy its lateral_relids and
+ * lateral_referencers to each child rel.
*/
if (root->simple_rte_array[rti]->inh)
{
childrel = root->simple_rel_array[appinfo->child_relid];
Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL);
Assert(childrel->lateral_relids == NULL);
- childrel->lateral_relids = lateral_relids;
+ childrel->lateral_relids = brel->lateral_relids;
+ Assert(childrel->lateral_referencers == NULL);
+ childrel->lateral_referencers = brel->lateral_referencers;
}
}
}
* add_lateral_info
* Add a LateralJoinInfo to root->lateral_info_list, if needed
*
- * We suppress redundant list entries. The passed lhs set must be freshly
- * made; we free it if not used in a new list entry.
+ * We suppress redundant list entries. The passed Relids are copied if saved.
*/
static void
-add_lateral_info(PlannerInfo *root, Index rhs, Relids lhs)
+add_lateral_info(PlannerInfo *root, Relids lhs, Relids rhs)
{
LateralJoinInfo *ljinfo;
- ListCell *l;
+ ListCell *lc;
- Assert(!bms_is_member(rhs, lhs));
+ /* Sanity-check the input */
+ Assert(!bms_is_empty(lhs));
+ Assert(!bms_is_empty(rhs));
+ Assert(!bms_overlap(lhs, rhs));
/*
- * If an existing list member has the same RHS and an LHS that is a subset
- * of the new one, it's redundant, but we don't trouble to get rid of it.
- * The only case that is really worth worrying about is identical entries,
- * and we handle that well enough with this simple logic.
+ * The input is redundant if it has the same RHS and an LHS that is a
+ * subset of an existing entry's. If an existing entry has the same RHS
+ * and an LHS that is a subset of the new one, it's redundant, but we
+ * don't trouble to get rid of it. The only case that is really worth
+ * worrying about is identical entries, and we handle that well enough
+ * with this simple logic.
*/
- foreach(l, root->lateral_info_list)
+ foreach(lc, root->lateral_info_list)
{
- ljinfo = (LateralJoinInfo *) lfirst(l);
- if (rhs == ljinfo->lateral_rhs &&
+ ljinfo = (LateralJoinInfo *) lfirst(lc);
+ if (bms_equal(rhs, ljinfo->lateral_rhs) &&
bms_is_subset(lhs, ljinfo->lateral_lhs))
- {
- bms_free(lhs);
return;
- }
}
/* Not there, so make a new entry */
ljinfo = makeNode(LateralJoinInfo);
- ljinfo->lateral_rhs = rhs;
- ljinfo->lateral_lhs = lhs;
+ ljinfo->lateral_lhs = bms_copy(lhs);
+ ljinfo->lateral_rhs = bms_copy(rhs);
root->lateral_info_list = lappend(root->lateral_info_list, ljinfo);
}
joinlist = deconstruct_jointree(root);
- /*
- * Create the LateralJoinInfo list now that we have finalized
- * PlaceHolderVar eval levels.
- */
- create_lateral_join_info(root);
-
/*
* Reconsider any postponed outer-join quals now that we have built up
* equivalence classes. (This could result in further additions or
*/
add_placeholders_to_base_rels(root);
+ /*
+ * Create the LateralJoinInfo list now that we have finalized
+ * PlaceHolderVar eval levels and made any necessary additions to the
+ * lateral_vars lists for lateral references within PlaceHolderVars.
+ */
+ create_lateral_join_info(root);
+
/*
* We should now have size estimates for every actual table involved in
* the query, and we also know which if any have been deleted from the
PlannerInfo *root;
List *targetlist; /* tlist of subquery being pulled up */
RangeTblEntry *target_rte; /* RTE of subquery */
+ Relids relids; /* relids within subquery, as numbered after
+ * pullup (set only if target_rte->lateral) */
bool *outer_hasSubLinks; /* -> outer query's hasSubLinks */
int varno; /* varno of subquery */
bool need_phvs; /* do we need PlaceHolderVars? */
/*
* The subquery's targetlist items are now in the appropriate form to
* insert into the top query, but if we are under an outer join then
- * non-nullable items may have to be turned into PlaceHolderVars. If we
- * are dealing with an appendrel member then anything that's not a simple
- * Var has to be turned into a PlaceHolderVar. Set up appropriate context
- * data for pullup_replace_vars.
+ * non-nullable items and lateral references may have to be turned into
+ * PlaceHolderVars. If we are dealing with an appendrel member then
+ * anything that's not a simple Var has to be turned into a
+ * PlaceHolderVar. Set up required context data for pullup_replace_vars.
*/
rvcontext.root = root;
rvcontext.targetlist = subquery->targetList;
rvcontext.target_rte = rte;
+ if (rte->lateral)
+ rvcontext.relids = get_relids_in_jointree((Node *) subquery->jointree,
+ true);
+ else /* won't need relids */
+ rvcontext.relids = NULL;
rvcontext.outer_hasSubLinks = &parse->hasSubLinks;
rvcontext.varno = varno;
rvcontext.need_phvs = (lowest_nulling_outer_join != NULL ||
if (newnode && IsA(newnode, Var) &&
((Var *) newnode)->varlevelsup == 0)
{
- /* Simple Vars always escape being wrapped */
- wrap = false;
+ /*
+ * Simple Vars always escape being wrapped, unless they are
+ * lateral references to something outside the subquery being
+ * pulled up. (Even then, we could omit the PlaceHolderVar if
+ * the referenced rel is under the same lowest outer join, but
+ * it doesn't seem worth the trouble to check that.)
+ */
+ if (rcon->target_rte->lateral &&
+ !bms_is_member(((Var *) newnode)->varno, rcon->relids))
+ wrap = true;
+ else
+ wrap = false;
}
else if (newnode && IsA(newnode, PlaceHolderVar) &&
((PlaceHolderVar *) newnode)->phlevelsup == 0)
else
{
/*
- * If it contains a Var of current level, and does not contain
- * any non-strict constructs, then it's certainly nullable so
- * we don't need to insert a PlaceHolderVar.
+ * If it contains a Var of the subquery being pulled up, and
+ * does not contain any non-strict constructs, then it's
+ * certainly nullable so we don't need to insert a
+ * PlaceHolderVar.
*
* This analysis could be tighter: in particular, a non-strict
* construct hidden within a lower-level PlaceHolderVar is not
*
* Note: in future maybe we should insert a PlaceHolderVar
* anyway, if the tlist item is expensive to evaluate?
+ *
+ * For a LATERAL subquery, we have to check the actual var
+ * membership of the node, but if it's non-lateral then any
+ * level-zero var must belong to the subquery.
*/
- if (contain_vars_of_level((Node *) newnode, 0) &&
+ if ((rcon->target_rte->lateral ?
+ bms_overlap(pull_varnos((Node *) newnode), rcon->relids) :
+ contain_vars_of_level((Node *) newnode, 0)) &&
!contain_nonstrict_functions((Node *) newnode))
{
/* No wrap needed */
bool create_new_ph)
{
PlaceHolderInfo *phinfo;
+ Relids rels_used;
ListCell *lc;
/* if this ever isn't true, we'd need to be able to look in parent lists */
phinfo->phid = phv->phid;
phinfo->ph_var = copyObject(phv);
- /* initialize ph_eval_at as the set of contained relids */
- phinfo->ph_eval_at = pull_varnos((Node *) phv);
+
+ /*
+ * Any referenced rels that are outside the PHV's syntactic scope are
+ * LATERAL references, which should be included in ph_lateral but not in
+ * ph_eval_at. If no referenced rels are within the syntactic scope,
+ * force evaluation at the syntactic location.
+ */
+ rels_used = pull_varnos((Node *) phv->phexpr);
+ phinfo->ph_lateral = bms_difference(rels_used, phv->phrels);
+ if (bms_is_empty(phinfo->ph_lateral))
+ phinfo->ph_lateral = NULL; /* make it exactly NULL if empty */
+ phinfo->ph_eval_at = bms_int_members(rels_used, phv->phrels);
+ /* If no contained vars, force evaluation at syntactic location */
+ if (bms_is_empty(phinfo->ph_eval_at))
+ {
+ phinfo->ph_eval_at = bms_copy(phv->phrels);
+ Assert(!bms_is_empty(phinfo->ph_eval_at));
+ }
/* ph_eval_at may change later, see update_placeholder_eval_levels */
phinfo->ph_needed = NULL; /* initially it's unused */
/* for the moment, estimate width using just the datatype info */
*
* We don't need to look at the targetlist because build_base_rel_tlists()
* will already have made entries for any PHVs in the tlist.
+ *
+ * This is called before we begin deconstruct_jointree. Once we begin
+ * deconstruct_jointree, all active placeholders must be present in
+ * root->placeholder_list, because make_outerjoininfo and
+ * update_placeholder_eval_levels require this info to be available
+ * while we crawl up the join tree.
*/
void
find_placeholders_in_jointree(PlannerInfo *root)
* The initial eval_at level set by find_placeholder_info was the set of
* rels used in the placeholder's expression (or the whole subselect below
* the placeholder's syntactic location, if the expr is variable-free).
- * If the subselect contains any outer joins that can null any of those rels,
+ * If the query contains any outer joins that can null any of those rels,
* we must delay evaluation to above those joins.
*
* We repeat this operation each time we add another outer join to
}
} while (found_some);
+ /* Can't move the PHV's eval_at level to above its syntactic level */
+ Assert(bms_is_subset(eval_at, syn_level));
+
phinfo->ph_eval_at = eval_at;
}
}
*
* This is called after we've finished determining the eval_at levels for
* all placeholders. We need to make sure that all vars and placeholders
- * needed to evaluate each placeholder will be available at the join level
- * where the evaluation will be done. Note that this loop can have
- * side-effects on the ph_needed sets of other PlaceHolderInfos; that's okay
- * because we don't examine ph_needed here, so there are no ordering issues
- * to worry about.
+ * needed to evaluate each placeholder will be available at the scan or join
+ * level where the evaluation will be done. (It might seem that scan-level
+ * evaluations aren't interesting, but that's not so: a LATERAL reference
+ * within a placeholder's expression needs to cause the referenced var or
+ * placeholder to be marked as needed in the scan where it's evaluated.)
+ * Note that this loop can have side-effects on the ph_needed sets of other
+ * PlaceHolderInfos; that's okay because we don't examine ph_needed here, so
+ * there are no ordering issues to worry about.
*/
void
fix_placeholder_input_needed_levels(PlannerInfo *root)
foreach(lc, root->placeholder_list)
{
PlaceHolderInfo *phinfo = (PlaceHolderInfo *) lfirst(lc);
- Relids eval_at = phinfo->ph_eval_at;
-
- /* No work unless it'll be evaluated above baserel level */
- if (bms_membership(eval_at) == BMS_MULTIPLE)
- {
- List *vars = pull_var_clause((Node *) phinfo->ph_var->phexpr,
- PVC_RECURSE_AGGREGATES,
- PVC_INCLUDE_PLACEHOLDERS);
+ List *vars = pull_var_clause((Node *) phinfo->ph_var->phexpr,
+ PVC_RECURSE_AGGREGATES,
+ PVC_INCLUDE_PLACEHOLDERS);
- add_vars_to_targetlist(root, vars, eval_at, false);
- list_free(vars);
- }
+ add_vars_to_targetlist(root, vars, phinfo->ph_eval_at, false);
+ list_free(vars);
}
}
/*
* add_placeholders_to_base_rels
- * Add any required PlaceHolderVars to base rels' targetlists.
+ * Add any required PlaceHolderVars to base rels' targetlists, and
+ * update lateral_vars lists for lateral references contained in them.
*
* If any placeholder can be computed at a base rel and is needed above it,
- * add it to that rel's targetlist. This might look like it could be merged
+ * add it to that rel's targetlist, and add any lateral references it requires
+ * to the rel's lateral_vars list. This might look like it could be merged
* with fix_placeholder_input_needed_levels, but it must be separate because
* join removal happens in between, and can change the ph_eval_at sets. There
* is essentially the same logic in add_placeholders_to_joinrel, but we can't
PlaceHolderInfo *phinfo = (PlaceHolderInfo *) lfirst(lc);
Relids eval_at = phinfo->ph_eval_at;
- if (bms_membership(eval_at) == BMS_SINGLETON &&
- bms_nonempty_difference(phinfo->ph_needed, eval_at))
+ if (bms_membership(eval_at) == BMS_SINGLETON)
{
int varno = bms_singleton_member(eval_at);
RelOptInfo *rel = find_base_rel(root, varno);
- rel->reltargetlist = lappend(rel->reltargetlist,
- copyObject(phinfo->ph_var));
+ /* add it to reltargetlist if needed above the rel scan level */
+ if (bms_nonempty_difference(phinfo->ph_needed, eval_at))
+ rel->reltargetlist = lappend(rel->reltargetlist,
+ copyObject(phinfo->ph_var));
+ /* if there are lateral refs in it, add them to lateral_vars */
+ if (phinfo->ph_lateral != NULL)
+ {
+ List *vars = pull_var_clause((Node *) phinfo->ph_var->phexpr,
+ PVC_RECURSE_AGGREGATES,
+ PVC_INCLUDE_PLACEHOLDERS);
+ ListCell *lc2;
+
+ foreach(lc2, vars)
+ {
+ Node *node = (Node *) lfirst(lc2);
+
+ if (IsA(node, Var))
+ {
+ Var *var = (Var *) node;
+
+ if (var->varno != varno)
+ rel->lateral_vars = lappend(rel->lateral_vars,
+ var);
+ }
+ else if (IsA(node, PlaceHolderVar))
+ {
+ PlaceHolderVar *other_phv = (PlaceHolderVar *) node;
+ PlaceHolderInfo *other_phi;
+
+ other_phi = find_placeholder_info(root, other_phv,
+ false);
+ if (!bms_is_subset(other_phi->ph_eval_at, eval_at))
+ rel->lateral_vars = lappend(rel->lateral_vars,
+ other_phv);
+ }
+ else
+ Assert(false);
+ }
+
+ list_free(vars);
+ }
}
}
}
/* min_attr, max_attr, attr_needed, attr_widths are set below */
rel->lateral_vars = NIL;
rel->lateral_relids = NULL;
+ rel->lateral_referencers = NULL;
rel->indexlist = NIL;
rel->pages = 0;
rel->tuples = 0;
joinrel->attr_widths = NULL;
joinrel->lateral_vars = NIL;
joinrel->lateral_relids = NULL;
+ joinrel->lateral_referencers = NULL;
joinrel->indexlist = NIL;
joinrel->pages = 0;
joinrel->tuples = 0;
* outer join, as that would change the results (rows would be suppressed
* rather than being null-extended).
*
- * And the target relation must not be in the clause's nullable_relids, i.e.,
+ * Also the target relation must not be in the clause's nullable_relids, i.e.,
* there must not be an outer join below the clause that would null the Vars
* coming from the target relation. Otherwise the clause might give results
* different from what it would give at its normal semantic level.
+ *
+ * Also, the join clause must not use any relations that have LATERAL
+ * references to the target relation, since we could not put such rels on
+ * the outer side of a nestloop with the target relation.
*/
bool
-join_clause_is_movable_to(RestrictInfo *rinfo, Index baserelid)
+join_clause_is_movable_to(RestrictInfo *rinfo, RelOptInfo *baserel)
{
/* Clause must physically reference target rel */
- if (!bms_is_member(baserelid, rinfo->clause_relids))
+ if (!bms_is_member(baserel->relid, rinfo->clause_relids))
return false;
/* Cannot move an outer-join clause into the join's outer side */
- if (bms_is_member(baserelid, rinfo->outer_relids))
+ if (bms_is_member(baserel->relid, rinfo->outer_relids))
return false;
/* Target rel must not be nullable below the clause */
- if (bms_is_member(baserelid, rinfo->nullable_relids))
+ if (bms_is_member(baserel->relid, rinfo->nullable_relids))
+ return false;
+
+ /* Clause must not use any rels with LATERAL references to this rel */
+ if (bms_overlap(baserel->lateral_referencers, rinfo->clause_relids))
return false;
return true;
* not pushing the clause into its outer-join outer side, nor down into
* a lower outer join's inner side.
*
+ * There's no check here equivalent to join_clause_is_movable_to's test on
+ * lateral_relids. We assume the caller wouldn't be inquiring unless it'd
+ * verified that the proposed outer rels don't have lateral references to
+ * the current rel(s).
+ *
* Note: get_joinrel_parampathinfo depends on the fact that if
* current_and_outer is NULL, this function will always return false
* (since one or the other of the first two tests must fail).
if (IsA(node, PlaceHolderVar))
{
/*
- * Normally, we can just take the varnos in the contained expression.
- * But if it is variable-free, use the PHV's syntactic relids.
+ * A PlaceHolderVar acts as a variable of its syntactic scope, or
+ * lower than that if it references only a subset of the rels in its
+ * syntactic scope. It might also contain lateral references, but we
+ * should ignore such references when computing the set of varnos in
+ * an expression tree. Also, if the PHV contains no variables within
+ * its syntactic scope, it will be forced to be evaluated exactly at
+ * the syntactic scope, so take that as the relid set.
*/
PlaceHolderVar *phv = (PlaceHolderVar *) node;
pull_varnos_context subcontext;
subcontext.varnos = NULL;
subcontext.sublevels_up = context->sublevels_up;
(void) pull_varnos_walker((Node *) phv->phexpr, &subcontext);
-
- if (bms_is_empty(subcontext.varnos) &&
- phv->phlevelsup == context->sublevels_up)
- context->varnos = bms_add_members(context->varnos, phv->phrels);
- else
- context->varnos = bms_join(context->varnos, subcontext.varnos);
+ if (phv->phlevelsup == context->sublevels_up)
+ {
+ subcontext.varnos = bms_int_members(subcontext.varnos,
+ phv->phrels);
+ if (bms_is_empty(subcontext.varnos))
+ context->varnos = bms_add_members(context->varnos,
+ phv->phrels);
+ }
+ context->varnos = bms_join(context->varnos, subcontext.varnos);
return false;
}
if (IsA(node, Query))
* Vars and PlaceHolderVars)
* lateral_relids - required outer rels for LATERAL, as a Relids set
* (for child rels this can be more than lateral_vars)
+ * lateral_referencers - relids of rels that reference this one laterally
* indexlist - list of IndexOptInfo nodes for relation's indexes
* (always NIL if it's not a table)
* pages - number of disk pages in relation (zero if not a table)
int32 *attr_widths; /* array indexed [min_attr .. max_attr] */
List *lateral_vars; /* LATERAL Vars and PHVs referenced by rel */
Relids lateral_relids; /* minimum parameterization of rel */
+ Relids lateral_referencers; /* rels that reference me laterally */
List *indexlist; /* list of IndexOptInfo */
BlockNumber pages; /* size estimates derived from pg_class */
double tuples;
/*
* "Lateral join" info.
*
- * Lateral references in subqueries constrain the join order in a way that's
- * somewhat like outer joins, though different in detail. We construct one or
- * more LateralJoinInfos for each RTE with lateral references, and add them to
- * the PlannerInfo node's lateral_info_list.
+ * Lateral references constrain the join order in a way that's somewhat like
+ * outer joins, though different in detail. We construct a LateralJoinInfo
+ * for each lateral cross-reference, placing them in the PlannerInfo node's
+ * lateral_info_list.
*
- * lateral_rhs is the relid of a baserel with lateral references, and
- * lateral_lhs is a set of relids of baserels it references, all of which
- * must be present on the LHS to compute a parameter needed by the RHS.
- * Typically, lateral_lhs is a singleton, but it can include multiple rels
- * if the RHS references a PlaceHolderVar with a multi-rel ph_eval_at level.
- * We disallow joining to only part of the LHS in such cases, since that would
- * result in a join tree with no convenient place to compute the PHV.
+ * For unflattened LATERAL RTEs, we generate LateralJoinInfo(s) in which
+ * lateral_rhs is the relid of the LATERAL baserel, and lateral_lhs is a set
+ * of relids of baserels it references, all of which must be present on the
+ * LHS to compute a parameter needed by the RHS. Typically, lateral_lhs is
+ * a singleton, but it can include multiple rels if the RHS references a
+ * PlaceHolderVar with a multi-rel ph_eval_at level. We disallow joining to
+ * only part of the LHS in such cases, since that would result in a join tree
+ * with no convenient place to compute the PHV.
*
* When an appendrel contains lateral references (eg "LATERAL (SELECT x.col1
* UNION ALL SELECT y.col2)"), the LateralJoinInfos reference the parent
* baserel not the member otherrels, since it is the parent relid that is
* considered for joining purposes.
+ *
+ * If any LATERAL RTEs were flattened into the parent query, it is possible
+ * that the query now contains PlaceHolderVars containing lateral references,
+ * representing expressions that need to be evaluated at particular spots in
+ * the jointree but contain lateral references to Vars from elsewhere. These
+ * give rise to LateralJoinInfos in which lateral_rhs is the evaluation point
+ * of a PlaceHolderVar and lateral_lhs is the set of lateral rels it needs.
*/
typedef struct LateralJoinInfo
{
NodeTag type;
- Index lateral_rhs; /* a baserel containing lateral refs */
- Relids lateral_lhs; /* some base relids it references */
+ Relids lateral_lhs; /* rels needed to compute a lateral value */
+ Relids lateral_rhs; /* rel where lateral value is needed */
} LateralJoinInfo;
/*
* then allow it to bubble up like a Var until the ph_needed join level.
* ph_needed has the same definition as attr_needed for a regular Var.
*
+ * The PlaceHolderVar's expression might contain LATERAL references to vars
+ * coming from outside its syntactic scope. If so, those rels are *not*
+ * included in ph_eval_at, but they are recorded in ph_lateral.
+ *
* Notice that when ph_eval_at is a join rather than a single baserel, the
* PlaceHolderInfo may create constraints on join order: the ph_eval_at join
* has to be formed below any outer joins that should null the PlaceHolderVar.
Index phid; /* ID for PH (unique within planner run) */
PlaceHolderVar *ph_var; /* copy of PlaceHolderVar tree */
Relids ph_eval_at; /* lowest level we can evaluate value at */
+ Relids ph_lateral; /* relids of contained lateral refs, if any */
Relids ph_needed; /* highest level the value is needed at */
int32 ph_width; /* estimated attribute width */
} PlaceHolderInfo;
extern void extract_actual_join_clauses(List *restrictinfo_list,
List **joinquals,
List **otherquals);
-extern bool join_clause_is_movable_to(RestrictInfo *rinfo, Index baserelid);
+extern bool join_clause_is_movable_to(RestrictInfo *rinfo, RelOptInfo *baserel);
extern bool join_clause_is_movable_into(RestrictInfo *rinfo,
Relids currentrelids,
Relids current_and_outer);
-4567890123456789 |
(20 rows)
+explain (verbose, costs off)
+select * from
+ int8_tbl a left join
+ lateral (select *, a.q2 as x from int8_tbl b) ss on a.q2 = ss.q1;
+ QUERY PLAN
+------------------------------------------
+ Nested Loop Left Join
+ Output: a.q1, a.q2, b.q1, b.q2, (a.q2)
+ -> Seq Scan on public.int8_tbl a
+ Output: a.q1, a.q2
+ -> Seq Scan on public.int8_tbl b
+ Output: b.q1, b.q2, a.q2
+ Filter: (a.q2 = b.q1)
+(7 rows)
+
+select * from
+ int8_tbl a left join
+ lateral (select *, a.q2 as x from int8_tbl b) ss on a.q2 = ss.q1;
+ q1 | q2 | q1 | q2 | x
+------------------+-------------------+------------------+-------------------+------------------
+ 123 | 456 | | |
+ 123 | 4567890123456789 | 4567890123456789 | 123 | 4567890123456789
+ 123 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789
+ 123 | 4567890123456789 | 4567890123456789 | -4567890123456789 | 4567890123456789
+ 4567890123456789 | 123 | 123 | 456 | 123
+ 4567890123456789 | 123 | 123 | 4567890123456789 | 123
+ 4567890123456789 | 4567890123456789 | 4567890123456789 | 123 | 4567890123456789
+ 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789
+ 4567890123456789 | 4567890123456789 | 4567890123456789 | -4567890123456789 | 4567890123456789
+ 4567890123456789 | -4567890123456789 | | |
+(10 rows)
+
+explain (verbose, costs off)
+select * from
+ int8_tbl a left join
+ lateral (select *, coalesce(a.q2, 42) as x from int8_tbl b) ss on a.q2 = ss.q1;
+ QUERY PLAN
+----------------------------------------------------------------
+ Nested Loop Left Join
+ Output: a.q1, a.q2, b.q1, b.q2, (COALESCE(a.q2, 42::bigint))
+ -> Seq Scan on public.int8_tbl a
+ Output: a.q1, a.q2
+ -> Seq Scan on public.int8_tbl b
+ Output: b.q1, b.q2, COALESCE(a.q2, 42::bigint)
+ Filter: (a.q2 = b.q1)
+(7 rows)
+
+select * from
+ int8_tbl a left join
+ lateral (select *, coalesce(a.q2, 42) as x from int8_tbl b) ss on a.q2 = ss.q1;
+ q1 | q2 | q1 | q2 | x
+------------------+-------------------+------------------+-------------------+------------------
+ 123 | 456 | | |
+ 123 | 4567890123456789 | 4567890123456789 | 123 | 4567890123456789
+ 123 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789
+ 123 | 4567890123456789 | 4567890123456789 | -4567890123456789 | 4567890123456789
+ 4567890123456789 | 123 | 123 | 456 | 123
+ 4567890123456789 | 123 | 123 | 4567890123456789 | 123
+ 4567890123456789 | 4567890123456789 | 4567890123456789 | 123 | 4567890123456789
+ 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789
+ 4567890123456789 | 4567890123456789 | 4567890123456789 | -4567890123456789 | 4567890123456789
+ 4567890123456789 | -4567890123456789 | | |
+(10 rows)
+
+-- lateral can result in join conditions appearing below their
+-- real semantic level
+explain (verbose, costs off)
+select * from int4_tbl i left join
+ lateral (select * from int2_tbl j where i.f1 = j.f1) k on true;
+ QUERY PLAN
+-------------------------------------------
+ Nested Loop Left Join
+ Output: i.f1, j.f1
+ Filter: (i.f1 = j.f1)
+ -> Seq Scan on public.int4_tbl i
+ Output: i.f1
+ -> Materialize
+ Output: j.f1
+ -> Seq Scan on public.int2_tbl j
+ Output: j.f1
+(9 rows)
+
+select * from int4_tbl i left join
+ lateral (select * from int2_tbl j where i.f1 = j.f1) k on true;
+ f1 | f1
+----+----
+ 0 | 0
+(1 row)
+
+explain (verbose, costs off)
+select * from int4_tbl i left join
+ lateral (select coalesce(i) from int2_tbl j where i.f1 = j.f1) k on true;
+ QUERY PLAN
+-------------------------------------
+ Nested Loop Left Join
+ Output: i.f1, (COALESCE(i.*))
+ -> Seq Scan on public.int4_tbl i
+ Output: i.f1, i.*
+ -> Seq Scan on public.int2_tbl j
+ Output: j.f1, COALESCE(i.*)
+ Filter: (i.f1 = j.f1)
+(7 rows)
+
+select * from int4_tbl i left join
+ lateral (select coalesce(i) from int2_tbl j where i.f1 = j.f1) k on true;
+ f1 | coalesce
+-------------+----------
+ 0 | (0)
+ 123456 |
+ -123456 |
+ 2147483647 |
+ -2147483647 |
+(5 rows)
+
+-- lateral reference in a PlaceHolderVar evaluated at join level
+explain (verbose, costs off)
+select * from
+ int8_tbl a left join lateral
+ (select b.q1 as bq1, c.q1 as cq1, least(a.q1,b.q1,c.q1) from
+ int8_tbl b cross join int8_tbl c) ss
+ on a.q2 = ss.bq1;
+ QUERY PLAN
+-------------------------------------------------------------
+ Nested Loop Left Join
+ Output: a.q1, a.q2, b.q1, c.q1, (LEAST(a.q1, b.q1, c.q1))
+ -> Seq Scan on public.int8_tbl a
+ Output: a.q1, a.q2
+ -> Nested Loop
+ Output: b.q1, c.q1, LEAST(a.q1, b.q1, c.q1)
+ Join Filter: (a.q2 = b.q1)
+ -> Seq Scan on public.int8_tbl b
+ Output: b.q1, b.q2
+ -> Materialize
+ Output: c.q1
+ -> Seq Scan on public.int8_tbl c
+ Output: c.q1
+(13 rows)
+
+select * from
+ int8_tbl a left join lateral
+ (select b.q1 as bq1, c.q1 as cq1, least(a.q1,b.q1,c.q1) from
+ int8_tbl b cross join int8_tbl c) ss
+ on a.q2 = ss.bq1;
+ q1 | q2 | bq1 | cq1 | least
+------------------+-------------------+------------------+------------------+------------------
+ 123 | 456 | | |
+ 123 | 4567890123456789 | 4567890123456789 | 123 | 123
+ 123 | 4567890123456789 | 4567890123456789 | 123 | 123
+ 123 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 123
+ 123 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 123
+ 123 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 123
+ 123 | 4567890123456789 | 4567890123456789 | 123 | 123
+ 123 | 4567890123456789 | 4567890123456789 | 123 | 123
+ 123 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 123
+ 123 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 123
+ 123 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 123
+ 123 | 4567890123456789 | 4567890123456789 | 123 | 123
+ 123 | 4567890123456789 | 4567890123456789 | 123 | 123
+ 123 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 123
+ 123 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 123
+ 123 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 123
+ 4567890123456789 | 123 | 123 | 123 | 123
+ 4567890123456789 | 123 | 123 | 123 | 123
+ 4567890123456789 | 123 | 123 | 4567890123456789 | 123
+ 4567890123456789 | 123 | 123 | 4567890123456789 | 123
+ 4567890123456789 | 123 | 123 | 4567890123456789 | 123
+ 4567890123456789 | 123 | 123 | 123 | 123
+ 4567890123456789 | 123 | 123 | 123 | 123
+ 4567890123456789 | 123 | 123 | 4567890123456789 | 123
+ 4567890123456789 | 123 | 123 | 4567890123456789 | 123
+ 4567890123456789 | 123 | 123 | 4567890123456789 | 123
+ 4567890123456789 | 4567890123456789 | 4567890123456789 | 123 | 123
+ 4567890123456789 | 4567890123456789 | 4567890123456789 | 123 | 123
+ 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789
+ 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789
+ 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789
+ 4567890123456789 | 4567890123456789 | 4567890123456789 | 123 | 123
+ 4567890123456789 | 4567890123456789 | 4567890123456789 | 123 | 123
+ 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789
+ 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789
+ 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789
+ 4567890123456789 | 4567890123456789 | 4567890123456789 | 123 | 123
+ 4567890123456789 | 4567890123456789 | 4567890123456789 | 123 | 123
+ 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789
+ 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789
+ 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789
+ 4567890123456789 | -4567890123456789 | | |
+(42 rows)
+
-- case requiring nested PlaceHolderVars
explain (verbose, costs off)
select * from
Output: c.q1, c.q2, a.q1, a.q2, b.q1, d.q1, (COALESCE(b.q2, 42::bigint)), (COALESCE((COALESCE(b.q2, 42::bigint)), d.q2))
Hash Cond: (d.q1 = c.q2)
-> Nested Loop
- Output: a.q1, a.q2, b.q1, d.q1, (COALESCE(b.q2, 42::bigint)), COALESCE((COALESCE(b.q2, 42::bigint)), d.q2)
+ Output: a.q1, a.q2, b.q1, d.q1, (COALESCE(b.q2, 42::bigint)), (COALESCE((COALESCE(b.q2, 42::bigint)), d.q2))
-> Hash Left Join
Output: a.q1, a.q2, b.q1, (COALESCE(b.q2, 42::bigint))
Hash Cond: (a.q2 = b.q1)
Output: b.q1, (COALESCE(b.q2, 42::bigint))
-> Seq Scan on public.int8_tbl b
Output: b.q1, COALESCE(b.q2, 42::bigint)
- -> Materialize
- Output: d.q1, d.q2
- -> Seq Scan on public.int8_tbl d
- Output: d.q1, d.q2
+ -> Seq Scan on public.int8_tbl d
+ Output: d.q1, COALESCE((COALESCE(b.q2, 42::bigint)), d.q2)
-> Hash
Output: c.q1, c.q2
-> Seq Scan on public.int8_tbl c
Output: c.q1, c.q2
-> Result
Output: (COALESCE((COALESCE(b.q2, 42::bigint)), d.q2))
-(26 rows)
+(24 rows)
-- case that breaks the old ph_may_need optimization
explain (verbose, costs off)
lateral (select q1, coalesce(ss1.x,q2) as y from int8_tbl d) ss2
) on c.q2 = ss2.q1,
lateral (select * from int4_tbl i where ss2.y > f1) ss3;
- QUERY PLAN
--------------------------------------------------------------------------------------------
- Hash Right Join
+ QUERY PLAN
+---------------------------------------------------------------------------------------------------------
+ Nested Loop
Output: c.q1, c.q2, a.q1, a.q2, b.q1, d.q1, i.f1
- Hash Cond: (d.q1 = c.q2)
- Filter: ((COALESCE((COALESCE(b.q2, (b2.f1)::bigint)), d.q2)) > i.f1)
- -> Nested Loop
- Output: a.q1, a.q2, b.q1, d.q1, COALESCE((COALESCE(b.q2, (b2.f1)::bigint)), d.q2)
- -> Hash Right Join
- Output: a.q1, a.q2, b.q1, (COALESCE(b.q2, (b2.f1)::bigint))
- Hash Cond: (b.q1 = a.q2)
- -> Nested Loop
- Output: b.q1, COALESCE(b.q2, (b2.f1)::bigint)
- Join Filter: (b.q1 < b2.f1)
- -> Seq Scan on public.int8_tbl b
- Output: b.q1, b.q2
- -> Materialize
- Output: b2.f1
- -> Seq Scan on public.int4_tbl b2
+ Join Filter: ((COALESCE((COALESCE(b.q2, (b2.f1)::bigint)), d.q2)) > i.f1)
+ -> Hash Right Join
+ Output: c.q1, c.q2, a.q1, a.q2, b.q1, d.q1, (COALESCE((COALESCE(b.q2, (b2.f1)::bigint)), d.q2))
+ Hash Cond: (d.q1 = c.q2)
+ -> Nested Loop
+ Output: a.q1, a.q2, b.q1, d.q1, (COALESCE((COALESCE(b.q2, (b2.f1)::bigint)), d.q2))
+ -> Hash Right Join
+ Output: a.q1, a.q2, b.q1, (COALESCE(b.q2, (b2.f1)::bigint))
+ Hash Cond: (b.q1 = a.q2)
+ -> Nested Loop
+ Output: b.q1, COALESCE(b.q2, (b2.f1)::bigint)
+ Join Filter: (b.q1 < b2.f1)
+ -> Seq Scan on public.int8_tbl b
+ Output: b.q1, b.q2
+ -> Materialize
Output: b2.f1
- -> Hash
- Output: a.q1, a.q2
- -> Seq Scan on public.int8_tbl a
+ -> Seq Scan on public.int4_tbl b2
+ Output: b2.f1
+ -> Hash
Output: a.q1, a.q2
- -> Materialize
- Output: d.q1, d.q2
+ -> Seq Scan on public.int8_tbl a
+ Output: a.q1, a.q2
-> Seq Scan on public.int8_tbl d
- Output: d.q1, d.q2
- -> Hash
- Output: c.q1, c.q2, i.f1
- -> Nested Loop
- Output: c.q1, c.q2, i.f1
+ Output: d.q1, COALESCE((COALESCE(b.q2, (b2.f1)::bigint)), d.q2)
+ -> Hash
+ Output: c.q1, c.q2
-> Seq Scan on public.int8_tbl c
Output: c.q1, c.q2
- -> Materialize
- Output: i.f1
- -> Seq Scan on public.int4_tbl i
- Output: i.f1
-(36 rows)
+ -> Materialize
+ Output: i.f1
+ -> Seq Scan on public.int4_tbl i
+ Output: i.f1
+(34 rows)
-- test some error cases where LATERAL should have been used but wasn't
select f1,g from int4_tbl a, (select f1 as g) ss;
left join int4_tbl z on z.f1 = x.q2,
lateral (select x.q1,y.q1 from dual union all select x.q2,y.q2 from dual) v(vx,vy);
+explain (verbose, costs off)
+select * from
+ int8_tbl a left join
+ lateral (select *, a.q2 as x from int8_tbl b) ss on a.q2 = ss.q1;
+select * from
+ int8_tbl a left join
+ lateral (select *, a.q2 as x from int8_tbl b) ss on a.q2 = ss.q1;
+explain (verbose, costs off)
+select * from
+ int8_tbl a left join
+ lateral (select *, coalesce(a.q2, 42) as x from int8_tbl b) ss on a.q2 = ss.q1;
+select * from
+ int8_tbl a left join
+ lateral (select *, coalesce(a.q2, 42) as x from int8_tbl b) ss on a.q2 = ss.q1;
+
+-- lateral can result in join conditions appearing below their
+-- real semantic level
+explain (verbose, costs off)
+select * from int4_tbl i left join
+ lateral (select * from int2_tbl j where i.f1 = j.f1) k on true;
+select * from int4_tbl i left join
+ lateral (select * from int2_tbl j where i.f1 = j.f1) k on true;
+explain (verbose, costs off)
+select * from int4_tbl i left join
+ lateral (select coalesce(i) from int2_tbl j where i.f1 = j.f1) k on true;
+select * from int4_tbl i left join
+ lateral (select coalesce(i) from int2_tbl j where i.f1 = j.f1) k on true;
+
+-- lateral reference in a PlaceHolderVar evaluated at join level
+explain (verbose, costs off)
+select * from
+ int8_tbl a left join lateral
+ (select b.q1 as bq1, c.q1 as cq1, least(a.q1,b.q1,c.q1) from
+ int8_tbl b cross join int8_tbl c) ss
+ on a.q2 = ss.bq1;
+select * from
+ int8_tbl a left join lateral
+ (select b.q1 as bq1, c.q1 as cq1, least(a.q1,b.q1,c.q1) from
+ int8_tbl b cross join int8_tbl c) ss
+ on a.q2 = ss.bq1;
+
-- case requiring nested PlaceHolderVars
explain (verbose, costs off)
select * from