summaryrefslogtreecommitdiff
path: root/src/backend/optimizer/plan/planmain.c
diff options
context:
space:
mode:
authorTom Lane <tgl@sss.pgh.pa.us>2013-08-05 15:00:57 -0400
committerTom Lane <tgl@sss.pgh.pa.us>2013-08-05 15:01:09 -0400
commit3ced8837db2cd602422bb36102cec73289691d40 (patch)
tree0fae9ca9ba7f5f259ef678d685717a70710ac0c4 /src/backend/optimizer/plan/planmain.c
parent841c29c8b3be98ee30486ee245ebee782d4dedd4 (diff)
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.
Diffstat (limited to 'src/backend/optimizer/plan/planmain.c')
-rw-r--r--src/backend/optimizer/plan/planmain.c227
1 files changed, 20 insertions, 207 deletions
diff --git a/src/backend/optimizer/plan/planmain.c b/src/backend/optimizer/plan/planmain.c
index 42a98945a38..d4c586ab102 100644
--- a/src/backend/optimizer/plan/planmain.c
+++ b/src/backend/optimizer/plan/planmain.c
@@ -20,14 +20,10 @@
*/
#include "postgres.h"
-#include "miscadmin.h"
-#include "optimizer/cost.h"
#include "optimizer/pathnode.h"
#include "optimizer/paths.h"
#include "optimizer/placeholder.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.
*
* Since query_planner does not handle the toplevel processing (grouping,
- * sorting, etc) it cannot select the best path by itself. It selects
- * two paths: the cheapest path that produces all the required tuples,
- * independent of any ordering considerations, and the cheapest path that
- * produces the expected fraction of the required tuples in the required
- * ordering, if there is a path that is cheaper for this than just sorting
- * the output of the cheapest overall path. The caller (grouping_planner)
- * will make the final decision about which to use.
+ * 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.
*
- * Input parameters:
* root describes the query to plan
* tlist is the target list the query should produce
* (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_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
* 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
* qp_callback once we have completed merging the query's equivalence classes.
* (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,
- double tuple_fraction, double limit_tuples,
- query_pathkeys_callback qp_callback, void *qp_extra,
- Path **cheapest_path, Path **sorted_path,
- double *num_groups)
+ query_pathkeys_callback qp_callback, void *qp_extra)
{
Query *parse = root->parse;
List *joinlist;
RelOptInfo *final_rel;
- Path *cheapestpath;
- Path *sortedpath;
Index rti;
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
* "SELECT 2+2;" or "INSERT ... VALUES()". Fall through quickly.
*/
if (parse->jointree->fromlist == NIL)
{
- /* We need a trivial path result */
- *cheapest_path = (Path *)
- create_result_path((List *) parse->jointree->quals);
- *sorted_path = NULL;
+ /* We need a dummy joinrel to describe the empty set of baserels */
+ final_rel = build_empty_join_rel(root);
+
+ /* 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
@@ -115,7 +82,8 @@ query_planner(PlannerInfo *root, List *tlist,
*/
root->canon_pathkeys = NIL;
(*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);
+ /* Check that we got at least one usable path */
if (!final_rel || !final_rel->cheapest_total_path ||
final_rel->cheapest_total_path->param_info != NULL)
elog(ERROR, "failed to construct the join relation");
- /*
- * 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;
+ return final_rel;
}