mirror of
https://github.com/postgres/postgres.git
synced 2025-07-17 06:41:09 +03:00
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.
This commit is contained in:
@ -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)
|
|||||||
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)
|
|||||||
*/
|
*/
|
||||||
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,
|
|||||||
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,
|
|||||||
* 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,
|
|||||||
* 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 @@
|
|||||||
*/
|
*/
|
||||||
#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 @@
|
|||||||
* 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,
|
|||||||
*/
|
*/
|
||||||
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,
|
|||||||
*/
|
*/
|
||||||
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 @@
|
|||||||
#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)
|
|||||||
{
|
{
|
||||||
/* 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)
|
|||||||
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)
|
|||||||
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)
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* 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)
|
||||||
{
|
{
|
||||||
path_rows = cheapest_path->parent->rows;
|
List *groupExprs;
|
||||||
path_width = cheapest_path->parent->width;
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* 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)
|
|||||||
/*
|
/*
|
||||||
* 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)
|
|||||||
* 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,
|
|||||||
* 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,
|
|||||||
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,
|
|||||||
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,
|
|||||||
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,
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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,
|
|||||||
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);
|
|||||||
/*
|
/*
|
||||||
* 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
|
||||||
|
Reference in New Issue
Block a user