1
0
mirror of https://github.com/postgres/postgres.git synced 2025-09-05 02:22:28 +03:00

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 4da6439bd8, 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.
This commit is contained in:
Tom Lane
2013-08-17 20:22:37 -04:00
parent 175ec8de47
commit 9e7e29c75a
25 changed files with 840 additions and 311 deletions

View File

@@ -69,6 +69,7 @@ find_placeholder_info(PlannerInfo *root, PlaceHolderVar *phv,
bool create_new_ph)
{
PlaceHolderInfo *phinfo;
Relids rels_used;
ListCell *lc;
/* if this ever isn't true, we'd need to be able to look in parent lists */
@@ -89,8 +90,24 @@ find_placeholder_info(PlannerInfo *root, PlaceHolderVar *phv,
phinfo->phid = phv->phid;
phinfo->ph_var = copyObject(phv);
/* initialize ph_eval_at as the set of contained relids */
phinfo->ph_eval_at = pull_varnos((Node *) phv);
/*
* Any referenced rels that are outside the PHV's syntactic scope are
* LATERAL references, which should be included in ph_lateral but not in
* ph_eval_at. If no referenced rels are within the syntactic scope,
* force evaluation at the syntactic location.
*/
rels_used = pull_varnos((Node *) phv->phexpr);
phinfo->ph_lateral = bms_difference(rels_used, phv->phrels);
if (bms_is_empty(phinfo->ph_lateral))
phinfo->ph_lateral = NULL; /* make it exactly NULL if empty */
phinfo->ph_eval_at = bms_int_members(rels_used, phv->phrels);
/* If no contained vars, force evaluation at syntactic location */
if (bms_is_empty(phinfo->ph_eval_at))
{
phinfo->ph_eval_at = bms_copy(phv->phrels);
Assert(!bms_is_empty(phinfo->ph_eval_at));
}
/* ph_eval_at may change later, see update_placeholder_eval_levels */
phinfo->ph_needed = NULL; /* initially it's unused */
/* for the moment, estimate width using just the datatype info */
@@ -115,6 +132,12 @@ find_placeholder_info(PlannerInfo *root, PlaceHolderVar *phv,
*
* We don't need to look at the targetlist because build_base_rel_tlists()
* will already have made entries for any PHVs in the tlist.
*
* This is called before we begin deconstruct_jointree. Once we begin
* deconstruct_jointree, all active placeholders must be present in
* root->placeholder_list, because make_outerjoininfo and
* update_placeholder_eval_levels require this info to be available
* while we crawl up the join tree.
*/
void
find_placeholders_in_jointree(PlannerInfo *root)
@@ -219,7 +242,7 @@ find_placeholders_in_expr(PlannerInfo *root, Node *expr)
* The initial eval_at level set by find_placeholder_info was the set of
* rels used in the placeholder's expression (or the whole subselect below
* the placeholder's syntactic location, if the expr is variable-free).
* If the subselect contains any outer joins that can null any of those rels,
* If the query contains any outer joins that can null any of those rels,
* we must delay evaluation to above those joins.
*
* We repeat this operation each time we add another outer join to
@@ -299,6 +322,9 @@ update_placeholder_eval_levels(PlannerInfo *root, SpecialJoinInfo *new_sjinfo)
}
} while (found_some);
/* Can't move the PHV's eval_at level to above its syntactic level */
Assert(bms_is_subset(eval_at, syn_level));
phinfo->ph_eval_at = eval_at;
}
}
@@ -309,11 +335,14 @@ update_placeholder_eval_levels(PlannerInfo *root, SpecialJoinInfo *new_sjinfo)
*
* This is called after we've finished determining the eval_at levels for
* all placeholders. We need to make sure that all vars and placeholders
* needed to evaluate each placeholder will be available at the join level
* where the evaluation will be done. Note that this loop can have
* side-effects on the ph_needed sets of other PlaceHolderInfos; that's okay
* because we don't examine ph_needed here, so there are no ordering issues
* to worry about.
* needed to evaluate each placeholder will be available at the scan or join
* level where the evaluation will be done. (It might seem that scan-level
* evaluations aren't interesting, but that's not so: a LATERAL reference
* within a placeholder's expression needs to cause the referenced var or
* placeholder to be marked as needed in the scan where it's evaluated.)
* Note that this loop can have side-effects on the ph_needed sets of other
* PlaceHolderInfos; that's okay because we don't examine ph_needed here, so
* there are no ordering issues to worry about.
*/
void
fix_placeholder_input_needed_levels(PlannerInfo *root)
@@ -323,27 +352,23 @@ fix_placeholder_input_needed_levels(PlannerInfo *root)
foreach(lc, root->placeholder_list)
{
PlaceHolderInfo *phinfo = (PlaceHolderInfo *) lfirst(lc);
Relids eval_at = phinfo->ph_eval_at;
List *vars = pull_var_clause((Node *) phinfo->ph_var->phexpr,
PVC_RECURSE_AGGREGATES,
PVC_INCLUDE_PLACEHOLDERS);
/* 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);
list_free(vars);
}
add_vars_to_targetlist(root, vars, phinfo->ph_eval_at, false);
list_free(vars);
}
}
/*
* add_placeholders_to_base_rels
* Add any required PlaceHolderVars to base rels' targetlists.
* Add any required PlaceHolderVars to base rels' targetlists, and
* update lateral_vars lists for lateral references contained in them.
*
* If any placeholder can be computed at a base rel and is needed above it,
* add it to that rel's targetlist. This might look like it could be merged
* add it to that rel's targetlist, and add any lateral references it requires
* to the rel's lateral_vars list. This might look like it could be merged
* with fix_placeholder_input_needed_levels, but it must be separate because
* join removal happens in between, and can change the ph_eval_at sets. There
* is essentially the same logic in add_placeholders_to_joinrel, but we can't
@@ -359,14 +384,52 @@ add_placeholders_to_base_rels(PlannerInfo *root)
PlaceHolderInfo *phinfo = (PlaceHolderInfo *) lfirst(lc);
Relids eval_at = phinfo->ph_eval_at;
if (bms_membership(eval_at) == BMS_SINGLETON &&
bms_nonempty_difference(phinfo->ph_needed, eval_at))
if (bms_membership(eval_at) == BMS_SINGLETON)
{
int varno = bms_singleton_member(eval_at);
RelOptInfo *rel = find_base_rel(root, varno);
rel->reltargetlist = lappend(rel->reltargetlist,
copyObject(phinfo->ph_var));
/* add it to reltargetlist if needed above the rel scan level */
if (bms_nonempty_difference(phinfo->ph_needed, eval_at))
rel->reltargetlist = lappend(rel->reltargetlist,
copyObject(phinfo->ph_var));
/* if there are lateral refs in it, add them to lateral_vars */
if (phinfo->ph_lateral != NULL)
{
List *vars = pull_var_clause((Node *) phinfo->ph_var->phexpr,
PVC_RECURSE_AGGREGATES,
PVC_INCLUDE_PLACEHOLDERS);
ListCell *lc2;
foreach(lc2, vars)
{
Node *node = (Node *) lfirst(lc2);
if (IsA(node, Var))
{
Var *var = (Var *) node;
if (var->varno != varno)
rel->lateral_vars = lappend(rel->lateral_vars,
var);
}
else if (IsA(node, PlaceHolderVar))
{
PlaceHolderVar *other_phv = (PlaceHolderVar *) node;
PlaceHolderInfo *other_phi;
other_phi = find_placeholder_info(root, other_phv,
false);
if (!bms_is_subset(other_phi->ph_eval_at, eval_at))
rel->lateral_vars = lappend(rel->lateral_vars,
other_phv);
}
else
Assert(false);
}
list_free(vars);
}
}
}
}