diff --git a/src/backend/optimizer/README b/src/backend/optimizer/README index 751766fb9dd604e40700e76ef5b8ae3b6a6dd469..1e30cfd4e1e832dabdfec9e6ed887a0a73282aae 100644 --- a/src/backend/optimizer/README +++ b/src/backend/optimizer/README @@ -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 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 diff --git a/src/backend/optimizer/plan/planagg.c b/src/backend/optimizer/plan/planagg.c index d098f3471206c1f0b689a9833dcd787ae75a641b..ef0c20f8f170971e066f68c748904f5a251bf9e1 100644 --- a/src/backend/optimizer/plan/planagg.c +++ b/src/backend/optimizer/plan/planagg.c @@ -316,6 +316,7 @@ find_minmax_aggs_walker(Node *node, List **context) 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 @@ -329,6 +330,7 @@ find_minmax_aggs_walker(Node *node, List **context) */ 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. @@ -399,9 +401,8 @@ build_minmax_path(PlannerInfo *root, MinMaxAggInfo *mminfo, TargetEntry *tle; NullTest *ntest; SortGroupClause *sortcl; - Path *cheapest_path; + RelOptInfo *final_rel; Path *sorted_path; - double dNumGroups; Cost path_cost; 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 * 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. @@ -496,11 +500,6 @@ build_minmax_path(PlannerInfo *root, MinMaxAggInfo *mminfo, * 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); diff --git a/src/backend/optimizer/plan/planmain.c b/src/backend/optimizer/plan/planmain.c index 42a98945a38d07972ee2e8b5de16b3639196eebd..d4c586ab102e66d7d8f6d650aa4d819fce2917d0 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; } diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 49bd9302fa5c1e8467d0b0617773f103e3502a0d..bcc0d451f0d48a5203704769ebb90e2be53d3231 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -39,6 +39,7 @@ #include "parser/parsetree.h" #include "rewrite/rewriteManip.h" #include "utils/rel.h" +#include "utils/selfuncs.h" /* GUC parameter */ @@ -1125,10 +1126,10 @@ grouping_planner(PlannerInfo *root, double tuple_fraction) { /* 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; @@ -1204,6 +1205,9 @@ grouping_planner(PlannerInfo *root, double tuple_fraction) 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 @@ -1215,9 +1219,9 @@ grouping_planner(PlannerInfo *root, double tuple_fraction) 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; @@ -1225,31 +1229,164 @@ grouping_planner(PlannerInfo *root, double tuple_fraction) /* * 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) { /* @@ -1288,7 +1425,7 @@ grouping_planner(PlannerInfo *root, double tuple_fraction) /* * 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; @@ -1658,7 +1795,7 @@ grouping_planner(PlannerInfo *root, double tuple_fraction) * 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; @@ -2576,8 +2713,8 @@ choose_hashed_grouping(PlannerInfo *root, * 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 @@ -2630,12 +2767,8 @@ choose_hashed_grouping(PlannerInfo *root, 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) { @@ -2781,12 +2914,8 @@ choose_hashed_distinct(PlannerInfo *root, 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) { diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c index 52842931ec5552c04fdec41e013c36e1897fd4e4..afeb6ddb8c446dfa5e01d7d603b16aaa7a4163b5 100644 --- a/src/backend/optimizer/prep/prepjointree.c +++ b/src/backend/optimizer/prep/prepjointree.c @@ -1333,15 +1333,16 @@ is_simple_subquery(Query *subquery, RangeTblEntry *rte, 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; diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index 8ee5671a551912a9bbc1020971b2fb9a3b5fadf1..1f198201c32fa9d11123bf313461a12a9ed5e35a 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -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 * Get the AppendRelInfo associated with an appendrel child rel. diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h index bc687895bfa1429271c996a96dfc463305833c56..9686229134e228be1187d6c727034abb5d186ffe 100644 --- a/src/include/optimizer/pathnode.h +++ b/src/include/optimizer/pathnode.h @@ -142,6 +142,7 @@ extern RelOptInfo *build_join_rel(PlannerInfo *root, 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, diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h index bd6841f6cdbdd07d477d3351ac8d10f86cd35fd5..ba7ae7c36ab62039d3ff6d422ed5126e8fa077b6 100644 --- a/src/include/optimizer/planmain.h +++ b/src/include/optimizer/planmain.h @@ -27,11 +27,8 @@ typedef void (*query_pathkeys_callback) (PlannerInfo *root, void *extra); /* * 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