mirror of
https://github.com/postgres/postgres.git
synced 2025-07-14 08:21:07 +03:00
Make the upper part of the planner work by generating and comparing Paths.
I've been saying we needed to do this for more than five years, and here it finally is. This patch removes the ever-growing tangle of spaghetti logic that grouping_planner() used to use to try to identify the best plan for post-scan/join query steps. Now, there is (nearly) independent consideration of each execution step, and entirely separate construction of Paths to represent each of the possible ways to do that step. We choose the best Path or set of Paths using the same add_path() logic that's been used inside query_planner() for years. In addition, this patch removes the old restriction that subquery_planner() could return only a single Plan. It now returns a RelOptInfo containing a set of Paths, just as query_planner() does, and the parent query level can use each of those Paths as the basis of a SubqueryScanPath at its level. This allows finding some optimizations that we missed before, wherein a subquery was capable of returning presorted data and thereby avoiding a sort in the parent level, making the overall cost cheaper even though delivering sorted output was not the cheapest plan for the subquery in isolation. (A couple of regression test outputs change in consequence of that. However, there is very little change in visible planner behavior overall, because the point of this patch is not to get immediate planning benefits but to create the infrastructure for future improvements.) There is a great deal left to do here. This patch unblocks a lot of planner work that was basically impractical in the old code structure, such as allowing FDWs to implement remote aggregation, or rewriting plan_set_operations() to allow consideration of multiple implementation orders for set operations. (The latter will likely require a full rewrite of plan_set_operations(); what I've done here is only to fix it to return Paths not Plans.) I have also left unfinished some localized refactoring in createplan.c and planner.c, because it was not necessary to get this patch to a working state. Thanks to Robert Haas, David Rowley, and Amit Kapila for review.
This commit is contained in:
@ -20,7 +20,7 @@ Paths and Join Pairs
|
||||
During the planning/optimizing process, we build "Path" trees representing
|
||||
the different ways of doing a query. We select the cheapest Path that
|
||||
generates the desired relation and turn it into a Plan to pass to the
|
||||
executor. (There is pretty much a one-to-one correspondence between the
|
||||
executor. (There is pretty nearly a one-to-one correspondence between the
|
||||
Path and Plan trees, but Path nodes omit info that won't be needed during
|
||||
planning, and include info needed for planning that won't be needed by the
|
||||
executor.)
|
||||
@ -43,10 +43,8 @@ base rels of the query.
|
||||
|
||||
Possible Paths for a primitive table relation include plain old sequential
|
||||
scan, plus index scans for any indexes that exist on the table, plus bitmap
|
||||
index scans using one or more indexes. A subquery base relation just has
|
||||
one Path, a "SubqueryScan" path (which links to the subplan that was built
|
||||
by a recursive invocation of the planner). Likewise a function-RTE base
|
||||
relation has only one possible Path.
|
||||
index scans using one or more indexes. Specialized RTE types, such as
|
||||
function RTEs, may have only one possible Path.
|
||||
|
||||
Joins always occur using two RelOptInfos. One is outer, the other inner.
|
||||
Outers drive lookups of values in the inner. In a nested loop, lookups of
|
||||
@ -59,9 +57,10 @@ hashjoin, the inner is scanned first and all its rows are entered in a
|
||||
hashtable, then the outer is scanned and for each row we lookup the join
|
||||
key in the hashtable.
|
||||
|
||||
A Path for a join relation is actually a tree structure, with the top
|
||||
Path node representing the join method. It has left and right subpaths
|
||||
that represent the scan or join methods used for the two input relations.
|
||||
A Path for a join relation is actually a tree structure, with the topmost
|
||||
Path node representing the last-applied join method. It has left and right
|
||||
subpaths that represent the scan or join methods used for the two input
|
||||
relations.
|
||||
|
||||
|
||||
Join Tree Construction
|
||||
@ -292,8 +291,7 @@ Optimizer Functions
|
||||
The primary entry point is planner().
|
||||
|
||||
planner()
|
||||
set up for recursive handling of subqueries
|
||||
do final cleanup after planning
|
||||
set up for recursive handling of subqueries
|
||||
-subquery_planner()
|
||||
pull up sublinks and subqueries from rangetable, if possible
|
||||
canonicalize qual
|
||||
@ -326,14 +324,15 @@ planner()
|
||||
Back at standard_join_search(), apply set_cheapest() to extract the
|
||||
cheapest path for each newly constructed joinrel.
|
||||
Loop back if this wasn't the top join level.
|
||||
Back at grouping_planner:
|
||||
convert Path tree returned by query_planner into a Plan tree
|
||||
do grouping(GROUP)
|
||||
do aggregates
|
||||
do window functions
|
||||
make unique(DISTINCT)
|
||||
make sort(ORDER BY)
|
||||
make limit(LIMIT/OFFSET)
|
||||
Back at grouping_planner:
|
||||
do grouping (GROUP BY) and aggregation
|
||||
do window functions
|
||||
make unique (DISTINCT)
|
||||
do sorting (ORDER BY)
|
||||
do limit (LIMIT/OFFSET)
|
||||
Back at planner():
|
||||
convert finished Path tree into a Plan tree
|
||||
do final cleanup after planning
|
||||
|
||||
|
||||
Optimizer Data Structures
|
||||
@ -355,12 +354,28 @@ RelOptInfo - a relation or joined relations
|
||||
IndexPath - index scan
|
||||
BitmapHeapPath - top of a bitmapped index scan
|
||||
TidPath - scan by CTID
|
||||
SubqueryScanPath - scan a subquery-in-FROM
|
||||
ForeignPath - scan a foreign table
|
||||
CustomPath - for custom scan providers
|
||||
AppendPath - append multiple subpaths together
|
||||
MergeAppendPath - merge multiple subpaths, preserving their common sort order
|
||||
ResultPath - a Result plan node (used for FROM-less SELECT)
|
||||
ResultPath - a childless Result plan node (used for FROM-less SELECT)
|
||||
MaterialPath - a Material plan node
|
||||
UniquePath - remove duplicate rows
|
||||
UniquePath - remove duplicate rows (either by hashing or sorting)
|
||||
GatherPath - collect the results of parallel workers
|
||||
ProjectionPath - a Result plan node with child (used for projection)
|
||||
SortPath - a Sort plan node applied to some sub-path
|
||||
GroupPath - a Group plan node applied to some sub-path
|
||||
UpperUniquePath - a Unique plan node applied to some sub-path
|
||||
AggPath - an Agg plan node applied to some sub-path
|
||||
GroupingSetsPath - an Agg plan node used to implement GROUPING SETS
|
||||
MinMaxAggPath - a Result plan node with subplans performing MIN/MAX
|
||||
WindowAggPath - a WindowAgg plan node applied to some sub-path
|
||||
SetOpPath - a SetOp plan node applied to some sub-path
|
||||
RecursiveUnionPath - a RecursiveUnion plan node applied to two sub-paths
|
||||
LockRowsPath - a LockRows plan node applied to some sub-path
|
||||
ModifyTablePath - a ModifyTable plan node applied to some sub-path(s)
|
||||
LimitPath - a Limit plan node applied to some sub-path
|
||||
NestPath - nested-loop joins
|
||||
MergePath - merge joins
|
||||
HashPath - hash joins
|
||||
@ -851,6 +866,59 @@ lateral reference. (Perhaps now that that stuff works, we could relax the
|
||||
pullup restriction?)
|
||||
|
||||
|
||||
Post scan/join planning
|
||||
-----------------------
|
||||
|
||||
So far we have discussed only scan/join planning, that is, implementation
|
||||
of the FROM and WHERE clauses of a SQL query. But the planner must also
|
||||
determine how to deal with GROUP BY, aggregation, and other higher-level
|
||||
features of queries; and in many cases there are multiple ways to do these
|
||||
steps and thus opportunities for optimization choices. These steps, like
|
||||
scan/join planning, are handled by constructing Paths representing the
|
||||
different ways to do a step, then choosing the cheapest Path.
|
||||
|
||||
Since all Paths require a RelOptInfo as "parent", we create RelOptInfos
|
||||
representing the outputs of these upper-level processing steps. These
|
||||
RelOptInfos are mostly dummy, but their pathlist lists hold all the Paths
|
||||
considered useful for each step. Currently, we may create these types of
|
||||
additional RelOptInfos during upper-level planning:
|
||||
|
||||
UPPERREL_SETOP result of UNION/INTERSECT/EXCEPT, if any
|
||||
UPPERREL_GROUP_AGG result of grouping/aggregation, if any
|
||||
UPPERREL_WINDOW result of window functions, if any
|
||||
UPPERREL_DISTINCT result of "SELECT DISTINCT", if any
|
||||
UPPERREL_ORDERED result of ORDER BY, if any
|
||||
UPPERREL_FINAL result of any remaining top-level actions
|
||||
|
||||
UPPERREL_FINAL is used to represent any final processing steps, currently
|
||||
LockRows (SELECT FOR UPDATE), LIMIT/OFFSET, and ModifyTable. There is no
|
||||
flexibility about the order in which these steps are done, and thus no need
|
||||
to subdivide this stage more finely.
|
||||
|
||||
These "upper relations" are identified by the UPPERREL enum values shown
|
||||
above, plus a relids set, which allows there to be more than one upperrel
|
||||
of the same kind. We use NULL for the relids if there's no need for more
|
||||
than one upperrel of the same kind. Currently, in fact, the relids set
|
||||
is vestigial because it's always NULL, but that's expected to change in
|
||||
future. For example, in planning set operations, we might need the relids
|
||||
to denote which subset of the leaf SELECTs has been combined in a
|
||||
particular group of Paths that are competing with each other.
|
||||
|
||||
The result of subquery_planner() is always returned as a set of Paths
|
||||
stored in the UPPERREL_FINAL rel with NULL relids. The other types of
|
||||
upperrels are created only if needed for the particular query.
|
||||
|
||||
The upper-relation infrastructure is designed so that things will work
|
||||
properly if a particular upper relation is created and Paths are added
|
||||
to it sooner than would normally happen. This allows, for example,
|
||||
for an FDW's GetForeignPaths function to insert a Path representing
|
||||
remote aggregation into the UPPERREL_GROUP_AGG upperrel, if it notices
|
||||
that the query represents an aggregation that could be done entirely on
|
||||
the foreign server. That Path will then compete with Paths representing
|
||||
local aggregation on a regular scan of the foreign table, once the core
|
||||
planner reaches the point of considering aggregation.
|
||||
|
||||
|
||||
Parallel Query and Partial Paths
|
||||
--------------------------------
|
||||
|
||||
|
@ -37,6 +37,7 @@
|
||||
#include "optimizer/planner.h"
|
||||
#include "optimizer/prep.h"
|
||||
#include "optimizer/restrictinfo.h"
|
||||
#include "optimizer/tlist.h"
|
||||
#include "optimizer/var.h"
|
||||
#include "parser/parse_clause.h"
|
||||
#include "parser/parsetree.h"
|
||||
@ -97,7 +98,6 @@ static Path *get_cheapest_parameterized_child_path(PlannerInfo *root,
|
||||
RelOptInfo *rel,
|
||||
Relids required_outer);
|
||||
static List *accumulate_append_subpath(List *subpaths, Path *path);
|
||||
static void set_dummy_rel_pathlist(RelOptInfo *rel);
|
||||
static void set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
|
||||
Index rti, RangeTblEntry *rte);
|
||||
static void set_function_pathlist(PlannerInfo *root, RelOptInfo *rel,
|
||||
@ -1507,8 +1507,10 @@ accumulate_append_subpath(List *subpaths, Path *path)
|
||||
*
|
||||
* Rather than inventing a special "dummy" path type, we represent this as an
|
||||
* AppendPath with no members (see also IS_DUMMY_PATH/IS_DUMMY_REL macros).
|
||||
*
|
||||
* This is exported because inheritance_planner() has need for it.
|
||||
*/
|
||||
static void
|
||||
void
|
||||
set_dummy_rel_pathlist(RelOptInfo *rel)
|
||||
{
|
||||
/* Set dummy size estimates --- we leave attr_widths[] as zeroes */
|
||||
@ -1554,15 +1556,15 @@ has_multiple_baserels(PlannerInfo *root)
|
||||
|
||||
/*
|
||||
* set_subquery_pathlist
|
||||
* Build the (single) access path for a subquery RTE
|
||||
* Generate SubqueryScan access paths for a subquery RTE
|
||||
*
|
||||
* 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.
|
||||
* the subquery multiple times to consider different alternatives.
|
||||
* (XXX that could stand to be reconsidered, now that we use Paths.)
|
||||
* So the paths made here will be parameterized if the subquery contains
|
||||
* LATERAL references, otherwise not. As long as that's true, there's no need
|
||||
* for a separate set_subquery_size phase: just make the paths right away.
|
||||
*/
|
||||
static void
|
||||
set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
|
||||
@ -1573,8 +1575,8 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
|
||||
Relids required_outer;
|
||||
pushdown_safety_info safetyInfo;
|
||||
double tuple_fraction;
|
||||
PlannerInfo *subroot;
|
||||
List *pathkeys;
|
||||
RelOptInfo *sub_final_rel;
|
||||
ListCell *lc;
|
||||
|
||||
/*
|
||||
* Must copy the Query so that planning doesn't mess up the RTE contents
|
||||
@ -1685,12 +1687,10 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
|
||||
/* plan_params should not be in use in current query level */
|
||||
Assert(root->plan_params == NIL);
|
||||
|
||||
/* Generate the plan for the subquery */
|
||||
rel->subplan = subquery_planner(root->glob, subquery,
|
||||
/* Generate a subroot and Paths for the subquery */
|
||||
rel->subroot = subquery_planner(root->glob, subquery,
|
||||
root,
|
||||
false, tuple_fraction,
|
||||
&subroot);
|
||||
rel->subroot = subroot;
|
||||
false, tuple_fraction);
|
||||
|
||||
/* Isolate the params needed by this specific subplan */
|
||||
rel->subplan_params = root->plan_params;
|
||||
@ -1698,23 +1698,44 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
|
||||
|
||||
/*
|
||||
* It's possible that constraint exclusion proved the subquery empty. If
|
||||
* so, it's convenient to turn it back into a dummy path so that we will
|
||||
* so, it's desirable to produce an unadorned dummy path so that we will
|
||||
* recognize appropriate optimizations at this query level.
|
||||
*/
|
||||
if (is_dummy_plan(rel->subplan))
|
||||
sub_final_rel = fetch_upper_rel(rel->subroot, UPPERREL_FINAL, NULL);
|
||||
|
||||
if (IS_DUMMY_REL(sub_final_rel))
|
||||
{
|
||||
set_dummy_rel_pathlist(rel);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Mark rel with estimated output rows, width, etc */
|
||||
/*
|
||||
* Mark rel with estimated output rows, width, etc. Note that we have to
|
||||
* do this before generating outer-query paths, else cost_subqueryscan is
|
||||
* not happy.
|
||||
*/
|
||||
set_subquery_size_estimates(root, rel);
|
||||
|
||||
/* Convert subquery pathkeys to outer representation */
|
||||
pathkeys = convert_subquery_pathkeys(root, rel, subroot->query_pathkeys);
|
||||
/*
|
||||
* For each Path that subquery_planner produced, make a SubqueryScanPath
|
||||
* in the outer query.
|
||||
*/
|
||||
foreach(lc, sub_final_rel->pathlist)
|
||||
{
|
||||
Path *subpath = (Path *) lfirst(lc);
|
||||
List *pathkeys;
|
||||
|
||||
/* Generate appropriate path */
|
||||
add_path(rel, create_subqueryscan_path(root, rel, pathkeys, required_outer));
|
||||
/* Convert subpath's pathkeys to outer representation */
|
||||
pathkeys = convert_subquery_pathkeys(root,
|
||||
rel,
|
||||
subpath->pathkeys,
|
||||
make_tlist_from_pathtarget(subpath->pathtarget));
|
||||
|
||||
/* Generate outer path using this subpath */
|
||||
add_path(rel, (Path *)
|
||||
create_subqueryscan_path(root, rel, subpath,
|
||||
pathkeys, required_outer));
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@ -1858,7 +1879,7 @@ set_cte_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
|
||||
cteplan = (Plan *) list_nth(root->glob->subplans, plan_id - 1);
|
||||
|
||||
/* Mark rel with estimated output rows, width, etc */
|
||||
set_cte_size_estimates(root, rel, cteplan);
|
||||
set_cte_size_estimates(root, rel, cteplan->plan_rows);
|
||||
|
||||
/*
|
||||
* We don't support pushing join clauses into the quals of a CTE scan, but
|
||||
@ -1881,13 +1902,13 @@ set_cte_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
|
||||
static void
|
||||
set_worktable_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
|
||||
{
|
||||
Plan *cteplan;
|
||||
Path *ctepath;
|
||||
PlannerInfo *cteroot;
|
||||
Index levelsup;
|
||||
Relids required_outer;
|
||||
|
||||
/*
|
||||
* We need to find the non-recursive term's plan, which is in the plan
|
||||
* We need to find the non-recursive term's path, which is in the plan
|
||||
* level that's processing the recursive UNION, which is one level *below*
|
||||
* where the CTE comes from.
|
||||
*/
|
||||
@ -1902,12 +1923,12 @@ set_worktable_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
|
||||
if (!cteroot) /* shouldn't happen */
|
||||
elog(ERROR, "bad levelsup for CTE \"%s\"", rte->ctename);
|
||||
}
|
||||
cteplan = cteroot->non_recursive_plan;
|
||||
if (!cteplan) /* shouldn't happen */
|
||||
elog(ERROR, "could not find plan for CTE \"%s\"", rte->ctename);
|
||||
ctepath = cteroot->non_recursive_path;
|
||||
if (!ctepath) /* shouldn't happen */
|
||||
elog(ERROR, "could not find path for CTE \"%s\"", rte->ctename);
|
||||
|
||||
/* Mark rel with estimated output rows, width, etc */
|
||||
set_cte_size_estimates(root, rel, cteplan);
|
||||
set_cte_size_estimates(root, rel, ctepath->rows);
|
||||
|
||||
/*
|
||||
* We don't support pushing join clauses into the quals of a worktable
|
||||
@ -2859,6 +2880,9 @@ print_path(PlannerInfo *root, Path *path, int indent)
|
||||
case T_TidPath:
|
||||
ptype = "TidScan";
|
||||
break;
|
||||
case T_SubqueryScanPath:
|
||||
ptype = "SubqueryScanScan";
|
||||
break;
|
||||
case T_ForeignPath:
|
||||
ptype = "ForeignScan";
|
||||
break;
|
||||
@ -2883,6 +2907,55 @@ print_path(PlannerInfo *root, Path *path, int indent)
|
||||
ptype = "Gather";
|
||||
subpath = ((GatherPath *) path)->subpath;
|
||||
break;
|
||||
case T_ProjectionPath:
|
||||
ptype = "Projection";
|
||||
subpath = ((ProjectionPath *) path)->subpath;
|
||||
break;
|
||||
case T_SortPath:
|
||||
ptype = "Sort";
|
||||
subpath = ((SortPath *) path)->subpath;
|
||||
break;
|
||||
case T_GroupPath:
|
||||
ptype = "Group";
|
||||
subpath = ((GroupPath *) path)->subpath;
|
||||
break;
|
||||
case T_UpperUniquePath:
|
||||
ptype = "UpperUnique";
|
||||
subpath = ((UpperUniquePath *) path)->subpath;
|
||||
break;
|
||||
case T_AggPath:
|
||||
ptype = "Agg";
|
||||
subpath = ((AggPath *) path)->subpath;
|
||||
break;
|
||||
case T_GroupingSetsPath:
|
||||
ptype = "GroupingSets";
|
||||
subpath = ((GroupingSetsPath *) path)->subpath;
|
||||
break;
|
||||
case T_MinMaxAggPath:
|
||||
ptype = "MinMaxAgg";
|
||||
break;
|
||||
case T_WindowAggPath:
|
||||
ptype = "WindowAgg";
|
||||
subpath = ((WindowAggPath *) path)->subpath;
|
||||
break;
|
||||
case T_SetOpPath:
|
||||
ptype = "SetOp";
|
||||
subpath = ((SetOpPath *) path)->subpath;
|
||||
break;
|
||||
case T_RecursiveUnionPath:
|
||||
ptype = "RecursiveUnion";
|
||||
break;
|
||||
case T_LockRowsPath:
|
||||
ptype = "LockRows";
|
||||
subpath = ((LockRowsPath *) path)->subpath;
|
||||
break;
|
||||
case T_ModifyTablePath:
|
||||
ptype = "ModifyTable";
|
||||
break;
|
||||
case T_LimitPath:
|
||||
ptype = "Limit";
|
||||
subpath = ((LimitPath *) path)->subpath;
|
||||
break;
|
||||
case T_NestPath:
|
||||
ptype = "NestLoop";
|
||||
join = true;
|
||||
|
@ -1169,7 +1169,7 @@ cost_tidscan(Path *path, PlannerInfo *root,
|
||||
* 'param_info' is the ParamPathInfo if this is a parameterized path, else NULL
|
||||
*/
|
||||
void
|
||||
cost_subqueryscan(Path *path, PlannerInfo *root,
|
||||
cost_subqueryscan(SubqueryScanPath *path, PlannerInfo *root,
|
||||
RelOptInfo *baserel, ParamPathInfo *param_info)
|
||||
{
|
||||
Cost startup_cost;
|
||||
@ -1183,17 +1183,18 @@ cost_subqueryscan(Path *path, PlannerInfo *root,
|
||||
|
||||
/* Mark the path with the correct row estimate */
|
||||
if (param_info)
|
||||
path->rows = param_info->ppi_rows;
|
||||
path->path.rows = param_info->ppi_rows;
|
||||
else
|
||||
path->rows = baserel->rows;
|
||||
path->path.rows = baserel->rows;
|
||||
|
||||
/*
|
||||
* Cost of path is cost of evaluating the subplan, plus cost of evaluating
|
||||
* any restriction clauses that will be attached to the SubqueryScan node,
|
||||
* plus cpu_tuple_cost to account for selection and projection overhead.
|
||||
* any restriction clauses and tlist that will be attached to the
|
||||
* SubqueryScan node, plus cpu_tuple_cost to account for selection and
|
||||
* projection overhead.
|
||||
*/
|
||||
path->startup_cost = baserel->subplan->startup_cost;
|
||||
path->total_cost = baserel->subplan->total_cost;
|
||||
path->path.startup_cost = path->subpath->startup_cost;
|
||||
path->path.total_cost = path->subpath->total_cost;
|
||||
|
||||
get_restriction_qual_cost(root, baserel, param_info, &qpqual_cost);
|
||||
|
||||
@ -1202,11 +1203,11 @@ cost_subqueryscan(Path *path, PlannerInfo *root,
|
||||
run_cost = cpu_per_tuple * baserel->tuples;
|
||||
|
||||
/* tlist eval costs are paid per output row, not per tuple scanned */
|
||||
startup_cost += path->pathtarget->cost.startup;
|
||||
run_cost += path->pathtarget->cost.per_tuple * path->rows;
|
||||
startup_cost += path->path.pathtarget->cost.startup;
|
||||
run_cost += path->path.pathtarget->cost.per_tuple * path->path.rows;
|
||||
|
||||
path->startup_cost += startup_cost;
|
||||
path->total_cost += startup_cost + run_cost;
|
||||
path->path.startup_cost += startup_cost;
|
||||
path->path.total_cost += startup_cost + run_cost;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -1369,14 +1370,10 @@ cost_ctescan(Path *path, PlannerInfo *root,
|
||||
* Determines and returns the cost of performing a recursive union,
|
||||
* and also the estimated output size.
|
||||
*
|
||||
* We are given Plans for the nonrecursive and recursive terms.
|
||||
*
|
||||
* Note that the arguments and output are Plans, not Paths as in most of
|
||||
* the rest of this module. That's because we don't bother setting up a
|
||||
* Path representation for recursive union --- we have only one way to do it.
|
||||
* We are given Paths for the nonrecursive and recursive terms.
|
||||
*/
|
||||
void
|
||||
cost_recursive_union(Plan *runion, Plan *nrterm, Plan *rterm)
|
||||
cost_recursive_union(Path *runion, Path *nrterm, Path *rterm)
|
||||
{
|
||||
Cost startup_cost;
|
||||
Cost total_cost;
|
||||
@ -1385,7 +1382,7 @@ cost_recursive_union(Plan *runion, Plan *nrterm, Plan *rterm)
|
||||
/* We probably have decent estimates for the non-recursive term */
|
||||
startup_cost = nrterm->startup_cost;
|
||||
total_cost = nrterm->total_cost;
|
||||
total_rows = nrterm->plan_rows;
|
||||
total_rows = nrterm->rows;
|
||||
|
||||
/*
|
||||
* We arbitrarily assume that about 10 recursive iterations will be
|
||||
@ -1394,7 +1391,7 @@ cost_recursive_union(Plan *runion, Plan *nrterm, Plan *rterm)
|
||||
* hard to see how to do better.
|
||||
*/
|
||||
total_cost += 10 * rterm->total_cost;
|
||||
total_rows += 10 * rterm->plan_rows;
|
||||
total_rows += 10 * rterm->rows;
|
||||
|
||||
/*
|
||||
* Also charge cpu_tuple_cost per row to account for the costs of
|
||||
@ -1405,8 +1402,9 @@ cost_recursive_union(Plan *runion, Plan *nrterm, Plan *rterm)
|
||||
|
||||
runion->startup_cost = startup_cost;
|
||||
runion->total_cost = total_cost;
|
||||
runion->plan_rows = total_rows;
|
||||
runion->plan_width = Max(nrterm->plan_width, rterm->plan_width);
|
||||
runion->rows = total_rows;
|
||||
runion->pathtarget->width = Max(nrterm->pathtarget->width,
|
||||
rterm->pathtarget->width);
|
||||
}
|
||||
|
||||
/*
|
||||
@ -3996,8 +3994,8 @@ calc_joinrel_size_estimate(PlannerInfo *root,
|
||||
* Set the size estimates for a base relation that is a subquery.
|
||||
*
|
||||
* The rel's targetlist and restrictinfo list must have been constructed
|
||||
* already, and the plan for the subquery must have been completed.
|
||||
* We look at the subquery's plan and PlannerInfo to extract data.
|
||||
* already, and the Paths for the subquery must have been completed.
|
||||
* We look at the subquery's PlannerInfo to extract data.
|
||||
*
|
||||
* We set the same fields as set_baserel_size_estimates.
|
||||
*/
|
||||
@ -4005,6 +4003,7 @@ void
|
||||
set_subquery_size_estimates(PlannerInfo *root, RelOptInfo *rel)
|
||||
{
|
||||
PlannerInfo *subroot = rel->subroot;
|
||||
RelOptInfo *sub_final_rel;
|
||||
RangeTblEntry *rte PG_USED_FOR_ASSERTS_ONLY;
|
||||
ListCell *lc;
|
||||
|
||||
@ -4013,8 +4012,12 @@ set_subquery_size_estimates(PlannerInfo *root, RelOptInfo *rel)
|
||||
rte = planner_rt_fetch(rel->relid, root);
|
||||
Assert(rte->rtekind == RTE_SUBQUERY);
|
||||
|
||||
/* Copy raw number of output rows from subplan */
|
||||
rel->tuples = rel->subplan->plan_rows;
|
||||
/*
|
||||
* Copy raw number of output rows from subquery. All of its paths should
|
||||
* have the same output rowcount, so just look at cheapest-total.
|
||||
*/
|
||||
sub_final_rel = fetch_upper_rel(subroot, UPPERREL_FINAL, NULL);
|
||||
rel->tuples = sub_final_rel->cheapest_total_path->rows;
|
||||
|
||||
/*
|
||||
* Compute per-output-column width estimates by examining the subquery's
|
||||
@ -4144,13 +4147,13 @@ set_values_size_estimates(PlannerInfo *root, RelOptInfo *rel)
|
||||
* Set the size estimates for a base relation that is a CTE reference.
|
||||
*
|
||||
* The rel's targetlist and restrictinfo list must have been constructed
|
||||
* already, and we need the completed plan for the CTE (if a regular CTE)
|
||||
* or the non-recursive term (if a self-reference).
|
||||
* already, and we need an estimate of the number of rows returned by the CTE
|
||||
* (if a regular CTE) or the non-recursive term (if a self-reference).
|
||||
*
|
||||
* We set the same fields as set_baserel_size_estimates.
|
||||
*/
|
||||
void
|
||||
set_cte_size_estimates(PlannerInfo *root, RelOptInfo *rel, Plan *cteplan)
|
||||
set_cte_size_estimates(PlannerInfo *root, RelOptInfo *rel, double cte_rows)
|
||||
{
|
||||
RangeTblEntry *rte;
|
||||
|
||||
@ -4165,12 +4168,12 @@ set_cte_size_estimates(PlannerInfo *root, RelOptInfo *rel, Plan *cteplan)
|
||||
* In a self-reference, arbitrarily assume the average worktable size
|
||||
* is about 10 times the nonrecursive term's size.
|
||||
*/
|
||||
rel->tuples = 10 * cteplan->plan_rows;
|
||||
rel->tuples = 10 * cte_rows;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Otherwise just believe the CTE plan's output estimate */
|
||||
rel->tuples = cteplan->plan_rows;
|
||||
/* Otherwise just believe the CTE's rowcount estimate */
|
||||
rel->tuples = cte_rows;
|
||||
}
|
||||
|
||||
/* Now estimate number of output rows, etc */
|
||||
@ -4225,7 +4228,7 @@ set_foreign_size_estimates(PlannerInfo *root, RelOptInfo *rel)
|
||||
* any better number.
|
||||
*
|
||||
* The per-attribute width estimates are cached for possible re-use while
|
||||
* building join relations.
|
||||
* building join relations or post-scan/join pathtargets.
|
||||
*/
|
||||
static void
|
||||
set_rel_width(PlannerInfo *root, RelOptInfo *rel)
|
||||
@ -4373,6 +4376,91 @@ set_rel_width(PlannerInfo *root, RelOptInfo *rel)
|
||||
rel->reltarget.width = tuple_width;
|
||||
}
|
||||
|
||||
/*
|
||||
* set_pathtarget_cost_width
|
||||
* Set the estimated eval cost and output width of a PathTarget tlist.
|
||||
*
|
||||
* As a notational convenience, returns the same PathTarget pointer passed in.
|
||||
*
|
||||
* Most, though not quite all, uses of this function occur after we've run
|
||||
* set_rel_width() for base relations; so we can usually obtain cached width
|
||||
* estimates for Vars. If we can't, fall back on datatype-based width
|
||||
* estimates. Present early-planning uses of PathTargets don't need accurate
|
||||
* widths badly enough to justify going to the catalogs for better data.
|
||||
*/
|
||||
PathTarget *
|
||||
set_pathtarget_cost_width(PlannerInfo *root, PathTarget *target)
|
||||
{
|
||||
int32 tuple_width = 0;
|
||||
ListCell *lc;
|
||||
|
||||
/* Vars are assumed to have cost zero, but other exprs do not */
|
||||
target->cost.startup = 0;
|
||||
target->cost.per_tuple = 0;
|
||||
|
||||
foreach(lc, target->exprs)
|
||||
{
|
||||
Node *node = (Node *) lfirst(lc);
|
||||
|
||||
if (IsA(node, Var))
|
||||
{
|
||||
Var *var = (Var *) node;
|
||||
int32 item_width;
|
||||
|
||||
/* We should not see any upper-level Vars here */
|
||||
Assert(var->varlevelsup == 0);
|
||||
|
||||
/* Try to get data from RelOptInfo cache */
|
||||
if (var->varno < root->simple_rel_array_size)
|
||||
{
|
||||
RelOptInfo *rel = root->simple_rel_array[var->varno];
|
||||
|
||||
if (rel != NULL &&
|
||||
var->varattno >= rel->min_attr &&
|
||||
var->varattno <= rel->max_attr)
|
||||
{
|
||||
int ndx = var->varattno - rel->min_attr;
|
||||
|
||||
if (rel->attr_widths[ndx] > 0)
|
||||
{
|
||||
tuple_width += rel->attr_widths[ndx];
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* No cached data available, so estimate using just the type info.
|
||||
*/
|
||||
item_width = get_typavgwidth(var->vartype, var->vartypmod);
|
||||
Assert(item_width > 0);
|
||||
tuple_width += item_width;
|
||||
}
|
||||
else
|
||||
{
|
||||
/*
|
||||
* Handle general expressions using type info.
|
||||
*/
|
||||
int32 item_width;
|
||||
QualCost cost;
|
||||
|
||||
item_width = get_typavgwidth(exprType(node), exprTypmod(node));
|
||||
Assert(item_width > 0);
|
||||
tuple_width += item_width;
|
||||
|
||||
/* Account for cost, too */
|
||||
cost_qual_eval_node(&cost, node, root);
|
||||
target->cost.startup += cost.startup;
|
||||
target->cost.per_tuple += cost.per_tuple;
|
||||
}
|
||||
}
|
||||
|
||||
Assert(tuple_width >= 0);
|
||||
target->width = tuple_width;
|
||||
|
||||
return target;
|
||||
}
|
||||
|
||||
/*
|
||||
* relation_byte_size
|
||||
* Estimate the storage space in bytes for a given number of tuples
|
||||
|
@ -1998,48 +1998,6 @@ add_child_rel_equivalences(PlannerInfo *root,
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* mutate_eclass_expressions
|
||||
* Apply an expression tree mutator to all expressions stored in
|
||||
* equivalence classes (but ignore child exprs unless include_child_exprs).
|
||||
*
|
||||
* This is a bit of a hack ... it's currently needed only by planagg.c,
|
||||
* which needs to do a global search-and-replace of MIN/MAX Aggrefs
|
||||
* after eclasses are already set up. Without changing the eclasses too,
|
||||
* subsequent matching of ORDER BY and DISTINCT clauses would fail.
|
||||
*
|
||||
* Note that we assume the mutation won't affect relation membership or any
|
||||
* other properties we keep track of (which is a bit bogus, but by the time
|
||||
* planagg.c runs, it no longer matters). Also we must be called in the
|
||||
* main planner memory context.
|
||||
*/
|
||||
void
|
||||
mutate_eclass_expressions(PlannerInfo *root,
|
||||
Node *(*mutator) (),
|
||||
void *context,
|
||||
bool include_child_exprs)
|
||||
{
|
||||
ListCell *lc1;
|
||||
|
||||
foreach(lc1, root->eq_classes)
|
||||
{
|
||||
EquivalenceClass *cur_ec = (EquivalenceClass *) lfirst(lc1);
|
||||
ListCell *lc2;
|
||||
|
||||
foreach(lc2, cur_ec->ec_members)
|
||||
{
|
||||
EquivalenceMember *cur_em = (EquivalenceMember *) lfirst(lc2);
|
||||
|
||||
if (cur_em->em_is_child && !include_child_exprs)
|
||||
continue; /* ignore children unless requested */
|
||||
|
||||
cur_em->em_expr = (Expr *)
|
||||
mutator((Node *) cur_em->em_expr, context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* generate_implied_equalities_for_column
|
||||
* Create EC-derived joinclauses usable with a specific column.
|
||||
|
@ -557,6 +557,7 @@ build_expression_pathkey(PlannerInfo *root,
|
||||
*
|
||||
* 'rel': outer query's RelOptInfo for the subquery relation.
|
||||
* 'subquery_pathkeys': the subquery's output pathkeys, in its terms.
|
||||
* 'subquery_tlist': the subquery's output targetlist, in its terms.
|
||||
*
|
||||
* It is not necessary for caller to do truncate_useless_pathkeys(),
|
||||
* because we select keys in a way that takes usefulness of the keys into
|
||||
@ -564,12 +565,12 @@ build_expression_pathkey(PlannerInfo *root,
|
||||
*/
|
||||
List *
|
||||
convert_subquery_pathkeys(PlannerInfo *root, RelOptInfo *rel,
|
||||
List *subquery_pathkeys)
|
||||
List *subquery_pathkeys,
|
||||
List *subquery_tlist)
|
||||
{
|
||||
List *retval = NIL;
|
||||
int retvallen = 0;
|
||||
int outer_query_keys = list_length(root->query_pathkeys);
|
||||
List *sub_tlist = rel->subplan->targetlist;
|
||||
ListCell *i;
|
||||
|
||||
foreach(i, subquery_pathkeys)
|
||||
@ -589,7 +590,7 @@ convert_subquery_pathkeys(PlannerInfo *root, RelOptInfo *rel,
|
||||
|
||||
if (sub_eclass->ec_sortref == 0) /* can't happen */
|
||||
elog(ERROR, "volatile EquivalenceClass has no sortref");
|
||||
tle = get_sortgroupref_tle(sub_eclass->ec_sortref, sub_tlist);
|
||||
tle = get_sortgroupref_tle(sub_eclass->ec_sortref, subquery_tlist);
|
||||
Assert(tle);
|
||||
/* resjunk items aren't visible to outer query */
|
||||
if (!tle->resjunk)
|
||||
@ -669,7 +670,7 @@ convert_subquery_pathkeys(PlannerInfo *root, RelOptInfo *rel,
|
||||
if (sub_member->em_is_child)
|
||||
continue; /* ignore children here */
|
||||
|
||||
foreach(k, sub_tlist)
|
||||
foreach(k, subquery_tlist)
|
||||
{
|
||||
TargetEntry *tle = (TargetEntry *) lfirst(k);
|
||||
Expr *tle_expr;
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -35,13 +35,14 @@
|
||||
#include "nodes/nodeFuncs.h"
|
||||
#include "optimizer/clauses.h"
|
||||
#include "optimizer/cost.h"
|
||||
#include "optimizer/pathnode.h"
|
||||
#include "optimizer/paths.h"
|
||||
#include "optimizer/planmain.h"
|
||||
#include "optimizer/planner.h"
|
||||
#include "optimizer/subselect.h"
|
||||
#include "optimizer/tlist.h"
|
||||
#include "parser/parsetree.h"
|
||||
#include "parser/parse_clause.h"
|
||||
#include "rewrite/rewriteManip.h"
|
||||
#include "utils/lsyscache.h"
|
||||
#include "utils/syscache.h"
|
||||
|
||||
@ -50,8 +51,6 @@ static bool find_minmax_aggs_walker(Node *node, List **context);
|
||||
static bool build_minmax_path(PlannerInfo *root, MinMaxAggInfo *mminfo,
|
||||
Oid eqop, Oid sortop, bool nulls_first);
|
||||
static void minmax_qp_callback(PlannerInfo *root, void *extra);
|
||||
static void make_agg_subplan(PlannerInfo *root, MinMaxAggInfo *mminfo);
|
||||
static Node *replace_aggs_with_params_mutator(Node *node, PlannerInfo *root);
|
||||
static Oid fetch_agg_sort_op(Oid aggfnoid);
|
||||
|
||||
|
||||
@ -60,8 +59,14 @@ static Oid fetch_agg_sort_op(Oid aggfnoid);
|
||||
*
|
||||
* Check to see whether the query contains MIN/MAX aggregate functions that
|
||||
* might be optimizable via indexscans. If it does, and all the aggregates
|
||||
* are potentially optimizable, then set up root->minmax_aggs with a list of
|
||||
* these aggregates.
|
||||
* are potentially optimizable, then create a MinMaxAggPath and add it to
|
||||
* the (UPPERREL_GROUP_AGG, NULL) upperrel.
|
||||
*
|
||||
* This should be called by grouping_planner() just before it's ready to call
|
||||
* query_planner(), because we generate indexscan paths by cloning the
|
||||
* planner's state and invoking query_planner() on a modified version of
|
||||
* the query parsetree. Thus, all preprocessing needed before query_planner()
|
||||
* must already be done.
|
||||
*
|
||||
* Note: we are passed the preprocessed targetlist separately, because it's
|
||||
* not necessarily equal to root->parse->targetList.
|
||||
@ -74,6 +79,7 @@ preprocess_minmax_aggregates(PlannerInfo *root, List *tlist)
|
||||
RangeTblRef *rtr;
|
||||
RangeTblEntry *rte;
|
||||
List *aggs_list;
|
||||
RelOptInfo *grouped_rel;
|
||||
ListCell *lc;
|
||||
|
||||
/* minmax_aggs list should be empty at this point */
|
||||
@ -91,12 +97,10 @@ preprocess_minmax_aggregates(PlannerInfo *root, List *tlist)
|
||||
*
|
||||
* We don't handle GROUP BY or windowing, because our current
|
||||
* implementations of grouping require looking at all the rows anyway, and
|
||||
* so there's not much point in optimizing MIN/MAX. (Note: relaxing this
|
||||
* would likely require some restructuring in grouping_planner(), since it
|
||||
* performs assorted processing related to these features between calling
|
||||
* preprocess_minmax_aggregates and optimize_minmax_aggregates.)
|
||||
* so there's not much point in optimizing MIN/MAX.
|
||||
*/
|
||||
if (parse->groupClause || list_length(parse->groupingSets) > 1 || parse->hasWindowFuncs)
|
||||
if (parse->groupClause || list_length(parse->groupingSets) > 1 ||
|
||||
parse->hasWindowFuncs)
|
||||
return;
|
||||
|
||||
/*
|
||||
@ -138,11 +142,9 @@ preprocess_minmax_aggregates(PlannerInfo *root, List *tlist)
|
||||
|
||||
/*
|
||||
* OK, there is at least the possibility of performing the optimization.
|
||||
* Build an access path for each aggregate. (We must do this now because
|
||||
* we need to call query_planner with a pristine copy of the current query
|
||||
* tree; it'll be too late when optimize_minmax_aggregates gets called.)
|
||||
* If any of the aggregates prove to be non-indexable, give up; there is
|
||||
* no point in optimizing just some of them.
|
||||
* Build an access path for each aggregate. If any of the aggregates
|
||||
* prove to be non-indexable, give up; there is no point in optimizing
|
||||
* just some of them.
|
||||
*/
|
||||
foreach(lc, aggs_list)
|
||||
{
|
||||
@ -177,111 +179,40 @@ preprocess_minmax_aggregates(PlannerInfo *root, List *tlist)
|
||||
}
|
||||
|
||||
/*
|
||||
* We're done until path generation is complete. Save info for later.
|
||||
* (Setting root->minmax_aggs non-NIL signals we succeeded in making index
|
||||
* access paths for all the aggregates.)
|
||||
*/
|
||||
root->minmax_aggs = aggs_list;
|
||||
}
|
||||
|
||||
/*
|
||||
* optimize_minmax_aggregates - check for optimizing MIN/MAX via indexes
|
||||
*
|
||||
* Check to see whether using the aggregate indexscans is cheaper than the
|
||||
* generic aggregate method. If so, generate and return a Plan that does it
|
||||
* that way. Otherwise, return NULL.
|
||||
*
|
||||
* Note: it seems likely that the generic method will never be cheaper
|
||||
* in practice, except maybe for tiny tables where it'd hardly matter.
|
||||
* Should we skip even trying to build the standard plan, if
|
||||
* preprocess_minmax_aggregates succeeds?
|
||||
*
|
||||
* We are passed the preprocessed tlist, as well as the estimated costs for
|
||||
* doing the aggregates the regular way, and the best path devised for
|
||||
* computing the input of a standard Agg node.
|
||||
*/
|
||||
Plan *
|
||||
optimize_minmax_aggregates(PlannerInfo *root, List *tlist,
|
||||
const AggClauseCosts *aggcosts, Path *best_path)
|
||||
{
|
||||
Query *parse = root->parse;
|
||||
Cost total_cost;
|
||||
Path agg_p;
|
||||
Plan *plan;
|
||||
Node *hqual;
|
||||
ListCell *lc;
|
||||
|
||||
/* Nothing to do if preprocess_minmax_aggs rejected the query */
|
||||
if (root->minmax_aggs == NIL)
|
||||
return NULL;
|
||||
|
||||
/*
|
||||
* Now we have enough info to compare costs against the generic aggregate
|
||||
* implementation.
|
||||
* OK, we can do the query this way. Prepare to create a MinMaxAggPath
|
||||
* node.
|
||||
*
|
||||
* Note that we don't include evaluation cost of the tlist here; this is
|
||||
* OK since it isn't included in best_path's cost either, and should be
|
||||
* the same in either case.
|
||||
* First, create an output Param node for each agg. (If we end up not
|
||||
* using the MinMaxAggPath, we'll waste a PARAM_EXEC slot for each agg,
|
||||
* which is not worth worrying about. We can't wait till create_plan time
|
||||
* to decide whether to make the Param, unfortunately.)
|
||||
*/
|
||||
total_cost = 0;
|
||||
foreach(lc, root->minmax_aggs)
|
||||
foreach(lc, aggs_list)
|
||||
{
|
||||
MinMaxAggInfo *mminfo = (MinMaxAggInfo *) lfirst(lc);
|
||||
|
||||
total_cost += mminfo->pathcost;
|
||||
}
|
||||
|
||||
cost_agg(&agg_p, root, AGG_PLAIN, aggcosts,
|
||||
0, 0,
|
||||
best_path->startup_cost, best_path->total_cost,
|
||||
best_path->parent->rows);
|
||||
|
||||
if (total_cost > agg_p.total_cost)
|
||||
return NULL; /* too expensive */
|
||||
|
||||
/*
|
||||
* OK, we are going to generate an optimized plan.
|
||||
*
|
||||
* First, generate a subplan and output Param node for each agg.
|
||||
*/
|
||||
foreach(lc, root->minmax_aggs)
|
||||
{
|
||||
MinMaxAggInfo *mminfo = (MinMaxAggInfo *) lfirst(lc);
|
||||
|
||||
make_agg_subplan(root, mminfo);
|
||||
mminfo->param =
|
||||
SS_make_initplan_output_param(root,
|
||||
exprType((Node *) mminfo->target),
|
||||
-1,
|
||||
exprCollation((Node *) mminfo->target));
|
||||
}
|
||||
|
||||
/*
|
||||
* Modify the targetlist and HAVING qual to reference subquery outputs
|
||||
*/
|
||||
tlist = (List *) replace_aggs_with_params_mutator((Node *) tlist, root);
|
||||
hqual = replace_aggs_with_params_mutator(parse->havingQual, root);
|
||||
|
||||
/*
|
||||
* We have to replace Aggrefs with Params in equivalence classes too, else
|
||||
* ORDER BY or DISTINCT on an optimized aggregate will fail. We don't
|
||||
* need to process child eclass members though, since they aren't of
|
||||
* interest anymore --- and replace_aggs_with_params_mutator isn't able to
|
||||
* handle Aggrefs containing translated child Vars, anyway.
|
||||
* Create a MinMaxAggPath node with the appropriate estimated costs and
|
||||
* other needed data, and add it to the UPPERREL_GROUP_AGG upperrel, where
|
||||
* it will compete against the standard aggregate implementation. (It
|
||||
* will likely always win, but we need not assume that here.)
|
||||
*
|
||||
* Note: at some point it might become necessary to mutate other data
|
||||
* structures too, such as the query's sortClause or distinctClause. Right
|
||||
* now, those won't be examined after this point.
|
||||
* Note: grouping_planner won't have created this upperrel yet, but it's
|
||||
* fine for us to create it first.
|
||||
*/
|
||||
mutate_eclass_expressions(root,
|
||||
replace_aggs_with_params_mutator,
|
||||
(void *) root,
|
||||
false);
|
||||
|
||||
/*
|
||||
* Generate the output plan --- basically just a Result
|
||||
*/
|
||||
plan = (Plan *) make_result(root, tlist, hqual, NULL);
|
||||
|
||||
/* Account for evaluation cost of the tlist (make_result did the rest) */
|
||||
add_tlist_costs_to_plan(root, plan, tlist);
|
||||
|
||||
return plan;
|
||||
grouped_rel = fetch_upper_rel(root, UPPERREL_GROUP_AGG, NULL);
|
||||
add_path(grouped_rel, (Path *)
|
||||
create_minmaxagg_path(root, grouped_rel,
|
||||
create_pathtarget(root, tlist),
|
||||
aggs_list,
|
||||
(List *) parse->havingQual));
|
||||
}
|
||||
|
||||
/*
|
||||
@ -403,6 +334,7 @@ build_minmax_path(PlannerInfo *root, MinMaxAggInfo *mminfo,
|
||||
PlannerInfo *subroot;
|
||||
Query *parse;
|
||||
TargetEntry *tle;
|
||||
List *tlist;
|
||||
NullTest *ntest;
|
||||
SortGroupClause *sortcl;
|
||||
RelOptInfo *final_rel;
|
||||
@ -410,40 +342,51 @@ build_minmax_path(PlannerInfo *root, MinMaxAggInfo *mminfo,
|
||||
Cost path_cost;
|
||||
double path_fraction;
|
||||
|
||||
/*
|
||||
* We are going to construct what is effectively a sub-SELECT query, so
|
||||
* clone the current query level's state and adjust it to make it look
|
||||
* like a subquery. Any outer references will now be one level higher
|
||||
* than before. (This means that when we are done, there will be no Vars
|
||||
* of level 1, which is why the subquery can become an initplan.)
|
||||
*/
|
||||
subroot = (PlannerInfo *) palloc(sizeof(PlannerInfo));
|
||||
memcpy(subroot, root, sizeof(PlannerInfo));
|
||||
subroot->query_level++;
|
||||
subroot->parent_root = root;
|
||||
/* reset subplan-related stuff */
|
||||
subroot->plan_params = NIL;
|
||||
subroot->outer_params = NULL;
|
||||
subroot->init_plans = NIL;
|
||||
subroot->cte_plan_ids = NIL;
|
||||
|
||||
subroot->parse = parse = (Query *) copyObject(root->parse);
|
||||
IncrementVarSublevelsUp((Node *) parse, 1, 1);
|
||||
|
||||
/* append_rel_list might contain outer Vars? */
|
||||
subroot->append_rel_list = (List *) copyObject(root->append_rel_list);
|
||||
IncrementVarSublevelsUp((Node *) subroot->append_rel_list, 1, 1);
|
||||
/* There shouldn't be any OJ info to translate, as yet */
|
||||
Assert(subroot->join_info_list == NIL);
|
||||
/* and we haven't made equivalence classes, either */
|
||||
Assert(subroot->eq_classes == NIL);
|
||||
/* and we haven't created PlaceHolderInfos, either */
|
||||
Assert(subroot->placeholder_list == NIL);
|
||||
|
||||
/*----------
|
||||
* Generate modified query of the form
|
||||
* (SELECT col FROM tab
|
||||
* WHERE col IS NOT NULL AND existing-quals
|
||||
* ORDER BY col ASC/DESC
|
||||
* LIMIT 1)
|
||||
*
|
||||
* We cheat a bit here by building what is effectively a subplan query
|
||||
* level without taking the trouble to increment varlevelsup of outer
|
||||
* references. Therefore we don't increment the subroot's query_level nor
|
||||
* repoint its parent_root to the parent level. We can get away with that
|
||||
* because the plan will be an initplan and therefore cannot need any
|
||||
* parameters from the parent level. But see hackery in make_agg_subplan;
|
||||
* we might someday need to do this the hard way.
|
||||
*----------
|
||||
*/
|
||||
subroot = (PlannerInfo *) palloc(sizeof(PlannerInfo));
|
||||
memcpy(subroot, root, sizeof(PlannerInfo));
|
||||
subroot->parse = parse = (Query *) copyObject(root->parse);
|
||||
/* reset subplan-related stuff */
|
||||
subroot->plan_params = NIL;
|
||||
subroot->outer_params = NULL;
|
||||
subroot->init_plans = NIL;
|
||||
/* There shouldn't be any OJ info to translate, as yet */
|
||||
Assert(subroot->join_info_list == NIL);
|
||||
/* and we haven't created PlaceHolderInfos, either */
|
||||
Assert(subroot->placeholder_list == NIL);
|
||||
|
||||
/* single tlist entry that is the aggregate target */
|
||||
tle = makeTargetEntry(copyObject(mminfo->target),
|
||||
(AttrNumber) 1,
|
||||
pstrdup("agg_target"),
|
||||
false);
|
||||
parse->targetList = list_make1(tle);
|
||||
tlist = list_make1(tle);
|
||||
subroot->processed_tlist = parse->targetList = tlist;
|
||||
|
||||
/* No HAVING, no DISTINCT, no aggregates anymore */
|
||||
parse->havingQual = NULL;
|
||||
@ -467,7 +410,7 @@ build_minmax_path(PlannerInfo *root, MinMaxAggInfo *mminfo,
|
||||
|
||||
/* Build suitable ORDER BY clause */
|
||||
sortcl = makeNode(SortGroupClause);
|
||||
sortcl->tleSortGroupRef = assignSortGroupRef(tle, parse->targetList);
|
||||
sortcl->tleSortGroupRef = assignSortGroupRef(tle, tlist);
|
||||
sortcl->eqop = eqop;
|
||||
sortcl->sortop = sortop;
|
||||
sortcl->nulls_first = nulls_first;
|
||||
@ -488,8 +431,16 @@ build_minmax_path(PlannerInfo *root, MinMaxAggInfo *mminfo,
|
||||
subroot->tuple_fraction = 1.0;
|
||||
subroot->limit_tuples = 1.0;
|
||||
|
||||
final_rel = query_planner(subroot, parse->targetList,
|
||||
minmax_qp_callback, NULL);
|
||||
final_rel = query_planner(subroot, tlist, minmax_qp_callback, NULL);
|
||||
|
||||
/*
|
||||
* Since we didn't go through subquery_planner() to handle the subquery,
|
||||
* we have to do some of the same cleanup it would do, in particular cope
|
||||
* with params and initplans used within this subquery. (This won't
|
||||
* matter if we end up not using the subplan.)
|
||||
*/
|
||||
SS_identify_outer_params(subroot);
|
||||
SS_charge_for_initplans(subroot, final_rel);
|
||||
|
||||
/*
|
||||
* Get the best presorted path, that being the one that's cheapest for
|
||||
@ -508,6 +459,14 @@ build_minmax_path(PlannerInfo *root, MinMaxAggInfo *mminfo,
|
||||
if (!sorted_path)
|
||||
return false;
|
||||
|
||||
/*
|
||||
* The path might not return exactly what we want, so fix that. (We
|
||||
* assume that this won't change any conclusions about which was the
|
||||
* cheapest path.)
|
||||
*/
|
||||
sorted_path = apply_projection_to_path(subroot, final_rel, sorted_path,
|
||||
create_pathtarget(root, tlist));
|
||||
|
||||
/*
|
||||
* Determine cost to get just the first row of the presorted path.
|
||||
*
|
||||
@ -526,7 +485,7 @@ build_minmax_path(PlannerInfo *root, MinMaxAggInfo *mminfo,
|
||||
}
|
||||
|
||||
/*
|
||||
* Compute query_pathkeys and other pathkeys during plan generation
|
||||
* Compute query_pathkeys and other pathkeys during query_planner()
|
||||
*/
|
||||
static void
|
||||
minmax_qp_callback(PlannerInfo *root, void *extra)
|
||||
@ -543,105 +502,6 @@ minmax_qp_callback(PlannerInfo *root, void *extra)
|
||||
root->query_pathkeys = root->sort_pathkeys;
|
||||
}
|
||||
|
||||
/*
|
||||
* Construct a suitable plan for a converted aggregate query
|
||||
*/
|
||||
static void
|
||||
make_agg_subplan(PlannerInfo *root, MinMaxAggInfo *mminfo)
|
||||
{
|
||||
PlannerInfo *subroot = mminfo->subroot;
|
||||
Query *subparse = subroot->parse;
|
||||
Plan *plan;
|
||||
|
||||
/*
|
||||
* Generate the plan for the subquery. We already have a Path, but we have
|
||||
* to convert it to a Plan and attach a LIMIT node above it.
|
||||
*/
|
||||
plan = create_plan(subroot, mminfo->path);
|
||||
|
||||
/*
|
||||
* If the top-level plan node is one that cannot do expression evaluation
|
||||
* and its existing target list isn't already what we need, we must insert
|
||||
* a Result node to project the desired tlist.
|
||||
*/
|
||||
if (!is_projection_capable_plan(plan) &&
|
||||
!tlist_same_exprs(subparse->targetList, plan->targetlist))
|
||||
{
|
||||
plan = (Plan *) make_result(subroot,
|
||||
subparse->targetList,
|
||||
NULL,
|
||||
plan);
|
||||
}
|
||||
else
|
||||
{
|
||||
/*
|
||||
* Otherwise, just replace the subplan's flat tlist with the desired
|
||||
* tlist.
|
||||
*/
|
||||
plan->targetlist = subparse->targetList;
|
||||
}
|
||||
|
||||
plan = (Plan *) make_limit(plan,
|
||||
subparse->limitOffset,
|
||||
subparse->limitCount,
|
||||
0, 1);
|
||||
|
||||
/*
|
||||
* We have to do some of the same cleanup that subquery_planner() would
|
||||
* do, namely cope with params and initplans used within this plan tree.
|
||||
*
|
||||
* This is a little bit messy because although we initially created the
|
||||
* subroot by cloning the outer root, it really is a subplan and needs to
|
||||
* consider initplans belonging to the outer root as providing available
|
||||
* parameters. So temporarily change its parent_root pointer.
|
||||
* (Fortunately, SS_identify_outer_params doesn't care whether the depth
|
||||
* of parent_root nesting matches query_level.)
|
||||
*/
|
||||
subroot->parent_root = root;
|
||||
SS_identify_outer_params(subroot);
|
||||
subroot->parent_root = root->parent_root;
|
||||
|
||||
SS_attach_initplans(subroot, plan);
|
||||
|
||||
/*
|
||||
* Convert the plan into an InitPlan, and make a Param for its result.
|
||||
*/
|
||||
mminfo->param =
|
||||
SS_make_initplan_from_plan(root, subroot, plan,
|
||||
exprType((Node *) mminfo->target),
|
||||
-1,
|
||||
exprCollation((Node *) mminfo->target));
|
||||
}
|
||||
|
||||
/*
|
||||
* Replace original aggregate calls with subplan output Params
|
||||
*/
|
||||
static Node *
|
||||
replace_aggs_with_params_mutator(Node *node, PlannerInfo *root)
|
||||
{
|
||||
if (node == NULL)
|
||||
return NULL;
|
||||
if (IsA(node, Aggref))
|
||||
{
|
||||
Aggref *aggref = (Aggref *) node;
|
||||
TargetEntry *curTarget = (TargetEntry *) linitial(aggref->args);
|
||||
ListCell *lc;
|
||||
|
||||
foreach(lc, root->minmax_aggs)
|
||||
{
|
||||
MinMaxAggInfo *mminfo = (MinMaxAggInfo *) lfirst(lc);
|
||||
|
||||
if (mminfo->aggfnoid == aggref->aggfnoid &&
|
||||
equal(mminfo->target, curTarget->expr))
|
||||
return (Node *) mminfo->param;
|
||||
}
|
||||
elog(ERROR, "failed to re-find MinMaxAggInfo record");
|
||||
}
|
||||
Assert(!IsA(node, SubLink));
|
||||
return expression_tree_mutator(node, replace_aggs_with_params_mutator,
|
||||
(void *) root);
|
||||
}
|
||||
|
||||
/*
|
||||
* Get the OID of the sort operator, if any, associated with an aggregate.
|
||||
* Returns InvalidOid if there is no such operator.
|
||||
|
@ -36,9 +36,7 @@
|
||||
* Since query_planner does not handle the toplevel processing (grouping,
|
||||
* sorting, etc) it cannot select the best path by itself. Instead, it
|
||||
* returns the RelOptInfo for the top level of joining, and the caller
|
||||
* (grouping_planner) can choose one of the surviving paths for the rel.
|
||||
* Normally it would choose either the rel's cheapest path, or the cheapest
|
||||
* path for the desired sort order.
|
||||
* (grouping_planner) can choose among the surviving paths for the rel.
|
||||
*
|
||||
* root describes the query to plan
|
||||
* tlist is the target list the query should produce
|
||||
@ -85,6 +83,7 @@ query_planner(PlannerInfo *root, List *tlist,
|
||||
/* The only path for it is a trivial Result path */
|
||||
add_path(final_rel, (Path *)
|
||||
create_result_path(final_rel,
|
||||
&(final_rel->reltarget),
|
||||
(List *) parse->jointree->quals));
|
||||
|
||||
/* Select cheapest path (pretty easy in this case...) */
|
||||
@ -104,7 +103,7 @@ query_planner(PlannerInfo *root, List *tlist,
|
||||
* Init planner lists to empty.
|
||||
*
|
||||
* NOTE: append_rel_list was set up by subquery_planner, so do not touch
|
||||
* here; eq_classes and minmax_aggs may contain data already, too.
|
||||
* here.
|
||||
*/
|
||||
root->join_rel_list = NIL;
|
||||
root->join_rel_hash = NULL;
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -304,8 +304,8 @@ add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing)
|
||||
* in our query level. In this case apply
|
||||
* flatten_unplanned_rtes.
|
||||
*
|
||||
* If it was planned but the plan is dummy, we assume that it
|
||||
* has been omitted from our plan tree (see
|
||||
* If it was planned but the result rel is dummy, we assume
|
||||
* that it has been omitted from our plan tree (see
|
||||
* set_subquery_pathlist), and recurse to pull up its RTEs.
|
||||
*
|
||||
* Otherwise, it should be represented by a SubqueryScan node
|
||||
@ -313,17 +313,16 @@ add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing)
|
||||
* we process that plan node.
|
||||
*
|
||||
* However, if we're recursing, then we should pull up RTEs
|
||||
* whether the subplan is dummy or not, because we've found
|
||||
* whether the subquery is dummy or not, because we've found
|
||||
* that some upper query level is treating this one as dummy,
|
||||
* and so we won't scan this level's plan tree at all.
|
||||
*/
|
||||
if (rel->subplan == NULL)
|
||||
if (rel->subroot == NULL)
|
||||
flatten_unplanned_rtes(glob, rte);
|
||||
else if (recursing || is_dummy_plan(rel->subplan))
|
||||
{
|
||||
Assert(rel->subroot != NULL);
|
||||
else if (recursing ||
|
||||
IS_DUMMY_REL(fetch_upper_rel(rel->subroot,
|
||||
UPPERREL_FINAL, NULL)))
|
||||
add_rtes_to_flat_rtable(rel->subroot, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
rti++;
|
||||
@ -979,7 +978,6 @@ set_subqueryscan_references(PlannerInfo *root,
|
||||
|
||||
/* Need to look up the subquery's RelOptInfo, since we need its subroot */
|
||||
rel = find_base_rel(root, plan->scan.scanrelid);
|
||||
Assert(rel->subplan == plan->subplan);
|
||||
|
||||
/* Recursively process the subplan */
|
||||
plan->subplan = set_plan_references(rel->subroot, plan->subplan);
|
||||
@ -1386,6 +1384,7 @@ fix_param_node(PlannerInfo *root, Param *p)
|
||||
*
|
||||
* This consists of incrementing all Vars' varnos by rtoffset,
|
||||
* replacing PARAM_MULTIEXPR Params, expanding PlaceHolderVars,
|
||||
* replacing Aggref nodes that should be replaced by initplan output Params,
|
||||
* looking up operator opcode info for OpExpr and related nodes,
|
||||
* and adding OIDs from regclass Const nodes into root->glob->relationOids.
|
||||
*/
|
||||
@ -1399,7 +1398,8 @@ fix_scan_expr(PlannerInfo *root, Node *node, int rtoffset)
|
||||
|
||||
if (rtoffset != 0 ||
|
||||
root->multiexpr_params != NIL ||
|
||||
root->glob->lastPHId != 0)
|
||||
root->glob->lastPHId != 0 ||
|
||||
root->minmax_aggs != NIL)
|
||||
{
|
||||
return fix_scan_expr_mutator(node, &context);
|
||||
}
|
||||
@ -1409,7 +1409,8 @@ fix_scan_expr(PlannerInfo *root, Node *node, int rtoffset)
|
||||
* If rtoffset == 0, we don't need to change any Vars, and if there
|
||||
* are no MULTIEXPR subqueries then we don't need to replace
|
||||
* PARAM_MULTIEXPR Params, and if there are no placeholders anywhere
|
||||
* we won't need to remove them. Then it's OK to just scribble on the
|
||||
* we won't need to remove them, and if there are no minmax Aggrefs we
|
||||
* won't need to replace them. Then it's OK to just scribble on the
|
||||
* input node tree instead of copying (since the only change, filling
|
||||
* in any unset opfuncid fields, is harmless). This saves just enough
|
||||
* cycles to be noticeable on trivial queries.
|
||||
@ -1444,6 +1445,28 @@ fix_scan_expr_mutator(Node *node, fix_scan_expr_context *context)
|
||||
}
|
||||
if (IsA(node, Param))
|
||||
return fix_param_node(context->root, (Param *) node);
|
||||
if (IsA(node, Aggref))
|
||||
{
|
||||
Aggref *aggref = (Aggref *) node;
|
||||
|
||||
/* See if the Aggref should be replaced by a Param */
|
||||
if (context->root->minmax_aggs != NIL &&
|
||||
list_length(aggref->args) == 1)
|
||||
{
|
||||
TargetEntry *curTarget = (TargetEntry *) linitial(aggref->args);
|
||||
ListCell *lc;
|
||||
|
||||
foreach(lc, context->root->minmax_aggs)
|
||||
{
|
||||
MinMaxAggInfo *mminfo = (MinMaxAggInfo *) lfirst(lc);
|
||||
|
||||
if (mminfo->aggfnoid == aggref->aggfnoid &&
|
||||
equal(mminfo->target, curTarget->expr))
|
||||
return (Node *) copyObject(mminfo->param);
|
||||
}
|
||||
}
|
||||
/* If no match, just fall through to process it normally */
|
||||
}
|
||||
if (IsA(node, CurrentOfExpr))
|
||||
{
|
||||
CurrentOfExpr *cexpr = (CurrentOfExpr *) copyObject(node);
|
||||
@ -2091,8 +2114,9 @@ fix_join_expr_mutator(Node *node, fix_join_expr_context *context)
|
||||
/*
|
||||
* fix_upper_expr
|
||||
* Modifies an expression tree so that all Var nodes reference outputs
|
||||
* of a subplan. Also performs opcode lookup, and adds regclass OIDs to
|
||||
* root->glob->relationOids.
|
||||
* of a subplan. Also looks for Aggref nodes that should be replaced
|
||||
* by initplan output Params. Also performs opcode lookup, and adds
|
||||
* regclass OIDs to root->glob->relationOids.
|
||||
*
|
||||
* This is used to fix up target and qual expressions of non-join upper-level
|
||||
* plan nodes, as well as index-only scan nodes.
|
||||
@ -2169,6 +2193,28 @@ fix_upper_expr_mutator(Node *node, fix_upper_expr_context *context)
|
||||
}
|
||||
if (IsA(node, Param))
|
||||
return fix_param_node(context->root, (Param *) node);
|
||||
if (IsA(node, Aggref))
|
||||
{
|
||||
Aggref *aggref = (Aggref *) node;
|
||||
|
||||
/* See if the Aggref should be replaced by a Param */
|
||||
if (context->root->minmax_aggs != NIL &&
|
||||
list_length(aggref->args) == 1)
|
||||
{
|
||||
TargetEntry *curTarget = (TargetEntry *) linitial(aggref->args);
|
||||
ListCell *lc;
|
||||
|
||||
foreach(lc, context->root->minmax_aggs)
|
||||
{
|
||||
MinMaxAggInfo *mminfo = (MinMaxAggInfo *) lfirst(lc);
|
||||
|
||||
if (mminfo->aggfnoid == aggref->aggfnoid &&
|
||||
equal(mminfo->target, curTarget->expr))
|
||||
return (Node *) copyObject(mminfo->param);
|
||||
}
|
||||
}
|
||||
/* If no match, just fall through to process it normally */
|
||||
}
|
||||
/* Try matching more complex expressions too, if tlist has any */
|
||||
if (context->subplan_itlist->has_non_vars)
|
||||
{
|
||||
|
@ -478,8 +478,10 @@ make_subplan(PlannerInfo *root, Query *orig_subquery,
|
||||
Query *subquery;
|
||||
bool simple_exists = false;
|
||||
double tuple_fraction;
|
||||
Plan *plan;
|
||||
PlannerInfo *subroot;
|
||||
RelOptInfo *final_rel;
|
||||
Path *best_path;
|
||||
Plan *plan;
|
||||
List *plan_params;
|
||||
Node *result;
|
||||
|
||||
@ -527,18 +529,24 @@ make_subplan(PlannerInfo *root, Query *orig_subquery,
|
||||
/* plan_params should not be in use in current query level */
|
||||
Assert(root->plan_params == NIL);
|
||||
|
||||
/*
|
||||
* Generate the plan for the subquery.
|
||||
*/
|
||||
plan = subquery_planner(root->glob, subquery,
|
||||
root,
|
||||
false, tuple_fraction,
|
||||
&subroot);
|
||||
/* Generate Paths for the subquery */
|
||||
subroot = subquery_planner(root->glob, subquery,
|
||||
root,
|
||||
false, tuple_fraction);
|
||||
|
||||
/* Isolate the params needed by this specific subplan */
|
||||
plan_params = root->plan_params;
|
||||
root->plan_params = NIL;
|
||||
|
||||
/*
|
||||
* Select best Path and turn it into a Plan. At least for now, there
|
||||
* seems no reason to postpone doing that.
|
||||
*/
|
||||
final_rel = fetch_upper_rel(subroot, UPPERREL_FINAL, NULL);
|
||||
best_path = get_cheapest_fractional_path(final_rel, tuple_fraction);
|
||||
|
||||
plan = create_plan(subroot, best_path);
|
||||
|
||||
/* And convert to SubPlan or InitPlan format. */
|
||||
result = build_subplan(root, plan, subroot, plan_params,
|
||||
subLinkType, subLinkId,
|
||||
@ -568,17 +576,23 @@ make_subplan(PlannerInfo *root, Query *orig_subquery,
|
||||
&newtestexpr, ¶mIds);
|
||||
if (subquery)
|
||||
{
|
||||
/* Generate the plan for the ANY subquery; we'll need all rows */
|
||||
plan = subquery_planner(root->glob, subquery,
|
||||
root,
|
||||
false, 0.0,
|
||||
&subroot);
|
||||
/* Generate Paths for the ANY subquery; we'll need all rows */
|
||||
subroot = subquery_planner(root->glob, subquery,
|
||||
root,
|
||||
false, 0.0);
|
||||
|
||||
/* Isolate the params needed by this specific subplan */
|
||||
plan_params = root->plan_params;
|
||||
root->plan_params = NIL;
|
||||
|
||||
/* Select best Path and turn it into a Plan */
|
||||
final_rel = fetch_upper_rel(subroot, UPPERREL_FINAL, NULL);
|
||||
best_path = final_rel->cheapest_total_path;
|
||||
|
||||
plan = create_plan(subroot, best_path);
|
||||
|
||||
/* Now we can check if it'll fit in work_mem */
|
||||
/* XXX can we check this at the Path stage? */
|
||||
if (subplan_is_hashable(plan))
|
||||
{
|
||||
SubPlan *hashplan;
|
||||
@ -1133,8 +1147,10 @@ SS_process_ctes(PlannerInfo *root)
|
||||
CommonTableExpr *cte = (CommonTableExpr *) lfirst(lc);
|
||||
CmdType cmdType = ((Query *) cte->ctequery)->commandType;
|
||||
Query *subquery;
|
||||
Plan *plan;
|
||||
PlannerInfo *subroot;
|
||||
RelOptInfo *final_rel;
|
||||
Path *best_path;
|
||||
Plan *plan;
|
||||
SubPlan *splan;
|
||||
int paramid;
|
||||
|
||||
@ -1158,13 +1174,12 @@ SS_process_ctes(PlannerInfo *root)
|
||||
Assert(root->plan_params == NIL);
|
||||
|
||||
/*
|
||||
* Generate the plan for the CTE query. Always plan for full
|
||||
* retrieval --- we don't have enough info to predict otherwise.
|
||||
* Generate Paths for the CTE query. Always plan for full retrieval
|
||||
* --- we don't have enough info to predict otherwise.
|
||||
*/
|
||||
plan = subquery_planner(root->glob, subquery,
|
||||
root,
|
||||
cte->cterecursive, 0.0,
|
||||
&subroot);
|
||||
subroot = subquery_planner(root->glob, subquery,
|
||||
root,
|
||||
cte->cterecursive, 0.0);
|
||||
|
||||
/*
|
||||
* Since the current query level doesn't yet contain any RTEs, it
|
||||
@ -1174,6 +1189,15 @@ SS_process_ctes(PlannerInfo *root)
|
||||
if (root->plan_params)
|
||||
elog(ERROR, "unexpected outer reference in CTE query");
|
||||
|
||||
/*
|
||||
* Select best Path and turn it into a Plan. At least for now, there
|
||||
* seems no reason to postpone doing that.
|
||||
*/
|
||||
final_rel = fetch_upper_rel(subroot, UPPERREL_FINAL, NULL);
|
||||
best_path = final_rel->cheapest_total_path;
|
||||
|
||||
plan = create_plan(subroot, best_path);
|
||||
|
||||
/*
|
||||
* Make a SubPlan node for it. This is just enough unlike
|
||||
* build_subplan that we can't share code.
|
||||
@ -2108,36 +2132,71 @@ SS_identify_outer_params(PlannerInfo *root)
|
||||
root->outer_params = outer_params;
|
||||
}
|
||||
|
||||
/*
|
||||
* SS_charge_for_initplans - account for cost of initplans in Path costs
|
||||
*
|
||||
* If any initPlans have been created in the current query level, they will
|
||||
* get attached to the Plan tree created from whichever Path we select from
|
||||
* the given rel; so increment all the rel's Paths' costs to account for them.
|
||||
*
|
||||
* This is separate from SS_attach_initplans because we might conditionally
|
||||
* create more initPlans during create_plan(), depending on which Path we
|
||||
* select. However, Paths that would generate such initPlans are expected
|
||||
* to have included their cost already.
|
||||
*/
|
||||
void
|
||||
SS_charge_for_initplans(PlannerInfo *root, RelOptInfo *final_rel)
|
||||
{
|
||||
Cost initplan_cost;
|
||||
ListCell *lc;
|
||||
|
||||
/* Nothing to do if no initPlans */
|
||||
if (root->init_plans == NIL)
|
||||
return;
|
||||
|
||||
/*
|
||||
* Compute the cost increment just once, since it will be the same for all
|
||||
* Paths. We assume each initPlan gets run once during top plan startup.
|
||||
* This is a conservative overestimate, since in fact an initPlan might be
|
||||
* executed later than plan startup, or even not at all.
|
||||
*/
|
||||
initplan_cost = 0;
|
||||
foreach(lc, root->init_plans)
|
||||
{
|
||||
SubPlan *initsubplan = (SubPlan *) lfirst(lc);
|
||||
|
||||
initplan_cost += initsubplan->startup_cost + initsubplan->per_call_cost;
|
||||
}
|
||||
|
||||
/*
|
||||
* Now adjust the costs.
|
||||
*/
|
||||
foreach(lc, final_rel->pathlist)
|
||||
{
|
||||
Path *path = (Path *) lfirst(lc);
|
||||
|
||||
path->startup_cost += initplan_cost;
|
||||
path->total_cost += initplan_cost;
|
||||
}
|
||||
|
||||
/* We needn't do set_cheapest() here, caller will do it */
|
||||
}
|
||||
|
||||
/*
|
||||
* SS_attach_initplans - attach initplans to topmost plan node
|
||||
*
|
||||
* Attach any initplans created in the current query level to the topmost plan
|
||||
* node for the query level, and increment that node's cost to account for
|
||||
* them. (The initPlans could actually go in any node at or above where
|
||||
* they're referenced, but there seems no reason to put them any lower than
|
||||
* the topmost node for the query level.)
|
||||
* Attach any initplans created in the current query level to the specified
|
||||
* plan node, which should normally be the topmost node for the query level.
|
||||
* (The initPlans could actually go in any node at or above where they're
|
||||
* referenced; but there seems no reason to put them any lower than the
|
||||
* topmost node, so we don't bother to track exactly where they came from.)
|
||||
* We do not touch the plan node's cost; the initplans should have been
|
||||
* accounted for in path costing.
|
||||
*/
|
||||
void
|
||||
SS_attach_initplans(PlannerInfo *root, Plan *plan)
|
||||
{
|
||||
ListCell *lc;
|
||||
|
||||
plan->initPlan = root->init_plans;
|
||||
foreach(lc, plan->initPlan)
|
||||
{
|
||||
SubPlan *initsubplan = (SubPlan *) lfirst(lc);
|
||||
Cost initplan_cost;
|
||||
|
||||
/*
|
||||
* Assume each initPlan gets run once during top plan startup. This
|
||||
* is a conservative overestimate, since in fact an initPlan might be
|
||||
* executed later than plan startup, or even not at all.
|
||||
*/
|
||||
initplan_cost = initsubplan->startup_cost + initsubplan->per_call_cost;
|
||||
|
||||
plan->startup_cost += initplan_cost;
|
||||
plan->total_cost += initplan_cost;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@ -2298,7 +2357,6 @@ finalize_plan(PlannerInfo *root, Plan *plan, Bitmapset *valid_params,
|
||||
|
||||
/* We must run SS_finalize_plan on the subquery */
|
||||
rel = find_base_rel(root, sscan->scan.scanrelid);
|
||||
Assert(rel->subplan == sscan->subplan);
|
||||
SS_finalize_plan(rel->subroot, sscan->subplan);
|
||||
|
||||
/* Now we can add its extParams to the parent's params */
|
||||
@ -2740,21 +2798,35 @@ finalize_primnode(Node *node, finalize_primnode_context *context)
|
||||
}
|
||||
|
||||
/*
|
||||
* SS_make_initplan_from_plan - given a plan tree, make it an InitPlan
|
||||
* SS_make_initplan_output_param - make a Param for an initPlan's output
|
||||
*
|
||||
* The plan is expected to return a scalar value of the given type/collation.
|
||||
* We build an EXPR_SUBLINK SubPlan node and put it into the initplan
|
||||
* list for the outer query level. A Param that represents the initplan's
|
||||
* output is returned.
|
||||
*
|
||||
* Note that in some cases the initplan may not ever appear in the finished
|
||||
* plan tree. If that happens, we'll have wasted a PARAM_EXEC slot, which
|
||||
* is no big deal.
|
||||
*/
|
||||
Param *
|
||||
SS_make_initplan_output_param(PlannerInfo *root,
|
||||
Oid resulttype, int32 resulttypmod,
|
||||
Oid resultcollation)
|
||||
{
|
||||
return generate_new_param(root, resulttype, resulttypmod, resultcollation);
|
||||
}
|
||||
|
||||
/*
|
||||
* SS_make_initplan_from_plan - given a plan tree, make it an InitPlan
|
||||
*
|
||||
* We build an EXPR_SUBLINK SubPlan node and put it into the initplan
|
||||
* list for the outer query level. A Param that represents the initplan's
|
||||
* output has already been assigned using SS_make_initplan_output_param.
|
||||
*/
|
||||
void
|
||||
SS_make_initplan_from_plan(PlannerInfo *root,
|
||||
PlannerInfo *subroot, Plan *plan,
|
||||
Oid resulttype, int32 resulttypmod,
|
||||
Oid resultcollation)
|
||||
Param *prm)
|
||||
{
|
||||
SubPlan *node;
|
||||
Param *prm;
|
||||
|
||||
/*
|
||||
* Add the subplan and its PlannerInfo to the global lists.
|
||||
@ -2769,9 +2841,12 @@ SS_make_initplan_from_plan(PlannerInfo *root,
|
||||
*/
|
||||
node = makeNode(SubPlan);
|
||||
node->subLinkType = EXPR_SUBLINK;
|
||||
node->plan_id = list_length(root->glob->subplans);
|
||||
node->plan_name = psprintf("InitPlan %d (returns $%d)",
|
||||
node->plan_id, prm->paramid);
|
||||
get_first_col_type(plan, &node->firstColType, &node->firstColTypmod,
|
||||
&node->firstColCollation);
|
||||
node->plan_id = list_length(root->glob->subplans);
|
||||
node->setParam = list_make1_int(prm->paramid);
|
||||
|
||||
root->init_plans = lappend(root->init_plans, node);
|
||||
|
||||
@ -2780,17 +2855,6 @@ SS_make_initplan_from_plan(PlannerInfo *root,
|
||||
* parParam and args lists remain empty.
|
||||
*/
|
||||
|
||||
/* Set costs of SubPlan using info from the plan tree */
|
||||
cost_subplan(subroot, node, plan);
|
||||
|
||||
/*
|
||||
* Make a Param that will be the subplan's output.
|
||||
*/
|
||||
prm = generate_new_param(root, resulttype, resulttypmod, resultcollation);
|
||||
node->setParam = list_make1_int(prm->paramid);
|
||||
|
||||
/* Label the subplan for EXPLAIN purposes */
|
||||
node->plan_name = psprintf("InitPlan %d (returns $%d)",
|
||||
node->plan_id, prm->paramid);
|
||||
|
||||
return prm;
|
||||
}
|
||||
|
@ -907,9 +907,14 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
|
||||
subroot->eq_classes = NIL;
|
||||
subroot->append_rel_list = NIL;
|
||||
subroot->rowMarks = NIL;
|
||||
memset(subroot->upper_rels, 0, sizeof(subroot->upper_rels));
|
||||
subroot->processed_tlist = NIL;
|
||||
subroot->grouping_map = NULL;
|
||||
subroot->minmax_aggs = NIL;
|
||||
subroot->hasInheritedTarget = false;
|
||||
subroot->hasRecursion = false;
|
||||
subroot->wt_param_id = -1;
|
||||
subroot->non_recursive_plan = NULL;
|
||||
subroot->non_recursive_path = NULL;
|
||||
|
||||
/* No CTEs to worry about */
|
||||
Assert(subquery->cteList == NIL);
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1142,7 +1142,27 @@ relation_excluded_by_constraints(PlannerInfo *root,
|
||||
List *safe_constraints;
|
||||
ListCell *lc;
|
||||
|
||||
/* Skip the test if constraint exclusion is disabled for the rel */
|
||||
/*
|
||||
* Regardless of the setting of constraint_exclusion, detect
|
||||
* constant-FALSE-or-NULL restriction clauses. Because const-folding will
|
||||
* reduce "anything AND FALSE" to just "FALSE", any such case should
|
||||
* result in exactly one baserestrictinfo entry. This doesn't fire very
|
||||
* often, but it seems cheap enough to be worth doing anyway. (Without
|
||||
* this, we'd miss some optimizations that 9.5 and earlier found via much
|
||||
* more roundabout methods.)
|
||||
*/
|
||||
if (list_length(rel->baserestrictinfo) == 1)
|
||||
{
|
||||
RestrictInfo *rinfo = (RestrictInfo *) linitial(rel->baserestrictinfo);
|
||||
Expr *clause = rinfo->clause;
|
||||
|
||||
if (clause && IsA(clause, Const) &&
|
||||
(((Const *) clause)->constisnull ||
|
||||
!DatumGetBool(((Const *) clause)->constvalue)))
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Skip further tests if constraint exclusion is disabled for the rel */
|
||||
if (constraint_exclusion == CONSTRAINT_EXCLUSION_OFF ||
|
||||
(constraint_exclusion == CONSTRAINT_EXCLUSION_PARTITION &&
|
||||
!(rel->reloptkind == RELOPT_OTHER_MEMBER_REL ||
|
||||
|
@ -107,6 +107,7 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptKind reloptkind)
|
||||
rel->consider_param_startup = false; /* might get changed later */
|
||||
rel->consider_parallel = false; /* might get changed later */
|
||||
rel->reltarget.exprs = NIL;
|
||||
rel->reltarget.sortgrouprefs = NULL;
|
||||
rel->reltarget.cost.startup = 0;
|
||||
rel->reltarget.cost.per_tuple = 0;
|
||||
rel->reltarget.width = 0;
|
||||
@ -128,7 +129,6 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptKind reloptkind)
|
||||
rel->pages = 0;
|
||||
rel->tuples = 0;
|
||||
rel->allvisfrac = 0;
|
||||
rel->subplan = NULL;
|
||||
rel->subroot = NULL;
|
||||
rel->subplan_params = NIL;
|
||||
rel->serverid = InvalidOid;
|
||||
@ -394,6 +394,7 @@ build_join_rel(PlannerInfo *root,
|
||||
joinrel->consider_param_startup = false;
|
||||
joinrel->consider_parallel = false;
|
||||
joinrel->reltarget.exprs = NIL;
|
||||
joinrel->reltarget.sortgrouprefs = NULL;
|
||||
joinrel->reltarget.cost.startup = 0;
|
||||
joinrel->reltarget.cost.per_tuple = 0;
|
||||
joinrel->reltarget.width = 0;
|
||||
@ -422,7 +423,6 @@ build_join_rel(PlannerInfo *root,
|
||||
joinrel->pages = 0;
|
||||
joinrel->tuples = 0;
|
||||
joinrel->allvisfrac = 0;
|
||||
joinrel->subplan = NULL;
|
||||
joinrel->subroot = NULL;
|
||||
joinrel->subplan_params = NIL;
|
||||
joinrel->serverid = InvalidOid;
|
||||
@ -839,6 +839,61 @@ build_empty_join_rel(PlannerInfo *root)
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* fetch_upper_rel
|
||||
* Build a RelOptInfo describing some post-scan/join query processing,
|
||||
* or return a pre-existing one if somebody already built it.
|
||||
*
|
||||
* An "upper" relation is identified by an UpperRelationKind and a Relids set.
|
||||
* The meaning of the Relids set is not specified here, and very likely will
|
||||
* vary for different relation kinds.
|
||||
*
|
||||
* Most of the fields in an upper-level RelOptInfo are not used and are not
|
||||
* set here (though makeNode should ensure they're zeroes). We basically only
|
||||
* care about fields that are of interest to add_path() and set_cheapest().
|
||||
*/
|
||||
RelOptInfo *
|
||||
fetch_upper_rel(PlannerInfo *root, UpperRelationKind kind, Relids relids)
|
||||
{
|
||||
RelOptInfo *upperrel;
|
||||
ListCell *lc;
|
||||
|
||||
/*
|
||||
* For the moment, our indexing data structure is just a List for each
|
||||
* relation kind. If we ever get so many of one kind that this stops
|
||||
* working well, we can improve it. No code outside this function should
|
||||
* assume anything about how to find a particular upperrel.
|
||||
*/
|
||||
|
||||
/* If we already made this upperrel for the query, return it */
|
||||
foreach(lc, root->upper_rels[kind])
|
||||
{
|
||||
upperrel = (RelOptInfo *) lfirst(lc);
|
||||
|
||||
if (bms_equal(upperrel->relids, relids))
|
||||
return upperrel;
|
||||
}
|
||||
|
||||
upperrel = makeNode(RelOptInfo);
|
||||
upperrel->reloptkind = RELOPT_UPPER_REL;
|
||||
upperrel->relids = bms_copy(relids);
|
||||
|
||||
/* cheap startup cost is interesting iff not all tuples to be retrieved */
|
||||
upperrel->consider_startup = (root->tuple_fraction > 0);
|
||||
upperrel->consider_param_startup = false;
|
||||
upperrel->consider_parallel = false; /* might get changed later */
|
||||
upperrel->pathlist = NIL;
|
||||
upperrel->cheapest_startup_path = NULL;
|
||||
upperrel->cheapest_total_path = NULL;
|
||||
upperrel->cheapest_unique_path = NULL;
|
||||
upperrel->cheapest_parameterized_paths = NIL;
|
||||
|
||||
root->upper_rels[kind] = lappend(root->upper_rels[kind], upperrel);
|
||||
|
||||
return upperrel;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* find_childrel_appendrelinfo
|
||||
* Get the AppendRelInfo associated with an appendrel child rel.
|
||||
|
@ -74,13 +74,12 @@ tlist_member_ignore_relabel(Node *node, List *targetlist)
|
||||
/*
|
||||
* tlist_member_match_var
|
||||
* Same as above, except that we match the provided Var on the basis
|
||||
* of varno/varattno/varlevelsup only, rather than using full equal().
|
||||
* of varno/varattno/varlevelsup/vartype only, rather than full equal().
|
||||
*
|
||||
* This is needed in some cases where we can't be sure of an exact typmod
|
||||
* match. It's probably a good idea to check the vartype anyway, but
|
||||
* we leave it to the caller to apply any suitable sanity checks.
|
||||
* match. For safety, though, we insist on vartype match.
|
||||
*/
|
||||
TargetEntry *
|
||||
static TargetEntry *
|
||||
tlist_member_match_var(Var *var, List *targetlist)
|
||||
{
|
||||
ListCell *temp;
|
||||
@ -94,7 +93,8 @@ tlist_member_match_var(Var *var, List *targetlist)
|
||||
continue;
|
||||
if (var->varno == tlvar->varno &&
|
||||
var->varattno == tlvar->varattno &&
|
||||
var->varlevelsup == tlvar->varlevelsup)
|
||||
var->varlevelsup == tlvar->varlevelsup &&
|
||||
var->vartype == tlvar->vartype)
|
||||
return tlentry;
|
||||
}
|
||||
return NULL;
|
||||
@ -316,6 +316,34 @@ tlist_same_collations(List *tlist, List *colCollations, bool junkOK)
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* apply_tlist_labeling
|
||||
* Apply the TargetEntry labeling attributes of src_tlist to dest_tlist
|
||||
*
|
||||
* This is useful for reattaching column names etc to a plan's final output
|
||||
* targetlist.
|
||||
*/
|
||||
void
|
||||
apply_tlist_labeling(List *dest_tlist, List *src_tlist)
|
||||
{
|
||||
ListCell *ld,
|
||||
*ls;
|
||||
|
||||
Assert(list_length(dest_tlist) == list_length(src_tlist));
|
||||
forboth(ld, dest_tlist, ls, src_tlist)
|
||||
{
|
||||
TargetEntry *dest_tle = (TargetEntry *) lfirst(ld);
|
||||
TargetEntry *src_tle = (TargetEntry *) lfirst(ls);
|
||||
|
||||
Assert(dest_tle->resno == src_tle->resno);
|
||||
dest_tle->resname = src_tle->resname;
|
||||
dest_tle->ressortgroupref = src_tle->ressortgroupref;
|
||||
dest_tle->resorigtbl = src_tle->resorigtbl;
|
||||
dest_tle->resorigcol = src_tle->resorigcol;
|
||||
dest_tle->resjunk = src_tle->resjunk;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* get_sortgroupref_tle
|
||||
@ -506,3 +534,119 @@ grouping_is_hashable(List *groupClause)
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/*****************************************************************************
|
||||
* PathTarget manipulation functions
|
||||
*
|
||||
* PathTarget is a somewhat stripped-down version of a full targetlist; it
|
||||
* omits all the TargetEntry decoration except (optionally) sortgroupref data,
|
||||
* and it adds evaluation cost and output data width info.
|
||||
*****************************************************************************/
|
||||
|
||||
/*
|
||||
* make_pathtarget_from_tlist
|
||||
* Construct a PathTarget equivalent to the given targetlist.
|
||||
*
|
||||
* This leaves the cost and width fields as zeroes. Most callers will want
|
||||
* to use create_pathtarget(), so as to get those set.
|
||||
*/
|
||||
PathTarget *
|
||||
make_pathtarget_from_tlist(List *tlist)
|
||||
{
|
||||
PathTarget *target = (PathTarget *) palloc0(sizeof(PathTarget));
|
||||
int i;
|
||||
ListCell *lc;
|
||||
|
||||
target->sortgrouprefs = (Index *) palloc(list_length(tlist) * sizeof(Index));
|
||||
|
||||
i = 0;
|
||||
foreach(lc, tlist)
|
||||
{
|
||||
TargetEntry *tle = (TargetEntry *) lfirst(lc);
|
||||
|
||||
target->exprs = lappend(target->exprs, tle->expr);
|
||||
target->sortgrouprefs[i] = tle->ressortgroupref;
|
||||
i++;
|
||||
}
|
||||
|
||||
return target;
|
||||
}
|
||||
|
||||
/*
|
||||
* make_tlist_from_pathtarget
|
||||
* Construct a targetlist from a PathTarget.
|
||||
*/
|
||||
List *
|
||||
make_tlist_from_pathtarget(PathTarget *target)
|
||||
{
|
||||
List *tlist = NIL;
|
||||
int i;
|
||||
ListCell *lc;
|
||||
|
||||
i = 0;
|
||||
foreach(lc, target->exprs)
|
||||
{
|
||||
Expr *expr = (Expr *) lfirst(lc);
|
||||
TargetEntry *tle;
|
||||
|
||||
tle = makeTargetEntry(expr,
|
||||
i + 1,
|
||||
NULL,
|
||||
false);
|
||||
if (target->sortgrouprefs)
|
||||
tle->ressortgroupref = target->sortgrouprefs[i];
|
||||
tlist = lappend(tlist, tle);
|
||||
i++;
|
||||
}
|
||||
|
||||
return tlist;
|
||||
}
|
||||
|
||||
/*
|
||||
* apply_pathtarget_labeling_to_tlist
|
||||
* Apply any sortgrouprefs in the PathTarget to matching tlist entries
|
||||
*
|
||||
* Here, we do not assume that the tlist entries are one-for-one with the
|
||||
* PathTarget. The intended use of this function is to deal with cases
|
||||
* where createplan.c has decided to use some other tlist and we have
|
||||
* to identify what matches exist.
|
||||
*/
|
||||
void
|
||||
apply_pathtarget_labeling_to_tlist(List *tlist, PathTarget *target)
|
||||
{
|
||||
int i;
|
||||
ListCell *lc;
|
||||
|
||||
/* Nothing to do if PathTarget has no sortgrouprefs data */
|
||||
if (target->sortgrouprefs == NULL)
|
||||
return;
|
||||
|
||||
i = 0;
|
||||
foreach(lc, target->exprs)
|
||||
{
|
||||
Expr *expr = (Expr *) lfirst(lc);
|
||||
TargetEntry *tle;
|
||||
|
||||
if (target->sortgrouprefs[i])
|
||||
{
|
||||
/*
|
||||
* For Vars, use tlist_member_match_var's weakened matching rule;
|
||||
* this allows us to deal with some cases where a set-returning
|
||||
* function has been inlined, so that we now have more knowledge
|
||||
* about what it returns than we did when the original Var was
|
||||
* created. Otherwise, use regular equal() to see if there's a
|
||||
* matching TLE. (In current usage, only the Var case is actually
|
||||
* needed; but it seems best to have sane behavior here for
|
||||
* non-Vars too.)
|
||||
*/
|
||||
if (expr && IsA(expr, Var))
|
||||
tle = tlist_member_match_var((Var *) expr, tlist);
|
||||
else
|
||||
tle = tlist_member((Node *) expr, tlist);
|
||||
if (tle)
|
||||
tle->ressortgroupref = target->sortgrouprefs[i];
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user