mirror of
https://github.com/postgres/postgres.git
synced 2025-07-08 11:42:09 +03:00
In the planner, replace an empty FROM clause with a dummy RTE.
The fact that "SELECT expression" has no base relations has long been a thorn in the side of the planner. It makes it hard to flatten a sub-query that looks like that, or is a trivial VALUES() item, because the planner generally uses relid sets to identify sub-relations, and such a sub-query would have an empty relid set if we flattened it. prepjointree.c contains some baroque logic that works around this in certain special cases --- but there is a much better answer. We can replace an empty FROM clause with a dummy RTE that acts like a table of one row and no columns, and then there are no such corner cases to worry about. Instead we need some logic to get rid of useless dummy RTEs, but that's simpler and covers more cases than what was there before. For really trivial cases, where the query is just "SELECT expression" and nothing else, there's a hazard that adding the extra RTE makes for a noticeable slowdown; even though it's not much processing, there's not that much for the planner to do overall. However testing says that the penalty is very small, close to the noise level. In more complex queries, this is able to find optimizations that we could not find before. The new RTE type is called RTE_RESULT, since the "scan" plan type it gives rise to is a Result node (the same plan we produced for a "SELECT expression" query before). To avoid confusion, rename the old ResultPath path type to GroupResultPath, reflecting that it's only used in degenerate grouping cases where we know the query produces just one grouped row. (It wouldn't work to unify the two cases, because there are different rules about where the associated quals live during query_planner.) Note: although this touches readfuncs.c, I don't think a catversion bump is required, because the added case can't occur in stored rules, only plans. Patch by me, reviewed by David Rowley and Mark Dilger Discussion: https://postgr.es/m/15944.1521127664@sss.pgh.pa.us
This commit is contained in:
@ -361,7 +361,16 @@ RelOptInfo - a relation or joined relations
|
||||
join clauses)
|
||||
|
||||
Path - every way to generate a RelOptInfo(sequential,index,joins)
|
||||
SeqScan - represents a sequential scan plan
|
||||
A plain Path node can represent several simple plans, per its pathtype:
|
||||
T_SeqScan - sequential scan
|
||||
T_SampleScan - tablesample scan
|
||||
T_FunctionScan - function-in-FROM scan
|
||||
T_TableFuncScan - table function scan
|
||||
T_ValuesScan - VALUES scan
|
||||
T_CteScan - CTE (WITH) scan
|
||||
T_NamedTuplestoreScan - ENR scan
|
||||
T_WorkTableScan - scan worktable of a recursive CTE
|
||||
T_Result - childless Result plan node (used for FROM-less SELECT)
|
||||
IndexPath - index scan
|
||||
BitmapHeapPath - top of a bitmapped index scan
|
||||
TidPath - scan by CTID
|
||||
@ -370,7 +379,7 @@ RelOptInfo - a relation or joined relations
|
||||
CustomPath - for custom scan providers
|
||||
AppendPath - append multiple subpaths together
|
||||
MergeAppendPath - merge multiple subpaths, preserving their common sort order
|
||||
ResultPath - a childless Result plan node (used for FROM-less SELECT)
|
||||
GroupResultPath - childless Result plan node (used for degenerate grouping)
|
||||
MaterialPath - a Material plan node
|
||||
UniquePath - remove duplicate rows (either by hashing or sorting)
|
||||
GatherPath - collect the results of parallel workers
|
||||
|
@ -117,6 +117,8 @@ static void set_cte_pathlist(PlannerInfo *root, RelOptInfo *rel,
|
||||
RangeTblEntry *rte);
|
||||
static void set_namedtuplestore_pathlist(PlannerInfo *root, RelOptInfo *rel,
|
||||
RangeTblEntry *rte);
|
||||
static void set_result_pathlist(PlannerInfo *root, RelOptInfo *rel,
|
||||
RangeTblEntry *rte);
|
||||
static void set_worktable_pathlist(PlannerInfo *root, RelOptInfo *rel,
|
||||
RangeTblEntry *rte);
|
||||
static RelOptInfo *make_rel_from_joinlist(PlannerInfo *root, List *joinlist);
|
||||
@ -437,8 +439,13 @@ set_rel_size(PlannerInfo *root, RelOptInfo *rel,
|
||||
set_cte_pathlist(root, rel, rte);
|
||||
break;
|
||||
case RTE_NAMEDTUPLESTORE:
|
||||
/* Might as well just build the path immediately */
|
||||
set_namedtuplestore_pathlist(root, rel, rte);
|
||||
break;
|
||||
case RTE_RESULT:
|
||||
/* Might as well just build the path immediately */
|
||||
set_result_pathlist(root, rel, rte);
|
||||
break;
|
||||
default:
|
||||
elog(ERROR, "unexpected rtekind: %d", (int) rel->rtekind);
|
||||
break;
|
||||
@ -510,6 +517,9 @@ set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
|
||||
case RTE_NAMEDTUPLESTORE:
|
||||
/* tuplestore reference --- fully handled during set_rel_size */
|
||||
break;
|
||||
case RTE_RESULT:
|
||||
/* simple Result --- fully handled during set_rel_size */
|
||||
break;
|
||||
default:
|
||||
elog(ERROR, "unexpected rtekind: %d", (int) rel->rtekind);
|
||||
break;
|
||||
@ -712,6 +722,10 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
|
||||
* infrastructure to support that.
|
||||
*/
|
||||
return;
|
||||
|
||||
case RTE_RESULT:
|
||||
/* RESULT RTEs, in themselves, are no problem. */
|
||||
break;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -2509,6 +2523,36 @@ set_namedtuplestore_pathlist(PlannerInfo *root, RelOptInfo *rel,
|
||||
set_cheapest(rel);
|
||||
}
|
||||
|
||||
/*
|
||||
* set_result_pathlist
|
||||
* Build the (single) access path for an RTE_RESULT RTE
|
||||
*
|
||||
* There's no need for a separate set_result_size phase, since we
|
||||
* don't support join-qual-parameterized paths for these RTEs.
|
||||
*/
|
||||
static void
|
||||
set_result_pathlist(PlannerInfo *root, RelOptInfo *rel,
|
||||
RangeTblEntry *rte)
|
||||
{
|
||||
Relids required_outer;
|
||||
|
||||
/* Mark rel with estimated output rows, width, etc */
|
||||
set_result_size_estimates(root, rel);
|
||||
|
||||
/*
|
||||
* We don't support pushing join clauses into the quals of a Result scan,
|
||||
* but it could still have required parameterization due to LATERAL refs
|
||||
* in its tlist.
|
||||
*/
|
||||
required_outer = rel->lateral_relids;
|
||||
|
||||
/* Generate appropriate path */
|
||||
add_path(rel, create_resultscan_path(root, rel, required_outer));
|
||||
|
||||
/* Select cheapest path (pretty easy in this case...) */
|
||||
set_cheapest(rel);
|
||||
}
|
||||
|
||||
/*
|
||||
* set_worktable_pathlist
|
||||
* Build the (single) access path for a self-reference CTE RTE
|
||||
@ -3677,9 +3721,6 @@ print_path(PlannerInfo *root, Path *path, int indent)
|
||||
case T_SampleScan:
|
||||
ptype = "SampleScan";
|
||||
break;
|
||||
case T_SubqueryScan:
|
||||
ptype = "SubqueryScan";
|
||||
break;
|
||||
case T_FunctionScan:
|
||||
ptype = "FunctionScan";
|
||||
break;
|
||||
@ -3692,6 +3733,12 @@ print_path(PlannerInfo *root, Path *path, int indent)
|
||||
case T_CteScan:
|
||||
ptype = "CteScan";
|
||||
break;
|
||||
case T_NamedTuplestoreScan:
|
||||
ptype = "NamedTuplestoreScan";
|
||||
break;
|
||||
case T_Result:
|
||||
ptype = "Result";
|
||||
break;
|
||||
case T_WorkTableScan:
|
||||
ptype = "WorkTableScan";
|
||||
break;
|
||||
@ -3716,7 +3763,7 @@ print_path(PlannerInfo *root, Path *path, int indent)
|
||||
ptype = "TidScan";
|
||||
break;
|
||||
case T_SubqueryScanPath:
|
||||
ptype = "SubqueryScanScan";
|
||||
ptype = "SubqueryScan";
|
||||
break;
|
||||
case T_ForeignPath:
|
||||
ptype = "ForeignScan";
|
||||
@ -3742,8 +3789,8 @@ print_path(PlannerInfo *root, Path *path, int indent)
|
||||
case T_MergeAppendPath:
|
||||
ptype = "MergeAppend";
|
||||
break;
|
||||
case T_ResultPath:
|
||||
ptype = "Result";
|
||||
case T_GroupResultPath:
|
||||
ptype = "GroupResult";
|
||||
break;
|
||||
case T_MaterialPath:
|
||||
ptype = "Material";
|
||||
|
@ -1570,6 +1570,40 @@ cost_namedtuplestorescan(Path *path, PlannerInfo *root,
|
||||
path->total_cost = startup_cost + run_cost;
|
||||
}
|
||||
|
||||
/*
|
||||
* cost_resultscan
|
||||
* Determines and returns the cost of scanning an RTE_RESULT relation.
|
||||
*/
|
||||
void
|
||||
cost_resultscan(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 RTE_RESULT base relations */
|
||||
Assert(baserel->relid > 0);
|
||||
Assert(baserel->rtekind == RTE_RESULT);
|
||||
|
||||
/* Mark the path with the correct row estimate */
|
||||
if (param_info)
|
||||
path->rows = param_info->ppi_rows;
|
||||
else
|
||||
path->rows = baserel->rows;
|
||||
|
||||
/* We charge qual cost plus cpu_tuple_cost */
|
||||
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;
|
||||
path->total_cost = startup_cost + run_cost;
|
||||
}
|
||||
|
||||
/*
|
||||
* cost_recursive_union
|
||||
* Determines and returns the cost of performing a recursive union,
|
||||
@ -5044,6 +5078,29 @@ set_namedtuplestore_size_estimates(PlannerInfo *root, RelOptInfo *rel)
|
||||
set_baserel_size_estimates(root, rel);
|
||||
}
|
||||
|
||||
/*
|
||||
* set_result_size_estimates
|
||||
* Set the size estimates for an RTE_RESULT base relation
|
||||
*
|
||||
* The rel's targetlist and restrictinfo list must have been constructed
|
||||
* already.
|
||||
*
|
||||
* We set the same fields as set_baserel_size_estimates.
|
||||
*/
|
||||
void
|
||||
set_result_size_estimates(PlannerInfo *root, RelOptInfo *rel)
|
||||
{
|
||||
/* Should only be applied to RTE_RESULT base relations */
|
||||
Assert(rel->relid > 0);
|
||||
Assert(planner_rt_fetch(rel->relid, root)->rtekind == RTE_RESULT);
|
||||
|
||||
/* RTE_RESULT always generates a single row, natively */
|
||||
rel->tuples = 1;
|
||||
|
||||
/* Now estimate number of output rows, etc */
|
||||
set_baserel_size_estimates(root, rel);
|
||||
}
|
||||
|
||||
/*
|
||||
* set_foreign_size_estimates
|
||||
* Set the size estimates for a base relation that is a foreign table.
|
||||
|
@ -84,7 +84,8 @@ static Plan *create_gating_plan(PlannerInfo *root, Path *path, Plan *plan,
|
||||
static Plan *create_join_plan(PlannerInfo *root, JoinPath *best_path);
|
||||
static Plan *create_append_plan(PlannerInfo *root, AppendPath *best_path);
|
||||
static Plan *create_merge_append_plan(PlannerInfo *root, MergeAppendPath *best_path);
|
||||
static Result *create_result_plan(PlannerInfo *root, ResultPath *best_path);
|
||||
static Result *create_group_result_plan(PlannerInfo *root,
|
||||
GroupResultPath *best_path);
|
||||
static ProjectSet *create_project_set_plan(PlannerInfo *root, ProjectSetPath *best_path);
|
||||
static Material *create_material_plan(PlannerInfo *root, MaterialPath *best_path,
|
||||
int flags);
|
||||
@ -138,6 +139,8 @@ static CteScan *create_ctescan_plan(PlannerInfo *root, Path *best_path,
|
||||
List *tlist, List *scan_clauses);
|
||||
static NamedTuplestoreScan *create_namedtuplestorescan_plan(PlannerInfo *root,
|
||||
Path *best_path, List *tlist, List *scan_clauses);
|
||||
static Result *create_resultscan_plan(PlannerInfo *root, Path *best_path,
|
||||
List *tlist, List *scan_clauses);
|
||||
static WorkTableScan *create_worktablescan_plan(PlannerInfo *root, Path *best_path,
|
||||
List *tlist, List *scan_clauses);
|
||||
static ForeignScan *create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path,
|
||||
@ -403,11 +406,16 @@ create_plan_recurse(PlannerInfo *root, Path *best_path, int flags)
|
||||
plan = (Plan *) create_minmaxagg_plan(root,
|
||||
(MinMaxAggPath *) best_path);
|
||||
}
|
||||
else if (IsA(best_path, GroupResultPath))
|
||||
{
|
||||
plan = (Plan *) create_group_result_plan(root,
|
||||
(GroupResultPath *) best_path);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert(IsA(best_path, ResultPath));
|
||||
plan = (Plan *) create_result_plan(root,
|
||||
(ResultPath *) best_path);
|
||||
/* Simple RTE_RESULT base relation */
|
||||
Assert(IsA(best_path, Path));
|
||||
plan = create_scan_plan(root, best_path, flags);
|
||||
}
|
||||
break;
|
||||
case T_ProjectSet:
|
||||
@ -691,6 +699,13 @@ create_scan_plan(PlannerInfo *root, Path *best_path, int flags)
|
||||
scan_clauses);
|
||||
break;
|
||||
|
||||
case T_Result:
|
||||
plan = (Plan *) create_resultscan_plan(root,
|
||||
best_path,
|
||||
tlist,
|
||||
scan_clauses);
|
||||
break;
|
||||
|
||||
case T_WorkTableScan:
|
||||
plan = (Plan *) create_worktablescan_plan(root,
|
||||
best_path,
|
||||
@ -922,9 +937,26 @@ create_gating_plan(PlannerInfo *root, Path *path, Plan *plan,
|
||||
List *gating_quals)
|
||||
{
|
||||
Plan *gplan;
|
||||
Plan *splan;
|
||||
|
||||
Assert(gating_quals);
|
||||
|
||||
/*
|
||||
* We might have a trivial Result plan already. Stacking one Result atop
|
||||
* another is silly, so if that applies, just discard the input plan.
|
||||
* (We're assuming its targetlist is uninteresting; it should be either
|
||||
* the same as the result of build_path_tlist, or a simplified version.)
|
||||
*/
|
||||
splan = plan;
|
||||
if (IsA(plan, Result))
|
||||
{
|
||||
Result *rplan = (Result *) plan;
|
||||
|
||||
if (rplan->plan.lefttree == NULL &&
|
||||
rplan->resconstantqual == NULL)
|
||||
splan = NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Since we need a Result node anyway, always return the path's requested
|
||||
* tlist; that's never a wrong choice, even if the parent node didn't ask
|
||||
@ -932,7 +964,7 @@ create_gating_plan(PlannerInfo *root, Path *path, Plan *plan,
|
||||
*/
|
||||
gplan = (Plan *) make_result(build_path_tlist(root, path),
|
||||
(Node *) gating_quals,
|
||||
plan);
|
||||
splan);
|
||||
|
||||
/*
|
||||
* Notice that we don't change cost or size estimates when doing gating.
|
||||
@ -1254,15 +1286,14 @@ create_merge_append_plan(PlannerInfo *root, MergeAppendPath *best_path)
|
||||
}
|
||||
|
||||
/*
|
||||
* create_result_plan
|
||||
* create_group_result_plan
|
||||
* Create a Result plan for 'best_path'.
|
||||
* This is only used for degenerate cases, such as a query with an empty
|
||||
* jointree.
|
||||
* This is only used for degenerate grouping cases.
|
||||
*
|
||||
* Returns a Plan node.
|
||||
*/
|
||||
static Result *
|
||||
create_result_plan(PlannerInfo *root, ResultPath *best_path)
|
||||
create_group_result_plan(PlannerInfo *root, GroupResultPath *best_path)
|
||||
{
|
||||
Result *plan;
|
||||
List *tlist;
|
||||
@ -3477,6 +3508,44 @@ create_namedtuplestorescan_plan(PlannerInfo *root, Path *best_path,
|
||||
return scan_plan;
|
||||
}
|
||||
|
||||
/*
|
||||
* create_resultscan_plan
|
||||
* Returns a Result plan for the RTE_RESULT base relation scanned by
|
||||
* 'best_path' with restriction clauses 'scan_clauses' and targetlist
|
||||
* 'tlist'.
|
||||
*/
|
||||
static Result *
|
||||
create_resultscan_plan(PlannerInfo *root, Path *best_path,
|
||||
List *tlist, List *scan_clauses)
|
||||
{
|
||||
Result *scan_plan;
|
||||
Index scan_relid = best_path->parent->relid;
|
||||
RangeTblEntry *rte PG_USED_FOR_ASSERTS_ONLY;
|
||||
|
||||
Assert(scan_relid > 0);
|
||||
rte = planner_rt_fetch(scan_relid, root);
|
||||
Assert(rte->rtekind == RTE_RESULT);
|
||||
|
||||
/* Sort clauses into best execution order */
|
||||
scan_clauses = order_qual_clauses(root, scan_clauses);
|
||||
|
||||
/* 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);
|
||||
}
|
||||
|
||||
scan_plan = make_result(tlist, (Node *) scan_clauses, NULL);
|
||||
|
||||
copy_generic_path_info(&scan_plan->plan, best_path);
|
||||
|
||||
return scan_plan;
|
||||
}
|
||||
|
||||
/*
|
||||
* create_worktablescan_plan
|
||||
* Returns a worktablescan plan for the base relation scanned by 'best_path'
|
||||
|
@ -827,7 +827,7 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
|
||||
* all below it, so we should report inner_join_rels = qualscope. If
|
||||
* there was exactly one element, we should (and already did) report
|
||||
* whatever its inner_join_rels were. If there were no elements (is
|
||||
* that possible?) the initialization before the loop fixed it.
|
||||
* that still possible?) the initialization before the loop fixed it.
|
||||
*/
|
||||
if (list_length(f->fromlist) > 1)
|
||||
*inner_join_rels = *qualscope;
|
||||
|
@ -60,44 +60,6 @@ query_planner(PlannerInfo *root, List *tlist,
|
||||
List *joinlist;
|
||||
RelOptInfo *final_rel;
|
||||
|
||||
/*
|
||||
* If the query has an empty join tree, then it's something easy like
|
||||
* "SELECT 2+2;" or "INSERT ... VALUES()". Fall through quickly.
|
||||
*/
|
||||
if (parse->jointree->fromlist == NIL)
|
||||
{
|
||||
/* We need a dummy joinrel to describe the empty set of baserels */
|
||||
final_rel = build_empty_join_rel(root);
|
||||
|
||||
/*
|
||||
* If query allows parallelism in general, check whether the quals are
|
||||
* parallel-restricted. (We need not check final_rel->reltarget
|
||||
* because it's empty at this point. Anything parallel-restricted in
|
||||
* the query tlist will be dealt with later.)
|
||||
*/
|
||||
if (root->glob->parallelModeOK)
|
||||
final_rel->consider_parallel =
|
||||
is_parallel_safe(root, parse->jointree->quals);
|
||||
|
||||
/* The only path for it is a trivial Result path */
|
||||
add_path(final_rel, (Path *)
|
||||
create_result_path(root, final_rel,
|
||||
final_rel->reltarget,
|
||||
(List *) parse->jointree->quals));
|
||||
|
||||
/* Select cheapest path (pretty easy in this case...) */
|
||||
set_cheapest(final_rel);
|
||||
|
||||
/*
|
||||
* We still are required to call qp_callback, in case it's something
|
||||
* like "SELECT 2+2 ORDER BY 1".
|
||||
*/
|
||||
root->canon_pathkeys = NIL;
|
||||
(*qp_callback) (root, qp_extra);
|
||||
|
||||
return final_rel;
|
||||
}
|
||||
|
||||
/*
|
||||
* Init planner lists to empty.
|
||||
*
|
||||
@ -124,6 +86,71 @@ query_planner(PlannerInfo *root, List *tlist,
|
||||
*/
|
||||
setup_simple_rel_arrays(root);
|
||||
|
||||
/*
|
||||
* In the trivial case where the jointree is a single RTE_RESULT relation,
|
||||
* bypass all the rest of this function and just make a RelOptInfo and its
|
||||
* one access path. This is worth optimizing because it applies for
|
||||
* common cases like "SELECT expression" and "INSERT ... VALUES()".
|
||||
*/
|
||||
Assert(parse->jointree->fromlist != NIL);
|
||||
if (list_length(parse->jointree->fromlist) == 1)
|
||||
{
|
||||
Node *jtnode = (Node *) linitial(parse->jointree->fromlist);
|
||||
|
||||
if (IsA(jtnode, RangeTblRef))
|
||||
{
|
||||
int varno = ((RangeTblRef *) jtnode)->rtindex;
|
||||
RangeTblEntry *rte = root->simple_rte_array[varno];
|
||||
|
||||
Assert(rte != NULL);
|
||||
if (rte->rtekind == RTE_RESULT)
|
||||
{
|
||||
/* Make the RelOptInfo for it directly */
|
||||
final_rel = build_simple_rel(root, varno, NULL);
|
||||
|
||||
/*
|
||||
* If query allows parallelism in general, check whether the
|
||||
* quals are parallel-restricted. (We need not check
|
||||
* final_rel->reltarget because it's empty at this point.
|
||||
* Anything parallel-restricted in the query tlist will be
|
||||
* dealt with later.) This is normally pretty silly, because
|
||||
* a Result-only plan would never be interesting to
|
||||
* parallelize. However, if force_parallel_mode is on, then
|
||||
* we want to execute the Result in a parallel worker if
|
||||
* possible, so we must do this.
|
||||
*/
|
||||
if (root->glob->parallelModeOK &&
|
||||
force_parallel_mode != FORCE_PARALLEL_OFF)
|
||||
final_rel->consider_parallel =
|
||||
is_parallel_safe(root, parse->jointree->quals);
|
||||
|
||||
/*
|
||||
* The only path for it is a trivial Result path. We cheat a
|
||||
* bit here by using a GroupResultPath, because that way we
|
||||
* can just jam the quals into it without preprocessing them.
|
||||
* (But, if you hold your head at the right angle, a FROM-less
|
||||
* SELECT is a kind of degenerate-grouping case, so it's not
|
||||
* that much of a cheat.)
|
||||
*/
|
||||
add_path(final_rel, (Path *)
|
||||
create_group_result_path(root, final_rel,
|
||||
final_rel->reltarget,
|
||||
(List *) parse->jointree->quals));
|
||||
|
||||
/* Select cheapest path (pretty easy in this case...) */
|
||||
set_cheapest(final_rel);
|
||||
|
||||
/*
|
||||
* We still are required to call qp_callback, in case it's
|
||||
* something like "SELECT 2+2 ORDER BY 1".
|
||||
*/
|
||||
(*qp_callback) (root, qp_extra);
|
||||
|
||||
return final_rel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Populate append_rel_array with each AppendRelInfo to allow direct
|
||||
* lookups by child relid.
|
||||
|
@ -611,6 +611,7 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
|
||||
List *newWithCheckOptions;
|
||||
List *newHaving;
|
||||
bool hasOuterJoins;
|
||||
bool hasResultRTEs;
|
||||
RelOptInfo *final_rel;
|
||||
ListCell *l;
|
||||
|
||||
@ -651,6 +652,12 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
|
||||
if (parse->cteList)
|
||||
SS_process_ctes(root);
|
||||
|
||||
/*
|
||||
* If the FROM clause is empty, replace it with a dummy RTE_RESULT RTE, so
|
||||
* that we don't need so many special cases to deal with that situation.
|
||||
*/
|
||||
replace_empty_jointree(parse);
|
||||
|
||||
/*
|
||||
* Look for ANY and EXISTS SubLinks in WHERE and JOIN/ON clauses, and try
|
||||
* to transform them into joins. Note that this step does not descend
|
||||
@ -684,14 +691,16 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
|
||||
|
||||
/*
|
||||
* Detect whether any rangetable entries are RTE_JOIN kind; if not, we can
|
||||
* avoid the expense of doing flatten_join_alias_vars(). Also check for
|
||||
* outer joins --- if none, we can skip reduce_outer_joins(). And check
|
||||
* for LATERAL RTEs, too. This must be done after we have done
|
||||
* pull_up_subqueries(), of course.
|
||||
* avoid the expense of doing flatten_join_alias_vars(). Likewise check
|
||||
* whether any are RTE_RESULT kind; if not, we can skip
|
||||
* remove_useless_result_rtes(). Also check for outer joins --- if none,
|
||||
* we can skip reduce_outer_joins(). And check for LATERAL RTEs, too.
|
||||
* This must be done after we have done pull_up_subqueries(), of course.
|
||||
*/
|
||||
root->hasJoinRTEs = false;
|
||||
root->hasLateralRTEs = false;
|
||||
hasOuterJoins = false;
|
||||
hasResultRTEs = false;
|
||||
foreach(l, parse->rtable)
|
||||
{
|
||||
RangeTblEntry *rte = lfirst_node(RangeTblEntry, l);
|
||||
@ -702,6 +711,10 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
|
||||
if (IS_OUTER_JOIN(rte->jointype))
|
||||
hasOuterJoins = true;
|
||||
}
|
||||
else if (rte->rtekind == RTE_RESULT)
|
||||
{
|
||||
hasResultRTEs = true;
|
||||
}
|
||||
if (rte->lateral)
|
||||
root->hasLateralRTEs = true;
|
||||
}
|
||||
@ -717,10 +730,10 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
|
||||
/*
|
||||
* Expand any rangetable entries that are inheritance sets into "append
|
||||
* relations". This can add entries to the rangetable, but they must be
|
||||
* plain base relations not joins, so it's OK (and marginally more
|
||||
* efficient) to do it after checking for join RTEs. We must do it after
|
||||
* pulling up subqueries, else we'd fail to handle inherited tables in
|
||||
* subqueries.
|
||||
* plain RTE_RELATION entries, so it's OK (and marginally more efficient)
|
||||
* to do it after checking for joins and other special RTEs. We must do
|
||||
* this after pulling up subqueries, else we'd fail to handle inherited
|
||||
* tables in subqueries.
|
||||
*/
|
||||
expand_inherited_tables(root);
|
||||
|
||||
@ -967,6 +980,14 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
|
||||
if (hasOuterJoins)
|
||||
reduce_outer_joins(root);
|
||||
|
||||
/*
|
||||
* If we have any RTE_RESULT relations, see if they can be deleted from
|
||||
* the jointree. This step is most effectively done after we've done
|
||||
* expression preprocessing and outer join reduction.
|
||||
*/
|
||||
if (hasResultRTEs)
|
||||
remove_useless_result_rtes(root);
|
||||
|
||||
/*
|
||||
* Do the main planning. If we have an inherited target relation, that
|
||||
* needs special processing, else go straight to grouping_planner.
|
||||
@ -3894,9 +3915,9 @@ create_degenerate_grouping_paths(PlannerInfo *root, RelOptInfo *input_rel,
|
||||
while (--nrows >= 0)
|
||||
{
|
||||
path = (Path *)
|
||||
create_result_path(root, grouped_rel,
|
||||
grouped_rel->reltarget,
|
||||
(List *) parse->havingQual);
|
||||
create_group_result_path(root, grouped_rel,
|
||||
grouped_rel->reltarget,
|
||||
(List *) parse->havingQual);
|
||||
paths = lappend(paths, path);
|
||||
}
|
||||
path = (Path *)
|
||||
@ -3914,9 +3935,9 @@ create_degenerate_grouping_paths(PlannerInfo *root, RelOptInfo *input_rel,
|
||||
{
|
||||
/* No grouping sets, or just one, so one output row */
|
||||
path = (Path *)
|
||||
create_result_path(root, grouped_rel,
|
||||
grouped_rel->reltarget,
|
||||
(List *) parse->havingQual);
|
||||
create_group_result_path(root, grouped_rel,
|
||||
grouped_rel->reltarget,
|
||||
(List *) parse->havingQual);
|
||||
}
|
||||
|
||||
add_path(grouped_rel, path);
|
||||
|
@ -1114,12 +1114,6 @@ convert_EXISTS_sublink_to_join(PlannerInfo *root, SubLink *sublink,
|
||||
if (!simplify_EXISTS_query(root, subselect))
|
||||
return NULL;
|
||||
|
||||
/*
|
||||
* The subquery must have a nonempty jointree, else we won't have a join.
|
||||
*/
|
||||
if (subselect->jointree->fromlist == NIL)
|
||||
return NULL;
|
||||
|
||||
/*
|
||||
* Separate out the WHERE clause. (We could theoretically also remove
|
||||
* top-level plain JOIN/ON clauses, but it's probably not worth the
|
||||
@ -1148,6 +1142,11 @@ convert_EXISTS_sublink_to_join(PlannerInfo *root, SubLink *sublink,
|
||||
if (contain_volatile_functions(whereClause))
|
||||
return NULL;
|
||||
|
||||
/*
|
||||
* The subquery must have a nonempty jointree, but we can make it so.
|
||||
*/
|
||||
replace_empty_jointree(subselect);
|
||||
|
||||
/*
|
||||
* Prepare to pull up the sub-select into top range table.
|
||||
*
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1707,7 +1707,7 @@ contain_leaked_vars_walker(Node *node, void *context)
|
||||
* find_nonnullable_vars() is that the tested conditions really are different:
|
||||
* a clause like "t1.v1 IS NOT NULL OR t1.v2 IS NOT NULL" does not prove
|
||||
* that either v1 or v2 can't be NULL, but it does prove that the t1 row
|
||||
* as a whole can't be all-NULL.
|
||||
* as a whole can't be all-NULL. Also, the behavior for PHVs is different.
|
||||
*
|
||||
* top_level is true while scanning top-level AND/OR structure; here, showing
|
||||
* the result is either FALSE or NULL is good enough. top_level is false when
|
||||
@ -1893,7 +1893,24 @@ find_nonnullable_rels_walker(Node *node, bool top_level)
|
||||
{
|
||||
PlaceHolderVar *phv = (PlaceHolderVar *) node;
|
||||
|
||||
/*
|
||||
* If the contained expression forces any rels non-nullable, so does
|
||||
* the PHV.
|
||||
*/
|
||||
result = find_nonnullable_rels_walker((Node *) phv->phexpr, top_level);
|
||||
|
||||
/*
|
||||
* If the PHV's syntactic scope is exactly one rel, it will be forced
|
||||
* to be evaluated at that rel, and so it will behave like a Var of
|
||||
* that rel: if the rel's entire output goes to null, so will the PHV.
|
||||
* (If the syntactic scope is a join, we know that the PHV will go to
|
||||
* null if the whole join does; but that is AND semantics while we
|
||||
* need OR semantics for find_nonnullable_rels' result, so we can't do
|
||||
* anything with the knowledge.)
|
||||
*/
|
||||
if (phv->phlevelsup == 0 &&
|
||||
bms_membership(phv->phrels) == BMS_SINGLETON)
|
||||
result = bms_add_members(result, phv->phrels);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
@ -1430,17 +1430,17 @@ create_merge_append_path(PlannerInfo *root,
|
||||
}
|
||||
|
||||
/*
|
||||
* create_result_path
|
||||
* create_group_result_path
|
||||
* Creates a path representing a Result-and-nothing-else plan.
|
||||
*
|
||||
* This is only used for degenerate cases, such as a query with an empty
|
||||
* jointree.
|
||||
* This is only used for degenerate grouping cases, in which we know we
|
||||
* need to produce one result row, possibly filtered by a HAVING qual.
|
||||
*/
|
||||
ResultPath *
|
||||
create_result_path(PlannerInfo *root, RelOptInfo *rel,
|
||||
PathTarget *target, List *resconstantqual)
|
||||
GroupResultPath *
|
||||
create_group_result_path(PlannerInfo *root, RelOptInfo *rel,
|
||||
PathTarget *target, List *havingqual)
|
||||
{
|
||||
ResultPath *pathnode = makeNode(ResultPath);
|
||||
GroupResultPath *pathnode = makeNode(GroupResultPath);
|
||||
|
||||
pathnode->path.pathtype = T_Result;
|
||||
pathnode->path.parent = rel;
|
||||
@ -1450,9 +1450,13 @@ create_result_path(PlannerInfo *root, RelOptInfo *rel,
|
||||
pathnode->path.parallel_safe = rel->consider_parallel;
|
||||
pathnode->path.parallel_workers = 0;
|
||||
pathnode->path.pathkeys = NIL;
|
||||
pathnode->quals = resconstantqual;
|
||||
pathnode->quals = havingqual;
|
||||
|
||||
/* Hardly worth defining a cost_result() function ... just do it */
|
||||
/*
|
||||
* We can't quite use cost_resultscan() because the quals we want to
|
||||
* account for are not baserestrict quals of the rel. Might as well just
|
||||
* hack it here.
|
||||
*/
|
||||
pathnode->path.rows = 1;
|
||||
pathnode->path.startup_cost = target->cost.startup;
|
||||
pathnode->path.total_cost = target->cost.startup +
|
||||
@ -1462,12 +1466,12 @@ create_result_path(PlannerInfo *root, RelOptInfo *rel,
|
||||
* Add cost of qual, if any --- but we ignore its selectivity, since our
|
||||
* rowcount estimate should be 1 no matter what the qual is.
|
||||
*/
|
||||
if (resconstantqual)
|
||||
if (havingqual)
|
||||
{
|
||||
QualCost qual_cost;
|
||||
|
||||
cost_qual_eval(&qual_cost, resconstantqual, root);
|
||||
/* resconstantqual is evaluated once at startup */
|
||||
cost_qual_eval(&qual_cost, havingqual, root);
|
||||
/* havingqual is evaluated once at startup */
|
||||
pathnode->path.startup_cost += qual_cost.startup + qual_cost.per_tuple;
|
||||
pathnode->path.total_cost += qual_cost.startup + qual_cost.per_tuple;
|
||||
}
|
||||
@ -2020,6 +2024,32 @@ create_namedtuplestorescan_path(PlannerInfo *root, RelOptInfo *rel,
|
||||
return pathnode;
|
||||
}
|
||||
|
||||
/*
|
||||
* create_resultscan_path
|
||||
* Creates a path corresponding to a scan of an RTE_RESULT relation,
|
||||
* returning the pathnode.
|
||||
*/
|
||||
Path *
|
||||
create_resultscan_path(PlannerInfo *root, RelOptInfo *rel,
|
||||
Relids required_outer)
|
||||
{
|
||||
Path *pathnode = makeNode(Path);
|
||||
|
||||
pathnode->pathtype = T_Result;
|
||||
pathnode->parent = rel;
|
||||
pathnode->pathtarget = rel->reltarget;
|
||||
pathnode->param_info = get_baserel_parampathinfo(root, rel,
|
||||
required_outer);
|
||||
pathnode->parallel_aware = false;
|
||||
pathnode->parallel_safe = rel->consider_parallel;
|
||||
pathnode->parallel_workers = 0;
|
||||
pathnode->pathkeys = NIL; /* result is always unordered */
|
||||
|
||||
cost_resultscan(pathnode, root, rel, pathnode->param_info);
|
||||
|
||||
return pathnode;
|
||||
}
|
||||
|
||||
/*
|
||||
* create_worktablescan_path
|
||||
* Creates a path corresponding to a scan of a self-reference CTE,
|
||||
@ -3560,6 +3590,11 @@ reparameterize_path(PlannerInfo *root, Path *path,
|
||||
spath->path.pathkeys,
|
||||
required_outer);
|
||||
}
|
||||
case T_Result:
|
||||
/* Supported only for RTE_RESULT scan paths */
|
||||
if (IsA(path, Path))
|
||||
return create_resultscan_path(root, rel, required_outer);
|
||||
break;
|
||||
case T_Append:
|
||||
{
|
||||
AppendPath *apath = (AppendPath *) path;
|
||||
|
@ -1628,6 +1628,7 @@ build_physical_tlist(PlannerInfo *root, RelOptInfo *rel)
|
||||
case RTE_VALUES:
|
||||
case RTE_CTE:
|
||||
case RTE_NAMEDTUPLESTORE:
|
||||
case RTE_RESULT:
|
||||
/* Not all of these can have dropped cols, but share code anyway */
|
||||
expandRTE(rte, varno, 0, -1, true /* include dropped */ ,
|
||||
NULL, &colvars);
|
||||
|
@ -194,7 +194,7 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent)
|
||||
rel->baserestrict_min_security = UINT_MAX;
|
||||
rel->joininfo = NIL;
|
||||
rel->has_eclass_joins = false;
|
||||
rel->consider_partitionwise_join = false; /* might get changed later */
|
||||
rel->consider_partitionwise_join = false; /* might get changed later */
|
||||
rel->part_scheme = NULL;
|
||||
rel->nparts = 0;
|
||||
rel->boundinfo = NULL;
|
||||
@ -247,6 +247,13 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent)
|
||||
rel->attr_widths = (int32 *)
|
||||
palloc0((rel->max_attr - rel->min_attr + 1) * sizeof(int32));
|
||||
break;
|
||||
case RTE_RESULT:
|
||||
/* RTE_RESULT has no columns, nor could it have whole-row Var */
|
||||
rel->min_attr = 0;
|
||||
rel->max_attr = -1;
|
||||
rel->attr_needed = NULL;
|
||||
rel->attr_widths = NULL;
|
||||
break;
|
||||
default:
|
||||
elog(ERROR, "unrecognized RTE kind: %d",
|
||||
(int) rte->rtekind);
|
||||
@ -609,7 +616,7 @@ build_join_rel(PlannerInfo *root,
|
||||
joinrel->baserestrict_min_security = UINT_MAX;
|
||||
joinrel->joininfo = NIL;
|
||||
joinrel->has_eclass_joins = false;
|
||||
joinrel->consider_partitionwise_join = false; /* might get changed later */
|
||||
joinrel->consider_partitionwise_join = false; /* might get changed later */
|
||||
joinrel->top_parent_relids = NULL;
|
||||
joinrel->part_scheme = NULL;
|
||||
joinrel->nparts = 0;
|
||||
@ -784,7 +791,7 @@ build_child_join_rel(PlannerInfo *root, RelOptInfo *outer_rel,
|
||||
joinrel->baserestrictcost.per_tuple = 0;
|
||||
joinrel->joininfo = NIL;
|
||||
joinrel->has_eclass_joins = false;
|
||||
joinrel->consider_partitionwise_join = false; /* might get changed later */
|
||||
joinrel->consider_partitionwise_join = false; /* might get changed later */
|
||||
joinrel->top_parent_relids = NULL;
|
||||
joinrel->part_scheme = NULL;
|
||||
joinrel->nparts = 0;
|
||||
@ -1108,36 +1115,6 @@ subbuild_joinrel_joinlist(RelOptInfo *joinrel,
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* build_empty_join_rel
|
||||
* Build a dummy join relation describing an empty set of base rels.
|
||||
*
|
||||
* This is used for queries with empty FROM clauses, such as "SELECT 2+2" or
|
||||
* "INSERT INTO foo VALUES(...)". We don't try very hard to make the empty
|
||||
* joinrel completely valid, since no real planning will be done with it ---
|
||||
* we just need it to carry a simple Result path out of query_planner().
|
||||
*/
|
||||
RelOptInfo *
|
||||
build_empty_join_rel(PlannerInfo *root)
|
||||
{
|
||||
RelOptInfo *joinrel;
|
||||
|
||||
/* The dummy join relation should be the only one ... */
|
||||
Assert(root->join_rel_list == NIL);
|
||||
|
||||
joinrel = makeNode(RelOptInfo);
|
||||
joinrel->reloptkind = RELOPT_JOINREL;
|
||||
joinrel->relids = NULL; /* empty set */
|
||||
joinrel->rows = 1; /* we produce one row for such cases */
|
||||
joinrel->rtekind = RTE_JOIN;
|
||||
joinrel->reltarget = create_empty_pathtarget();
|
||||
|
||||
root->join_rel_list = lappend(root->join_rel_list, joinrel);
|
||||
|
||||
return joinrel;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* fetch_upper_rel
|
||||
* Build a RelOptInfo describing some post-scan/join query processing,
|
||||
|
Reference in New Issue
Block a user