From: Tom Lane Date: Sun, 18 Aug 2013 00:22:41 +0000 (-0400) Subject: Fix planner problems with LATERAL references in PlaceHolderVars. X-Git-Tag: REL9_3_RC1~11 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=517db4945560358a82b9152d01cfad3bbd2af17e;p=postgresql Fix planner problems with LATERAL references in PlaceHolderVars. The planner largely failed to consider the possibility that a PlaceHolderVar's expression might contain a lateral reference to a Var coming from somewhere outside the PHV's syntactic scope. We had a previous report of a problem in this area, which I tried to fix in a quick-hack way in commit 4da6439bd8553059766011e2a42c6e39df08717f, but Antonin Houska pointed out that there were still some problems, and investigation turned up other issues. This patch largely reverts that commit in favor of a more thoroughly thought-through solution. The new theory is that a PHV's ph_eval_at level cannot be higher than its original syntactic level. If it contains lateral references, those don't change the ph_eval_at level, but rather they create a lateral-reference requirement for the ph_eval_at join relation. The code in joinpath.c needs to handle that. Another issue is that createplan.c wasn't handling nested PlaceHolderVars properly. In passing, push knowledge of lateral-reference checks for join clauses into join_clause_is_movable_to. This is mainly so that FDWs don't need to deal with it. This patch doesn't fix the original join-qual-placement problem reported by Jeremy Evans (and indeed, one of the new regression test cases shows the wrong answer because of that). But the PlaceHolderVar problems need to be fixed before that issue can be addressed, so committing this separately seems reasonable. --- diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c index 1c93e0c5ac..8713eabc64 100644 --- a/contrib/postgres_fdw/postgres_fdw.c +++ b/contrib/postgres_fdw/postgres_fdw.c @@ -540,7 +540,6 @@ postgresGetForeignPaths(PlannerInfo *root, { PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) baserel->fdw_private; ForeignPath *path; - Relids lateral_referencers; List *join_quals; Relids required_outer; double rows; @@ -579,34 +578,13 @@ postgresGetForeignPaths(PlannerInfo *root, * 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 */ @@ -667,7 +645,7 @@ postgresGetForeignPaths(PlannerInfo *root, 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) @@ -682,12 +660,9 @@ postgresGetForeignPaths(PlannerInfo *root, 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; diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 525e3fa2d1..6b20e31732 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -1917,8 +1917,8 @@ _copyLateralJoinInfo(const LateralJoinInfo *from) { LateralJoinInfo *newnode = makeNode(LateralJoinInfo); - COPY_SCALAR_FIELD(lateral_rhs); COPY_BITMAPSET_FIELD(lateral_lhs); + COPY_BITMAPSET_FIELD(lateral_rhs); return newnode; } @@ -1952,6 +1952,7 @@ _copyPlaceHolderInfo(const PlaceHolderInfo *from) 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); diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index 379cbc0c20..b49e1e731d 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -761,15 +761,19 @@ _equalPlaceHolderVar(const PlaceHolderVar *a, const PlaceHolderVar *b) /* * 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); @@ -794,8 +798,8 @@ _equalSpecialJoinInfo(const SpecialJoinInfo *a, const SpecialJoinInfo *b) 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; } @@ -817,8 +821,9 @@ static bool _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); diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index 67aeb7e65c..f6e211429c 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -1752,6 +1752,7 @@ _outRelOptInfo(StringInfo str, const RelOptInfo *node) 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"); @@ -1909,8 +1910,8 @@ _outLateralJoinInfo(StringInfo str, const LateralJoinInfo *node) { WRITE_NODE_TYPE("LATERALJOININFO"); - WRITE_UINT_FIELD(lateral_rhs); WRITE_BITMAPSET_FIELD(lateral_lhs); + WRITE_BITMAPSET_FIELD(lateral_rhs); } static void @@ -1934,6 +1935,7 @@ _outPlaceHolderInfo(StringInfo str, const PlaceHolderInfo *node) 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); } diff --git a/src/backend/optimizer/README b/src/backend/optimizer/README index 751766fb9d..a8b014843a 100644 --- a/src/backend/optimizer/README +++ b/src/backend/optimizer/README @@ -802,5 +802,19 @@ parameterized paths still apply, though; in particular, each such path is 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 diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index 4b8a73d60f..bfd3809a00 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -386,8 +386,7 @@ set_plain_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte) /* * 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; @@ -550,8 +549,8 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, * 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, @@ -1355,8 +1354,7 @@ set_cte_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte) /* * 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; @@ -1408,10 +1406,8 @@ set_worktable_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte) /* * 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; diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c index 3507f18007..a2cc697959 100644 --- a/src/backend/optimizer/path/costsize.c +++ b/src/backend/optimizer/path/costsize.c @@ -3935,10 +3935,9 @@ set_rel_width(PlannerInfo *root, RelOptInfo *rel) /* * 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) diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c index 65eb344cde..606734a122 100644 --- a/src/backend/optimizer/path/indxpath.c +++ b/src/backend/optimizer/path/indxpath.c @@ -141,12 +141,10 @@ static void match_restriction_clauses_to_index(RelOptInfo *rel, 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, @@ -220,14 +218,14 @@ static Const *string_to_const(const char *str, Oid datatype); * * 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) @@ -236,7 +234,6 @@ create_index_paths(PlannerInfo *root, RelOptInfo *rel) List *bitindexpaths; List *bitjoinpaths; List *joinorclauses; - Relids lateral_referencers; IndexClauseSet rclauseset; IndexClauseSet jclauseset; IndexClauseSet eclauseset; @@ -246,23 +243,6 @@ create_index_paths(PlannerInfo *root, RelOptInfo *rel) 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; @@ -303,7 +283,7 @@ create_index_paths(PlannerInfo *root, RelOptInfo *rel) * 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); /* @@ -311,7 +291,7 @@ create_index_paths(PlannerInfo *root, RelOptInfo *rel) * the index. */ MemSet(&eclauseset, 0, sizeof(eclauseset)); - match_eclass_clauses_to_index(root, index, lateral_referencers, + match_eclass_clauses_to_index(root, index, &eclauseset); /* @@ -1957,7 +1937,6 @@ match_restriction_clauses_to_index(RelOptInfo *rel, IndexOptInfo *index, static void match_join_clauses_to_index(PlannerInfo *root, RelOptInfo *rel, IndexOptInfo *index, - Relids lateral_referencers, IndexClauseSet *clauseset, List **joinorclauses) { @@ -1969,11 +1948,7 @@ match_join_clauses_to_index(PlannerInfo *root, 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 */ @@ -1991,7 +1966,6 @@ match_join_clauses_to_index(PlannerInfo *root, */ static void match_eclass_clauses_to_index(PlannerInfo *root, IndexOptInfo *index, - Relids lateral_referencers, IndexClauseSet *clauseset) { int indexcol; @@ -2012,7 +1986,7 @@ match_eclass_clauses_to_index(PlannerInfo *root, IndexOptInfo *index, 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, @@ -2644,7 +2618,7 @@ check_partial_indexes(PlannerInfo *root, RelOptInfo *rel) 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); diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c index d6050a616c..5b477e52d3 100644 --- a/src/backend/optimizer/path/joinpath.c +++ b/src/backend/optimizer/path/joinpath.c @@ -29,19 +29,19 @@ static void sort_inner_and_outer(PlannerInfo *root, RelOptInfo *joinrel, 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, @@ -87,6 +87,7 @@ add_paths_to_joinrel(PlannerInfo *root, bool mergejoin_allowed = true; SemiAntiJoinFactors semifactors; Relids param_source_rels = NULL; + Relids extra_lateral_rels = NULL; ListCell *lc; /* @@ -162,12 +163,49 @@ add_paths_to_joinrel(PlannerInfo *root, { 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. @@ -175,7 +213,8 @@ add_paths_to_joinrel(PlannerInfo *root, 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 @@ -187,7 +226,8 @@ add_paths_to_joinrel(PlannerInfo *root, 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 @@ -205,7 +245,8 @@ add_paths_to_joinrel(PlannerInfo *root, 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 /* @@ -216,7 +257,8 @@ add_paths_to_joinrel(PlannerInfo *root, 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); } /* @@ -231,6 +273,7 @@ try_nestloop_path(PlannerInfo *root, SpecialJoinInfo *sjinfo, SemiAntiJoinFactors *semifactors, Relids param_source_rels, + Relids extra_lateral_rels, Path *outer_path, Path *inner_path, List *restrict_clauses, @@ -253,6 +296,12 @@ try_nestloop_path(PlannerInfo *root, 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 @@ -301,6 +350,7 @@ try_mergejoin_path(PlannerInfo *root, JoinType jointype, SpecialJoinInfo *sjinfo, Relids param_source_rels, + Relids extra_lateral_rels, Path *outer_path, Path *inner_path, List *restrict_clauses, @@ -326,6 +376,12 @@ try_mergejoin_path(PlannerInfo *root, 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. @@ -383,6 +439,7 @@ try_hashjoin_path(PlannerInfo *root, SpecialJoinInfo *sjinfo, SemiAntiJoinFactors *semifactors, Relids param_source_rels, + Relids extra_lateral_rels, Path *outer_path, Path *inner_path, List *restrict_clauses, @@ -405,6 +462,12 @@ try_hashjoin_path(PlannerInfo *root, 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. @@ -483,6 +546,7 @@ clause_sides_match_join(RestrictInfo *rinfo, RelOptInfo *outerrel, * '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, @@ -493,7 +557,8 @@ 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; @@ -623,6 +688,7 @@ sort_inner_and_outer(PlannerInfo *root, jointype, sjinfo, param_source_rels, + extra_lateral_rels, outer_path, inner_path, restrictlist, @@ -668,6 +734,7 @@ sort_inner_and_outer(PlannerInfo *root, * '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, @@ -679,7 +746,8 @@ 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; @@ -809,6 +877,7 @@ match_unsorted_outer(PlannerInfo *root, sjinfo, semifactors, param_source_rels, + extra_lateral_rels, outerpath, inner_cheapest_total, restrictlist, @@ -834,6 +903,7 @@ match_unsorted_outer(PlannerInfo *root, sjinfo, semifactors, param_source_rels, + extra_lateral_rels, outerpath, innerpath, restrictlist, @@ -848,6 +918,7 @@ match_unsorted_outer(PlannerInfo *root, sjinfo, semifactors, param_source_rels, + extra_lateral_rels, outerpath, matpath, restrictlist, @@ -903,6 +974,7 @@ match_unsorted_outer(PlannerInfo *root, jointype, sjinfo, param_source_rels, + extra_lateral_rels, outerpath, inner_cheapest_total, restrictlist, @@ -1001,6 +1073,7 @@ match_unsorted_outer(PlannerInfo *root, jointype, sjinfo, param_source_rels, + extra_lateral_rels, outerpath, innerpath, restrictlist, @@ -1046,6 +1119,7 @@ match_unsorted_outer(PlannerInfo *root, jointype, sjinfo, param_source_rels, + extra_lateral_rels, outerpath, innerpath, restrictlist, @@ -1080,6 +1154,7 @@ match_unsorted_outer(PlannerInfo *root, * '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, @@ -1090,7 +1165,8 @@ 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; @@ -1164,6 +1240,7 @@ hash_inner_and_outer(PlannerInfo *root, sjinfo, semifactors, param_source_rels, + extra_lateral_rels, cheapest_total_outer, cheapest_total_inner, restrictlist, @@ -1183,6 +1260,7 @@ hash_inner_and_outer(PlannerInfo *root, sjinfo, semifactors, param_source_rels, + extra_lateral_rels, cheapest_total_outer, cheapest_total_inner, restrictlist, @@ -1195,6 +1273,7 @@ hash_inner_and_outer(PlannerInfo *root, sjinfo, semifactors, param_source_rels, + extra_lateral_rels, cheapest_startup_outer, cheapest_total_inner, restrictlist, @@ -1219,6 +1298,7 @@ hash_inner_and_outer(PlannerInfo *root, sjinfo, semifactors, param_source_rels, + extra_lateral_rels, cheapest_startup_outer, cheapest_total_inner, restrictlist, @@ -1256,6 +1336,7 @@ hash_inner_and_outer(PlannerInfo *root, sjinfo, semifactors, param_source_rels, + extra_lateral_rels, outerpath, innerpath, restrictlist, diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c index 819498a428..d627f9e130 100644 --- a/src/backend/optimizer/path/joinrels.c +++ b/src/backend/optimizer/path/joinrels.c @@ -526,7 +526,7 @@ join_is_legal(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2, { 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 */ @@ -539,7 +539,7 @@ join_is_legal(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2, (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 */ @@ -829,10 +829,10 @@ have_join_order_restriction(PlannerInfo *root, { 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; } @@ -928,7 +928,7 @@ has_join_restriction(PlannerInfo *root, RelOptInfo *rel) { 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; } diff --git a/src/backend/optimizer/path/orindxpath.c b/src/backend/optimizer/path/orindxpath.c index e51e5c08b5..16f29d350f 100644 --- a/src/backend/optimizer/path/orindxpath.c +++ b/src/backend/optimizer/path/orindxpath.c @@ -103,7 +103,7 @@ create_or_index_quals(PlannerInfo *root, RelOptInfo *rel) 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 diff --git a/src/backend/optimizer/path/tidpath.c b/src/backend/optimizer/path/tidpath.c index f49d0f96d0..256856da35 100644 --- a/src/backend/optimizer/path/tidpath.c +++ b/src/backend/optimizer/path/tidpath.c @@ -255,8 +255,7 @@ create_tidscan_paths(PlannerInfo *root, RelOptInfo *rel) /* * 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; diff --git a/src/backend/optimizer/plan/analyzejoins.c b/src/backend/optimizer/plan/analyzejoins.c index daab355b1d..2271a7c35e 100644 --- a/src/backend/optimizer/plan/analyzejoins.c +++ b/src/backend/optimizer/plan/analyzejoins.c @@ -202,7 +202,9 @@ join_is_removable(PlannerInfo *root, SpecialJoinInfo *sjinfo) * 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) { @@ -210,9 +212,13 @@ join_is_removable(PlannerInfo *root, SpecialJoinInfo *sjinfo) 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 */ } @@ -355,7 +361,7 @@ remove_rel_from_query(PlannerInfo *root, int relid, Relids joinrelids) * 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.) */ @@ -364,29 +370,27 @@ remove_rel_from_query(PlannerInfo *root, int relid, Relids joinrelids) 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); } diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c index 52bab79007..c501737a26 100644 --- a/src/backend/optimizer/plan/createplan.c +++ b/src/backend/optimizer/plan/createplan.c @@ -44,9 +44,9 @@ 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); @@ -305,21 +305,12 @@ create_scan_plan(PlannerInfo *root, Path *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); } /* @@ -439,11 +430,12 @@ create_scan_plan(PlannerInfo *root, Path *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; @@ -453,6 +445,15 @@ build_relation_tlist(RelOptInfo *rel) /* 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, @@ -528,7 +529,7 @@ use_physical_tlist(PlannerInfo *root, RelOptInfo *rel) * 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) @@ -544,7 +545,7 @@ disuse_physical_tlist(Plan *plan, Path *path) 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; @@ -678,7 +679,7 @@ static Plan * 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; @@ -733,7 +734,7 @@ create_merge_append_plan(PlannerInfo *root, MergeAppendPath *best_path) { 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; @@ -862,7 +863,7 @@ create_material_plan(PlannerInfo *root, MaterialPath *best_path) 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); @@ -911,7 +912,7 @@ create_unique_plan(PlannerInfo *root, UniquePath *best_path) * 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. */ @@ -919,7 +920,7 @@ create_unique_plan(PlannerInfo *root, UniquePath *best_path) 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; @@ -1009,7 +1010,7 @@ create_unique_plan(PlannerInfo *root, UniquePath *best_path) * 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, @@ -2028,7 +2029,7 @@ create_nestloop_plan(PlannerInfo *root, 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; @@ -2118,7 +2119,7 @@ create_mergejoin_plan(PlannerInfo *root, 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; @@ -2186,7 +2187,7 @@ create_mergejoin_plan(PlannerInfo *root, */ 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, @@ -2199,7 +2200,7 @@ create_mergejoin_plan(PlannerInfo *root, 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, @@ -2413,7 +2414,7 @@ create_hashjoin_plan(PlannerInfo *root, 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; @@ -2470,11 +2471,11 @@ create_hashjoin_plan(PlannerInfo *root, 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 @@ -2604,16 +2605,37 @@ replace_nestloop_params_mutator(Node *node, PlannerInfo *root) 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? */ diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c index 07c4dddd24..98f601cded 100644 --- a/src/backend/optimizer/plan/initsplan.c +++ b/src/backend/optimizer/plan/initsplan.c @@ -38,7 +38,7 @@ int join_collapse_limit; 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); @@ -177,6 +177,8 @@ add_vars_to_targetlist(PlannerInfo *root, List *vars, 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) @@ -221,6 +223,12 @@ add_vars_to_targetlist(PlannerInfo *root, List *vars, * 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. */ @@ -250,16 +258,18 @@ find_lateral_references(PlannerInfo *root) * 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" */ @@ -346,7 +356,10 @@ extract_lateral_references(PlannerInfo *root, RelOptInfo *brel, Index rtindex) */ 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 */ @@ -355,16 +368,20 @@ extract_lateral_references(PlannerInfo *root, RelOptInfo *brel, Index rtindex) /* * 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) @@ -377,7 +394,6 @@ create_lateral_join_info(PlannerInfo *root) { 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) @@ -400,7 +416,8 @@ create_lateral_join_info(PlannerInfo *root) { 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); } @@ -410,7 +427,7 @@ create_lateral_join_info(PlannerInfo *root) 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); } @@ -422,15 +439,100 @@ create_lateral_join_info(PlannerInfo *root) 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) { @@ -444,7 +546,9 @@ create_lateral_join_info(PlannerInfo *root) 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; } } } @@ -454,38 +558,39 @@ create_lateral_join_info(PlannerInfo *root) * 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); } diff --git a/src/backend/optimizer/plan/planmain.c b/src/backend/optimizer/plan/planmain.c index 42a98945a3..284929f125 100644 --- a/src/backend/optimizer/plan/planmain.c +++ b/src/backend/optimizer/plan/planmain.c @@ -175,12 +175,6 @@ query_planner(PlannerInfo *root, List *tlist, 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 @@ -225,6 +219,13 @@ query_planner(PlannerInfo *root, List *tlist, */ 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 diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c index 52842931ec..1178b0fc99 100644 --- a/src/backend/optimizer/prep/prepjointree.c +++ b/src/backend/optimizer/prep/prepjointree.c @@ -41,6 +41,8 @@ typedef struct pullup_replace_vars_context 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? */ @@ -884,14 +886,19 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte, /* * 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 || @@ -1674,8 +1681,18 @@ pullup_replace_vars_callback(Var *var, 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) @@ -1691,9 +1708,10 @@ pullup_replace_vars_callback(Var *var, 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 @@ -1702,8 +1720,14 @@ pullup_replace_vars_callback(Var *var, * * 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 */ diff --git a/src/backend/optimizer/util/placeholder.c b/src/backend/optimizer/util/placeholder.c index da92497689..5049ba1c5a 100644 --- a/src/backend/optimizer/util/placeholder.c +++ b/src/backend/optimizer/util/placeholder.c @@ -69,6 +69,7 @@ find_placeholder_info(PlannerInfo *root, PlaceHolderVar *phv, 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 */ @@ -89,8 +90,24 @@ find_placeholder_info(PlannerInfo *root, PlaceHolderVar *phv, 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 */ @@ -115,6 +132,12 @@ find_placeholder_info(PlannerInfo *root, PlaceHolderVar *phv, * * 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) @@ -219,7 +242,7 @@ find_placeholders_in_expr(PlannerInfo *root, Node *expr) * 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 @@ -299,6 +322,9 @@ update_placeholder_eval_levels(PlannerInfo *root, SpecialJoinInfo *new_sjinfo) } } 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; } } @@ -309,11 +335,14 @@ update_placeholder_eval_levels(PlannerInfo *root, SpecialJoinInfo *new_sjinfo) * * 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) @@ -323,27 +352,23 @@ 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 @@ -359,14 +384,52 @@ add_placeholders_to_base_rels(PlannerInfo *root) 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); + } } } } diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index 8ee5671a55..b6265b3167 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -113,6 +113,7 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptKind reloptkind) /* 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; @@ -374,6 +375,7 @@ build_join_rel(PlannerInfo *root, 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; diff --git a/src/backend/optimizer/util/restrictinfo.c b/src/backend/optimizer/util/restrictinfo.c index cd7109b687..33b029b0e4 100644 --- a/src/backend/optimizer/util/restrictinfo.c +++ b/src/backend/optimizer/util/restrictinfo.c @@ -651,24 +651,32 @@ extract_actual_join_clauses(List *restrictinfo_list, * 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; @@ -695,6 +703,11 @@ join_clause_is_movable_to(RestrictInfo *rinfo, Index baserelid) * 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). diff --git a/src/backend/optimizer/util/var.c b/src/backend/optimizer/util/var.c index 5f736ad6c4..4a3d5c8408 100644 --- a/src/backend/optimizer/util/var.c +++ b/src/backend/optimizer/util/var.c @@ -161,8 +161,13 @@ pull_varnos_walker(Node *node, pull_varnos_context *context) 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; @@ -170,12 +175,15 @@ pull_varnos_walker(Node *node, pull_varnos_context *context) 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)) diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h index b611e0b905..a2853fbf04 100644 --- a/src/include/nodes/relation.h +++ b/src/include/nodes/relation.h @@ -339,6 +339,7 @@ typedef struct PlannerInfo * 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) @@ -432,6 +433,7 @@ typedef struct RelOptInfo 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; @@ -1344,30 +1346,38 @@ typedef struct SpecialJoinInfo /* * "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; /* @@ -1465,6 +1475,10 @@ typedef struct AppendRelInfo * 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. @@ -1481,6 +1495,7 @@ typedef struct PlaceHolderInfo 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; diff --git a/src/include/optimizer/restrictinfo.h b/src/include/optimizer/restrictinfo.h index db0ba71ed9..47b19691c2 100644 --- a/src/include/optimizer/restrictinfo.h +++ b/src/include/optimizer/restrictinfo.h @@ -41,7 +41,7 @@ extern List *extract_actual_clauses(List *restrictinfo_list, 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); diff --git a/src/test/regress/expected/join.out b/src/test/regress/expected/join.out index 814ddd8046..fc3e168806 100644 --- a/src/test/regress/expected/join.out +++ b/src/test/regress/expected/join.out @@ -3577,6 +3577,195 @@ select v.* from -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 @@ -3595,7 +3784,7 @@ 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) @@ -3605,17 +3794,15 @@ select * from 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) @@ -3629,45 +3816,43 @@ select c.*,a.*,ss1.q1,ss2.q1,ss3.* from 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; diff --git a/src/test/regress/sql/join.sql b/src/test/regress/sql/join.sql index 7ec2cbeea4..36853ddce4 100644 --- a/src/test/regress/sql/join.sql +++ b/src/test/regress/sql/join.sql @@ -995,6 +995,47 @@ select v.* from 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