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,
{
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;
......
......@@ -1921,8 +1921,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;
}
......@@ -1956,6 +1956,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);
......
......@@ -763,15 +763,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);
......@@ -796,8 +800,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;
}
......@@ -819,8 +823,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);
......
......@@ -1756,6 +1756,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");
......@@ -1913,8 +1914,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
......@@ -1938,6 +1939,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);
}
......
......@@ -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
......@@ -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;
......
......@@ -3943,10 +3943,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)
......
......@@ -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);
......
This diff is collapsed.
......@@ -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;
}
......
......@@ -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
......
......@@ -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;
......
......@@ -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);
}
......
......@@ -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,
......@@ -2029,7 +2030,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;
......@@ -2119,7 +2120,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;
......@@ -2187,7 +2188,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,
......@@ -2200,7 +2201,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,
......@@ -2414,7 +2415,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;
......@@ -2471,11 +2472,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
......@@ -2605,16 +2606,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,
if (!bms_overlap(phv->phrels, root->curOuterRels) ||
!bms_is_subset(find_placeholder_info(root, phv, false)->ph_eval_at,
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 */
param = assign_nestloop_param_placeholdervar(root, phv);
/* Is this param already listed in root->curOuterParams? */
......
This diff is collapsed.
......@@ -143,12 +143,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
......@@ -193,6 +187,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
......
......@@ -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 ||
......@@ -1675,7 +1682,17 @@ pullup_replace_vars_callback(Var *var,
if (newnode && IsA(newnode, Var) &&
((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;
}
else if (newnode && IsA(newnode, PlaceHolderVar) &&
......@@ -1692,9 +1709,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
......@@ -1703,8 +1721,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 */
......
......@@ -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);
add_vars_to_targetlist(root, vars, eval_at, false);
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);
/* 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);
}
}
}
}
......
......@@ -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;
......
......@@ -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).
......
......@@ -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,11 +175,14 @@ 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
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;
}
......
......@@ -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;
......
......@@ -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);
......
This diff is collapsed.
......@@ -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
......
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