1
0
mirror of https://github.com/postgres/postgres.git synced 2025-07-05 07:21:24 +03:00

More fixes for planner's handling of LATERAL.

Re-allow subquery pullup for LATERAL subqueries, except when the subquery
is below an outer join and contains lateral references to relations outside
that outer join.  If we pull up in such a case, we risk introducing lateral
cross-references into outer joins' ON quals, which is something the code is
entirely unprepared to cope with right now; and I'm not sure it'll ever be
worth coping with.

Support lateral refs in VALUES (this seems to be the only additional path
type that needs such support as a consequence of re-allowing subquery
pullup).

Put in a slightly hacky fix for joinpath.c's refusal to consider
parameterized join paths even when there cannot be any unparameterized
ones.  This was causing "could not devise a query plan for the given query"
failures in queries involving more than two FROM items.

Put in an even more hacky fix for distribute_qual_to_rels() being unhappy
with join quals that contain references to rels outside their syntactic
scope; which is to say, disable that test altogether.  Need to think about
how to preserve some sort of debugging cross-check here, while not
expending more cycles than befits a debugging cross-check.
This commit is contained in:
Tom Lane
2012-08-12 16:01:26 -04:00
parent e76af54137
commit c1774d2c81
13 changed files with 390 additions and 89 deletions

View File

