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
-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,
-and grouping_planner() will compare its cost to the cost of using the
-cheapest-overall path and doing an explicit sort.
+then compare its cost to the cost of using the cheapest-overall path and
+doing an explicit sort on that.
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
Assert(aggref->agglevelsup == 0);
if (list_length(aggref->args) != 1)
return true; /* it couldn't be MIN/MAX */
+
/*
* ORDER BY is usually irrelevant for MIN/MAX, but it can change the
* outcome if the aggsortop's operator class recognizes non-identical
*/
if (aggref->aggorder != NIL)
return true;
+
/*
* We might implement the optimization when a FILTER clause is present
* by adding the filter to the quals of the generated subquery.
TargetEntry *tle;
NullTest *ntest;
SortGroupClause *sortcl;
- Path *cheapest_path;
+ RelOptInfo *final_rel;
Path *sorted_path;
- double dNumGroups;
Cost path_cost;
double path_fraction;
* Generate the best paths for this query, telling query_planner that we
* have LIMIT 1.
*/
- query_planner(subroot, parse->targetList, 1.0, 1.0,
- minmax_qp_callback, NULL,
- &cheapest_path, &sorted_path, &dNumGroups);
+ subroot->tuple_fraction = 1.0;
+ subroot->limit_tuples = 1.0;
+
+ final_rel = query_planner(subroot, parse->targetList,
+ minmax_qp_callback, NULL);
/*
- * Fail if no presorted path. However, if query_planner determines that
- * the presorted path is also the cheapest, it will set sorted_path to
- * NULL ... don't be fooled. (This is kind of a pain here, but it
- * simplifies life for grouping_planner, so leave it be.)
+ * Get the best presorted path, that being the one that's cheapest for
+ * fetching just one row. If there's no such path, fail.
*/
+ 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 (cheapest_path &&
- pathkeys_contained_in(subroot->sort_pathkeys,
- cheapest_path->pathkeys))
- sorted_path = cheapest_path;
- else
- return false;
- }
+ return false;
/*
* Determine cost to get just the first row of the presorted path.
* Note: cost calculation here should match
* 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_fraction * (sorted_path->total_cost - sorted_path->startup_cost);
*/
#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"
/*
* 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
*/
root->canon_pathkeys = NIL;
(*qp_callback) (root, qp_extra);
- return;
+
+ return final_rel;
}
/*
*/
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;
}
#include "parser/parsetree.h"
#include "rewrite/rewriteManip.h"
#include "utils/rel.h"
+#include "utils/selfuncs.h"
/* GUC parameter */
{
/* No set operations, do regular planning */
List *sub_tlist;
- double sub_limit_tuples;
AttrNumber *groupColIdx = NULL;
bool need_tlist_eval = true;
standard_qp_extra qp_extra;
+ RelOptInfo *final_rel;
Path *cheapest_path;
Path *sorted_path;
Path *best_path;
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
* query_planner's result subplan needs to return. Even if we know a
parse->hasAggs ||
parse->hasWindowFuncs ||
root->hasHavingQual)
- sub_limit_tuples = -1.0;
+ root->limit_tuples = -1.0;
else
- sub_limit_tuples = limit_tuples;
+ root->limit_tuples = limit_tuples;
/* Set up data needed by standard_qp_callback */
qp_extra.tlist = tlist;
/*
* 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
- * clause, distinct clause, etc. query_planner will also estimate the
- * number of groups in the query.
+ * clause, distinct clause, etc.
*/
- query_planner(root, sub_tlist, tuple_fraction, sub_limit_tuples,
- standard_qp_callback, &qp_extra,
- &cheapest_path, &sorted_path, &dNumGroups);
+ final_rel = query_planner(root, sub_tlist,
+ standard_qp_callback, &qp_extra);
/*
- * Extract rowcount and width estimates for possible use in grouping
- * decisions. Beware here of the possibility that
- * cheapest_path->parent is NULL (ie, there is no FROM clause).
+ * Extract rowcount and width estimates for use below.
*/
- 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
{
- 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)
{
/*
/*
* Select the best path. If we are doing hashed grouping, we will
* 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)
best_path = cheapest_path;
* If there was grouping or aggregation, use the current number of
* rows as the estimated number of DISTINCT rows (ie, assume the
* 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)
dNumDistinctRows = result_plan->plan_rows;
* We need to consider cheapest_path + hashagg [+ final sort] versus
* either cheapest_path [+ sort] + group or agg [+ final sort] or
* presorted_path + group or agg [+ final sort] where brackets indicate a
- * step that may not be needed. We assume query_planner() will have
- * returned a presorted path only if it's a winner compared to
+ * step that may not be needed. We assume grouping_planner() will have
+ * passed us a presorted path only if it's a winner compared to
* cheapest_path for this purpose.
*
* These path variables are dummies that just hold cost fields; we don't
0.0, work_mem, limit_tuples);
/*
- * Now make the decision using the top-level tuple fraction. First we
- * have to convert an absolute count (LIMIT) into fractional form.
+ * Now make the decision using the top-level tuple fraction.
*/
- if (tuple_fraction >= 1.0)
- tuple_fraction /= dNumGroups;
-
if (compare_fractional_path_costs(&hashed_p, &sorted_p,
tuple_fraction) < 0)
{
0.0, work_mem, limit_tuples);
/*
- * Now make the decision using the top-level tuple fraction. First we
- * have to convert an absolute count (LIMIT) into fractional form.
+ * Now make the decision using the top-level tuple fraction.
*/
- if (tuple_fraction >= 1.0)
- tuple_fraction /= dNumDistinctRows;
-
if (compare_fractional_path_costs(&hashed_p, &sorted_p,
tuple_fraction) < 0)
{
return false;
/*
- * Hack: don't try to pull up a subquery with an empty jointree.
- * query_planner() will correctly generate a Result plan for a jointree
- * that's totally empty, but I don't think the right things happen if an
- * empty FromExpr appears lower down in a jointree. It would pose a
- * problem for the PlaceHolderVar mechanism too, since we'd have no way to
- * identify where to evaluate a PHV coming out of the subquery. Not worth
- * working hard on this, just to collapse SubqueryScan/Result into Result;
- * especially since the SubqueryScan can often be optimized away by
- * setrefs.c anyway.
+ * Don't pull up a subquery with an empty jointree. query_planner() will
+ * correctly generate a Result plan for a jointree that's totally empty,
+ * but we can't cope with an empty FromExpr appearing lower down in a
+ * jointree: we identify join rels via baserelid sets, so we couldn't
+ * distinguish a join containing such a FromExpr from one without it.
+ * This would for example break the PlaceHolderVar mechanism, since we'd
+ * have no way to identify where to evaluate a PHV coming out of the
+ * subquery. Not worth working hard on this, just to collapse
+ * SubqueryScan/Result into Result; especially since the SubqueryScan can
+ * often be optimized away by setrefs.c anyway.
*/
if (subquery->jointree->fromlist == NIL)
return false;
}
+/*
+ * 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
* Get the AppendRelInfo associated with an appendrel child rel.
RelOptInfo *inner_rel,
SpecialJoinInfo *sjinfo,
List **restrictlist_ptr);
+extern RelOptInfo *build_empty_join_rel(PlannerInfo *root);
extern AppendRelInfo *find_childrel_appendrelinfo(PlannerInfo *root,
RelOptInfo *rel);
extern ParamPathInfo *get_baserel_parampathinfo(PlannerInfo *root,
/*
* prototypes for plan/planmain.c
*/
-extern void 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);
+extern RelOptInfo *query_planner(PlannerInfo *root, List *tlist,
+ query_pathkeys_callback qp_callback, void *qp_extra);
/*
* prototypes for plan/planagg.c