]> granicus.if.org Git - postgresql/commitdiff
Fix planner problems with LATERAL references in PlaceHolderVars.
authorTom Lane <tgl@sss.pgh.pa.us>
Sun, 18 Aug 2013 00:22:41 +0000 (20:22 -0400)
committerTom Lane <tgl@sss.pgh.pa.us>
Sun, 18 Aug 2013 00:22:41 +0000 (20:22 -0400)
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.

25 files changed:
contrib/postgres_fdw/postgres_fdw.c
src/backend/nodes/copyfuncs.c
src/backend/nodes/equalfuncs.c
src/backend/nodes/outfuncs.c
src/backend/optimizer/README
src/backend/optimizer/path/allpaths.c
src/backend/optimizer/path/costsize.c
src/backend/optimizer/path/indxpath.c
src/backend/optimizer/path/joinpath.c
src/backend/optimizer/path/joinrels.c
src/backend/optimizer/path/orindxpath.c
src/backend/optimizer/path/tidpath.c
src/backend/optimizer/plan/analyzejoins.c
src/backend/optimizer/plan/createplan.c
src/backend/optimizer/plan/initsplan.c
src/backend/optimizer/plan/planmain.c
src/backend/optimizer/prep/prepjointree.c
src/backend/optimizer/util/placeholder.c
src/backend/optimizer/util/relnode.c
src/backend/optimizer/util/restrictinfo.c
src/backend/optimizer/util/var.c
src/include/nodes/relation.h
src/include/optimizer/restrictinfo.h
src/test/regress/expected/join.out
src/test/regress/sql/join.sql

index 1c93e0c5ac30aa70694308d8bc799c01b0e88360..8713eabc64606d5859b4549742498337e7fc581a 100644 (file)
@@ -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;
index 525e3fa2d196138f3d35aa8bccc960d443ee6a91..6b20e317323ebc31a1f46a255466a6b6ee5215e1 100644 (file)
@@ -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);
 
index 379cbc0c203d708f59fcbe3b2af8f4d38070d93a..b49e1e731deccf79345077ce5c2e2bc779bbeaae 100644 (file)
@@ -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);
 
index 67aeb7e65c3d52c0ce0c444f6ba34c801089ae00..f6e211429c3520199c261370953119bfb0348c55 100644 (file)
@@ -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);
 }
index 751766fb9dd604e40700e76ef5b8ae3b6a6dd469..a8b014843a1bb721080c07832cec0668453cf591 100644 (file)
@@ -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
index 4b8a73d60f42a414b49a481e9911a810e7caeed1..bfd3809a007a38b8252ebde2813adfe143e865f6 100644 (file)
@@ -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;
 
index 3507f18007e967da6ca24e2a0fb56db527055667..a2cc6979594d79c27eb27b013cfbbda7f9afee78 100644 (file)
@@ -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)
index 65eb344cde449b9cfeab3da87672ff06734b7d1b..606734a12214af30ffcebad2e43bc04acdeb5946 100644 (file)
@@ -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);
index d6050a616c73093b0b10c9c454ddd85829353b6d..5b477e52d3fc72bcffe0d7cfb4f62f03610756c9 100644 (file)
@@ -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,
index 819498a4281d191f4ecb927fe821e027cc55e8b0..d627f9e130c00e1a40e88294ccfb11d70fbeb380 100644 (file)
@@ -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;
        }
index e51e5c08b523210f6940baf937da3f7d419b604b..16f29d350fd84a2071da422530e6be9f8fffd595 100644 (file)
@@ -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
index f49d0f96d042f8c7d66446eae51fbec2a251d4ab..256856da35e2dc33c03425e531e7a272ead8a6ed 100644 (file)
@@ -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;
 
index daab355b1d3e8454dc1ce019330c8cc7868bf1e3..2271a7c35e0c97c7a570f523ea328ebd2581943f 100644 (file)
@@ -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);
        }
 
index 52bab79007e66d80205a07da08bc4437c424232c..c501737a2671e8c0f6a0fc21157e7e125a7b1a34 100644 (file)
@@ -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? */
index 07c4dddd24ec040ec31241899d227aaef404df46..98f601cdede54866878039bdfa42877f6b285581 100644 (file)
@@ -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);
 }
 
index 42a98945a38d07972ee2e8b5de16b3639196eebd..284929f125e9d8c6eef502d0e112321e65cf5bbf 100644 (file)
@@ -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
index 52842931ec5552c04fdec41e013c36e1897fd4e4..1178b0fc99680d7677efdb8c9a7c0d3521772fe7 100644 (file)
@@ -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 */
index da92497689ba9a305e61e09b06446648c7f1a95c..5049ba1c5a954936b5d8f5608b078b4601554e32 100644 (file)
@@ -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);
+                       }
                }
        }
 }
index 8ee5671a551912a9bbc1020971b2fb9a3b5fadf1..b6265b316758ce9befce628c4d48f7fa9cec36dc 100644 (file)
@@ -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;
index cd7109b687b1a1f694824598db7f4cb74f7d59b5..33b029b0e4ea9f10f0367ebe1d7b3218ab009039 100644 (file)
@@ -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).
index 5f736ad6c4060ff4d7dabb3844a04185e01fa3ef..4a3d5c8408ef08dbac43dfa142a8a3cfcc937918 100644 (file)
@@ -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))
index b611e0b905a9b7edef67f6e11aa763bc91dfc3a4..a2853fbf044b73f5e1cf2c7bbce4668842630df4 100644 (file)
@@ -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;
index db0ba71ed964c8e466d0fadc96736c3eaef36d00..47b19691c297a085b9db38c4f96ec0a1caee3677 100644 (file)
@@ -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);
index 814ddd8046913393e6031610fbd4e43e2bc9a871..fc3e16880687a80a50da61910199cf6538c7d69f 100644 (file)
@@ -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;
index 7ec2cbeea4b5aae66d381a3ea771d54003b5e663..36853ddce49b5f277ff78cc6352a696c5fe20617 100644 (file)
@@ -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