提交 3ced8837 编写于 作者: T Tom Lane

Simplify query_planner's API by having it return the top-level RelOptInfo.

Formerly, query_planner returned one or possibly two Paths for the topmost
join relation, so that grouping_planner didn't see the join RelOptInfo
(at least not directly; it didn't have any hesitation about examining
cheapest_path->parent, though).  However, correct selection of the Paths
involved a significant amount of coupling between query_planner and
grouping_planner, a problem which has gotten worse over time.  It seems
best to give up on this API choice and instead return the topmost
RelOptInfo explicitly.  Then grouping_planner can pull out the Paths it
wants from the rel's path list.  In this way we can remove all knowledge
of grouping behaviors from query_planner.

The only real benefit of the old way is that in the case of an empty
FROM clause, we never made any RelOptInfos at all, just a Path.  Now
we have to gin up a dummy RelOptInfo to represent the empty FROM clause.
That's not a very big deal though.

While at it, simplify query_planner's API a bit more by having the caller
set up root->tuple_fraction and root->limit_tuples, rather than passing
those values as separate parameters.  Since query_planner no longer does
anything with either value, requiring it to fill the PlannerInfo fields
seemed pretty arbitrary.

This patch just rearranges code; it doesn't (intentionally) change any
behaviors.  Followup patches will do more interesting things.
上级 841c29c8
...@@ -372,10 +372,10 @@ generated during the optimization process are marked with their sort order ...@@ -372,10 +372,10 @@ generated during the optimization process are marked with their sort order
It is also possible to avoid an explicit sort step to implement a user's It is also possible to avoid an explicit sort step to implement a user's
ORDER BY clause if the final path has the right ordering already, so the ORDER BY clause if the final path has the right ordering already, so the
sort ordering is of interest even at the top level. query_planner() will sort ordering is of interest even at the top level. grouping_planner() will
look for the cheapest path with a sort order matching the desired order, look for the cheapest path with a sort order matching the desired order,
and grouping_planner() will compare its cost to the cost of using the then compare its cost to the cost of using the cheapest-overall path and
cheapest-overall path and doing an explicit sort. doing an explicit sort on that.
When we are generating paths for a particular RelOptInfo, we discard a path When we are generating paths for a particular RelOptInfo, we discard a path
if it is more expensive than another known path that has the same or better if it is more expensive than another known path that has the same or better
......
...@@ -316,6 +316,7 @@ find_minmax_aggs_walker(Node *node, List **context) ...@@ -316,6 +316,7 @@ find_minmax_aggs_walker(Node *node, List **context)
Assert(aggref->agglevelsup == 0); Assert(aggref->agglevelsup == 0);
if (list_length(aggref->args) != 1) if (list_length(aggref->args) != 1)
return true; /* it couldn't be MIN/MAX */ return true; /* it couldn't be MIN/MAX */
/* /*
* ORDER BY is usually irrelevant for MIN/MAX, but it can change the * ORDER BY is usually irrelevant for MIN/MAX, but it can change the
* outcome if the aggsortop's operator class recognizes non-identical * outcome if the aggsortop's operator class recognizes non-identical
...@@ -329,6 +330,7 @@ find_minmax_aggs_walker(Node *node, List **context) ...@@ -329,6 +330,7 @@ find_minmax_aggs_walker(Node *node, List **context)
*/ */
if (aggref->aggorder != NIL) if (aggref->aggorder != NIL)
return true; return true;
/* /*
* We might implement the optimization when a FILTER clause is present * We might implement the optimization when a FILTER clause is present
* by adding the filter to the quals of the generated subquery. * by adding the filter to the quals of the generated subquery.
...@@ -399,9 +401,8 @@ build_minmax_path(PlannerInfo *root, MinMaxAggInfo *mminfo, ...@@ -399,9 +401,8 @@ build_minmax_path(PlannerInfo *root, MinMaxAggInfo *mminfo,
TargetEntry *tle; TargetEntry *tle;
NullTest *ntest; NullTest *ntest;
SortGroupClause *sortcl; SortGroupClause *sortcl;
Path *cheapest_path; RelOptInfo *final_rel;
Path *sorted_path; Path *sorted_path;
double dNumGroups;
Cost path_cost; Cost path_cost;
double path_fraction; double path_fraction;
...@@ -470,25 +471,28 @@ build_minmax_path(PlannerInfo *root, MinMaxAggInfo *mminfo, ...@@ -470,25 +471,28 @@ build_minmax_path(PlannerInfo *root, MinMaxAggInfo *mminfo,
* Generate the best paths for this query, telling query_planner that we * Generate the best paths for this query, telling query_planner that we
* have LIMIT 1. * have LIMIT 1.
*/ */
query_planner(subroot, parse->targetList, 1.0, 1.0, subroot->tuple_fraction = 1.0;
minmax_qp_callback, NULL, subroot->limit_tuples = 1.0;
&cheapest_path, &sorted_path, &dNumGroups);
final_rel = query_planner(subroot, parse->targetList,
minmax_qp_callback, NULL);
/* /*
* Fail if no presorted path. However, if query_planner determines that * Get the best presorted path, that being the one that's cheapest for
* the presorted path is also the cheapest, it will set sorted_path to * fetching just one row. If there's no such path, fail.
* NULL ... don't be fooled. (This is kind of a pain here, but it
* simplifies life for grouping_planner, so leave it be.)
*/ */
if (final_rel->rows > 1.0)
path_fraction = 1.0 / final_rel->rows;
else
path_fraction = 1.0;
sorted_path =
get_cheapest_fractional_path_for_pathkeys(final_rel->pathlist,
subroot->query_pathkeys,
NULL,
path_fraction);
if (!sorted_path) if (!sorted_path)
{ return false;
if (cheapest_path &&
pathkeys_contained_in(subroot->sort_pathkeys,
cheapest_path->pathkeys))
sorted_path = cheapest_path;
else
return false;
}
/* /*
* Determine cost to get just the first row of the presorted path. * Determine cost to get just the first row of the presorted path.
...@@ -496,11 +500,6 @@ build_minmax_path(PlannerInfo *root, MinMaxAggInfo *mminfo, ...@@ -496,11 +500,6 @@ build_minmax_path(PlannerInfo *root, MinMaxAggInfo *mminfo,
* Note: cost calculation here should match * Note: cost calculation here should match
* compare_fractional_path_costs(). * compare_fractional_path_costs().
*/ */
if (sorted_path->parent->rows > 1.0)
path_fraction = 1.0 / sorted_path->parent->rows;
else
path_fraction = 1.0;
path_cost = sorted_path->startup_cost + path_cost = sorted_path->startup_cost +
path_fraction * (sorted_path->total_cost - sorted_path->startup_cost); path_fraction * (sorted_path->total_cost - sorted_path->startup_cost);
......
...@@ -20,14 +20,10 @@ ...@@ -20,14 +20,10 @@
*/ */
#include "postgres.h" #include "postgres.h"
#include "miscadmin.h"
#include "optimizer/cost.h"
#include "optimizer/pathnode.h" #include "optimizer/pathnode.h"
#include "optimizer/paths.h" #include "optimizer/paths.h"
#include "optimizer/placeholder.h" #include "optimizer/placeholder.h"
#include "optimizer/planmain.h" #include "optimizer/planmain.h"
#include "optimizer/tlist.h"
#include "utils/selfuncs.h"
/* /*
...@@ -36,78 +32,49 @@ ...@@ -36,78 +32,49 @@
* which may involve joins but not any fancier features. * which may involve joins but not any fancier features.
* *
* Since query_planner does not handle the toplevel processing (grouping, * Since query_planner does not handle the toplevel processing (grouping,
* sorting, etc) it cannot select the best path by itself. It selects * sorting, etc) it cannot select the best path by itself. Instead, it
* two paths: the cheapest path that produces all the required tuples, * returns the RelOptInfo for the top level of joining, and the caller
* independent of any ordering considerations, and the cheapest path that * (grouping_planner) can choose one of the surviving paths for the rel.
* produces the expected fraction of the required tuples in the required * Normally it would choose either the rel's cheapest path, or the cheapest
* ordering, if there is a path that is cheaper for this than just sorting * path for the desired sort order.
* the output of the cheapest overall path. The caller (grouping_planner)
* will make the final decision about which to use.
* *
* Input parameters:
* root describes the query to plan * root describes the query to plan
* tlist is the target list the query should produce * tlist is the target list the query should produce
* (this is NOT necessarily root->parse->targetList!) * (this is NOT necessarily root->parse->targetList!)
* tuple_fraction is the fraction of tuples we expect will be retrieved
* limit_tuples is a hard limit on number of tuples to retrieve,
* or -1 if no limit
* qp_callback is a function to compute query_pathkeys once it's safe to do so * qp_callback is a function to compute query_pathkeys once it's safe to do so
* qp_extra is optional extra data to pass to qp_callback * qp_extra is optional extra data to pass to qp_callback
* *
* Output parameters:
* *cheapest_path receives the overall-cheapest path for the query
* *sorted_path receives the cheapest presorted path for the query,
* if any (NULL if there is no useful presorted path)
* *num_groups receives the estimated number of groups, or 1 if query
* does not use grouping
*
* Note: the PlannerInfo node also includes a query_pathkeys field, which * Note: the PlannerInfo node also includes a query_pathkeys field, which
* tells query_planner the sort order that is desired in the final output * tells query_planner the sort order that is desired in the final output
* plan. This value is *not* available at call time, but is computed by * plan. This value is *not* available at call time, but is computed by
* qp_callback once we have completed merging the query's equivalence classes. * qp_callback once we have completed merging the query's equivalence classes.
* (We cannot construct canonical pathkeys until that's done.) * (We cannot construct canonical pathkeys until that's done.)
*
* tuple_fraction is interpreted as follows:
* 0: expect all tuples to be retrieved (normal case)
* 0 < tuple_fraction < 1: expect the given fraction of tuples available
* from the plan to be retrieved
* tuple_fraction >= 1: tuple_fraction is the absolute number of tuples
* expected to be retrieved (ie, a LIMIT specification)
* Note that a nonzero tuple_fraction could come from outer context; it is
* therefore not redundant with limit_tuples. We use limit_tuples to determine
* whether a bounded sort can be used at runtime.
*/ */
void RelOptInfo *
query_planner(PlannerInfo *root, List *tlist, query_planner(PlannerInfo *root, List *tlist,
double tuple_fraction, double limit_tuples, query_pathkeys_callback qp_callback, void *qp_extra)
query_pathkeys_callback qp_callback, void *qp_extra,
Path **cheapest_path, Path **sorted_path,
double *num_groups)
{ {
Query *parse = root->parse; Query *parse = root->parse;
List *joinlist; List *joinlist;
RelOptInfo *final_rel; RelOptInfo *final_rel;
Path *cheapestpath;
Path *sortedpath;
Index rti; Index rti;
double total_pages; double total_pages;
/* Make tuple_fraction, limit_tuples accessible to lower-level routines */
root->tuple_fraction = tuple_fraction;
root->limit_tuples = limit_tuples;
*num_groups = 1; /* default result */
/* /*
* If the query has an empty join tree, then it's something easy like * If the query has an empty join tree, then it's something easy like
* "SELECT 2+2;" or "INSERT ... VALUES()". Fall through quickly. * "SELECT 2+2;" or "INSERT ... VALUES()". Fall through quickly.
*/ */
if (parse->jointree->fromlist == NIL) if (parse->jointree->fromlist == NIL)
{ {
/* We need a trivial path result */ /* We need a dummy joinrel to describe the empty set of baserels */
*cheapest_path = (Path *) final_rel = build_empty_join_rel(root);
create_result_path((List *) parse->jointree->quals);
*sorted_path = NULL; /* The only path for it is a trivial Result path */
add_path(final_rel, (Path *)
create_result_path((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 * We still are required to call qp_callback, in case it's something
...@@ -115,7 +82,8 @@ query_planner(PlannerInfo *root, List *tlist, ...@@ -115,7 +82,8 @@ query_planner(PlannerInfo *root, List *tlist,
*/ */
root->canon_pathkeys = NIL; root->canon_pathkeys = NIL;
(*qp_callback) (root, qp_extra); (*qp_callback) (root, qp_extra);
return;
return final_rel;
} }
/* /*
...@@ -259,165 +227,10 @@ query_planner(PlannerInfo *root, List *tlist, ...@@ -259,165 +227,10 @@ query_planner(PlannerInfo *root, List *tlist,
*/ */
final_rel = make_one_rel(root, joinlist); final_rel = make_one_rel(root, joinlist);
/* Check that we got at least one usable path */
if (!final_rel || !final_rel->cheapest_total_path || if (!final_rel || !final_rel->cheapest_total_path ||
final_rel->cheapest_total_path->param_info != NULL) final_rel->cheapest_total_path->param_info != NULL)
elog(ERROR, "failed to construct the join relation"); elog(ERROR, "failed to construct the join relation");
/* return final_rel;
* If there's grouping going on, estimate the number of result groups. We
* couldn't do this any earlier because it depends on relation size
* estimates that were set up above.
*
* Then convert tuple_fraction to fractional form if it is absolute, and
* adjust it based on the knowledge that grouping_planner will be doing
* grouping or aggregation work with our result.
*
* This introduces some undesirable coupling between this code and
* grouping_planner, but the alternatives seem even uglier; we couldn't
* pass back completed paths without making these decisions here.
*/
if (parse->groupClause)
{
List *groupExprs;
groupExprs = get_sortgrouplist_exprs(parse->groupClause,
parse->targetList);
*num_groups = estimate_num_groups(root,
groupExprs,
final_rel->rows);
/*
* In GROUP BY mode, an absolute LIMIT is relative to the number of
* groups not the number of tuples. If the caller gave us a fraction,
* keep it as-is. (In both cases, we are effectively assuming that
* all the groups are about the same size.)
*/
if (tuple_fraction >= 1.0)
tuple_fraction /= *num_groups;
/*
* If both GROUP BY and ORDER BY are specified, we will need two
* levels of sort --- and, therefore, certainly need to read all the
* tuples --- unless ORDER BY is a subset of GROUP BY. Likewise if we
* have both DISTINCT and GROUP BY, or if we have a window
* specification not compatible with the GROUP BY.
*/
if (!pathkeys_contained_in(root->sort_pathkeys, root->group_pathkeys) ||
!pathkeys_contained_in(root->distinct_pathkeys, root->group_pathkeys) ||
!pathkeys_contained_in(root->window_pathkeys, root->group_pathkeys))
tuple_fraction = 0.0;
/* In any case, limit_tuples shouldn't be specified here */
Assert(limit_tuples < 0);
}
else if (parse->hasAggs || root->hasHavingQual)
{
/*
* Ungrouped aggregate will certainly want to read all the tuples, and
* it will deliver a single result row (so leave *num_groups 1).
*/
tuple_fraction = 0.0;
/* limit_tuples shouldn't be specified here */
Assert(limit_tuples < 0);
}
else if (parse->distinctClause)
{
/*
* Since there was no grouping or aggregation, it's reasonable to
* assume the UNIQUE filter has effects comparable to GROUP BY. Return
* the estimated number of output rows for use by caller. (If DISTINCT
* is used with grouping, we ignore its effects for rowcount
* estimation purposes; this amounts to assuming the grouped rows are
* distinct already.)
*/
List *distinctExprs;
distinctExprs = get_sortgrouplist_exprs(parse->distinctClause,
parse->targetList);
*num_groups = estimate_num_groups(root,
distinctExprs,
final_rel->rows);
/*
* Adjust tuple_fraction the same way as for GROUP BY, too.
*/
if (tuple_fraction >= 1.0)
tuple_fraction /= *num_groups;
/* limit_tuples shouldn't be specified here */
Assert(limit_tuples < 0);
}
else
{
/*
* Plain non-grouped, non-aggregated query: an absolute tuple fraction
* can be divided by the number of tuples.
*/
if (tuple_fraction >= 1.0)
tuple_fraction /= final_rel->rows;
}
/*
* Pick out the cheapest-total path and the cheapest presorted path for
* the requested pathkeys (if there is one). We should take the tuple
* fraction into account when selecting the cheapest presorted path, but
* not when selecting the cheapest-total path, since if we have to sort
* then we'll have to fetch all the tuples. (But there's a special case:
* if query_pathkeys is NIL, meaning order doesn't matter, then the
* "cheapest presorted" path will be the cheapest overall for the tuple
* fraction.)
*
* The cheapest-total path is also the one to use if grouping_planner
* decides to use hashed aggregation, so we return it separately even if
* this routine thinks the presorted path is the winner.
*/
cheapestpath = final_rel->cheapest_total_path;
sortedpath =
get_cheapest_fractional_path_for_pathkeys(final_rel->pathlist,
root->query_pathkeys,
NULL,
tuple_fraction);
/* Don't return same path in both guises; just wastes effort */
if (sortedpath == cheapestpath)
sortedpath = NULL;
/*
* Forget about the presorted path if it would be cheaper to sort the
* cheapest-total path. Here we need consider only the behavior at the
* tuple fraction point.
*/
if (sortedpath)
{
Path sort_path; /* dummy for result of cost_sort */
if (root->query_pathkeys == NIL ||
pathkeys_contained_in(root->query_pathkeys,
cheapestpath->pathkeys))
{
/* No sort needed for cheapest path */
sort_path.startup_cost = cheapestpath->startup_cost;
sort_path.total_cost = cheapestpath->total_cost;
}
else
{
/* Figure cost for sorting */
cost_sort(&sort_path, root, root->query_pathkeys,
cheapestpath->total_cost,
final_rel->rows, final_rel->width,
0.0, work_mem, limit_tuples);
}
if (compare_fractional_path_costs(sortedpath, &sort_path,
tuple_fraction) > 0)
{
/* Presorted path is a loser */
sortedpath = NULL;
}
}
*cheapest_path = cheapestpath;
*sorted_path = sortedpath;
} }
...@@ -39,6 +39,7 @@ ...@@ -39,6 +39,7 @@
#include "parser/parsetree.h" #include "parser/parsetree.h"
#include "rewrite/rewriteManip.h" #include "rewrite/rewriteManip.h"
#include "utils/rel.h" #include "utils/rel.h"
#include "utils/selfuncs.h"
/* GUC parameter */ /* GUC parameter */
...@@ -1125,10 +1126,10 @@ grouping_planner(PlannerInfo *root, double tuple_fraction) ...@@ -1125,10 +1126,10 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
{ {
/* No set operations, do regular planning */ /* No set operations, do regular planning */
List *sub_tlist; List *sub_tlist;
double sub_limit_tuples;
AttrNumber *groupColIdx = NULL; AttrNumber *groupColIdx = NULL;
bool need_tlist_eval = true; bool need_tlist_eval = true;
standard_qp_extra qp_extra; standard_qp_extra qp_extra;
RelOptInfo *final_rel;
Path *cheapest_path; Path *cheapest_path;
Path *sorted_path; Path *sorted_path;
Path *best_path; Path *best_path;
...@@ -1204,6 +1205,9 @@ grouping_planner(PlannerInfo *root, double tuple_fraction) ...@@ -1204,6 +1205,9 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
preprocess_minmax_aggregates(root, tlist); preprocess_minmax_aggregates(root, tlist);
} }
/* Make tuple_fraction accessible to lower-level routines */
root->tuple_fraction = tuple_fraction;
/* /*
* Figure out whether there's a hard limit on the number of rows that * Figure out whether there's a hard limit on the number of rows that
* query_planner's result subplan needs to return. Even if we know a * query_planner's result subplan needs to return. Even if we know a
...@@ -1215,9 +1219,9 @@ grouping_planner(PlannerInfo *root, double tuple_fraction) ...@@ -1215,9 +1219,9 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
parse->hasAggs || parse->hasAggs ||
parse->hasWindowFuncs || parse->hasWindowFuncs ||
root->hasHavingQual) root->hasHavingQual)
sub_limit_tuples = -1.0; root->limit_tuples = -1.0;
else else
sub_limit_tuples = limit_tuples; root->limit_tuples = limit_tuples;
/* Set up data needed by standard_qp_callback */ /* Set up data needed by standard_qp_callback */
qp_extra.tlist = tlist; qp_extra.tlist = tlist;
...@@ -1225,31 +1229,164 @@ grouping_planner(PlannerInfo *root, double tuple_fraction) ...@@ -1225,31 +1229,164 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
/* /*
* Generate the best unsorted and presorted paths for this Query (but * Generate the best unsorted and presorted paths for this Query (but
* note there may not be any presorted path). We also generate (in * note there may not be any presorted paths). We also generate (in
* standard_qp_callback) pathkey representations of the query's sort * standard_qp_callback) pathkey representations of the query's sort
* clause, distinct clause, etc. query_planner will also estimate the * clause, distinct clause, etc.
* number of groups in the query.
*/ */
query_planner(root, sub_tlist, tuple_fraction, sub_limit_tuples, final_rel = query_planner(root, sub_tlist,
standard_qp_callback, &qp_extra, standard_qp_callback, &qp_extra);
&cheapest_path, &sorted_path, &dNumGroups);
/* /*
* Extract rowcount and width estimates for possible use in grouping * Extract rowcount and width estimates for use below.
* decisions. Beware here of the possibility that
* cheapest_path->parent is NULL (ie, there is no FROM clause).
*/ */
if (cheapest_path->parent) path_rows = final_rel->rows;
path_width = final_rel->width;
/*
* If there's grouping going on, estimate the number of result groups.
* We couldn't do this any earlier because it depends on relation size
* estimates that are created within query_planner().
*
* Then convert tuple_fraction to fractional form if it is absolute,
* and if grouping or aggregation is involved, adjust tuple_fraction
* to describe the fraction of the underlying un-aggregated tuples
* that will be fetched.
*/
dNumGroups = 1; /* in case not grouping */
if (parse->groupClause)
{
List *groupExprs;
groupExprs = get_sortgrouplist_exprs(parse->groupClause,
parse->targetList);
dNumGroups = estimate_num_groups(root, groupExprs, path_rows);
/*
* In GROUP BY mode, an absolute LIMIT is relative to the number
* of groups not the number of tuples. If the caller gave us a
* fraction, keep it as-is. (In both cases, we are effectively
* assuming that all the groups are about the same size.)
*/
if (tuple_fraction >= 1.0)
tuple_fraction /= dNumGroups;
/*
* If both GROUP BY and ORDER BY are specified, we will need two
* levels of sort --- and, therefore, certainly need to read all
* the tuples --- unless ORDER BY is a subset of GROUP BY.
* Likewise if we have both DISTINCT and GROUP BY, or if we have a
* window specification not compatible with the GROUP BY.
*/
if (!pathkeys_contained_in(root->sort_pathkeys,
root->group_pathkeys) ||
!pathkeys_contained_in(root->distinct_pathkeys,
root->group_pathkeys) ||
!pathkeys_contained_in(root->window_pathkeys,
root->group_pathkeys))
tuple_fraction = 0.0;
}
else if (parse->hasAggs || root->hasHavingQual)
{ {
path_rows = cheapest_path->parent->rows; /*
path_width = cheapest_path->parent->width; * Ungrouped aggregate will certainly want to read all the tuples,
* and it will deliver a single result row (so leave dNumGroups
* set to 1).
*/
tuple_fraction = 0.0;
}
else if (parse->distinctClause)
{
/*
* Since there was no grouping or aggregation, it's reasonable to
* assume the UNIQUE filter has effects comparable to GROUP BY.
* (If DISTINCT is used with grouping, we ignore its effects for
* rowcount estimation purposes; this amounts to assuming the
* grouped rows are distinct already.)
*/
List *distinctExprs;
distinctExprs = get_sortgrouplist_exprs(parse->distinctClause,
parse->targetList);
dNumGroups = estimate_num_groups(root, distinctExprs, path_rows);
/*
* Adjust tuple_fraction the same way as for GROUP BY, too.
*/
if (tuple_fraction >= 1.0)
tuple_fraction /= dNumGroups;
} }
else else
{ {
path_rows = 1; /* assume non-set result */ /*
path_width = 100; /* arbitrary */ * Plain non-grouped, non-aggregated query: an absolute tuple
* fraction can be divided by the number of tuples.
*/
if (tuple_fraction >= 1.0)
tuple_fraction /= path_rows;
}
/*
* Pick out the cheapest-total path as well as the cheapest presorted
* path for the requested pathkeys (if there is one). We should take
* the tuple fraction into account when selecting the cheapest
* presorted path, but not when selecting the cheapest-total path,
* since if we have to sort then we'll have to fetch all the tuples.
* (But there's a special case: if query_pathkeys is NIL, meaning
* order doesn't matter, then the "cheapest presorted" path will be
* the cheapest overall for the tuple fraction.)
*/
cheapest_path = final_rel->cheapest_total_path;
sorted_path =
get_cheapest_fractional_path_for_pathkeys(final_rel->pathlist,
root->query_pathkeys,
NULL,
tuple_fraction);
/* Don't consider same path in both guises; just wastes effort */
if (sorted_path == cheapest_path)
sorted_path = NULL;
/*
* Forget about the presorted path if it would be cheaper to sort the
* cheapest-total path. Here we need consider only the behavior at
* the tuple_fraction point. Also, limit_tuples is only relevant if
* not grouping/aggregating, so use root->limit_tuples in the
* cost_sort call.
*/
if (sorted_path)
{
Path sort_path; /* dummy for result of cost_sort */
if (root->query_pathkeys == NIL ||
pathkeys_contained_in(root->query_pathkeys,
cheapest_path->pathkeys))
{
/* No sort needed for cheapest path */
sort_path.startup_cost = cheapest_path->startup_cost;
sort_path.total_cost = cheapest_path->total_cost;
}
else
{
/* Figure cost for sorting */
cost_sort(&sort_path, root, root->query_pathkeys,
cheapest_path->total_cost,
path_rows, path_width,
0.0, work_mem, root->limit_tuples);
}
if (compare_fractional_path_costs(sorted_path, &sort_path,
tuple_fraction) > 0)
{
/* Presorted path is a loser */
sorted_path = NULL;
}
} }
/*
* Consider whether we want to use hashing instead of sorting.
*/
if (parse->groupClause) if (parse->groupClause)
{ {
/* /*
...@@ -1288,7 +1425,7 @@ grouping_planner(PlannerInfo *root, double tuple_fraction) ...@@ -1288,7 +1425,7 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
/* /*
* Select the best path. If we are doing hashed grouping, we will * Select the best path. If we are doing hashed grouping, we will
* always read all the input tuples, so use the cheapest-total path. * always read all the input tuples, so use the cheapest-total path.
* Otherwise, trust query_planner's decision about which to use. * Otherwise, the comparison above is correct.
*/ */
if (use_hashed_grouping || use_hashed_distinct || !sorted_path) if (use_hashed_grouping || use_hashed_distinct || !sorted_path)
best_path = cheapest_path; best_path = cheapest_path;
...@@ -1658,7 +1795,7 @@ grouping_planner(PlannerInfo *root, double tuple_fraction) ...@@ -1658,7 +1795,7 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
* If there was grouping or aggregation, use the current number of * If there was grouping or aggregation, use the current number of
* rows as the estimated number of DISTINCT rows (ie, assume the * rows as the estimated number of DISTINCT rows (ie, assume the
* result was already mostly unique). If not, use the number of * result was already mostly unique). If not, use the number of
* distinct-groups calculated by query_planner. * distinct-groups calculated previously.
*/ */
if (parse->groupClause || root->hasHavingQual || parse->hasAggs) if (parse->groupClause || root->hasHavingQual || parse->hasAggs)
dNumDistinctRows = result_plan->plan_rows; dNumDistinctRows = result_plan->plan_rows;
...@@ -2576,8 +2713,8 @@ choose_hashed_grouping(PlannerInfo *root, ...@@ -2576,8 +2713,8 @@ choose_hashed_grouping(PlannerInfo *root,
* We need to consider cheapest_path + hashagg [+ final sort] versus * We need to consider cheapest_path + hashagg [+ final sort] versus
* either cheapest_path [+ sort] + group or agg [+ final sort] or * either cheapest_path [+ sort] + group or agg [+ final sort] or
* presorted_path + group or agg [+ final sort] where brackets indicate a * presorted_path + group or agg [+ final sort] where brackets indicate a
* step that may not be needed. We assume query_planner() will have * step that may not be needed. We assume grouping_planner() will have
* returned a presorted path only if it's a winner compared to * passed us a presorted path only if it's a winner compared to
* cheapest_path for this purpose. * cheapest_path for this purpose.
* *
* These path variables are dummies that just hold cost fields; we don't * These path variables are dummies that just hold cost fields; we don't
...@@ -2630,12 +2767,8 @@ choose_hashed_grouping(PlannerInfo *root, ...@@ -2630,12 +2767,8 @@ choose_hashed_grouping(PlannerInfo *root,
0.0, work_mem, limit_tuples); 0.0, work_mem, limit_tuples);
/* /*
* Now make the decision using the top-level tuple fraction. First we * Now make the decision using the top-level tuple fraction.
* have to convert an absolute count (LIMIT) into fractional form.
*/ */
if (tuple_fraction >= 1.0)
tuple_fraction /= dNumGroups;
if (compare_fractional_path_costs(&hashed_p, &sorted_p, if (compare_fractional_path_costs(&hashed_p, &sorted_p,
tuple_fraction) < 0) tuple_fraction) < 0)
{ {
...@@ -2781,12 +2914,8 @@ choose_hashed_distinct(PlannerInfo *root, ...@@ -2781,12 +2914,8 @@ choose_hashed_distinct(PlannerInfo *root,
0.0, work_mem, limit_tuples); 0.0, work_mem, limit_tuples);
/* /*
* Now make the decision using the top-level tuple fraction. First we * Now make the decision using the top-level tuple fraction.
* have to convert an absolute count (LIMIT) into fractional form.
*/ */
if (tuple_fraction >= 1.0)
tuple_fraction /= dNumDistinctRows;
if (compare_fractional_path_costs(&hashed_p, &sorted_p, if (compare_fractional_path_costs(&hashed_p, &sorted_p,
tuple_fraction) < 0) tuple_fraction) < 0)
{ {
......
...@@ -1333,15 +1333,16 @@ is_simple_subquery(Query *subquery, RangeTblEntry *rte, ...@@ -1333,15 +1333,16 @@ is_simple_subquery(Query *subquery, RangeTblEntry *rte,
return false; return false;
/* /*
* Hack: don't try to pull up a subquery with an empty jointree. * Don't pull up a subquery with an empty jointree. query_planner() will
* query_planner() will correctly generate a Result plan for a jointree * correctly generate a Result plan for a jointree that's totally empty,
* that's totally empty, but I don't think the right things happen if an * but we can't cope with an empty FromExpr appearing lower down in a
* empty FromExpr appears lower down in a jointree. It would pose a * jointree: we identify join rels via baserelid sets, so we couldn't
* problem for the PlaceHolderVar mechanism too, since we'd have no way to * distinguish a join containing such a FromExpr from one without it.
* identify where to evaluate a PHV coming out of the subquery. Not worth * This would for example break the PlaceHolderVar mechanism, since we'd
* working hard on this, just to collapse SubqueryScan/Result into Result; * have no way to identify where to evaluate a PHV coming out of the
* especially since the SubqueryScan can often be optimized away by * subquery. Not worth working hard on this, just to collapse
* setrefs.c anyway. * SubqueryScan/Result into Result; especially since the SubqueryScan can
* often be optimized away by setrefs.c anyway.
*/ */
if (subquery->jointree->fromlist == NIL) if (subquery->jointree->fromlist == NIL)
return false; return false;
......
...@@ -676,6 +676,36 @@ subbuild_joinrel_joinlist(RelOptInfo *joinrel, ...@@ -676,6 +676,36 @@ 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->width = 0; /* it contains no Vars */
joinrel->rtekind = RTE_JOIN;
root->join_rel_list = lappend(root->join_rel_list, joinrel);
return joinrel;
}
/* /*
* find_childrel_appendrelinfo * find_childrel_appendrelinfo
* Get the AppendRelInfo associated with an appendrel child rel. * Get the AppendRelInfo associated with an appendrel child rel.
......
...@@ -142,6 +142,7 @@ extern RelOptInfo *build_join_rel(PlannerInfo *root, ...@@ -142,6 +142,7 @@ extern RelOptInfo *build_join_rel(PlannerInfo *root,
RelOptInfo *inner_rel, RelOptInfo *inner_rel,
SpecialJoinInfo *sjinfo, SpecialJoinInfo *sjinfo,
List **restrictlist_ptr); List **restrictlist_ptr);
extern RelOptInfo *build_empty_join_rel(PlannerInfo *root);
extern AppendRelInfo *find_childrel_appendrelinfo(PlannerInfo *root, extern AppendRelInfo *find_childrel_appendrelinfo(PlannerInfo *root,
RelOptInfo *rel); RelOptInfo *rel);
extern ParamPathInfo *get_baserel_parampathinfo(PlannerInfo *root, extern ParamPathInfo *get_baserel_parampathinfo(PlannerInfo *root,
......
...@@ -27,11 +27,8 @@ typedef void (*query_pathkeys_callback) (PlannerInfo *root, void *extra); ...@@ -27,11 +27,8 @@ typedef void (*query_pathkeys_callback) (PlannerInfo *root, void *extra);
/* /*
* prototypes for plan/planmain.c * prototypes for plan/planmain.c
*/ */
extern void query_planner(PlannerInfo *root, List *tlist, extern RelOptInfo *query_planner(PlannerInfo *root, List *tlist,
double tuple_fraction, double limit_tuples, query_pathkeys_callback qp_callback, void *qp_extra);
query_pathkeys_callback qp_callback, void *qp_extra,
Path **cheapest_path, Path **sorted_path,
double *num_groups);
/* /*
* prototypes for plan/planagg.c * prototypes for plan/planagg.c
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册