mirror of
https://github.com/postgres/postgres.git
synced 2025-07-05 07:21:24 +03:00
Implement SQL-standard LATERAL subqueries.
This patch implements the standard syntax of LATERAL attached to a sub-SELECT in FROM, and also allows LATERAL attached to a function in FROM, since set-returning function calls are expected to be one of the principal use-cases. The main change here is a rewrite of the mechanism for keeping track of which relations are visible for column references while the FROM clause is being scanned. The parser "namespace" lists are no longer lists of bare RTEs, but are lists of ParseNamespaceItem structs, which carry an RTE pointer as well as some visibility-controlling flags. Aside from supporting LATERAL correctly, this lets us get rid of the ancient hacks that required rechecking subqueries and JOIN/ON and function-in-FROM expressions for invalid references after they were initially parsed. Invalid column references are now always correctly detected on sight. In passing, remove assorted parser error checks that are now dead code by virtue of our having gotten rid of add_missing_from, as well as some comments that are obsolete for the same reason. (It was mainly add_missing_from that caused so much fudging here in the first place.) The planner support for this feature is very minimal, and will be improved in future patches. It works well enough for testing purposes, though. catversion bump forced due to new field in RangeTblEntry.
This commit is contained in:
@ -56,6 +56,7 @@ geqo_eval(PlannerInfo *root, Gene *tour, int num_gene)
|
||||
MemoryContext mycontext;
|
||||
MemoryContext oldcxt;
|
||||
RelOptInfo *joinrel;
|
||||
Path *best_path;
|
||||
Cost fitness;
|
||||
int savelength;
|
||||
struct HTAB *savehash;
|
||||
@ -99,6 +100,14 @@ geqo_eval(PlannerInfo *root, Gene *tour, int num_gene)
|
||||
|
||||
/* construct the best path for the given combination of relations */
|
||||
joinrel = gimme_tree(root, tour, num_gene);
|
||||
best_path = joinrel->cheapest_total_path;
|
||||
|
||||
/*
|
||||
* If no unparameterized path, use the cheapest parameterized path for
|
||||
* costing purposes. XXX revisit this after LATERAL dust settles
|
||||
*/
|
||||
if (!best_path)
|
||||
best_path = linitial(joinrel->cheapest_parameterized_paths);
|
||||
|
||||
/*
|
||||
* compute fitness
|
||||
@ -106,7 +115,7 @@ geqo_eval(PlannerInfo *root, Gene *tour, int num_gene)
|
||||
* XXX geqo does not currently support optimization for partial result
|
||||
* retrieval --- how to fix?
|
||||
*/
|
||||
fitness = joinrel->cheapest_total_path->total_cost;
|
||||
fitness = best_path->total_cost;
|
||||
|
||||
/*
|
||||
* Restore join_rel_list to its former state, and put back original
|
||||
|
@ -253,8 +253,9 @@ set_rel_size(PlannerInfo *root, RelOptInfo *rel,
|
||||
case RTE_SUBQUERY:
|
||||
|
||||
/*
|
||||
* Subqueries don't support parameterized paths, so just go
|
||||
* ahead and build their paths immediately.
|
||||
* Subqueries don't support making a choice between
|
||||
* parameterized and unparameterized paths, so just go ahead
|
||||
* and build their paths immediately.
|
||||
*/
|
||||
set_subquery_pathlist(root, rel, rti, rte);
|
||||
break;
|
||||
@ -698,6 +699,10 @@ set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
|
||||
if (IS_DUMMY_REL(childrel))
|
||||
continue;
|
||||
|
||||
/* XXX need to figure out what to do for LATERAL */
|
||||
if (childrel->cheapest_total_path == NULL)
|
||||
elog(ERROR, "LATERAL within an append relation is not supported yet");
|
||||
|
||||
/*
|
||||
* Child is live, so add its cheapest access path to the Append path
|
||||
* we are constructing for the parent.
|
||||
@ -906,6 +911,10 @@ generate_mergeappend_paths(PlannerInfo *root, RelOptInfo *rel,
|
||||
*/
|
||||
if (cheapest_startup == NULL || cheapest_total == NULL)
|
||||
{
|
||||
/* XXX need to figure out what to do for LATERAL */
|
||||
if (childrel->cheapest_total_path == NULL)
|
||||
elog(ERROR, "LATERAL within an append relation is not supported yet");
|
||||
|
||||
cheapest_startup = cheapest_total =
|
||||
childrel->cheapest_total_path;
|
||||
Assert(cheapest_total != NULL);
|
||||
@ -1012,8 +1021,13 @@ has_multiple_baserels(PlannerInfo *root)
|
||||
* set_subquery_pathlist
|
||||
* Build the (single) access path for a subquery RTE
|
||||
*
|
||||
* There's no need for a separate set_subquery_size phase, since we don't
|
||||
* support parameterized paths for subqueries.
|
||||
* We don't currently support generating parameterized paths for subqueries
|
||||
* by pushing join clauses down into them; it seems too expensive to re-plan
|
||||
* the subquery multiple times to consider different alternatives. So the
|
||||
* subquery will have exactly one path. (The path will be parameterized
|
||||
* if the subquery contains LATERAL references, otherwise not.) Since there's
|
||||
* no freedom of action here, there's no need for a separate set_subquery_size
|
||||
* phase: we just make the path right away.
|
||||
*/
|
||||
static void
|
||||
set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
|
||||
@ -1021,6 +1035,7 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
|
||||
{
|
||||
Query *parse = root->parse;
|
||||
Query *subquery = rte->subquery;
|
||||
Relids required_outer;
|
||||
bool *differentTypes;
|
||||
double tuple_fraction;
|
||||
PlannerInfo *subroot;
|
||||
@ -1033,6 +1048,20 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
|
||||
*/
|
||||
subquery = copyObject(subquery);
|
||||
|
||||
/*
|
||||
* If it's a LATERAL subquery, it might contain some Vars of the current
|
||||
* query level, requiring it to be treated as parameterized.
|
||||
*/
|
||||
if (rte->lateral)
|
||||
{
|
||||
required_outer = pull_varnos_of_level((Node *) subquery, 1);
|
||||
/* Enforce convention that empty required_outer is exactly NULL */
|
||||
if (bms_is_empty(required_outer))
|
||||
required_outer = NULL;
|
||||
}
|
||||
else
|
||||
required_outer = NULL;
|
||||
|
||||
/* We need a workspace for keeping track of set-op type coercions */
|
||||
differentTypes = (bool *)
|
||||
palloc0((list_length(subquery->targetList) + 1) * sizeof(bool));
|
||||
@ -1051,10 +1080,9 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
|
||||
* pseudoconstant clauses; better to have the gating node above the
|
||||
* subquery.
|
||||
*
|
||||
* Also, if the sub-query has "security_barrier" flag, it means the
|
||||
* Also, if the sub-query has the "security_barrier" flag, it means the
|
||||
* sub-query originated from a view that must enforce row-level security.
|
||||
* We must not push down quals in order to avoid information leaks, either
|
||||
* via side-effects or error output.
|
||||
* Then we must not push down quals that contain leaky functions.
|
||||
*
|
||||
* Non-pushed-down clauses will get evaluated as qpquals of the
|
||||
* SubqueryScan node.
|
||||
@ -1134,7 +1162,7 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
|
||||
pathkeys = convert_subquery_pathkeys(root, rel, subroot->query_pathkeys);
|
||||
|
||||
/* Generate appropriate path */
|
||||
add_path(rel, create_subqueryscan_path(root, rel, pathkeys, NULL));
|
||||
add_path(rel, create_subqueryscan_path(root, rel, pathkeys, required_outer));
|
||||
|
||||
/* Select cheapest path (pretty easy in this case...) */
|
||||
set_cheapest(rel);
|
||||
@ -1143,12 +1171,32 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
|
||||
/*
|
||||
* set_function_pathlist
|
||||
* Build the (single) access path for a function RTE
|
||||
*
|
||||
* As with subqueries, a function RTE's path might be parameterized due to
|
||||
* LATERAL references, but that's inherent in the function expression and
|
||||
* not a result of pushing down join quals.
|
||||
*/
|
||||
static void
|
||||
set_function_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
|
||||
{
|
||||
Relids required_outer;
|
||||
|
||||
/*
|
||||
* If it's a LATERAL function, it might contain some Vars of the current
|
||||
* query level, requiring it to be treated as parameterized.
|
||||
*/
|
||||
if (rte->lateral)
|
||||
{
|
||||
required_outer = pull_varnos_of_level(rte->funcexpr, 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_functionscan_path(root, rel));
|
||||
add_path(rel, create_functionscan_path(root, rel, required_outer));
|
||||
|
||||
/* Select cheapest path (pretty easy in this case...) */
|
||||
set_cheapest(rel);
|
||||
@ -1157,6 +1205,10 @@ 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.)
|
||||
*/
|
||||
static void
|
||||
set_values_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
|
||||
@ -1988,10 +2040,16 @@ debug_print_rel(PlannerInfo *root, RelOptInfo *rel)
|
||||
printf("\tpath list:\n");
|
||||
foreach(l, rel->pathlist)
|
||||
print_path(root, lfirst(l), 1);
|
||||
printf("\n\tcheapest startup path:\n");
|
||||
print_path(root, rel->cheapest_startup_path, 1);
|
||||
printf("\n\tcheapest total path:\n");
|
||||
print_path(root, rel->cheapest_total_path, 1);
|
||||
if (rel->cheapest_startup_path)
|
||||
{
|
||||
printf("\n\tcheapest startup path:\n");
|
||||
print_path(root, rel->cheapest_startup_path, 1);
|
||||
}
|
||||
if (rel->cheapest_total_path)
|
||||
{
|
||||
printf("\n\tcheapest total path:\n");
|
||||
print_path(root, rel->cheapest_total_path, 1);
|
||||
}
|
||||
printf("\n");
|
||||
fflush(stdout);
|
||||
}
|
||||
|
@ -989,12 +989,17 @@ cost_subqueryscan(Path *path, PlannerInfo *root,
|
||||
/*
|
||||
* cost_functionscan
|
||||
* Determines and returns the cost of scanning a function RTE.
|
||||
*
|
||||
* 'baserel' is the relation to be scanned
|
||||
* 'param_info' is the ParamPathInfo if this is a parameterized path, else NULL
|
||||
*/
|
||||
void
|
||||
cost_functionscan(Path *path, PlannerInfo *root, RelOptInfo *baserel)
|
||||
cost_functionscan(Path *path, PlannerInfo *root,
|
||||
RelOptInfo *baserel, ParamPathInfo *param_info)
|
||||
{
|
||||
Cost startup_cost = 0;
|
||||
Cost run_cost = 0;
|
||||
QualCost qpqual_cost;
|
||||
Cost cpu_per_tuple;
|
||||
RangeTblEntry *rte;
|
||||
QualCost exprcost;
|
||||
@ -1004,8 +1009,11 @@ cost_functionscan(Path *path, PlannerInfo *root, RelOptInfo *baserel)
|
||||
rte = planner_rt_fetch(baserel->relid, root);
|
||||
Assert(rte->rtekind == RTE_FUNCTION);
|
||||
|
||||
/* functionscans 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;
|
||||
|
||||
/*
|
||||
* Estimate costs of executing the function expression.
|
||||
@ -1025,8 +1033,10 @@ cost_functionscan(Path *path, PlannerInfo *root, RelOptInfo *baserel)
|
||||
startup_cost += exprcost.startup + exprcost.per_tuple;
|
||||
|
||||
/* 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;
|
||||
|
@ -491,12 +491,18 @@ sort_inner_and_outer(PlannerInfo *root,
|
||||
* explosion of mergejoin paths of dubious value. This interacts with
|
||||
* decisions elsewhere that also discriminate against mergejoins with
|
||||
* parameterized inputs; see comments in src/backend/optimizer/README.
|
||||
*
|
||||
* If unique-ification is requested, do it and then handle as a plain
|
||||
* inner join.
|
||||
*/
|
||||
outer_path = outerrel->cheapest_total_path;
|
||||
inner_path = innerrel->cheapest_total_path;
|
||||
|
||||
/* Punt if either rel has only parameterized paths */
|
||||
if (!outer_path || !inner_path)
|
||||
return;
|
||||
|
||||
/*
|
||||
* If unique-ification is requested, do it and then handle as a plain
|
||||
* inner join.
|
||||
*/
|
||||
if (jointype == JOIN_UNIQUE_OUTER)
|
||||
{
|
||||
outer_path = (Path *) create_unique_path(root, outerrel,
|
||||
@ -696,6 +702,10 @@ match_unsorted_outer(PlannerInfo *root,
|
||||
*/
|
||||
if (save_jointype == JOIN_UNIQUE_INNER)
|
||||
{
|
||||
/* XXX for the moment, don't crash on LATERAL --- rethink this */
|
||||
if (inner_cheapest_total == NULL)
|
||||
return;
|
||||
|
||||
inner_cheapest_total = (Path *)
|
||||
create_unique_path(root, innerrel, inner_cheapest_total, sjinfo);
|
||||
Assert(inner_cheapest_total);
|
||||
@ -707,7 +717,7 @@ match_unsorted_outer(PlannerInfo *root,
|
||||
* enable_material is off or the path in question materializes its
|
||||
* output anyway.
|
||||
*/
|
||||
if (enable_material &&
|
||||
if (enable_material && inner_cheapest_total != NULL &&
|
||||
!ExecMaterializesOutput(inner_cheapest_total->pathtype))
|
||||
matpath = (Path *)
|
||||
create_material_path(innerrel, inner_cheapest_total);
|
||||
@ -735,6 +745,8 @@ match_unsorted_outer(PlannerInfo *root,
|
||||
* If we need to unique-ify the outer path, it's pointless to consider
|
||||
* any but the cheapest outer. (XXX we don't consider parameterized
|
||||
* outers, nor inners, for unique-ified cases. Should we?)
|
||||
*
|
||||
* XXX does nothing for LATERAL, rethink
|
||||
*/
|
||||
if (save_jointype == JOIN_UNIQUE_OUTER)
|
||||
{
|
||||
@ -814,6 +826,10 @@ match_unsorted_outer(PlannerInfo *root,
|
||||
if (save_jointype == JOIN_UNIQUE_OUTER)
|
||||
continue;
|
||||
|
||||
/* Can't do anything else if inner has no unparameterized paths */
|
||||
if (!inner_cheapest_total)
|
||||
continue;
|
||||
|
||||
/* Look for useful mergeclauses (if any) */
|
||||
mergeclauses = find_mergeclauses_for_pathkeys(root,
|
||||
outerpath->pathkeys,
|
||||
@ -1092,6 +1108,12 @@ hash_inner_and_outer(PlannerInfo *root,
|
||||
Path *cheapest_total_outer = outerrel->cheapest_total_path;
|
||||
Path *cheapest_total_inner = innerrel->cheapest_total_path;
|
||||
|
||||
/* Punt if either rel has only parameterized paths */
|
||||
if (!cheapest_startup_outer ||
|
||||
!cheapest_total_outer ||
|
||||
!cheapest_total_inner)
|
||||
return;
|
||||
|
||||
/* Unique-ify if need be; we ignore parameterized possibilities */
|
||||
if (jointype == JOIN_UNIQUE_OUTER)
|
||||
{
|
||||
|
@ -84,6 +84,7 @@ static HashJoin *create_hashjoin_plan(PlannerInfo *root, HashPath *best_path,
|
||||
Plan *outer_plan, Plan *inner_plan);
|
||||
static Node *replace_nestloop_params(PlannerInfo *root, Node *expr);
|
||||
static Node *replace_nestloop_params_mutator(Node *node, PlannerInfo *root);
|
||||
static void identify_nestloop_extparams(PlannerInfo *root, Plan *subplan);
|
||||
static List *fix_indexqual_references(PlannerInfo *root, IndexPath *index_path);
|
||||
static List *fix_indexorderby_references(PlannerInfo *root, IndexPath *index_path);
|
||||
static Node *fix_indexqual_operand(Node *node, IndexOptInfo *index, int indexcol);
|
||||
@ -1640,6 +1641,7 @@ create_subqueryscan_plan(PlannerInfo *root, Path *best_path,
|
||||
{
|
||||
scan_clauses = (List *)
|
||||
replace_nestloop_params(root, (Node *) scan_clauses);
|
||||
identify_nestloop_extparams(root, best_path->parent->subplan);
|
||||
}
|
||||
|
||||
scan_plan = make_subqueryscan(tlist,
|
||||
@ -1664,11 +1666,13 @@ create_functionscan_plan(PlannerInfo *root, Path *best_path,
|
||||
FunctionScan *scan_plan;
|
||||
Index scan_relid = best_path->parent->relid;
|
||||
RangeTblEntry *rte;
|
||||
Node *funcexpr;
|
||||
|
||||
/* it should be a function base rel... */
|
||||
Assert(scan_relid > 0);
|
||||
rte = planner_rt_fetch(scan_relid, root);
|
||||
Assert(rte->rtekind == RTE_FUNCTION);
|
||||
funcexpr = rte->funcexpr;
|
||||
|
||||
/* Sort clauses into best execution order */
|
||||
scan_clauses = order_qual_clauses(root, scan_clauses);
|
||||
@ -1676,8 +1680,17 @@ create_functionscan_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 func expression itself could contain nestloop params, too */
|
||||
funcexpr = replace_nestloop_params(root, funcexpr);
|
||||
}
|
||||
|
||||
scan_plan = make_functionscan(tlist, scan_clauses, scan_relid,
|
||||
rte->funcexpr,
|
||||
funcexpr,
|
||||
rte->eref->colnames,
|
||||
rte->funccoltypes,
|
||||
rte->funccoltypmods,
|
||||
@ -2559,6 +2572,102 @@ replace_nestloop_params_mutator(Node *node, PlannerInfo *root)
|
||||
(void *) root);
|
||||
}
|
||||
|
||||
/*
|
||||
* identify_nestloop_extparams
|
||||
* Identify extParams of a parameterized subquery that need to be fed
|
||||
* from an outer nestloop.
|
||||
*
|
||||
* The subplan's references to the outer variables are already represented
|
||||
* as PARAM_EXEC Params, so we need not modify the subplan here. What we
|
||||
* do need to do is add entries to root->curOuterParams to signal the parent
|
||||
* nestloop plan node that it must provide these values.
|
||||
*/
|
||||
static void
|
||||
identify_nestloop_extparams(PlannerInfo *root, Plan *subplan)
|
||||
{
|
||||
Bitmapset *tmpset;
|
||||
int paramid;
|
||||
|
||||
/* Examine each extParam of the subquery's plan */
|
||||
tmpset = bms_copy(subplan->extParam);
|
||||
while ((paramid = bms_first_member(tmpset)) >= 0)
|
||||
{
|
||||
PlannerParamItem *pitem = list_nth(root->glob->paramlist, paramid);
|
||||
|
||||
/* Ignore anything coming from an upper query level */
|
||||
if (pitem->abslevel != root->query_level)
|
||||
continue;
|
||||
|
||||
if (IsA(pitem->item, Var))
|
||||
{
|
||||
Var *var = (Var *) pitem->item;
|
||||
NestLoopParam *nlp;
|
||||
ListCell *lc;
|
||||
|
||||
/* If not from a nestloop outer rel, nothing to do */
|
||||
if (!bms_is_member(var->varno, root->curOuterRels))
|
||||
continue;
|
||||
/* Is this param already listed in root->curOuterParams? */
|
||||
foreach(lc, root->curOuterParams)
|
||||
{
|
||||
nlp = (NestLoopParam *) lfirst(lc);
|
||||
if (nlp->paramno == paramid)
|
||||
{
|
||||
Assert(equal(var, nlp->paramval));
|
||||
/* Present, so nothing to do */
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (lc == NULL)
|
||||
{
|
||||
/* No, so add it */
|
||||
nlp = makeNode(NestLoopParam);
|
||||
nlp->paramno = paramid;
|
||||
nlp->paramval = copyObject(var);
|
||||
root->curOuterParams = lappend(root->curOuterParams, nlp);
|
||||
}
|
||||
}
|
||||
else if (IsA(pitem->item, PlaceHolderVar))
|
||||
{
|
||||
PlaceHolderVar *phv = (PlaceHolderVar *) pitem->item;
|
||||
NestLoopParam *nlp;
|
||||
ListCell *lc;
|
||||
|
||||
/*
|
||||
* If not from a nestloop outer rel, nothing to do. 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))
|
||||
continue;
|
||||
if (!bms_is_subset(find_placeholder_info(root, phv, false)->ph_eval_at,
|
||||
root->curOuterRels))
|
||||
continue;
|
||||
/* Is this param already listed in root->curOuterParams? */
|
||||
foreach(lc, root->curOuterParams)
|
||||
{
|
||||
nlp = (NestLoopParam *) lfirst(lc);
|
||||
if (nlp->paramno == paramid)
|
||||
{
|
||||
Assert(equal(phv, nlp->paramval));
|
||||
/* Present, so nothing to do */
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (lc == NULL)
|
||||
{
|
||||
/* No, so add it */
|
||||
nlp = makeNode(NestLoopParam);
|
||||
nlp->paramno = paramid;
|
||||
nlp->paramval = copyObject(phv);
|
||||
root->curOuterParams = lappend(root->curOuterParams, nlp);
|
||||
}
|
||||
}
|
||||
}
|
||||
bms_free(tmpset);
|
||||
}
|
||||
|
||||
/*
|
||||
* fix_indexqual_references
|
||||
* Adjust indexqual clauses to the form the executor's indexqual
|
||||
|
@ -204,6 +204,64 @@ add_vars_to_targetlist(PlannerInfo *root, List *vars,
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* extract_lateral_references
|
||||
* If the specified RTE is a LATERAL subquery, extract all its references
|
||||
* to Vars of the current query level, and make sure those Vars will be
|
||||
* available for evaluation of the RTE.
|
||||
*
|
||||
* XXX this is rather duplicative of processing that has to happen elsewhere.
|
||||
* Maybe it'd be a good idea to do this type of extraction further upstream
|
||||
* and save the results?
|
||||
*/
|
||||
static void
|
||||
extract_lateral_references(PlannerInfo *root, int rtindex)
|
||||
{
|
||||
RangeTblEntry *rte = root->simple_rte_array[rtindex];
|
||||
List *vars;
|
||||
List *newvars;
|
||||
Relids where_needed;
|
||||
ListCell *lc;
|
||||
|
||||
/* No cross-references are possible if it's not LATERAL */
|
||||
if (!rte->lateral)
|
||||
return;
|
||||
|
||||
/* Fetch the appropriate variables */
|
||||
if (rte->rtekind == RTE_SUBQUERY)
|
||||
vars = pull_vars_of_level((Node *) rte->subquery, 1);
|
||||
else if (rte->rtekind == RTE_FUNCTION)
|
||||
vars = pull_vars_of_level(rte->funcexpr, 0);
|
||||
else
|
||||
return;
|
||||
|
||||
/* Copy each Var and adjust it to match our level */
|
||||
newvars = NIL;
|
||||
foreach(lc, vars)
|
||||
{
|
||||
Var *var = (Var *) lfirst(lc);
|
||||
|
||||
var = copyObject(var);
|
||||
var->varlevelsup = 0;
|
||||
newvars = lappend(newvars, var);
|
||||
}
|
||||
|
||||
/*
|
||||
* We mark the Vars as being "needed" at the LATERAL RTE. This is a bit
|
||||
* of a cheat: a more formal approach would be to mark each one as needed
|
||||
* at the join of the LATERAL RTE with its source RTE. But it will work,
|
||||
* and it's much less tedious than computing a separate where_needed for
|
||||
* each Var.
|
||||
*/
|
||||
where_needed = bms_make_singleton(rtindex);
|
||||
|
||||
/* Push the Vars into their source relations' targetlists */
|
||||
add_vars_to_targetlist(root, newvars, where_needed, false);
|
||||
|
||||
list_free(newvars);
|
||||
list_free(vars);
|
||||
}
|
||||
|
||||
|
||||
/*****************************************************************************
|
||||
*
|
||||
@ -286,7 +344,9 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
|
||||
{
|
||||
int varno = ((RangeTblRef *) jtnode)->rtindex;
|
||||
|
||||
/* No quals to deal with, just return correct result */
|
||||
/* No quals to deal with, but do check for LATERAL subqueries */
|
||||
extract_lateral_references(root, varno);
|
||||
/* Result qualscope is just the one Relid */
|
||||
*qualscope = bms_make_singleton(varno);
|
||||
/* A single baserel does not create an inner join */
|
||||
*inner_join_rels = NULL;
|
||||
|
@ -1817,7 +1817,7 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
|
||||
*
|
||||
* Once grouping_planner() has applied a general tlist to the topmost
|
||||
* scan/join plan node, any tlist eval cost for added-on nodes should be
|
||||
* accounted for as we create those nodes. Presently, of the node types we
|
||||
* accounted for as we create those nodes. Presently, of the node types we
|
||||
* can add on later, only Agg, WindowAgg, and Group project new tlists (the
|
||||
* rest just copy their input tuples) --- so make_agg(), make_windowagg() and
|
||||
* make_group() are responsible for calling this function to account for their
|
||||
@ -3257,6 +3257,7 @@ plan_cluster_use_sort(Oid tableOid, Oid indexOid)
|
||||
rte->rtekind = RTE_RELATION;
|
||||
rte->relid = tableOid;
|
||||
rte->relkind = RELKIND_RELATION;
|
||||
rte->lateral = false;
|
||||
rte->inh = false;
|
||||
rte->inFromCl = true;
|
||||
query->rtable = list_make1(rte);
|
||||
|
@ -1231,6 +1231,7 @@ convert_ANY_sublink_to_join(PlannerInfo *root, SubLink *sublink,
|
||||
rte = addRangeTableEntryForSubquery(NULL,
|
||||
subselect,
|
||||
makeAlias("ANY_subquery", NIL),
|
||||
false,
|
||||
false);
|
||||
parse->rtable = lappend(parse->rtable, rte);
|
||||
rtindex = list_length(parse->rtable);
|
||||
|
@ -1175,6 +1175,13 @@ is_simple_subquery(Query *subquery)
|
||||
subquery->cteList)
|
||||
return false;
|
||||
|
||||
/*
|
||||
* Don't pull up a LATERAL subquery (hopefully, this is just a temporary
|
||||
* implementation restriction).
|
||||
*/
|
||||
if (contain_vars_of_level((Node *) subquery, 1))
|
||||
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
|
||||
|
@ -193,7 +193,7 @@ compare_path_costs_fuzzily(Path *path1, Path *path2, double fuzz_factor)
|
||||
* and cheapest_total. The cheapest_parameterized_paths list collects paths
|
||||
* that are cheapest-total for their parameterization (i.e., there is no
|
||||
* cheaper path with the same or weaker parameterization). This list always
|
||||
* includes the unparameterized cheapest-total path, too.
|
||||
* includes the unparameterized cheapest-total path, too, if there is one.
|
||||
*
|
||||
* This is normally called only after we've finished constructing the path
|
||||
* list for the rel node.
|
||||
@ -250,15 +250,18 @@ set_cheapest(RelOptInfo *parent_rel)
|
||||
cheapest_total_path = path;
|
||||
}
|
||||
|
||||
if (cheapest_total_path == NULL)
|
||||
if (cheapest_total_path == NULL && !have_parameterized_paths)
|
||||
elog(ERROR, "could not devise a query plan for the given query");
|
||||
|
||||
parent_rel->cheapest_startup_path = cheapest_startup_path;
|
||||
parent_rel->cheapest_total_path = cheapest_total_path;
|
||||
parent_rel->cheapest_unique_path = NULL; /* computed only if needed */
|
||||
|
||||
/* Seed the parameterized-paths list with the cheapest total */
|
||||
parent_rel->cheapest_parameterized_paths = list_make1(cheapest_total_path);
|
||||
/* Seed the parameterized-paths list with the cheapest total, if any */
|
||||
if (cheapest_total_path)
|
||||
parent_rel->cheapest_parameterized_paths = list_make1(cheapest_total_path);
|
||||
else
|
||||
parent_rel->cheapest_parameterized_paths = NIL;
|
||||
|
||||
/* And, if there are any parameterized paths, add them in one at a time */
|
||||
if (have_parameterized_paths)
|
||||
@ -1131,6 +1134,13 @@ create_unique_path(PlannerInfo *root, RelOptInfo *rel, Path *subpath,
|
||||
int numCols;
|
||||
ListCell *lc;
|
||||
|
||||
/* XXX temporary band-aid to not crash on LATERAL queries */
|
||||
if (subpath == NULL)
|
||||
{
|
||||
Assert(subpath == rel->cheapest_total_path);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Caller made a mistake if subpath isn't cheapest_total ... */
|
||||
Assert(subpath == rel->cheapest_total_path);
|
||||
Assert(subpath->parent == rel);
|
||||
@ -1657,16 +1667,18 @@ create_subqueryscan_path(PlannerInfo *root, RelOptInfo *rel,
|
||||
* returning the pathnode.
|
||||
*/
|
||||
Path *
|
||||
create_functionscan_path(PlannerInfo *root, RelOptInfo *rel)
|
||||
create_functionscan_path(PlannerInfo *root, RelOptInfo *rel,
|
||||
Relids required_outer)
|
||||
{
|
||||
Path *pathnode = makeNode(Path);
|
||||
|
||||
pathnode->pathtype = T_FunctionScan;
|
||||
pathnode->parent = rel;
|
||||
pathnode->param_info = NULL; /* never parameterized at present */
|
||||
pathnode->param_info = get_baserel_parampathinfo(root, rel,
|
||||
required_outer);
|
||||
pathnode->pathkeys = NIL; /* for now, assume unordered result */
|
||||
|
||||
cost_functionscan(pathnode, root, rel);
|
||||
cost_functionscan(pathnode, root, rel, pathnode->param_info);
|
||||
|
||||
return pathnode;
|
||||
}
|
||||
|
@ -42,16 +42,15 @@ typedef struct
|
||||
|
||||
typedef struct
|
||||
{
|
||||
int var_location;
|
||||
List *vars;
|
||||
int sublevels_up;
|
||||
} locate_var_of_level_context;
|
||||
} pull_vars_context;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
int var_location;
|
||||
int relid;
|
||||
int sublevels_up;
|
||||
} locate_var_of_relation_context;
|
||||
} locate_var_of_level_context;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
@ -77,12 +76,11 @@ typedef struct
|
||||
static bool pull_varnos_walker(Node *node,
|
||||
pull_varnos_context *context);
|
||||
static bool pull_varattnos_walker(Node *node, pull_varattnos_context *context);
|
||||
static bool pull_vars_walker(Node *node, pull_vars_context *context);
|
||||
static bool contain_var_clause_walker(Node *node, void *context);
|
||||
static bool contain_vars_of_level_walker(Node *node, int *sublevels_up);
|
||||
static bool locate_var_of_level_walker(Node *node,
|
||||
locate_var_of_level_context *context);
|
||||
static bool locate_var_of_relation_walker(Node *node,
|
||||
locate_var_of_relation_context *context);
|
||||
static bool find_minimum_var_level_walker(Node *node,
|
||||
find_minimum_var_level_context *context);
|
||||
static bool pull_var_clause_walker(Node *node,
|
||||
@ -122,6 +120,31 @@ pull_varnos(Node *node)
|
||||
return context.varnos;
|
||||
}
|
||||
|
||||
/*
|
||||
* pull_varnos_of_level
|
||||
* Create a set of all the distinct varnos present in a parsetree.
|
||||
* Only Vars of the specified level are considered.
|
||||
*/
|
||||
Relids
|
||||
pull_varnos_of_level(Node *node, int levelsup)
|
||||
{
|
||||
pull_varnos_context context;
|
||||
|
||||
context.varnos = NULL;
|
||||
context.sublevels_up = levelsup;
|
||||
|
||||
/*
|
||||
* Must be prepared to start with a Query or a bare expression tree; if
|
||||
* it's a Query, we don't want to increment sublevels_up.
|
||||
*/
|
||||
query_or_expression_tree_walker(node,
|
||||
pull_varnos_walker,
|
||||
(void *) &context,
|
||||
0);
|
||||
|
||||
return context.varnos;
|
||||
}
|
||||
|
||||
static bool
|
||||
pull_varnos_walker(Node *node, pull_varnos_context *context)
|
||||
{
|
||||
@ -230,6 +253,66 @@ pull_varattnos_walker(Node *node, pull_varattnos_context *context)
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* pull_vars_of_level
|
||||
* Create a list of all Vars referencing the specified query level
|
||||
* in the given parsetree.
|
||||
*
|
||||
* This is used on unplanned parsetrees, so we don't expect to see any
|
||||
* PlaceHolderVars.
|
||||
*
|
||||
* Caution: the Vars are not copied, only linked into the list.
|
||||
*/
|
||||
List *
|
||||
pull_vars_of_level(Node *node, int levelsup)
|
||||
{
|
||||
pull_vars_context context;
|
||||
|
||||
context.vars = NIL;
|
||||
context.sublevels_up = levelsup;
|
||||
|
||||
/*
|
||||
* Must be prepared to start with a Query or a bare expression tree; if
|
||||
* it's a Query, we don't want to increment sublevels_up.
|
||||
*/
|
||||
query_or_expression_tree_walker(node,
|
||||
pull_vars_walker,
|
||||
(void *) &context,
|
||||
0);
|
||||
|
||||
return context.vars;
|
||||
}
|
||||
|
||||
static bool
|
||||
pull_vars_walker(Node *node, pull_vars_context *context)
|
||||
{
|
||||
if (node == NULL)
|
||||
return false;
|
||||
if (IsA(node, Var))
|
||||
{
|
||||
Var *var = (Var *) node;
|
||||
|
||||
if (var->varlevelsup == context->sublevels_up)
|
||||
context->vars = lappend(context->vars, var);
|
||||
return false;
|
||||
}
|
||||
Assert(!IsA(node, PlaceHolderVar));
|
||||
if (IsA(node, Query))
|
||||
{
|
||||
/* Recurse into RTE subquery or not-yet-planned sublink subquery */
|
||||
bool result;
|
||||
|
||||
context->sublevels_up++;
|
||||
result = query_tree_walker((Query *) node, pull_vars_walker,
|
||||
(void *) context, 0);
|
||||
context->sublevels_up--;
|
||||
return result;
|
||||
}
|
||||
return expression_tree_walker(node, pull_vars_walker,
|
||||
(void *) context);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* contain_var_clause
|
||||
* Recursively scan a clause to discover whether it contains any Var nodes
|
||||
@ -405,76 +488,6 @@ locate_var_of_level_walker(Node *node,
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* locate_var_of_relation
|
||||
* Find the parse location of any Var of the specified relation.
|
||||
*
|
||||
* Returns -1 if no such Var is in the querytree, or if they all have
|
||||
* unknown parse location.
|
||||
*
|
||||
* Will recurse into sublinks. Also, may be invoked directly on a Query.
|
||||
*/
|
||||
int
|
||||
locate_var_of_relation(Node *node, int relid, int levelsup)
|
||||
{
|
||||
locate_var_of_relation_context context;
|
||||
|
||||
context.var_location = -1; /* in case we find nothing */
|
||||
context.relid = relid;
|
||||
context.sublevels_up = levelsup;
|
||||
|
||||
(void) query_or_expression_tree_walker(node,
|
||||
locate_var_of_relation_walker,
|
||||
(void *) &context,
|
||||
0);
|
||||
|
||||
return context.var_location;
|
||||
}
|
||||
|
||||
static bool
|
||||
locate_var_of_relation_walker(Node *node,
|
||||
locate_var_of_relation_context *context)
|
||||
{
|
||||
if (node == NULL)
|
||||
return false;
|
||||
if (IsA(node, Var))
|
||||
{
|
||||
Var *var = (Var *) node;
|
||||
|
||||
if (var->varno == context->relid &&
|
||||
var->varlevelsup == context->sublevels_up &&
|
||||
var->location >= 0)
|
||||
{
|
||||
context->var_location = var->location;
|
||||
return true; /* abort tree traversal and return true */
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (IsA(node, CurrentOfExpr))
|
||||
{
|
||||
/* since CurrentOfExpr doesn't carry location, nothing we can do */
|
||||
return false;
|
||||
}
|
||||
/* No extra code needed for PlaceHolderVar; just look in contained expr */
|
||||
if (IsA(node, Query))
|
||||
{
|
||||
/* Recurse into subselects */
|
||||
bool result;
|
||||
|
||||
context->sublevels_up++;
|
||||
result = query_tree_walker((Query *) node,
|
||||
locate_var_of_relation_walker,
|
||||
(void *) context,
|
||||
0);
|
||||
context->sublevels_up--;
|
||||
return result;
|
||||
}
|
||||
return expression_tree_walker(node,
|
||||
locate_var_of_relation_walker,
|
||||
(void *) context);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* find_minimum_var_level
|
||||
* Recursively scan a clause to find the lowest variable level it
|
||||
|
Reference in New Issue
Block a user