Commit 9e7e29c7 authored by Tom Lane's avatar Tom Lane

Fix planner problems with LATERAL references in PlaceHolderVars.

The planner largely failed to consider the possibility that a
PlaceHolderVar's expression might contain a lateral reference to a Var
coming from somewhere outside the PHV's syntactic scope.  We had a previous
report of a problem in this area, which I tried to fix in a quick-hack way
in commit 4da6439b, 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.
parent 175ec8de
...@@ -540,7 +540,6 @@ postgresGetForeignPaths(PlannerInfo *root, ...@@ -540,7 +540,6 @@ postgresGetForeignPaths(PlannerInfo *root,
{ {
PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) baserel->fdw_private; PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) baserel->fdw_private;
ForeignPath *path; ForeignPath *path;
Relids lateral_referencers;
List *join_quals; List *join_quals;
Relids required_outer; Relids required_outer;
double rows; double rows;
...@@ -579,34 +578,13 @@ postgresGetForeignPaths(PlannerInfo *root, ...@@ -579,34 +578,13 @@ postgresGetForeignPaths(PlannerInfo *root,
* consider combinations of clauses, probably. * 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 */ /* Scan the rel's join clauses */
foreach(lc, baserel->joininfo) foreach(lc, baserel->joininfo)
{ {
RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
/* Check if clause can be moved to this rel */ /* 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;
/* Not useful if it conflicts with any LATERAL references */
if (bms_overlap(rinfo->clause_relids, lateral_referencers))
continue; continue;
/* See if it is safe to send to remote */ /* See if it is safe to send to remote */
...@@ -667,7 +645,7 @@ postgresGetForeignPaths(PlannerInfo *root, ...@@ -667,7 +645,7 @@ postgresGetForeignPaths(PlannerInfo *root,
baserel, baserel,
ec_member_matches_foreign, ec_member_matches_foreign,
(void *) &arg, (void *) &arg,
lateral_referencers); baserel->lateral_referencers);
/* Done if there are no more expressions in the foreign rel */ /* Done if there are no more expressions in the foreign rel */
if (arg.current == NULL) if (arg.current == NULL)
...@@ -682,12 +660,9 @@ postgresGetForeignPaths(PlannerInfo *root, ...@@ -682,12 +660,9 @@ postgresGetForeignPaths(PlannerInfo *root,
RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
/* Check if clause can be moved to this rel */ /* 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; 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 */ /* See if it is safe to send to remote */
if (!is_foreign_expr(root, baserel, rinfo->clause)) if (!is_foreign_expr(root, baserel, rinfo->clause))
continue; continue;
......
...@@ -1921,8 +1921,8 @@ _copyLateralJoinInfo(const LateralJoinInfo *from) ...@@ -1921,8 +1921,8 @@ _copyLateralJoinInfo(const LateralJoinInfo *from)
{ {
LateralJoinInfo *newnode = makeNode(LateralJoinInfo); LateralJoinInfo *newnode = makeNode(LateralJoinInfo);
COPY_SCALAR_FIELD(lateral_rhs);
COPY_BITMAPSET_FIELD(lateral_lhs); COPY_BITMAPSET_FIELD(lateral_lhs);
COPY_BITMAPSET_FIELD(lateral_rhs);
return newnode; return newnode;
} }
...@@ -1956,6 +1956,7 @@ _copyPlaceHolderInfo(const PlaceHolderInfo *from) ...@@ -1956,6 +1956,7 @@ _copyPlaceHolderInfo(const PlaceHolderInfo *from)
COPY_SCALAR_FIELD(phid); COPY_SCALAR_FIELD(phid);
COPY_NODE_FIELD(ph_var); COPY_NODE_FIELD(ph_var);
COPY_BITMAPSET_FIELD(ph_eval_at); COPY_BITMAPSET_FIELD(ph_eval_at);
COPY_BITMAPSET_FIELD(ph_lateral);
COPY_BITMAPSET_FIELD(ph_needed); COPY_BITMAPSET_FIELD(ph_needed);
COPY_SCALAR_FIELD(ph_width); COPY_SCALAR_FIELD(ph_width);
......
...@@ -763,15 +763,19 @@ _equalPlaceHolderVar(const PlaceHolderVar *a, const PlaceHolderVar *b) ...@@ -763,15 +763,19 @@ _equalPlaceHolderVar(const PlaceHolderVar *a, const PlaceHolderVar *b)
/* /*
* We intentionally do not compare phexpr. Two PlaceHolderVars with the * We intentionally do not compare phexpr. Two PlaceHolderVars with the
* same ID and levelsup should be considered equal even if the contained * same ID and levelsup should be considered equal even if the contained
* expressions have managed to mutate to different states. One way in * expressions have managed to mutate to different states. This will
* which that can happen is that initplan sublinks would get replaced by * happen during final plan construction when there are nested PHVs, since
* differently-numbered Params when sublink folding is done. (The end * the inner PHV will get replaced by a Param in some copies of the outer
* result of such a situation would be some unreferenced initplans, which * PHV. Another way in which it can happen is that initplan sublinks
* is annoying but not really a problem.) * 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_NODE_FIELD(phexpr);
*
* COMPARE_BITMAPSET_FIELD(phrels);
*/ */
COMPARE_BITMAPSET_FIELD(phrels);
COMPARE_SCALAR_FIELD(phid); COMPARE_SCALAR_FIELD(phid);
COMPARE_SCALAR_FIELD(phlevelsup); COMPARE_SCALAR_FIELD(phlevelsup);
...@@ -796,8 +800,8 @@ _equalSpecialJoinInfo(const SpecialJoinInfo *a, const SpecialJoinInfo *b) ...@@ -796,8 +800,8 @@ _equalSpecialJoinInfo(const SpecialJoinInfo *a, const SpecialJoinInfo *b)
static bool static bool
_equalLateralJoinInfo(const LateralJoinInfo *a, const LateralJoinInfo *b) _equalLateralJoinInfo(const LateralJoinInfo *a, const LateralJoinInfo *b)
{ {
COMPARE_SCALAR_FIELD(lateral_rhs);
COMPARE_BITMAPSET_FIELD(lateral_lhs); COMPARE_BITMAPSET_FIELD(lateral_lhs);
COMPARE_BITMAPSET_FIELD(lateral_rhs);
return true; return true;
} }
...@@ -819,8 +823,9 @@ static bool ...@@ -819,8 +823,9 @@ static bool
_equalPlaceHolderInfo(const PlaceHolderInfo *a, const PlaceHolderInfo *b) _equalPlaceHolderInfo(const PlaceHolderInfo *a, const PlaceHolderInfo *b)
{ {
COMPARE_SCALAR_FIELD(phid); 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_eval_at);
COMPARE_BITMAPSET_FIELD(ph_lateral);
COMPARE_BITMAPSET_FIELD(ph_needed); COMPARE_BITMAPSET_FIELD(ph_needed);
COMPARE_SCALAR_FIELD(ph_width); COMPARE_SCALAR_FIELD(ph_width);
......
...@@ -1756,6 +1756,7 @@ _outRelOptInfo(StringInfo str, const RelOptInfo *node) ...@@ -1756,6 +1756,7 @@ _outRelOptInfo(StringInfo str, const RelOptInfo *node)
WRITE_INT_FIELD(max_attr); WRITE_INT_FIELD(max_attr);
WRITE_NODE_FIELD(lateral_vars); WRITE_NODE_FIELD(lateral_vars);
WRITE_BITMAPSET_FIELD(lateral_relids); WRITE_BITMAPSET_FIELD(lateral_relids);
WRITE_BITMAPSET_FIELD(lateral_referencers);
WRITE_NODE_FIELD(indexlist); WRITE_NODE_FIELD(indexlist);
WRITE_UINT_FIELD(pages); WRITE_UINT_FIELD(pages);
WRITE_FLOAT_FIELD(tuples, "%.0f"); WRITE_FLOAT_FIELD(tuples, "%.0f");
...@@ -1913,8 +1914,8 @@ _outLateralJoinInfo(StringInfo str, const LateralJoinInfo *node) ...@@ -1913,8 +1914,8 @@ _outLateralJoinInfo(StringInfo str, const LateralJoinInfo *node)
{ {
WRITE_NODE_TYPE("LATERALJOININFO"); WRITE_NODE_TYPE("LATERALJOININFO");
WRITE_UINT_FIELD(lateral_rhs);
WRITE_BITMAPSET_FIELD(lateral_lhs); WRITE_BITMAPSET_FIELD(lateral_lhs);
WRITE_BITMAPSET_FIELD(lateral_rhs);
} }
static void static void
...@@ -1938,6 +1939,7 @@ _outPlaceHolderInfo(StringInfo str, const PlaceHolderInfo *node) ...@@ -1938,6 +1939,7 @@ _outPlaceHolderInfo(StringInfo str, const PlaceHolderInfo *node)
WRITE_UINT_FIELD(phid); WRITE_UINT_FIELD(phid);
WRITE_NODE_FIELD(ph_var); WRITE_NODE_FIELD(ph_var);
WRITE_BITMAPSET_FIELD(ph_eval_at); WRITE_BITMAPSET_FIELD(ph_eval_at);
WRITE_BITMAPSET_FIELD(ph_lateral);
WRITE_BITMAPSET_FIELD(ph_needed); WRITE_BITMAPSET_FIELD(ph_needed);
WRITE_INT_FIELD(ph_width); WRITE_INT_FIELD(ph_width);
} }
......
...@@ -802,5 +802,19 @@ parameterized paths still apply, though; in particular, each such path is ...@@ -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, 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. 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 -- bjm & tgl
...@@ -386,8 +386,7 @@ set_plain_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte) ...@@ -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 * 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 * it could still have required parameterization due to LATERAL refs in
* its tlist. (That can only happen if the seqscan is on a relation * its tlist.
* pulled up out of a UNION ALL appendrel.)
*/ */
required_outer = rel->lateral_relids; required_outer = rel->lateral_relids;
...@@ -550,8 +549,8 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, ...@@ -550,8 +549,8 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel,
* Note: the resulting childrel->reltargetlist may contain arbitrary * Note: the resulting childrel->reltargetlist may contain arbitrary
* expressions, which otherwise would not occur in a reltargetlist. * expressions, which otherwise would not occur in a reltargetlist.
* Code that might be looking at an appendrel child must cope with * Code that might be looking at an appendrel child must cope with
* such. Note in particular that "arbitrary expression" can include * such. (Normally, a reltargetlist would only include Vars and
* "Var belonging to another relation", due to LATERAL references. * PlaceHolderVars.)
*/ */
childrel->joininfo = (List *) childrel->joininfo = (List *)
adjust_appendrel_attrs(root, adjust_appendrel_attrs(root,
...@@ -1355,8 +1354,7 @@ set_cte_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte) ...@@ -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 * 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 * 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 * its tlist.
* pulled up out of a UNION ALL appendrel.)
*/ */
required_outer = rel->lateral_relids; required_outer = rel->lateral_relids;
...@@ -1408,10 +1406,8 @@ set_worktable_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte) ...@@ -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 * We don't support pushing join clauses into the quals of a worktable
* scan, but it could still have required parameterization due to LATERAL * 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 * refs in its tlist. (I'm not sure this is actually possible given the
* relation pulled up out of a UNION ALL appendrel. I'm not sure this is * restrictions on recursive references, but it's easy enough to support.)
* actually possible given the restrictions on recursive references, but
* it's easy enough to support.)
*/ */
required_outer = rel->lateral_relids; required_outer = rel->lateral_relids;
......
...@@ -3943,10 +3943,9 @@ set_rel_width(PlannerInfo *root, RelOptInfo *rel) ...@@ -3943,10 +3943,9 @@ set_rel_width(PlannerInfo *root, RelOptInfo *rel)
/* /*
* Ordinarily, a Var in a rel's reltargetlist must belong to that rel; * Ordinarily, a Var in a rel's reltargetlist must belong to that rel;
* but there are corner cases involving LATERAL references in * but there are corner cases involving LATERAL references where that
* appendrel members where that isn't so (see set_append_rel_size()). * isn't so. If the Var has the wrong varno, fall through to the
* If the Var has the wrong varno, fall through to the generic case * generic case (it doesn't seem worth the trouble to be any smarter).
* (it doesn't seem worth the trouble to be any smarter).
*/ */
if (IsA(node, Var) && if (IsA(node, Var) &&
((Var *) node)->varno == rel->relid) ((Var *) node)->varno == rel->relid)
......
...@@ -141,12 +141,10 @@ static void match_restriction_clauses_to_index(RelOptInfo *rel, ...@@ -141,12 +141,10 @@ static void match_restriction_clauses_to_index(RelOptInfo *rel,
IndexClauseSet *clauseset); IndexClauseSet *clauseset);
static void match_join_clauses_to_index(PlannerInfo *root, static void match_join_clauses_to_index(PlannerInfo *root,
RelOptInfo *rel, IndexOptInfo *index, RelOptInfo *rel, IndexOptInfo *index,
Relids lateral_referencers,
IndexClauseSet *clauseset, IndexClauseSet *clauseset,
List **joinorclauses); List **joinorclauses);
static void match_eclass_clauses_to_index(PlannerInfo *root, static void match_eclass_clauses_to_index(PlannerInfo *root,
IndexOptInfo *index, IndexOptInfo *index,
Relids lateral_referencers,
IndexClauseSet *clauseset); IndexClauseSet *clauseset);
static void match_clauses_to_index(IndexOptInfo *index, static void match_clauses_to_index(IndexOptInfo *index,
List *clauses, List *clauses,
...@@ -220,14 +218,14 @@ static Const *string_to_const(const char *str, Oid datatype); ...@@ -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: check_partial_indexes() must have been run previously for this rel.
* *
* Note: in corner cases involving LATERAL appendrel children, it's possible * Note: in cases involving LATERAL references in the relation's tlist, it's
* that rel->lateral_relids is nonempty. Currently, we include lateral_relids * possible that rel->lateral_relids is nonempty. Currently, we include
* into the parameterization reported for each path, but don't take it into * lateral_relids into the parameterization reported for each path, but don't
* account otherwise. The fact that any such rels *must* be available as * take it into account otherwise. The fact that any such rels *must* be
* parameter sources perhaps should influence our choices of index quals ... * available as parameter sources perhaps should influence our choices of
* but for now, it doesn't seem worth troubling over. In particular, comments * index quals ... but for now, it doesn't seem worth troubling over.
* below about "unparameterized" paths should be read as meaning * In particular, comments below about "unparameterized" paths should be read
* "unparameterized so far as the indexquals are concerned". * as meaning "unparameterized so far as the indexquals are concerned".
*/ */
void void
create_index_paths(PlannerInfo *root, RelOptInfo *rel) create_index_paths(PlannerInfo *root, RelOptInfo *rel)
...@@ -236,7 +234,6 @@ create_index_paths(PlannerInfo *root, RelOptInfo *rel) ...@@ -236,7 +234,6 @@ create_index_paths(PlannerInfo *root, RelOptInfo *rel)
List *bitindexpaths; List *bitindexpaths;
List *bitjoinpaths; List *bitjoinpaths;
List *joinorclauses; List *joinorclauses;
Relids lateral_referencers;
IndexClauseSet rclauseset; IndexClauseSet rclauseset;
IndexClauseSet jclauseset; IndexClauseSet jclauseset;
IndexClauseSet eclauseset; IndexClauseSet eclauseset;
...@@ -246,23 +243,6 @@ create_index_paths(PlannerInfo *root, RelOptInfo *rel) ...@@ -246,23 +243,6 @@ create_index_paths(PlannerInfo *root, RelOptInfo *rel)
if (rel->indexlist == NIL) if (rel->indexlist == NIL)
return; 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 */ /* Bitmap paths are collected and then dealt with at the end */
bitindexpaths = bitjoinpaths = joinorclauses = NIL; bitindexpaths = bitjoinpaths = joinorclauses = NIL;
...@@ -303,7 +283,7 @@ create_index_paths(PlannerInfo *root, RelOptInfo *rel) ...@@ -303,7 +283,7 @@ create_index_paths(PlannerInfo *root, RelOptInfo *rel)
* EquivalenceClasses. Also, collect join OR clauses for later. * EquivalenceClasses. Also, collect join OR clauses for later.
*/ */
MemSet(&jclauseset, 0, sizeof(jclauseset)); 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); &jclauseset, &joinorclauses);
/* /*
...@@ -311,7 +291,7 @@ create_index_paths(PlannerInfo *root, RelOptInfo *rel) ...@@ -311,7 +291,7 @@ create_index_paths(PlannerInfo *root, RelOptInfo *rel)
* the index. * the index.
*/ */
MemSet(&eclauseset, 0, sizeof(eclauseset)); MemSet(&eclauseset, 0, sizeof(eclauseset));
match_eclass_clauses_to_index(root, index, lateral_referencers, match_eclass_clauses_to_index(root, index,
&eclauseset); &eclauseset);
/* /*
...@@ -1957,7 +1937,6 @@ match_restriction_clauses_to_index(RelOptInfo *rel, IndexOptInfo *index, ...@@ -1957,7 +1937,6 @@ match_restriction_clauses_to_index(RelOptInfo *rel, IndexOptInfo *index,
static void static void
match_join_clauses_to_index(PlannerInfo *root, match_join_clauses_to_index(PlannerInfo *root,
RelOptInfo *rel, IndexOptInfo *index, RelOptInfo *rel, IndexOptInfo *index,
Relids lateral_referencers,
IndexClauseSet *clauseset, IndexClauseSet *clauseset,
List **joinorclauses) List **joinorclauses)
{ {
...@@ -1969,11 +1948,7 @@ match_join_clauses_to_index(PlannerInfo *root, ...@@ -1969,11 +1948,7 @@ match_join_clauses_to_index(PlannerInfo *root,
RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
/* Check if clause can be moved to this rel */ /* 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;
/* Not useful if it conflicts with any LATERAL references */
if (bms_overlap(rinfo->clause_relids, lateral_referencers))
continue; continue;
/* Potentially usable, so see if it matches the index or is an OR */ /* Potentially usable, so see if it matches the index or is an OR */
...@@ -1991,7 +1966,6 @@ match_join_clauses_to_index(PlannerInfo *root, ...@@ -1991,7 +1966,6 @@ match_join_clauses_to_index(PlannerInfo *root,
*/ */
static void static void
match_eclass_clauses_to_index(PlannerInfo *root, IndexOptInfo *index, match_eclass_clauses_to_index(PlannerInfo *root, IndexOptInfo *index,
Relids lateral_referencers,
IndexClauseSet *clauseset) IndexClauseSet *clauseset)
{ {
int indexcol; int indexcol;
...@@ -2012,7 +1986,7 @@ match_eclass_clauses_to_index(PlannerInfo *root, IndexOptInfo *index, ...@@ -2012,7 +1986,7 @@ match_eclass_clauses_to_index(PlannerInfo *root, IndexOptInfo *index,
index->rel, index->rel,
ec_member_matches_indexcol, ec_member_matches_indexcol,
(void *) &arg, (void *) &arg,
lateral_referencers); index->rel->lateral_referencers);
/* /*
* We have to check whether the results actually do match the index, * We have to check whether the results actually do match the index,
...@@ -2644,7 +2618,7 @@ check_partial_indexes(PlannerInfo *root, RelOptInfo *rel) ...@@ -2644,7 +2618,7 @@ check_partial_indexes(PlannerInfo *root, RelOptInfo *rel)
RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
/* Check if clause can be moved to this rel */ /* 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; continue;
clauselist = lappend(clauselist, rinfo); clauselist = lappend(clauselist, rinfo);
......
This diff is collapsed.
...@@ -526,7 +526,7 @@ join_is_legal(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2, ...@@ -526,7 +526,7 @@ join_is_legal(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2,
{ {
LateralJoinInfo *ljinfo = (LateralJoinInfo *) lfirst(l); 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)) bms_overlap(ljinfo->lateral_lhs, rel1->relids))
{ {
/* has to be implemented as nestloop with rel1 on left */ /* has to be implemented as nestloop with rel1 on left */
...@@ -539,7 +539,7 @@ join_is_legal(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2, ...@@ -539,7 +539,7 @@ join_is_legal(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2,
(reversed || match_sjinfo->jointype == JOIN_FULL)) (reversed || match_sjinfo->jointype == JOIN_FULL))
return false; /* not implementable as nestloop */ 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)) bms_overlap(ljinfo->lateral_lhs, rel2->relids))
{ {
/* has to be implemented as nestloop with rel2 on left */ /* has to be implemented as nestloop with rel2 on left */
...@@ -829,10 +829,10 @@ have_join_order_restriction(PlannerInfo *root, ...@@ -829,10 +829,10 @@ have_join_order_restriction(PlannerInfo *root,
{ {
LateralJoinInfo *ljinfo = (LateralJoinInfo *) lfirst(l); 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)) bms_overlap(ljinfo->lateral_lhs, rel1->relids))
return true; 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)) bms_overlap(ljinfo->lateral_lhs, rel2->relids))
return true; return true;
} }
...@@ -928,7 +928,7 @@ has_join_restriction(PlannerInfo *root, RelOptInfo *rel) ...@@ -928,7 +928,7 @@ has_join_restriction(PlannerInfo *root, RelOptInfo *rel)
{ {
LateralJoinInfo *ljinfo = (LateralJoinInfo *) lfirst(l); 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)) bms_overlap(ljinfo->lateral_lhs, rel->relids))
return true; return true;
} }
......
...@@ -103,7 +103,7 @@ create_or_index_quals(PlannerInfo *root, RelOptInfo *rel) ...@@ -103,7 +103,7 @@ create_or_index_quals(PlannerInfo *root, RelOptInfo *rel)
RestrictInfo *rinfo = (RestrictInfo *) lfirst(i); RestrictInfo *rinfo = (RestrictInfo *) lfirst(i);
if (restriction_is_or_clause(rinfo) && 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 * Use the generate_bitmap_or_paths() machinery to estimate the
......
...@@ -255,8 +255,7 @@ create_tidscan_paths(PlannerInfo *root, RelOptInfo *rel) ...@@ -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 * 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 * it could still have required parameterization due to LATERAL refs in
* its tlist. (That can only happen if the tidscan is on a relation * its tlist.
* pulled up out of a UNION ALL appendrel.)
*/ */
required_outer = rel->lateral_relids; required_outer = rel->lateral_relids;
......
...@@ -202,7 +202,9 @@ join_is_removable(PlannerInfo *root, SpecialJoinInfo *sjinfo) ...@@ -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 * 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 * actually references some inner-rel attributes; but the correct check
* for that is relatively expensive, so we first check against ph_eval_at, * 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) foreach(l, root->placeholder_list)
{ {
...@@ -210,9 +212,13 @@ join_is_removable(PlannerInfo *root, SpecialJoinInfo *sjinfo) ...@@ -210,9 +212,13 @@ join_is_removable(PlannerInfo *root, SpecialJoinInfo *sjinfo)
if (bms_is_subset(phinfo->ph_needed, joinrelids)) if (bms_is_subset(phinfo->ph_needed, joinrelids))
continue; /* PHV is not used above the join */ 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)) if (!bms_overlap(phinfo->ph_eval_at, innerrel->relids))
continue; /* it definitely doesn't reference innerrel */ 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)) innerrel->relids))
return false; /* it does reference innerrel */ return false; /* it does reference innerrel */
} }
...@@ -355,7 +361,7 @@ remove_rel_from_query(PlannerInfo *root, int relid, Relids joinrelids) ...@@ -355,7 +361,7 @@ remove_rel_from_query(PlannerInfo *root, int relid, Relids joinrelids)
* Likewise remove references from LateralJoinInfo data structures. * Likewise remove references from LateralJoinInfo data structures.
* *
* If we are deleting a LATERAL subquery, we can forget its * 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 * 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.) * 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) ...@@ -364,29 +370,27 @@ remove_rel_from_query(PlannerInfo *root, int relid, Relids joinrelids)
LateralJoinInfo *ljinfo = (LateralJoinInfo *) lfirst(l); LateralJoinInfo *ljinfo = (LateralJoinInfo *) lfirst(l);
nextl = lnext(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, root->lateral_info_list = list_delete_ptr(root->lateral_info_list,
ljinfo); ljinfo);
else else
{
ljinfo->lateral_lhs = bms_del_member(ljinfo->lateral_lhs, relid); ljinfo->lateral_lhs = bms_del_member(ljinfo->lateral_lhs, relid);
Assert(!bms_is_empty(ljinfo->lateral_lhs));
}
} }
/* /*
* Likewise remove references from PlaceHolderVar data structures. * 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) foreach(l, root->placeholder_list)
{ {
PlaceHolderInfo *phinfo = (PlaceHolderInfo *) lfirst(l); PlaceHolderInfo *phinfo = (PlaceHolderInfo *) lfirst(l);
phinfo->ph_eval_at = bms_del_member(phinfo->ph_eval_at, relid); phinfo->ph_eval_at = bms_del_member(phinfo->ph_eval_at, relid);
if (bms_is_empty(phinfo->ph_eval_at)) /* oops, belay that */ Assert(!bms_is_empty(phinfo->ph_eval_at));
phinfo->ph_eval_at = bms_add_member(phinfo->ph_eval_at, relid); Assert(!bms_is_member(relid, phinfo->ph_lateral));
phinfo->ph_needed = bms_del_member(phinfo->ph_needed, relid); phinfo->ph_needed = bms_del_member(phinfo->ph_needed, relid);
} }
......
...@@ -44,9 +44,9 @@ ...@@ -44,9 +44,9 @@
static Plan *create_plan_recurse(PlannerInfo *root, Path *best_path); static Plan *create_plan_recurse(PlannerInfo *root, Path *best_path);
static Plan *create_scan_plan(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 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_gating_plan(PlannerInfo *root, Plan *plan, List *quals);
static Plan *create_join_plan(PlannerInfo *root, JoinPath *best_path); static Plan *create_join_plan(PlannerInfo *root, JoinPath *best_path);
static Plan *create_append_plan(PlannerInfo *root, AppendPath *best_path); static Plan *create_append_plan(PlannerInfo *root, AppendPath *best_path);
...@@ -305,21 +305,12 @@ create_scan_plan(PlannerInfo *root, Path *best_path) ...@@ -305,21 +305,12 @@ create_scan_plan(PlannerInfo *root, Path *best_path)
tlist = build_physical_tlist(root, rel); tlist = build_physical_tlist(root, rel);
/* if fail because of dropped cols, use regular method */ /* if fail because of dropped cols, use regular method */
if (tlist == NIL) if (tlist == NIL)
tlist = build_relation_tlist(rel); tlist = build_path_tlist(root, best_path);
} }
} }
else else
{ {
tlist = build_relation_tlist(rel); tlist = build_path_tlist(root, best_path);
/*
* 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);
} }
/* /*
...@@ -439,11 +430,12 @@ create_scan_plan(PlannerInfo *root, Path *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 * static List *
build_relation_tlist(RelOptInfo *rel) build_path_tlist(PlannerInfo *root, Path *path)
{ {
RelOptInfo *rel = path->parent;
List *tlist = NIL; List *tlist = NIL;
int resno = 1; int resno = 1;
ListCell *v; ListCell *v;
...@@ -453,6 +445,15 @@ build_relation_tlist(RelOptInfo *rel) ...@@ -453,6 +445,15 @@ build_relation_tlist(RelOptInfo *rel)
/* Do we really need to copy here? Not sure */ /* Do we really need to copy here? Not sure */
Node *node = (Node *) copyObject(lfirst(v)); 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, tlist = lappend(tlist, makeTargetEntry((Expr *) node,
resno, resno,
NULL, NULL,
...@@ -528,7 +529,7 @@ use_physical_tlist(PlannerInfo *root, RelOptInfo *rel) ...@@ -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. * and Material nodes want this, so they don't have to store useless columns.
*/ */
static void 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() */ /* Only need to undo it for path types handled by create_scan_plan() */
switch (path->pathtype) switch (path->pathtype)
...@@ -544,7 +545,7 @@ disuse_physical_tlist(Plan *plan, Path *path) ...@@ -544,7 +545,7 @@ disuse_physical_tlist(Plan *plan, Path *path)
case T_CteScan: case T_CteScan:
case T_WorkTableScan: case T_WorkTableScan:
case T_ForeignScan: case T_ForeignScan:
plan->targetlist = build_relation_tlist(path->parent); plan->targetlist = build_path_tlist(root, path);
break; break;
default: default:
break; break;
...@@ -678,7 +679,7 @@ static Plan * ...@@ -678,7 +679,7 @@ static Plan *
create_append_plan(PlannerInfo *root, AppendPath *best_path) create_append_plan(PlannerInfo *root, AppendPath *best_path)
{ {
Append *plan; Append *plan;
List *tlist = build_relation_tlist(best_path->path.parent); List *tlist = build_path_tlist(root, &best_path->path);
List *subplans = NIL; List *subplans = NIL;
ListCell *subpaths; ListCell *subpaths;
...@@ -733,7 +734,7 @@ create_merge_append_plan(PlannerInfo *root, MergeAppendPath *best_path) ...@@ -733,7 +734,7 @@ create_merge_append_plan(PlannerInfo *root, MergeAppendPath *best_path)
{ {
MergeAppend *node = makeNode(MergeAppend); MergeAppend *node = makeNode(MergeAppend);
Plan *plan = &node->plan; 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 *pathkeys = best_path->path.pathkeys;
List *subplans = NIL; List *subplans = NIL;
ListCell *subpaths; ListCell *subpaths;
...@@ -862,7 +863,7 @@ create_material_plan(PlannerInfo *root, MaterialPath *best_path) ...@@ -862,7 +863,7 @@ create_material_plan(PlannerInfo *root, MaterialPath *best_path)
subplan = create_plan_recurse(root, best_path->subpath); subplan = create_plan_recurse(root, best_path->subpath);
/* We don't want any excess columns in the materialized tuples */ /* 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); plan = make_material(subplan);
...@@ -911,7 +912,7 @@ create_unique_plan(PlannerInfo *root, UniquePath *best_path) ...@@ -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 * 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 * do have to add expressions, then a projection step will be needed at
* runtime anyway, so we may as well remove unneeded items. Therefore * 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 * subplan's tlist; and we don't install it into the subplan unless we are
* sorting or stuff has to be added. * sorting or stuff has to be added.
*/ */
...@@ -919,7 +920,7 @@ create_unique_plan(PlannerInfo *root, UniquePath *best_path) ...@@ -919,7 +920,7 @@ create_unique_plan(PlannerInfo *root, UniquePath *best_path)
uniq_exprs = best_path->uniq_exprs; uniq_exprs = best_path->uniq_exprs;
/* initialize modified subplan tlist as just the "required" vars */ /* 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; nextresno = list_length(newtlist) + 1;
newitems = false; newitems = false;
...@@ -1009,7 +1010,7 @@ create_unique_plan(PlannerInfo *root, UniquePath *best_path) ...@@ -1009,7 +1010,7 @@ create_unique_plan(PlannerInfo *root, UniquePath *best_path)
* subplan tlist. * subplan tlist.
*/ */
plan = (Plan *) make_agg(root, plan = (Plan *) make_agg(root,
build_relation_tlist(best_path->path.parent), build_path_tlist(root, &best_path->path),
NIL, NIL,
AGG_HASHED, AGG_HASHED,
NULL, NULL,
...@@ -2029,7 +2030,7 @@ create_nestloop_plan(PlannerInfo *root, ...@@ -2029,7 +2030,7 @@ create_nestloop_plan(PlannerInfo *root,
Plan *inner_plan) Plan *inner_plan)
{ {
NestLoop *join_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 *joinrestrictclauses = best_path->joinrestrictinfo;
List *joinclauses; List *joinclauses;
List *otherclauses; List *otherclauses;
...@@ -2119,7 +2120,7 @@ create_mergejoin_plan(PlannerInfo *root, ...@@ -2119,7 +2120,7 @@ create_mergejoin_plan(PlannerInfo *root,
Plan *outer_plan, Plan *outer_plan,
Plan *inner_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 *joinclauses;
List *otherclauses; List *otherclauses;
List *mergeclauses; List *mergeclauses;
...@@ -2187,7 +2188,7 @@ create_mergejoin_plan(PlannerInfo *root, ...@@ -2187,7 +2188,7 @@ create_mergejoin_plan(PlannerInfo *root,
*/ */
if (best_path->outersortkeys) 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 *) outer_plan = (Plan *)
make_sort_from_pathkeys(root, make_sort_from_pathkeys(root,
outer_plan, outer_plan,
...@@ -2200,7 +2201,7 @@ create_mergejoin_plan(PlannerInfo *root, ...@@ -2200,7 +2201,7 @@ create_mergejoin_plan(PlannerInfo *root,
if (best_path->innersortkeys) 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 *) inner_plan = (Plan *)
make_sort_from_pathkeys(root, make_sort_from_pathkeys(root,
inner_plan, inner_plan,
...@@ -2414,7 +2415,7 @@ create_hashjoin_plan(PlannerInfo *root, ...@@ -2414,7 +2415,7 @@ create_hashjoin_plan(PlannerInfo *root,
Plan *outer_plan, Plan *outer_plan,
Plan *inner_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 *joinclauses;
List *otherclauses; List *otherclauses;
List *hashclauses; List *hashclauses;
...@@ -2471,11 +2472,11 @@ create_hashjoin_plan(PlannerInfo *root, ...@@ -2471,11 +2472,11 @@ create_hashjoin_plan(PlannerInfo *root,
best_path->jpath.outerjoinpath->parent->relids); best_path->jpath.outerjoinpath->parent->relids);
/* We don't want any excess columns in the hashed tuples */ /* 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 we expect batching, suppress excess columns in outer tuples too */
if (best_path->num_batches > 1) 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 * If there is a single join clause and we can identify the outer variable
...@@ -2605,16 +2606,37 @@ replace_nestloop_params_mutator(Node *node, PlannerInfo *root) ...@@ -2605,16 +2606,37 @@ replace_nestloop_params_mutator(Node *node, PlannerInfo *root)
Assert(phv->phlevelsup == 0); Assert(phv->phlevelsup == 0);
/* /*
* If not to be replaced, just return the PlaceHolderVar unmodified. * Check whether we need to replace the PHV. We use bms_overlap as a
* We use bms_overlap as a cheap/quick test to see if the PHV might be * cheap/quick test to see if the PHV might be evaluated in the outer
* evaluated in the outer rels, and then grab its PlaceHolderInfo to * rels, and then grab its PlaceHolderInfo to tell for sure.
* tell for sure.
*/ */
if (!bms_overlap(phv->phrels, root->curOuterRels)) if (!bms_overlap(phv->phrels, root->curOuterRels) ||
return node; !bms_is_subset(find_placeholder_info(root, phv, false)->ph_eval_at,
if (!bms_is_subset(find_placeholder_info(root, phv, false)->ph_eval_at,
root->curOuterRels)) root->curOuterRels))
return node; {
/*
* 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 */ /* Create a Param representing the PlaceHolderVar */
param = assign_nestloop_param_placeholdervar(root, phv); param = assign_nestloop_param_placeholdervar(root, phv);
/* Is this param already listed in root->curOuterParams? */ /* Is this param already listed in root->curOuterParams? */
......
This diff is collapsed.
...@@ -143,12 +143,6 @@ query_planner(PlannerInfo *root, List *tlist, ...@@ -143,12 +143,6 @@ query_planner(PlannerInfo *root, List *tlist,
joinlist = deconstruct_jointree(root); 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 * Reconsider any postponed outer-join quals now that we have built up
* equivalence classes. (This could result in further additions or * equivalence classes. (This could result in further additions or
...@@ -193,6 +187,13 @@ query_planner(PlannerInfo *root, List *tlist, ...@@ -193,6 +187,13 @@ query_planner(PlannerInfo *root, List *tlist,
*/ */
add_placeholders_to_base_rels(root); 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 * 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 * the query, and we also know which if any have been deleted from the
......
...@@ -41,6 +41,8 @@ typedef struct pullup_replace_vars_context ...@@ -41,6 +41,8 @@ typedef struct pullup_replace_vars_context
PlannerInfo *root; PlannerInfo *root;
List *targetlist; /* tlist of subquery being pulled up */ List *targetlist; /* tlist of subquery being pulled up */
RangeTblEntry *target_rte; /* RTE of subquery */ 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 */ bool *outer_hasSubLinks; /* -> outer query's hasSubLinks */
int varno; /* varno of subquery */ int varno; /* varno of subquery */
bool need_phvs; /* do we need PlaceHolderVars? */ bool need_phvs; /* do we need PlaceHolderVars? */
...@@ -884,14 +886,19 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte, ...@@ -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 * 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 * 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 * non-nullable items and lateral references may have to be turned into
* are dealing with an appendrel member then anything that's not a simple * PlaceHolderVars. If we are dealing with an appendrel member then
* Var has to be turned into a PlaceHolderVar. Set up appropriate context * anything that's not a simple Var has to be turned into a
* data for pullup_replace_vars. * PlaceHolderVar. Set up required context data for pullup_replace_vars.
*/ */
rvcontext.root = root; rvcontext.root = root;
rvcontext.targetlist = subquery->targetList; rvcontext.targetlist = subquery->targetList;
rvcontext.target_rte = rte; 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.outer_hasSubLinks = &parse->hasSubLinks;
rvcontext.varno = varno; rvcontext.varno = varno;
rvcontext.need_phvs = (lowest_nulling_outer_join != NULL || rvcontext.need_phvs = (lowest_nulling_outer_join != NULL ||
...@@ -1675,7 +1682,17 @@ pullup_replace_vars_callback(Var *var, ...@@ -1675,7 +1682,17 @@ pullup_replace_vars_callback(Var *var,
if (newnode && IsA(newnode, Var) && if (newnode && IsA(newnode, Var) &&
((Var *) newnode)->varlevelsup == 0) ((Var *) newnode)->varlevelsup == 0)
{ {
/* Simple Vars always escape being wrapped */ /*
* 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; wrap = false;
} }
else if (newnode && IsA(newnode, PlaceHolderVar) && else if (newnode && IsA(newnode, PlaceHolderVar) &&
...@@ -1692,9 +1709,10 @@ pullup_replace_vars_callback(Var *var, ...@@ -1692,9 +1709,10 @@ pullup_replace_vars_callback(Var *var,
else else
{ {
/* /*
* If it contains a Var of current level, and does not contain * If it contains a Var of the subquery being pulled up, and
* any non-strict constructs, then it's certainly nullable so * does not contain any non-strict constructs, then it's
* we don't need to insert a PlaceHolderVar. * certainly nullable so we don't need to insert a
* PlaceHolderVar.
* *
* This analysis could be tighter: in particular, a non-strict * This analysis could be tighter: in particular, a non-strict
* construct hidden within a lower-level PlaceHolderVar is not * construct hidden within a lower-level PlaceHolderVar is not
...@@ -1703,8 +1721,14 @@ pullup_replace_vars_callback(Var *var, ...@@ -1703,8 +1721,14 @@ pullup_replace_vars_callback(Var *var,
* *
* Note: in future maybe we should insert a PlaceHolderVar * Note: in future maybe we should insert a PlaceHolderVar
* anyway, if the tlist item is expensive to evaluate? * 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)) !contain_nonstrict_functions((Node *) newnode))
{ {
/* No wrap needed */ /* No wrap needed */
......
...@@ -69,6 +69,7 @@ find_placeholder_info(PlannerInfo *root, PlaceHolderVar *phv, ...@@ -69,6 +69,7 @@ find_placeholder_info(PlannerInfo *root, PlaceHolderVar *phv,
bool create_new_ph) bool create_new_ph)
{ {
PlaceHolderInfo *phinfo; PlaceHolderInfo *phinfo;
Relids rels_used;
ListCell *lc; ListCell *lc;
/* if this ever isn't true, we'd need to be able to look in parent lists */ /* 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, ...@@ -89,8 +90,24 @@ find_placeholder_info(PlannerInfo *root, PlaceHolderVar *phv,
phinfo->phid = phv->phid; phinfo->phid = phv->phid;
phinfo->ph_var = copyObject(phv); 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 */ /* ph_eval_at may change later, see update_placeholder_eval_levels */
phinfo->ph_needed = NULL; /* initially it's unused */ phinfo->ph_needed = NULL; /* initially it's unused */
/* for the moment, estimate width using just the datatype info */ /* for the moment, estimate width using just the datatype info */
...@@ -115,6 +132,12 @@ find_placeholder_info(PlannerInfo *root, PlaceHolderVar *phv, ...@@ -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() * 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. * 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 void
find_placeholders_in_jointree(PlannerInfo *root) find_placeholders_in_jointree(PlannerInfo *root)
...@@ -219,7 +242,7 @@ find_placeholders_in_expr(PlannerInfo *root, Node *expr) ...@@ -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 * 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 * rels used in the placeholder's expression (or the whole subselect below
* the placeholder's syntactic location, if the expr is variable-free). * 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 must delay evaluation to above those joins.
* *
* We repeat this operation each time we add another outer join to * 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) ...@@ -299,6 +322,9 @@ update_placeholder_eval_levels(PlannerInfo *root, SpecialJoinInfo *new_sjinfo)
} }
} while (found_some); } 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; phinfo->ph_eval_at = eval_at;
} }
} }
...@@ -309,11 +335,14 @@ update_placeholder_eval_levels(PlannerInfo *root, SpecialJoinInfo *new_sjinfo) ...@@ -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 * 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 * all placeholders. We need to make sure that all vars and placeholders
* needed to evaluate each placeholder will be available at the join level * needed to evaluate each placeholder will be available at the scan or join
* where the evaluation will be done. Note that this loop can have * level where the evaluation will be done. (It might seem that scan-level
* side-effects on the ph_needed sets of other PlaceHolderInfos; that's okay * evaluations aren't interesting, but that's not so: a LATERAL reference
* because we don't examine ph_needed here, so there are no ordering issues * within a placeholder's expression needs to cause the referenced var or
* to worry about. * 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 void
fix_placeholder_input_needed_levels(PlannerInfo *root) fix_placeholder_input_needed_levels(PlannerInfo *root)
...@@ -323,27 +352,23 @@ fix_placeholder_input_needed_levels(PlannerInfo *root) ...@@ -323,27 +352,23 @@ fix_placeholder_input_needed_levels(PlannerInfo *root)
foreach(lc, root->placeholder_list) foreach(lc, root->placeholder_list)
{ {
PlaceHolderInfo *phinfo = (PlaceHolderInfo *) lfirst(lc); 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, List *vars = pull_var_clause((Node *) phinfo->ph_var->phexpr,
PVC_RECURSE_AGGREGATES, PVC_RECURSE_AGGREGATES,
PVC_INCLUDE_PLACEHOLDERS); PVC_INCLUDE_PLACEHOLDERS);
add_vars_to_targetlist(root, vars, eval_at, false); add_vars_to_targetlist(root, vars, phinfo->ph_eval_at, false);
list_free(vars); list_free(vars);
} }
}
} }
/* /*
* add_placeholders_to_base_rels * 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, * 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 * 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 * 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 * 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) ...@@ -359,14 +384,52 @@ add_placeholders_to_base_rels(PlannerInfo *root)
PlaceHolderInfo *phinfo = (PlaceHolderInfo *) lfirst(lc); PlaceHolderInfo *phinfo = (PlaceHolderInfo *) lfirst(lc);
Relids eval_at = phinfo->ph_eval_at; Relids eval_at = phinfo->ph_eval_at;
if (bms_membership(eval_at) == BMS_SINGLETON && if (bms_membership(eval_at) == BMS_SINGLETON)
bms_nonempty_difference(phinfo->ph_needed, eval_at))
{ {
int varno = bms_singleton_member(eval_at); int varno = bms_singleton_member(eval_at);
RelOptInfo *rel = find_base_rel(root, varno); RelOptInfo *rel = find_base_rel(root, varno);
/* 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, rel->reltargetlist = lappend(rel->reltargetlist,
copyObject(phinfo->ph_var)); 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);
}
} }
} }
} }
......
...@@ -113,6 +113,7 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptKind reloptkind) ...@@ -113,6 +113,7 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptKind reloptkind)
/* min_attr, max_attr, attr_needed, attr_widths are set below */ /* min_attr, max_attr, attr_needed, attr_widths are set below */
rel->lateral_vars = NIL; rel->lateral_vars = NIL;
rel->lateral_relids = NULL; rel->lateral_relids = NULL;
rel->lateral_referencers = NULL;
rel->indexlist = NIL; rel->indexlist = NIL;
rel->pages = 0; rel->pages = 0;
rel->tuples = 0; rel->tuples = 0;
...@@ -374,6 +375,7 @@ build_join_rel(PlannerInfo *root, ...@@ -374,6 +375,7 @@ build_join_rel(PlannerInfo *root,
joinrel->attr_widths = NULL; joinrel->attr_widths = NULL;
joinrel->lateral_vars = NIL; joinrel->lateral_vars = NIL;
joinrel->lateral_relids = NULL; joinrel->lateral_relids = NULL;
joinrel->lateral_referencers = NULL;
joinrel->indexlist = NIL; joinrel->indexlist = NIL;
joinrel->pages = 0; joinrel->pages = 0;
joinrel->tuples = 0; joinrel->tuples = 0;
......
...@@ -651,24 +651,32 @@ extract_actual_join_clauses(List *restrictinfo_list, ...@@ -651,24 +651,32 @@ extract_actual_join_clauses(List *restrictinfo_list,
* outer join, as that would change the results (rows would be suppressed * outer join, as that would change the results (rows would be suppressed
* rather than being null-extended). * 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 * 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 * coming from the target relation. Otherwise the clause might give results
* different from what it would give at its normal semantic level. * 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 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 */ /* 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; return false;
/* Cannot move an outer-join clause into the join's outer side */ /* 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; return false;
/* Target rel must not be nullable below the clause */ /* 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 false;
return true; return true;
...@@ -695,6 +703,11 @@ join_clause_is_movable_to(RestrictInfo *rinfo, Index baserelid) ...@@ -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 * not pushing the clause into its outer-join outer side, nor down into
* a lower outer join's inner side. * 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 * Note: get_joinrel_parampathinfo depends on the fact that if
* current_and_outer is NULL, this function will always return false * current_and_outer is NULL, this function will always return false
* (since one or the other of the first two tests must fail). * (since one or the other of the first two tests must fail).
......
...@@ -161,8 +161,13 @@ pull_varnos_walker(Node *node, pull_varnos_context *context) ...@@ -161,8 +161,13 @@ pull_varnos_walker(Node *node, pull_varnos_context *context)
if (IsA(node, PlaceHolderVar)) if (IsA(node, PlaceHolderVar))
{ {
/* /*
* Normally, we can just take the varnos in the contained expression. * A PlaceHolderVar acts as a variable of its syntactic scope, or
* But if it is variable-free, use the PHV's syntactic relids. * 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; PlaceHolderVar *phv = (PlaceHolderVar *) node;
pull_varnos_context subcontext; pull_varnos_context subcontext;
...@@ -170,11 +175,14 @@ pull_varnos_walker(Node *node, pull_varnos_context *context) ...@@ -170,11 +175,14 @@ pull_varnos_walker(Node *node, pull_varnos_context *context)
subcontext.varnos = NULL; subcontext.varnos = NULL;
subcontext.sublevels_up = context->sublevels_up; subcontext.sublevels_up = context->sublevels_up;
(void) pull_varnos_walker((Node *) phv->phexpr, &subcontext); (void) pull_varnos_walker((Node *) phv->phexpr, &subcontext);
if (phv->phlevelsup == context->sublevels_up)
if (bms_is_empty(subcontext.varnos) && {
phv->phlevelsup == context->sublevels_up) subcontext.varnos = bms_int_members(subcontext.varnos,
context->varnos = bms_add_members(context->varnos, phv->phrels); phv->phrels);
else if (bms_is_empty(subcontext.varnos))
context->varnos = bms_add_members(context->varnos,
phv->phrels);
}
context->varnos = bms_join(context->varnos, subcontext.varnos); context->varnos = bms_join(context->varnos, subcontext.varnos);
return false; return false;
} }
......
...@@ -339,6 +339,7 @@ typedef struct PlannerInfo ...@@ -339,6 +339,7 @@ typedef struct PlannerInfo
* Vars and PlaceHolderVars) * Vars and PlaceHolderVars)
* lateral_relids - required outer rels for LATERAL, as a Relids set * lateral_relids - required outer rels for LATERAL, as a Relids set
* (for child rels this can be more than lateral_vars) * (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 * indexlist - list of IndexOptInfo nodes for relation's indexes
* (always NIL if it's not a table) * (always NIL if it's not a table)
* pages - number of disk pages in relation (zero if not a table) * pages - number of disk pages in relation (zero if not a table)
...@@ -432,6 +433,7 @@ typedef struct RelOptInfo ...@@ -432,6 +433,7 @@ typedef struct RelOptInfo
int32 *attr_widths; /* array indexed [min_attr .. max_attr] */ int32 *attr_widths; /* array indexed [min_attr .. max_attr] */
List *lateral_vars; /* LATERAL Vars and PHVs referenced by rel */ List *lateral_vars; /* LATERAL Vars and PHVs referenced by rel */
Relids lateral_relids; /* minimum parameterization of rel */ Relids lateral_relids; /* minimum parameterization of rel */
Relids lateral_referencers; /* rels that reference me laterally */
List *indexlist; /* list of IndexOptInfo */ List *indexlist; /* list of IndexOptInfo */
BlockNumber pages; /* size estimates derived from pg_class */ BlockNumber pages; /* size estimates derived from pg_class */
double tuples; double tuples;
...@@ -1344,30 +1346,38 @@ typedef struct SpecialJoinInfo ...@@ -1344,30 +1346,38 @@ typedef struct SpecialJoinInfo
/* /*
* "Lateral join" info. * "Lateral join" info.
* *
* Lateral references in subqueries constrain the join order in a way that's * Lateral references constrain the join order in a way that's somewhat like
* somewhat like outer joins, though different in detail. We construct one or * outer joins, though different in detail. We construct a LateralJoinInfo
* more LateralJoinInfos for each RTE with lateral references, and add them to * for each lateral cross-reference, placing them in the PlannerInfo node's
* the PlannerInfo node's lateral_info_list. * lateral_info_list.
* *
* lateral_rhs is the relid of a baserel with lateral references, and * For unflattened LATERAL RTEs, we generate LateralJoinInfo(s) in which
* lateral_lhs is a set of relids of baserels it references, all of which * lateral_rhs is the relid of the LATERAL baserel, and lateral_lhs is a set
* must be present on the LHS to compute a parameter needed by the RHS. * of relids of baserels it references, all of which must be present on the
* Typically, lateral_lhs is a singleton, but it can include multiple rels * LHS to compute a parameter needed by the RHS. Typically, lateral_lhs is
* if the RHS references a PlaceHolderVar with a multi-rel ph_eval_at level. * a singleton, but it can include multiple rels if the RHS references a
* We disallow joining to only part of the LHS in such cases, since that would * PlaceHolderVar with a multi-rel ph_eval_at level. We disallow joining to
* result in a join tree with no convenient place to compute the PHV. * 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 * When an appendrel contains lateral references (eg "LATERAL (SELECT x.col1
* UNION ALL SELECT y.col2)"), the LateralJoinInfos reference the parent * UNION ALL SELECT y.col2)"), the LateralJoinInfos reference the parent
* baserel not the member otherrels, since it is the parent relid that is * baserel not the member otherrels, since it is the parent relid that is
* considered for joining purposes. * 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 typedef struct LateralJoinInfo
{ {
NodeTag type; NodeTag type;
Index lateral_rhs; /* a baserel containing lateral refs */ Relids lateral_lhs; /* rels needed to compute a lateral value */
Relids lateral_lhs; /* some base relids it references */ Relids lateral_rhs; /* rel where lateral value is needed */
} LateralJoinInfo; } LateralJoinInfo;
/* /*
...@@ -1465,6 +1475,10 @@ typedef struct AppendRelInfo ...@@ -1465,6 +1475,10 @@ typedef struct AppendRelInfo
* then allow it to bubble up like a Var until the ph_needed join level. * 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. * 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 * 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 * 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. * has to be formed below any outer joins that should null the PlaceHolderVar.
...@@ -1481,6 +1495,7 @@ typedef struct PlaceHolderInfo ...@@ -1481,6 +1495,7 @@ typedef struct PlaceHolderInfo
Index phid; /* ID for PH (unique within planner run) */ Index phid; /* ID for PH (unique within planner run) */
PlaceHolderVar *ph_var; /* copy of PlaceHolderVar tree */ PlaceHolderVar *ph_var; /* copy of PlaceHolderVar tree */
Relids ph_eval_at; /* lowest level we can evaluate value at */ 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 */ Relids ph_needed; /* highest level the value is needed at */
int32 ph_width; /* estimated attribute width */ int32 ph_width; /* estimated attribute width */
} PlaceHolderInfo; } PlaceHolderInfo;
......
...@@ -41,7 +41,7 @@ extern List *extract_actual_clauses(List *restrictinfo_list, ...@@ -41,7 +41,7 @@ extern List *extract_actual_clauses(List *restrictinfo_list,
extern void extract_actual_join_clauses(List *restrictinfo_list, extern void extract_actual_join_clauses(List *restrictinfo_list,
List **joinquals, List **joinquals,
List **otherquals); 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, extern bool join_clause_is_movable_into(RestrictInfo *rinfo,
Relids currentrelids, Relids currentrelids,
Relids current_and_outer); Relids current_and_outer);
......
This diff is collapsed.
...@@ -995,6 +995,47 @@ select v.* from ...@@ -995,6 +995,47 @@ select v.* from
left join int4_tbl z on z.f1 = x.q2, 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); 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 -- case requiring nested PlaceHolderVars
explain (verbose, costs off) explain (verbose, costs off)
select * from select * from
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment