Commit 3ced8837 authored by Tom Lane's avatar 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.
parent 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 (!sorted_path) if (final_rel->rows > 1.0)
{ path_fraction = 1.0 / final_rel->rows;
if (cheapest_path &&
pathkeys_contained_in(subroot->sort_pathkeys,
cheapest_path->pathkeys))
sorted_path = cheapest_path;
else 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)
return false; 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);
......
This diff is collapsed.
This diff is collapsed.
...@@ -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% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment