diff options
Diffstat (limited to 'src/backend/optimizer/plan/planner.c')
-rw-r--r-- | src/backend/optimizer/plan/planner.c | 424 |
1 files changed, 203 insertions, 221 deletions
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 014b179c3f0..2e2458b1284 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -140,7 +140,7 @@ static double preprocess_limit(PlannerInfo *root, double tuple_fraction, int64 *offset_est, int64 *count_est); static void remove_useless_groupby_columns(PlannerInfo *root); -static List *preprocess_groupclause(PlannerInfo *root, List *force); +static List *groupclause_apply_groupingset(PlannerInfo *root, List *force); static List *extract_rollup_sets(List *groupingSets); static List *reorder_grouping_sets(List *groupingSets, List *sortclause); static void standard_qp_callback(PlannerInfo *root, void *extra); @@ -1423,7 +1423,7 @@ grouping_planner(PlannerInfo *root, double tuple_fraction) else if (parse->groupClause) { /* Preprocess regular GROUP BY clause, if any */ - root->processed_groupClause = preprocess_groupclause(root, NIL); + root->processed_groupClause = list_copy(parse->groupClause);; /* Remove any redundant GROUP BY columns */ remove_useless_groupby_columns(root); } @@ -2144,7 +2144,7 @@ preprocess_grouping_sets(PlannerInfo *root) * The groupClauses for hashed grouping sets are built later on.) */ if (gs->set) - rollup->groupClause = preprocess_groupclause(root, gs->set); + rollup->groupClause = groupclause_apply_groupingset(root, gs->set); else rollup->groupClause = NIL; @@ -2796,111 +2796,24 @@ remove_useless_groupby_columns(PlannerInfo *root) } /* - * preprocess_groupclause - do preparatory work on GROUP BY clause - * - * The idea here is to adjust the ordering of the GROUP BY elements - * (which in itself is semantically insignificant) to match ORDER BY, - * thereby allowing a single sort operation to both implement the ORDER BY - * requirement and set up for a Unique step that implements GROUP BY. - * - * In principle it might be interesting to consider other orderings of the - * GROUP BY elements, which could match the sort ordering of other - * possible plans (eg an indexscan) and thereby reduce cost. We don't - * bother with that, though. Hashed grouping will frequently win anyway. - * - * Note: we need no comparable processing of the distinctClause because - * the parser already enforced that that matches ORDER BY. - * - * Note: we return a fresh List, but its elements are the same - * SortGroupClauses appearing in parse->groupClause. This is important - * because later processing may modify the processed_groupClause list. - * - * For grouping sets, the order of items is instead forced to agree with that - * of the grouping set (and items not in the grouping set are skipped). The - * work of sorting the order of grouping set elements to match the ORDER BY if - * possible is done elsewhere. + * groupclause_apply_groupingset + * Apply the order of GROUP BY clauses defined by grouping sets. Items + * not in the grouping set are skipped. */ static List * -preprocess_groupclause(PlannerInfo *root, List *force) +groupclause_apply_groupingset(PlannerInfo *root, List *gset) { Query *parse = root->parse; List *new_groupclause = NIL; - bool partial_match; ListCell *sl; - ListCell *gl; - /* For grouping sets, we need to force the ordering */ - if (force) + foreach(sl, gset) { - foreach(sl, force) - { - Index ref = lfirst_int(sl); - SortGroupClause *cl = get_sortgroupref_clause(ref, parse->groupClause); + Index ref = lfirst_int(sl); + SortGroupClause *cl = get_sortgroupref_clause(ref, parse->groupClause); - new_groupclause = lappend(new_groupclause, cl); - } - - return new_groupclause; + new_groupclause = lappend(new_groupclause, cl); } - - /* If no ORDER BY, nothing useful to do here */ - if (parse->sortClause == NIL) - return list_copy(parse->groupClause); - - /* - * Scan the ORDER BY clause and construct a list of matching GROUP BY - * items, but only as far as we can make a matching prefix. - * - * This code assumes that the sortClause contains no duplicate items. - */ - foreach(sl, parse->sortClause) - { - SortGroupClause *sc = lfirst_node(SortGroupClause, sl); - - foreach(gl, parse->groupClause) - { - SortGroupClause *gc = lfirst_node(SortGroupClause, gl); - - if (equal(gc, sc)) - { - new_groupclause = lappend(new_groupclause, gc); - break; - } - } - if (gl == NULL) - break; /* no match, so stop scanning */ - } - - /* Did we match all of the ORDER BY list, or just some of it? */ - partial_match = (sl != NULL); - - /* If no match at all, no point in reordering GROUP BY */ - if (new_groupclause == NIL) - return list_copy(parse->groupClause); - - /* - * Add any remaining GROUP BY items to the new list, but only if we were - * able to make a complete match. In other words, we only rearrange the - * GROUP BY list if the result is that one list is a prefix of the other - * --- otherwise there's no possibility of a common sort. Also, give up - * if there are any non-sortable GROUP BY items, since then there's no - * hope anyway. - */ - foreach(gl, parse->groupClause) - { - SortGroupClause *gc = lfirst_node(SortGroupClause, gl); - - if (list_member_ptr(new_groupclause, gc)) - continue; /* it matched an ORDER BY item */ - if (partial_match) /* give up, no common sort possible */ - return list_copy(parse->groupClause); - if (!OidIsValid(gc->sortop)) /* give up, GROUP BY can't be sorted */ - return list_copy(parse->groupClause); - new_groupclause = lappend(new_groupclause, gc); - } - - /* Success --- install the rearranged GROUP BY list */ - Assert(list_length(parse->groupClause) == list_length(new_groupclause)); return new_groupclause; } @@ -4200,7 +4113,7 @@ consider_groupingsets_paths(PlannerInfo *root, { rollup = makeNode(RollupData); - rollup->groupClause = preprocess_groupclause(root, gset); + rollup->groupClause = groupclause_apply_groupingset(root, gset); rollup->gsets_data = list_make1(gs); rollup->gsets = remap_to_groupclause_idx(rollup->groupClause, rollup->gsets_data, @@ -4389,7 +4302,7 @@ consider_groupingsets_paths(PlannerInfo *root, Assert(gs->set != NIL); - rollup->groupClause = preprocess_groupclause(root, gs->set); + rollup->groupClause = groupclause_apply_groupingset(root, gs->set); rollup->gsets_data = list_make1(gs); rollup->gsets = remap_to_groupclause_idx(rollup->groupClause, rollup->gsets_data, @@ -6891,103 +6804,135 @@ add_paths_to_grouping_rel(PlannerInfo *root, RelOptInfo *input_rel, */ foreach(lc, input_rel->pathlist) { + ListCell *lc2; Path *path = (Path *) lfirst(lc); + Path *path_save = path; + List *pathkey_orderings = NIL; - path = make_ordered_path(root, - grouped_rel, - path, - cheapest_path, - root->group_pathkeys); + /* generate alternative group orderings that might be useful */ + pathkey_orderings = get_useful_group_keys_orderings(root, path); - if (path == NULL) - continue; + Assert(list_length(pathkey_orderings) > 0); - /* Now decide what to stick atop it */ - if (parse->groupingSets) - { - consider_groupingsets_paths(root, grouped_rel, - path, true, can_hash, - gd, agg_costs, dNumGroups); - } - else if (parse->hasAggs) - { - /* - * We have aggregation, possibly with plain GROUP BY. Make an - * AggPath. - */ - add_path(grouped_rel, (Path *) - create_agg_path(root, - grouped_rel, - path, - grouped_rel->reltarget, - parse->groupClause ? AGG_SORTED : AGG_PLAIN, - AGGSPLIT_SIMPLE, - root->processed_groupClause, - havingQual, - agg_costs, - dNumGroups)); - } - else if (parse->groupClause) + foreach(lc2, pathkey_orderings) { - /* - * We have GROUP BY without aggregation or grouping sets. Make - * a GroupPath. - */ - add_path(grouped_rel, (Path *) - create_group_path(root, - grouped_rel, - path, - root->processed_groupClause, - havingQual, - dNumGroups)); - } - else - { - /* Other cases should have been handled above */ - Assert(false); - } - } + PathKeyInfo *info = (PathKeyInfo *) lfirst(lc2); - /* - * Instead of operating directly on the input relation, we can - * consider finalizing a partially aggregated path. - */ - if (partially_grouped_rel != NULL) - { - foreach(lc, partially_grouped_rel->pathlist) - { - Path *path = (Path *) lfirst(lc); + /* restore the path (we replace it in the loop) */ + path = path_save; path = make_ordered_path(root, grouped_rel, path, - partially_grouped_rel->cheapest_total_path, - root->group_pathkeys); - + cheapest_path, + info->pathkeys); if (path == NULL) continue; - if (parse->hasAggs) + /* Now decide what to stick atop it */ + if (parse->groupingSets) + { + consider_groupingsets_paths(root, grouped_rel, + path, true, can_hash, + gd, agg_costs, dNumGroups); + } + else if (parse->hasAggs) + { + /* + * We have aggregation, possibly with plain GROUP BY. Make + * an AggPath. + */ add_path(grouped_rel, (Path *) create_agg_path(root, grouped_rel, path, grouped_rel->reltarget, parse->groupClause ? AGG_SORTED : AGG_PLAIN, - AGGSPLIT_FINAL_DESERIAL, - root->processed_groupClause, + AGGSPLIT_SIMPLE, + info->clauses, havingQual, - agg_final_costs, + agg_costs, dNumGroups)); - else + } + else if (parse->groupClause) + { + /* + * We have GROUP BY without aggregation or grouping sets. + * Make a GroupPath. + */ add_path(grouped_rel, (Path *) create_group_path(root, grouped_rel, path, - root->processed_groupClause, + info->clauses, havingQual, dNumGroups)); + } + else + { + /* Other cases should have been handled above */ + Assert(false); + } + } + } + /* + * Instead of operating directly on the input relation, we can + * consider finalizing a partially aggregated path. + */ + if (partially_grouped_rel != NULL) + { + foreach(lc, partially_grouped_rel->pathlist) + { + ListCell *lc2; + Path *path = (Path *) lfirst(lc); + Path *path_save = path; + List *pathkey_orderings = NIL; + + /* generate alternative group orderings that might be useful */ + pathkey_orderings = get_useful_group_keys_orderings(root, path); + + Assert(list_length(pathkey_orderings) > 0); + + /* process all potentially interesting grouping reorderings */ + foreach(lc2, pathkey_orderings) + { + PathKeyInfo *info = (PathKeyInfo *) lfirst(lc2); + + /* restore the path (we replace it in the loop) */ + path = path_save; + + path = make_ordered_path(root, + grouped_rel, + path, + partially_grouped_rel->cheapest_total_path, + info->pathkeys); + + if (path == NULL) + continue; + + if (parse->hasAggs) + add_path(grouped_rel, (Path *) + create_agg_path(root, + grouped_rel, + path, + grouped_rel->reltarget, + parse->groupClause ? AGG_SORTED : AGG_PLAIN, + AGGSPLIT_FINAL_DESERIAL, + info->clauses, + havingQual, + agg_final_costs, + dNumGroups)); + else + add_path(grouped_rel, (Path *) + create_group_path(root, + grouped_rel, + path, + info->clauses, + havingQual, + dNumGroups)); + + } } } } @@ -7190,37 +7135,54 @@ create_partial_grouping_paths(PlannerInfo *root, */ foreach(lc, input_rel->pathlist) { + ListCell *lc2; Path *path = (Path *) lfirst(lc); + Path *path_save = path; + List *pathkey_orderings = NIL; - path = make_ordered_path(root, - partially_grouped_rel, - path, - cheapest_total_path, - root->group_pathkeys); + /* generate alternative group orderings that might be useful */ + pathkey_orderings = get_useful_group_keys_orderings(root, path); - if (path == NULL) - continue; + Assert(list_length(pathkey_orderings) > 0); - if (parse->hasAggs) - add_path(partially_grouped_rel, (Path *) - create_agg_path(root, + /* process all potentially interesting grouping reorderings */ + foreach(lc2, pathkey_orderings) + { + PathKeyInfo *info = (PathKeyInfo *) lfirst(lc2); + + /* restore the path (we replace it in the loop) */ + path = path_save; + + path = make_ordered_path(root, partially_grouped_rel, path, - partially_grouped_rel->reltarget, - parse->groupClause ? AGG_SORTED : AGG_PLAIN, - AGGSPLIT_INITIAL_SERIAL, - root->processed_groupClause, - NIL, - agg_partial_costs, - dNumPartialGroups)); - else - add_path(partially_grouped_rel, (Path *) - create_group_path(root, - partially_grouped_rel, - path, - root->processed_groupClause, - NIL, - dNumPartialGroups)); + cheapest_total_path, + info->pathkeys); + + if (path == NULL) + continue; + + if (parse->hasAggs) + add_path(partially_grouped_rel, (Path *) + create_agg_path(root, + partially_grouped_rel, + path, + partially_grouped_rel->reltarget, + parse->groupClause ? AGG_SORTED : AGG_PLAIN, + AGGSPLIT_INITIAL_SERIAL, + info->clauses, + NIL, + agg_partial_costs, + dNumPartialGroups)); + else + add_path(partially_grouped_rel, (Path *) + create_group_path(root, + partially_grouped_rel, + path, + info->clauses, + NIL, + dNumPartialGroups)); + } } } @@ -7229,37 +7191,55 @@ create_partial_grouping_paths(PlannerInfo *root, /* Similar to above logic, but for partial paths. */ foreach(lc, input_rel->partial_pathlist) { + ListCell *lc2; Path *path = (Path *) lfirst(lc); + Path *path_save = path; + List *pathkey_orderings = NIL; - path = make_ordered_path(root, - partially_grouped_rel, - path, - cheapest_partial_path, - root->group_pathkeys); + /* generate alternative group orderings that might be useful */ + pathkey_orderings = get_useful_group_keys_orderings(root, path); - if (path == NULL) - continue; + Assert(list_length(pathkey_orderings) > 0); - if (parse->hasAggs) - add_partial_path(partially_grouped_rel, (Path *) - create_agg_path(root, - partially_grouped_rel, - path, - partially_grouped_rel->reltarget, - parse->groupClause ? AGG_SORTED : AGG_PLAIN, - AGGSPLIT_INITIAL_SERIAL, - root->processed_groupClause, - NIL, - agg_partial_costs, - dNumPartialPartialGroups)); - else - add_partial_path(partially_grouped_rel, (Path *) - create_group_path(root, - partially_grouped_rel, - path, - root->processed_groupClause, - NIL, - dNumPartialPartialGroups)); + /* process all potentially interesting grouping reorderings */ + foreach(lc2, pathkey_orderings) + { + PathKeyInfo *info = (PathKeyInfo *) lfirst(lc2); + + + /* restore the path (we replace it in the loop) */ + path = path_save; + + path = make_ordered_path(root, + partially_grouped_rel, + path, + cheapest_partial_path, + info->pathkeys); + + if (path == NULL) + continue; + + if (parse->hasAggs) + add_partial_path(partially_grouped_rel, (Path *) + create_agg_path(root, + partially_grouped_rel, + path, + partially_grouped_rel->reltarget, + parse->groupClause ? AGG_SORTED : AGG_PLAIN, + AGGSPLIT_INITIAL_SERIAL, + info->clauses, + NIL, + agg_partial_costs, + dNumPartialPartialGroups)); + else + add_partial_path(partially_grouped_rel, (Path *) + create_group_path(root, + partially_grouped_rel, + path, + info->clauses, + NIL, + dNumPartialPartialGroups)); + } } } @@ -7373,6 +7353,8 @@ gather_grouping_paths(PlannerInfo *root, RelOptInfo *rel) * We can also skip the entire loop when we only have a single-item * group_pathkeys because then we can't possibly have a presorted prefix * of the list without having the list be fully sorted. + * + * XXX Shouldn't this also consider the group-key-reordering? */ if (!enable_incremental_sort || list_length(root->group_pathkeys) == 1) return; |