@ -1210,15 +1210,33 @@ set_function_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
* set_values_pathlist
* Build the (single) access path for a VALUES RTE
*
* There can be no need for a parameterized path here. (Although the SQL
* spec does allow LATERAL (VALUES (x)), the parser will transform that
* into a subquery, so it doesn't end up here.)
* As with subqueries, a VALUES RTE's path might be parameterized due to
* LATERAL references, but that's inherent in the values expressions and
* not a result of pushing down join quals.
*/
static void
set_values_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
{
Relids required_outer;
/*
* If it's a LATERAL RTE, it might contain some Vars of the current query
* level, requiring it to be treated as parameterized. (NB: even though
* the parser never marks VALUES RTEs as LATERAL, they could be so marked
* by now, as a result of subquery pullup.)
*/
if (rte->lateral)
{
required_outer = pull_varnos_of_level((Node *) rte->values_lists, 0);
/* Enforce convention that empty required_outer is exactly NULL */
if (bms_is_empty(required_outer))
required_outer = NULL;
}
else
required_outer = NULL;
/* Generate appropriate path */
add_path(rel, create_valuesscan_path(root, rel));
add_path(rel, create_valuesscan_path(root, rel, required_outer));
/* Select cheapest path (pretty easy in this case...) */
set_cheapest(rel);

View File

@ -1046,20 +1046,28 @@ cost_functionscan(Path *path, PlannerInfo *root,
/*
* cost_valuesscan
* Determines and returns the cost of scanning a VALUES RTE.
*
* 'baserel' is the relation to be scanned
* 'param_info' is the ParamPathInfo if this is a parameterized path, else NULL
*/
void
cost_valuesscan(Path *path, PlannerInfo *root, RelOptInfo *baserel)
cost_valuesscan(Path *path, PlannerInfo *root,
RelOptInfo *baserel, ParamPathInfo *param_info)
{
Cost startup_cost = 0;
Cost run_cost = 0;
QualCost qpqual_cost;
Cost cpu_per_tuple;
/* Should only be applied to base relations that are values lists */
Assert(baserel->relid > 0);
Assert(baserel->rtekind == RTE_VALUES);
/* valuesscans are never parameterized */
path->rows = baserel->rows;
/* Mark the path with the correct row estimate */
if (param_info)
path->rows = param_info->ppi_rows;
else
path->rows = baserel->rows;
/*
* For now, estimate list evaluation cost at one operator eval per list
@ -1068,8 +1076,10 @@ cost_valuesscan(Path *path, PlannerInfo *root, RelOptInfo *baserel)
cpu_per_tuple = cpu_operator_cost;
/* Add scanning CPU costs */
startup_cost += baserel->baserestrictcost.startup;
cpu_per_tuple += cpu_tuple_cost + baserel->baserestrictcost.per_tuple;
get_restriction_qual_cost(root, baserel, param_info, &qpqual_cost);
startup_cost += qpqual_cost.startup;
cpu_per_tuple += cpu_tuple_cost + qpqual_cost.per_tuple;
run_cost += cpu_per_tuple * baserel->tuples;
path->startup_cost = startup_cost;

View File

@ -147,6 +147,32 @@ add_paths_to_joinrel(PlannerInfo *root,
sjinfo->min_lefthand));
}
/*
* However, when a LATERAL subquery is involved, we have to be a bit
* laxer, because there may simply not be any paths for the joinrel that
* aren't parameterized by whatever the subquery is parameterized by.
* Hence, add to param_source_rels anything that is in the minimum
* parameterization of either input (and not in the other input).
*
* XXX need a more principled way of determining minimum parameterization.
*/
if (outerrel->cheapest_total_path == NULL)
{
Path *cheapest = (Path *) linitial(outerrel->cheapest_parameterized_paths);
param_source_rels = bms_join(param_source_rels,
bms_difference(PATH_REQ_OUTER(cheapest),
innerrel->relids));
}
if (innerrel->cheapest_total_path == NULL)
{
Path *cheapest = (Path *) linitial(innerrel->cheapest_parameterized_paths);
param_source_rels = bms_join(param_source_rels,
bms_difference(PATH_REQ_OUTER(cheapest),
outerrel->relids));
}
/*
* 1. Consider mergejoin paths where both relations must be explicitly
* sorted. Skip this if we can't mergejoin.

View File

@ -1713,11 +1713,13 @@ create_valuesscan_plan(PlannerInfo *root, Path *best_path,
ValuesScan *scan_plan;
Index scan_relid = best_path->parent->relid;
RangeTblEntry *rte;
List *values_lists;
/* it should be a values base rel... */
Assert(scan_relid > 0);
rte = planner_rt_fetch(scan_relid, root);
Assert(rte->rtekind == RTE_VALUES);
values_lists = rte->values_lists;
/* Sort clauses into best execution order */
scan_clauses = order_qual_clauses(root, scan_clauses);
@ -1725,8 +1727,18 @@ create_valuesscan_plan(PlannerInfo *root, Path *best_path,
/* Reduce RestrictInfo list to bare expressions; ignore pseudoconstants */
scan_clauses = extract_actual_clauses(scan_clauses, false);
/* Replace any outer-relation variables with nestloop params */
if (best_path->param_info)
{
scan_clauses = (List *)
replace_nestloop_params(root, (Node *) scan_clauses);
/* The values lists could contain nestloop params, too */
values_lists = (List *)
replace_nestloop_params(root, (Node *) values_lists);
}
scan_plan = make_valuesscan(tlist, scan_clauses, scan_relid,
rte->values_lists);
values_lists);
copy_path_costsize(&scan_plan->scan.plan, best_path);

View File

@ -232,6 +232,8 @@ extract_lateral_references(PlannerInfo *root, int rtindex)
vars = pull_vars_of_level((Node *) rte->subquery, 1);
else if (rte->rtekind == RTE_FUNCTION)
vars = pull_vars_of_level(rte->funcexpr, 0);
else if (rte->rtekind == RTE_VALUES)
vars = pull_vars_of_level((Node *) rte->values_lists, 0);
else
return;
@ -874,9 +876,19 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
/*
* Cross-check: clause should contain no relids not within its scope.
* Otherwise the parser messed up.
*
* XXX temporarily disable the qualscope cross-check, which tends to
* reject quals pulled up from LATERAL subqueries. This is only in the
* nature of a debugging crosscheck anyway. I'm loath to remove it
* permanently, but need to think a bit harder about how to replace it.
* See also disabled Assert below. (The ojscope test is still okay
* because we prevent pullup of LATERAL subqueries that might cause it to
* be violated.)
*/
#ifdef NOT_USED
if (!bms_is_subset(relids, qualscope))
elog(ERROR, "JOIN qualification cannot refer to other relations");
#endif
if (ojscope && !bms_is_subset(relids, ojscope))
elog(ERROR, "JOIN qualification cannot refer to other relations");
@ -1031,7 +1043,9 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
if (outerjoin_delayed)
{
/* Should still be a subset of current scope ... */
#ifdef NOT_USED /* XXX temporarily disabled for LATERAL */
Assert(bms_is_subset(relids, qualscope));
#endif
/*
* Because application of the qual will be delayed by outer join,

View File

@ -334,7 +334,7 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
* query.
*/
parse->jointree = (FromExpr *)
pull_up_subqueries(root, (Node *) parse->jointree, NULL, NULL);
pull_up_subqueries(root, (Node *) parse->jointree);
/*
* If this is a simple UNION ALL query, flatten it into an appendrel. We

View File

@ -60,9 +60,14 @@ static Node *pull_up_sublinks_jointree_recurse(PlannerInfo *root, Node *jtnode,
static Node *pull_up_sublinks_qual_recurse(PlannerInfo *root, Node *node,
Node **jtlink1, Relids available_rels1,
Node **jtlink2, Relids available_rels2);
static Node *pull_up_subqueries_recurse(PlannerInfo *root, Node *jtnode,
JoinExpr *lowest_outer_join,
JoinExpr *lowest_nulling_outer_join,
AppendRelInfo *containing_appendrel);
static Node *pull_up_simple_subquery(PlannerInfo *root, Node *jtnode,
RangeTblEntry *rte,
JoinExpr *lowest_outer_join,
JoinExpr *lowest_nulling_outer_join,
AppendRelInfo *containing_appendrel);
static Node *pull_up_simple_union_all(PlannerInfo *root, Node *jtnode,
RangeTblEntry *rte);
@ -71,14 +76,15 @@ static void pull_up_union_leaf_queries(Node *setOp, PlannerInfo *root,
int childRToffset);
static void make_setop_translation_list(Query *query, Index newvarno,
List **translated_vars);
static bool is_simple_subquery(Query *subquery);
static bool is_simple_subquery(Query *subquery, RangeTblEntry *rte,
JoinExpr *lowest_outer_join);
static bool is_simple_union_all(Query *subquery);
static bool is_simple_union_all_recurse(Node *setOp, Query *setOpQuery,
List *colTypes);
static bool is_safe_append_member(Query *subquery);
static void replace_vars_in_jointree(Node *jtnode,
pullup_replace_vars_context *context,
JoinExpr *lowest_outer_join);
JoinExpr *lowest_nulling_outer_join);
static Node *pullup_replace_vars(Node *expr,
pullup_replace_vars_context *context);
static Node *pullup_replace_vars_callback(Var *var,
@ -585,10 +591,28 @@ inline_set_returning_functions(PlannerInfo *root)
* Also, subqueries that are simple UNION ALL structures can be
* converted into "append relations".
*
* This recursively processes the jointree and returns a modified jointree.
*/
Node *
pull_up_subqueries(PlannerInfo *root, Node *jtnode)
{
/* Start off with no containing join nor appendrel */
return pull_up_subqueries_recurse(root, jtnode, NULL, NULL, NULL);
}
/*
* pull_up_subqueries_recurse
* Recursive guts of pull_up_subqueries.
*
* If this jointree node is within either side of an outer join, then
* lowest_outer_join references the lowest such JoinExpr node; otherwise
* it is NULL. We use this to constrain the effects of LATERAL subqueries.
*
* If this jointree node is within the nullable side of an outer join, then
* lowest_outer_join references the lowest such JoinExpr node; otherwise it
* is NULL. This forces use of the PlaceHolderVar mechanism for references
* to non-nullable targetlist items, but only for references above that join.
* lowest_nulling_outer_join references the lowest such JoinExpr node;
* otherwise it is NULL. This forces use of the PlaceHolderVar mechanism for
* references to non-nullable targetlist items, but only for references above
* that join.
*
* If we are looking at a member subquery of an append relation,
* containing_appendrel describes that relation; else it is NULL.
@ -603,15 +627,17 @@ inline_set_returning_functions(PlannerInfo *root)
* subquery RangeTblRef entries will be replaced. Also, we can't turn
* pullup_replace_vars loose on the whole jointree, because it'll return a
* mutated copy of the tree; we have to invoke it just on the quals, instead.
* This behavior is what makes it reasonable to pass lowest_outer_join as a
* pointer rather than some more-indirect way of identifying the lowest OJ.
* Likewise, we don't replace append_rel_list members but only their
* substructure, so the containing_appendrel reference is safe to use.
* This behavior is what makes it reasonable to pass lowest_outer_join and
* lowest_nulling_outer_join as pointers rather than some more-indirect way
* of identifying the lowest OJs. Likewise, we don't replace append_rel_list
* members but only their substructure, so the containing_appendrel reference
* is safe to use.
*/
Node *
pull_up_subqueries(PlannerInfo *root, Node *jtnode,
JoinExpr *lowest_outer_join,
AppendRelInfo *containing_appendrel)
static Node *
pull_up_subqueries_recurse(PlannerInfo *root, Node *jtnode,
JoinExpr *lowest_outer_join,
JoinExpr *lowest_nulling_outer_join,
AppendRelInfo *containing_appendrel)
{
if (jtnode == NULL)
return NULL;
@ -628,12 +654,12 @@ pull_up_subqueries(PlannerInfo *root, Node *jtnode,
* unless is_safe_append_member says so.
*/
if (rte->rtekind == RTE_SUBQUERY &&
is_simple_subquery(rte->subquery) &&
!rte->security_barrier &&
is_simple_subquery(rte->subquery, rte, lowest_outer_join) &&
(containing_appendrel == NULL ||
is_safe_append_member(rte->subquery)))
return pull_up_simple_subquery(root, jtnode, rte,
lowest_outer_join,
lowest_nulling_outer_join,
containing_appendrel);
/*
@ -658,8 +684,10 @@ pull_up_subqueries(PlannerInfo *root, Node *jtnode,
Assert(containing_appendrel == NULL);
foreach(l, f->fromlist)
lfirst(l) = pull_up_subqueries(root, lfirst(l),
lowest_outer_join, NULL);
lfirst(l) = pull_up_subqueries_recurse(root, lfirst(l),
lowest_outer_join,
lowest_nulling_outer_join,
NULL);
}
else if (IsA(jtnode, JoinExpr))
{
@ -670,30 +698,46 @@ pull_up_subqueries(PlannerInfo *root, Node *jtnode,
switch (j->jointype)
{
case JOIN_INNER:
j->larg = pull_up_subqueries(root, j->larg,
lowest_outer_join, NULL);
j->rarg = pull_up_subqueries(root, j->rarg,
lowest_outer_join, NULL);
j->larg = pull_up_subqueries_recurse(root, j->larg,
lowest_outer_join,
lowest_nulling_outer_join,
NULL);
j->rarg = pull_up_subqueries_recurse(root, j->rarg,
lowest_outer_join,
lowest_nulling_outer_join,
NULL);
break;
case JOIN_LEFT:
case JOIN_SEMI:
case JOIN_ANTI:
j->larg = pull_up_subqueries(root, j->larg,
lowest_outer_join, NULL);
j->rarg = pull_up_subqueries(root, j->rarg,
j, NULL);
j->larg = pull_up_subqueries_recurse(root, j->larg,
j,
lowest_nulling_outer_join,
NULL);
j->rarg = pull_up_subqueries_recurse(root, j->rarg,
j,
j,
NULL);
break;
case JOIN_FULL:
j->larg = pull_up_subqueries(root, j->larg,
j, NULL);
j->rarg = pull_up_subqueries(root, j->rarg,
j, NULL);
j->larg = pull_up_subqueries_recurse(root, j->larg,
j,
j,
NULL);
j->rarg = pull_up_subqueries_recurse(root, j->rarg,
j,
j,
NULL);
break;
case JOIN_RIGHT:
j->larg = pull_up_subqueries(root, j->larg,
j, NULL);
j->rarg = pull_up_subqueries(root, j->rarg,
lowest_outer_join, NULL);
j->larg = pull_up_subqueries_recurse(root, j->larg,
j,
j,
NULL);
j->rarg = pull_up_subqueries_recurse(root, j->rarg,
j,
lowest_nulling_outer_join,
NULL);
break;
default:
elog(ERROR, "unrecognized join type: %d",
@ -717,11 +761,12 @@ pull_up_subqueries(PlannerInfo *root, Node *jtnode,
* all.
*
* rte is the RangeTblEntry referenced by jtnode. Remaining parameters are
* as for pull_up_subqueries.
* as for pull_up_subqueries_recurse.
*/
static Node *
pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
JoinExpr *lowest_outer_join,
JoinExpr *lowest_nulling_outer_join,
AppendRelInfo *containing_appendrel)
{
Query *parse = root->parse;
@ -788,7 +833,8 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
* handling an appendrel member.
*/
subquery->jointree = (FromExpr *)
pull_up_subqueries(subroot, (Node *) subquery->jointree, NULL, NULL);
pull_up_subqueries_recurse(subroot, (Node *) subquery->jointree,
NULL, NULL, NULL);
/*
* Now we must recheck whether the subquery is still simple enough to pull
@ -796,10 +842,9 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
*
* We don't really need to recheck all the conditions involved, but it's
* easier just to keep this "if" looking the same as the one in
* pull_up_subqueries.
* pull_up_subqueries_recurse.
*/
if (is_simple_subquery(subquery) &&
!rte->security_barrier &&
if (is_simple_subquery(subquery, rte, lowest_outer_join) &&
(containing_appendrel == NULL || is_safe_append_member(subquery)))
{
/* good to go */
@ -846,7 +891,7 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
rvcontext.target_rte = rte;
rvcontext.outer_hasSubLinks = &parse->hasSubLinks;
rvcontext.varno = varno;
rvcontext.need_phvs = (lowest_outer_join != NULL ||
rvcontext.need_phvs = (lowest_nulling_outer_join != NULL ||
containing_appendrel != NULL);
rvcontext.wrap_non_vars = (containing_appendrel != NULL);
/* initialize cache array with indexes 0 .. length(tlist) */
@ -867,7 +912,7 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
parse->returningList = (List *)
pullup_replace_vars((Node *) parse->returningList, &rvcontext);
replace_vars_in_jointree((Node *) parse->jointree, &rvcontext,
lowest_outer_join);
lowest_nulling_outer_join);
Assert(parse->setOperations == NULL);
parse->havingQual = pullup_replace_vars(parse->havingQual, &rvcontext);
@ -894,10 +939,10 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
* Replace references in the joinaliasvars lists of join RTEs.
*
* You might think that we could avoid using PHVs for alias vars of joins
* below lowest_outer_join, but that doesn't work because the alias vars
* could be referenced above that join; we need the PHVs to be present in
* such references after the alias vars get flattened. (It might be worth
* trying to be smarter here, someday.)
* below lowest_nulling_outer_join, but that doesn't work because the
* alias vars could be referenced above that join; we need the PHVs to be
* present in such references after the alias vars get flattened. (It
* might be worth trying to be smarter here, someday.)
*/
foreach(lc, parse->rtable)
{
@ -909,6 +954,38 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
&rvcontext);
}
/*
* If the subquery had a LATERAL marker, propagate that to any of its
* child RTEs that could possibly now contain lateral cross-references.
* The children might or might not contain any actual lateral
* cross-references, but we have to mark the pulled-up child RTEs so that
* later planner stages will check for such.
*
* NB: although the parser only sets the lateral flag in subquery and
* function RTEs, after this step it can also be set in VALUES RTEs.
*/
if (rte->lateral)
{
foreach(lc, subquery->rtable)
{
RangeTblEntry *child_rte = (RangeTblEntry *) lfirst(lc);
switch (child_rte->rtekind)
{
case RTE_SUBQUERY:
case RTE_FUNCTION:
case RTE_VALUES:
child_rte->lateral = true;
break;
case RTE_RELATION:
case RTE_JOIN:
case RTE_CTE:
/* these can't contain any lateral references */
break;
}
}
}
/*
* Now append the adjusted rtable entries to upper query. (We hold off
* until after fixing the upper rtable entries; no point in running that
@ -1104,11 +1181,12 @@ pull_up_union_leaf_queries(Node *setOp, PlannerInfo *root, int parentRTindex,
* must build the AppendRelInfo first, because this will modify it.)
* Note that we can pass NULL for containing-join info even if we're
* actually under an outer join, because the child's expressions
* aren't going to propagate up above the join.
* aren't going to propagate up to the join.
*/
rtr = makeNode(RangeTblRef);
rtr->rtindex = childRTindex;
(void) pull_up_subqueries(root, (Node *) rtr, NULL, appinfo);
(void) pull_up_subqueries_recurse(root, (Node *) rtr,
NULL, NULL, appinfo);
}
else if (IsA(setOp, SetOperationStmt))
{
@ -1158,9 +1236,15 @@ make_setop_translation_list(Query *query, Index newvarno,
* is_simple_subquery
* Check a subquery in the range table to see if it's simple enough
* to pull up into the parent query.
*
* rte is the RTE_SUBQUERY RangeTblEntry that contained the subquery.
* (Note subquery is not necessarily equal to rte->subquery; it could be a
* processed copy of that.)
* lowest_outer_join is the lowest outer join above the subquery, or NULL.
*/
static bool
is_simple_subquery(Query *subquery)
is_simple_subquery(Query *subquery, RangeTblEntry *rte,
JoinExpr *lowest_outer_join)
{
/*
* Let's just make sure it's a valid subselect ...
@ -1201,12 +1285,30 @@ is_simple_subquery(Query *subquery)
return false;
/*
* Don't pull up a LATERAL subquery (hopefully, this is just a temporary
* implementation restriction).
* Don't pull up if the RTE represents a security-barrier view; we couldn't
* prevent information leakage once the RTE's Vars are scattered about in
* the upper query.
*/
if (contain_vars_of_level((Node *) subquery, 1))
if (rte->security_barrier)
return false;
/*
* If the subquery is LATERAL, and we're below any outer join, and the
* subquery contains lateral references to rels outside the outer join,
* don't pull up. Doing so would risk creating outer-join quals that
* contain references to rels outside the outer join, which is a semantic
* mess that doesn't seem worth addressing at the moment.
*/
if (rte->lateral && lowest_outer_join != NULL)
{
Relids lvarnos = pull_varnos_of_level((Node *) subquery, 1);
Relids jvarnos = get_relids_in_jointree((Node *) lowest_outer_join,
true);
if (!bms_is_subset(lvarnos, jvarnos))
return false;
}
/*
* Don't pull up a subquery that has any set-returning functions in its
* targetlist. Otherwise we might well wind up inserting set-returning
@ -1358,13 +1460,13 @@ is_safe_append_member(Query *subquery)
* expression in the jointree, without changing the jointree structure itself.
* Ugly, but there's no other way...
*
* If we are at or below lowest_outer_join, we can suppress use of
* If we are at or below lowest_nulling_outer_join, we can suppress use of
* PlaceHolderVars wrapped around the replacement expressions.
*/
static void
replace_vars_in_jointree(Node *jtnode,
pullup_replace_vars_context *context,
JoinExpr *lowest_outer_join)
JoinExpr *lowest_nulling_outer_join)
{
if (jtnode == NULL)
return;
@ -1378,7 +1480,8 @@ replace_vars_in_jointree(Node *jtnode,
ListCell *l;
foreach(l, f->fromlist)
replace_vars_in_jointree(lfirst(l), context, lowest_outer_join);
replace_vars_in_jointree(lfirst(l), context,
lowest_nulling_outer_join);
f->quals = pullup_replace_vars(f->quals, context);
}
else if (IsA(jtnode, JoinExpr))
@ -1386,14 +1489,14 @@ replace_vars_in_jointree(Node *jtnode,
JoinExpr *j = (JoinExpr *) jtnode;
bool save_need_phvs = context->need_phvs;
if (j == lowest_outer_join)
if (j == lowest_nulling_outer_join)
{
/* no more PHVs in or below this join */
context->need_phvs = false;
lowest_outer_join = NULL;
lowest_nulling_outer_join = NULL;
}
replace_vars_in_jointree(j->larg, context, lowest_outer_join);
replace_vars_in_jointree(j->rarg, context, lowest_outer_join);
replace_vars_in_jointree(j->larg, context, lowest_nulling_outer_join);
replace_vars_in_jointree(j->rarg, context, lowest_nulling_outer_join);
j->quals = pullup_replace_vars(j->quals, context);
/*

View File

@ -1689,16 +1689,18 @@ create_functionscan_path(PlannerInfo *root, RelOptInfo *rel,
* returning the pathnode.
*/
Path *
create_valuesscan_path(PlannerInfo *root, RelOptInfo *rel)
create_valuesscan_path(PlannerInfo *root, RelOptInfo *rel,
Relids required_outer)
{
Path *pathnode = makeNode(Path);
pathnode->pathtype = T_ValuesScan;
pathnode->parent = rel;
pathnode->param_info = NULL; /* never parameterized at present */
pathnode->param_info = get_baserel_parampathinfo(root, rel,
required_outer);
pathnode->pathkeys = NIL; /* result is always unordered */
cost_valuesscan(pathnode, root, rel);
cost_valuesscan(pathnode, root, rel, pathnode->param_info);
return pathnode;
}