summaryrefslogtreecommitdiff
path: root/src/backend/optimizer/path
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend/optimizer/path')
-rw-r--r--src/backend/optimizer/path/allpaths.c467
-rw-r--r--src/backend/optimizer/path/joinrels.c193
-rw-r--r--src/backend/optimizer/path/pathkeys.c24
3 files changed, 681 insertions, 3 deletions
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 1f82239b4e0..ec27c1a4994 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -40,6 +40,7 @@
#include "optimizer/paths.h"
#include "optimizer/plancat.h"
#include "optimizer/planner.h"
+#include "optimizer/prep.h"
#include "optimizer/tlist.h"
#include "parser/parse_clause.h"
#include "parser/parsetree.h"
@@ -47,6 +48,7 @@
#include "port/pg_bitutils.h"
#include "rewrite/rewriteManip.h"
#include "utils/lsyscache.h"
+#include "utils/selfuncs.h"
/* Bitmask flags for pushdown_safety_info.unsafeFlags */
@@ -77,7 +79,9 @@ typedef enum pushdown_safe_type
/* These parameters are set by GUC */
bool enable_geqo = false; /* just in case GUC doesn't set it */
+bool enable_eager_aggregate = true;
int geqo_threshold;
+double min_eager_agg_group_size;
int min_parallel_table_scan_size;
int min_parallel_index_scan_size;
@@ -90,6 +94,7 @@ join_search_hook_type join_search_hook = NULL;
static void set_base_rel_consider_startup(PlannerInfo *root);
static void set_base_rel_sizes(PlannerInfo *root);
+static void setup_simple_grouped_rels(PlannerInfo *root);
static void set_base_rel_pathlists(PlannerInfo *root);
static void set_rel_size(PlannerInfo *root, RelOptInfo *rel,
Index rti, RangeTblEntry *rte);
@@ -114,6 +119,7 @@ static void set_append_rel_size(PlannerInfo *root, RelOptInfo *rel,
Index rti, RangeTblEntry *rte);
static void set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
Index rti, RangeTblEntry *rte);
+static void set_grouped_rel_pathlist(PlannerInfo *root, RelOptInfo *rel);
static void generate_orderedappend_paths(PlannerInfo *root, RelOptInfo *rel,
List *live_childrels,
List *all_child_pathkeys);
@@ -183,6 +189,12 @@ make_one_rel(PlannerInfo *root, List *joinlist)
set_base_rel_sizes(root);
/*
+ * Build grouped relations for simple rels (i.e., base or "other" member
+ * relations) where possible.
+ */
+ setup_simple_grouped_rels(root);
+
+ /*
* We should now have size estimates for every actual table involved in
* the query, and we also know which if any have been deleted from the
* query by join removal, pruned by partition pruning, or eliminated by
@@ -324,6 +336,39 @@ set_base_rel_sizes(PlannerInfo *root)
}
/*
+ * setup_simple_grouped_rels
+ * For each simple relation, build a grouped simple relation if eager
+ * aggregation is possible and if this relation can produce grouped paths.
+ */
+static void
+setup_simple_grouped_rels(PlannerInfo *root)
+{
+ Index rti;
+
+ /*
+ * If there are no aggregate expressions or grouping expressions, eager
+ * aggregation is not possible.
+ */
+ if (root->agg_clause_list == NIL ||
+ root->group_expr_list == NIL)
+ return;
+
+ for (rti = 1; rti < root->simple_rel_array_size; rti++)
+ {
+ RelOptInfo *rel = root->simple_rel_array[rti];
+
+ /* there may be empty slots corresponding to non-baserel RTEs */
+ if (rel == NULL)
+ continue;
+
+ Assert(rel->relid == rti); /* sanity check on array */
+ Assert(IS_SIMPLE_REL(rel)); /* sanity check on rel */
+
+ (void) build_simple_grouped_rel(root, rel);
+ }
+}
+
+/*
* set_base_rel_pathlists
* Finds all paths available for scanning each base-relation entry.
* Sequential scan and any available indices are considered.
@@ -559,6 +604,15 @@ set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
/* Now find the cheapest of the paths for this rel */
set_cheapest(rel);
+ /*
+ * If a grouped relation for this rel exists, build partial aggregation
+ * paths for it.
+ *
+ * Note that this can only happen after we've called set_cheapest() for
+ * this base rel, because we need its cheapest paths.
+ */
+ set_grouped_rel_pathlist(root, rel);
+
#ifdef OPTIMIZER_DEBUG
pprint(rel);
#endif
@@ -1305,6 +1359,35 @@ set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
add_paths_to_append_rel(root, rel, live_childrels);
}
+/*
+ * set_grouped_rel_pathlist
+ * If a grouped relation for the given 'rel' exists, build partial
+ * aggregation paths for it.
+ */
+static void
+set_grouped_rel_pathlist(PlannerInfo *root, RelOptInfo *rel)
+{
+ RelOptInfo *grouped_rel;
+
+ /*
+ * If there are no aggregate expressions or grouping expressions, eager
+ * aggregation is not possible.
+ */
+ if (root->agg_clause_list == NIL ||
+ root->group_expr_list == NIL)
+ return;
+
+ /* Add paths to the grouped base relation if one exists. */
+ grouped_rel = rel->grouped_rel;
+ if (grouped_rel)
+ {
+ Assert(IS_GROUPED_REL(grouped_rel));
+
+ generate_grouped_paths(root, grouped_rel, rel);
+ set_cheapest(grouped_rel);
+ }
+}
+
/*
* add_paths_to_append_rel
@@ -3335,6 +3418,345 @@ generate_useful_gather_paths(PlannerInfo *root, RelOptInfo *rel, bool override_r
}
/*
+ * generate_grouped_paths
+ * Generate paths for a grouped relation by adding sorted and hashed
+ * partial aggregation paths on top of paths of the ungrouped relation.
+ *
+ * The information needed is provided by the RelAggInfo structure stored in
+ * "grouped_rel".
+ */
+void
+generate_grouped_paths(PlannerInfo *root, RelOptInfo *grouped_rel,
+ RelOptInfo *rel)
+{
+ RelAggInfo *agg_info = grouped_rel->agg_info;
+ AggClauseCosts agg_costs;
+ bool can_hash;
+ bool can_sort;
+ Path *cheapest_total_path = NULL;
+ Path *cheapest_partial_path = NULL;
+ double dNumGroups = 0;
+ double dNumPartialGroups = 0;
+ List *group_pathkeys = NIL;
+
+ if (IS_DUMMY_REL(rel))
+ {
+ mark_dummy_rel(grouped_rel);
+ return;
+ }
+
+ /*
+ * We push partial aggregation only to the lowest possible level in the
+ * join tree that is deemed useful.
+ */
+ if (!bms_equal(agg_info->apply_at, rel->relids) ||
+ !agg_info->agg_useful)
+ return;
+
+ MemSet(&agg_costs, 0, sizeof(AggClauseCosts));
+ get_agg_clause_costs(root, AGGSPLIT_INITIAL_SERIAL, &agg_costs);
+
+ /*
+ * Determine whether it's possible to perform sort-based implementations
+ * of grouping, and generate the pathkeys that represent the grouping
+ * requirements in that case.
+ */
+ can_sort = grouping_is_sortable(agg_info->group_clauses);
+ if (can_sort)
+ {
+ RelOptInfo *top_grouped_rel;
+ List *top_group_tlist;
+
+ top_grouped_rel = IS_OTHER_REL(rel) ?
+ rel->top_parent->grouped_rel : grouped_rel;
+ top_group_tlist =
+ make_tlist_from_pathtarget(top_grouped_rel->agg_info->target);
+
+ group_pathkeys =
+ make_pathkeys_for_sortclauses(root, agg_info->group_clauses,
+ top_group_tlist);
+ }
+
+ /*
+ * Determine whether we should consider hash-based implementations of
+ * grouping.
+ */
+ Assert(root->numOrderedAggs == 0);
+ can_hash = (agg_info->group_clauses != NIL &&
+ grouping_is_hashable(agg_info->group_clauses));
+
+ /*
+ * Consider whether we should generate partially aggregated non-partial
+ * paths. We can only do this if we have a non-partial path.
+ */
+ if (rel->pathlist != NIL)
+ {
+ cheapest_total_path = rel->cheapest_total_path;
+ Assert(cheapest_total_path != NULL);
+ }
+
+ /*
+ * If parallelism is possible for grouped_rel, then we should consider
+ * generating partially-grouped partial paths. However, if the ungrouped
+ * rel has no partial paths, then we can't.
+ */
+ if (grouped_rel->consider_parallel && rel->partial_pathlist != NIL)
+ {
+ cheapest_partial_path = linitial(rel->partial_pathlist);
+ Assert(cheapest_partial_path != NULL);
+ }
+
+ /* Estimate number of partial groups. */
+ if (cheapest_total_path != NULL)
+ dNumGroups = estimate_num_groups(root,
+ agg_info->group_exprs,
+ cheapest_total_path->rows,
+ NULL, NULL);
+ if (cheapest_partial_path != NULL)
+ dNumPartialGroups = estimate_num_groups(root,
+ agg_info->group_exprs,
+ cheapest_partial_path->rows,
+ NULL, NULL);
+
+ if (can_sort && cheapest_total_path != NULL)
+ {
+ ListCell *lc;
+
+ /*
+ * Use any available suitably-sorted path as input, and also consider
+ * sorting the cheapest-total path and incremental sort on any paths
+ * with presorted keys.
+ *
+ * To save planning time, we ignore parameterized input paths unless
+ * they are the cheapest-total path.
+ */
+ foreach(lc, rel->pathlist)
+ {
+ Path *input_path = (Path *) lfirst(lc);
+ Path *path;
+ bool is_sorted;
+ int presorted_keys;
+
+ /*
+ * Ignore parameterized paths that are not the cheapest-total
+ * path.
+ */
+ if (input_path->param_info &&
+ input_path != cheapest_total_path)
+ continue;
+
+ is_sorted = pathkeys_count_contained_in(group_pathkeys,
+ input_path->pathkeys,
+ &presorted_keys);
+
+ /*
+ * Ignore paths that are not suitably or partially sorted, unless
+ * they are the cheapest total path (no need to deal with paths
+ * which have presorted keys when incremental sort is disabled).
+ */
+ if (!is_sorted && input_path != cheapest_total_path &&
+ (presorted_keys == 0 || !enable_incremental_sort))
+ continue;
+
+ /*
+ * Since the path originates from a non-grouped relation that is
+ * not aware of eager aggregation, we must ensure that it provides
+ * the correct input for partial aggregation.
+ */
+ path = (Path *) create_projection_path(root,
+ grouped_rel,
+ input_path,
+ agg_info->agg_input);
+
+ if (!is_sorted)
+ {
+ /*
+ * We've no need to consider both a sort and incremental sort.
+ * We'll just do a sort if there are no presorted keys and an
+ * incremental sort when there are presorted keys.
+ */
+ if (presorted_keys == 0 || !enable_incremental_sort)
+ path = (Path *) create_sort_path(root,
+ grouped_rel,
+ path,
+ group_pathkeys,
+ -1.0);
+ else
+ path = (Path *) create_incremental_sort_path(root,
+ grouped_rel,
+ path,
+ group_pathkeys,
+ presorted_keys,
+ -1.0);
+ }
+
+ /*
+ * qual is NIL because the HAVING clause cannot be evaluated until
+ * the final value of the aggregate is known.
+ */
+ path = (Path *) create_agg_path(root,
+ grouped_rel,
+ path,
+ agg_info->target,
+ AGG_SORTED,
+ AGGSPLIT_INITIAL_SERIAL,
+ agg_info->group_clauses,
+ NIL,
+ &agg_costs,
+ dNumGroups);
+
+ add_path(grouped_rel, path);
+ }
+ }
+
+ if (can_sort && cheapest_partial_path != NULL)
+ {
+ ListCell *lc;
+
+ /* Similar to above logic, but for partial paths. */
+ foreach(lc, rel->partial_pathlist)
+ {
+ Path *input_path = (Path *) lfirst(lc);
+ Path *path;
+ bool is_sorted;
+ int presorted_keys;
+
+ is_sorted = pathkeys_count_contained_in(group_pathkeys,
+ input_path->pathkeys,
+ &presorted_keys);
+
+ /*
+ * Ignore paths that are not suitably or partially sorted, unless
+ * they are the cheapest partial path (no need to deal with paths
+ * which have presorted keys when incremental sort is disabled).
+ */
+ if (!is_sorted && input_path != cheapest_partial_path &&
+ (presorted_keys == 0 || !enable_incremental_sort))
+ continue;
+
+ /*
+ * Since the path originates from a non-grouped relation that is
+ * not aware of eager aggregation, we must ensure that it provides
+ * the correct input for partial aggregation.
+ */
+ path = (Path *) create_projection_path(root,
+ grouped_rel,
+ input_path,
+ agg_info->agg_input);
+
+ if (!is_sorted)
+ {
+ /*
+ * We've no need to consider both a sort and incremental sort.
+ * We'll just do a sort if there are no presorted keys and an
+ * incremental sort when there are presorted keys.
+ */
+ if (presorted_keys == 0 || !enable_incremental_sort)
+ path = (Path *) create_sort_path(root,
+ grouped_rel,
+ path,
+ group_pathkeys,
+ -1.0);
+ else
+ path = (Path *) create_incremental_sort_path(root,
+ grouped_rel,
+ path,
+ group_pathkeys,
+ presorted_keys,
+ -1.0);
+ }
+
+ /*
+ * qual is NIL because the HAVING clause cannot be evaluated until
+ * the final value of the aggregate is known.
+ */
+ path = (Path *) create_agg_path(root,
+ grouped_rel,
+ path,
+ agg_info->target,
+ AGG_SORTED,
+ AGGSPLIT_INITIAL_SERIAL,
+ agg_info->group_clauses,
+ NIL,
+ &agg_costs,
+ dNumPartialGroups);
+
+ add_partial_path(grouped_rel, path);
+ }
+ }
+
+ /*
+ * Add a partially-grouped HashAgg Path where possible
+ */
+ if (can_hash && cheapest_total_path != NULL)
+ {
+ Path *path;
+
+ /*
+ * Since the path originates from a non-grouped relation that is not
+ * aware of eager aggregation, we must ensure that it provides the
+ * correct input for partial aggregation.
+ */
+ path = (Path *) create_projection_path(root,
+ grouped_rel,
+ cheapest_total_path,
+ agg_info->agg_input);
+
+ /*
+ * qual is NIL because the HAVING clause cannot be evaluated until the
+ * final value of the aggregate is known.
+ */
+ path = (Path *) create_agg_path(root,
+ grouped_rel,
+ path,
+ agg_info->target,
+ AGG_HASHED,
+ AGGSPLIT_INITIAL_SERIAL,
+ agg_info->group_clauses,
+ NIL,
+ &agg_costs,
+ dNumGroups);
+
+ add_path(grouped_rel, path);
+ }
+
+ /*
+ * Now add a partially-grouped HashAgg partial Path where possible
+ */
+ if (can_hash && cheapest_partial_path != NULL)
+ {
+ Path *path;
+
+ /*
+ * Since the path originates from a non-grouped relation that is not
+ * aware of eager aggregation, we must ensure that it provides the
+ * correct input for partial aggregation.
+ */
+ path = (Path *) create_projection_path(root,
+ grouped_rel,
+ cheapest_partial_path,
+ agg_info->agg_input);
+
+ /*
+ * qual is NIL because the HAVING clause cannot be evaluated until the
+ * final value of the aggregate is known.
+ */
+ path = (Path *) create_agg_path(root,
+ grouped_rel,
+ path,
+ agg_info->target,
+ AGG_HASHED,
+ AGGSPLIT_INITIAL_SERIAL,
+ agg_info->group_clauses,
+ NIL,
+ &agg_costs,
+ dNumPartialGroups);
+
+ add_partial_path(grouped_rel, path);
+ }
+}
+
+/*
* make_rel_from_joinlist
* Build access paths using a "joinlist" to guide the join path search.
*
@@ -3493,11 +3915,19 @@ standard_join_search(PlannerInfo *root, int levels_needed, List *initial_rels)
*
* After that, we're done creating paths for the joinrel, so run
* set_cheapest().
+ *
+ * In addition, we also run generate_grouped_paths() for the grouped
+ * relation of each just-processed joinrel, and run set_cheapest() for
+ * the grouped relation afterwards.
*/
foreach(lc, root->join_rel_level[lev])
{
+ bool is_top_rel;
+
rel = (RelOptInfo *) lfirst(lc);
+ is_top_rel = bms_equal(rel->relids, root->all_query_rels);
+
/* Create paths for partitionwise joins. */
generate_partitionwise_join_paths(root, rel);
@@ -3507,12 +3937,28 @@ standard_join_search(PlannerInfo *root, int levels_needed, List *initial_rels)
* once we know the final targetlist (see grouping_planner's and
* its call to apply_scanjoin_target_to_paths).
*/
- if (!bms_equal(rel->relids, root->all_query_rels))
+ if (!is_top_rel)
generate_useful_gather_paths(root, rel, false);
/* Find and save the cheapest paths for this rel */
set_cheapest(rel);
+ /*
+ * Except for the topmost scan/join rel, consider generating
+ * partial aggregation paths for the grouped relation on top of
+ * the paths of this rel. After that, we're done creating paths
+ * for the grouped relation, so run set_cheapest().
+ */
+ if (rel->grouped_rel != NULL && !is_top_rel)
+ {
+ RelOptInfo *grouped_rel = rel->grouped_rel;
+
+ Assert(IS_GROUPED_REL(grouped_rel));
+
+ generate_grouped_paths(root, grouped_rel, rel);
+ set_cheapest(grouped_rel);
+ }
+
#ifdef OPTIMIZER_DEBUG
pprint(rel);
#endif
@@ -4382,6 +4828,25 @@ generate_partitionwise_join_paths(PlannerInfo *root, RelOptInfo *rel)
if (IS_DUMMY_REL(child_rel))
continue;
+ /*
+ * Except for the topmost scan/join rel, consider generating partial
+ * aggregation paths for the grouped relation on top of the paths of
+ * this partitioned child-join. After that, we're done creating paths
+ * for the grouped relation, so run set_cheapest().
+ */
+ if (child_rel->grouped_rel != NULL &&
+ !bms_equal(IS_OTHER_REL(rel) ?
+ rel->top_parent_relids : rel->relids,
+ root->all_query_rels))
+ {
+ RelOptInfo *grouped_rel = child_rel->grouped_rel;
+
+ Assert(IS_GROUPED_REL(grouped_rel));
+
+ generate_grouped_paths(root, grouped_rel, child_rel);
+ set_cheapest(grouped_rel);
+ }
+
#ifdef OPTIMIZER_DEBUG
pprint(child_rel);
#endif
diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c
index 535248aa525..43b84d239ed 100644
--- a/src/backend/optimizer/path/joinrels.c
+++ b/src/backend/optimizer/path/joinrels.c
@@ -16,6 +16,7 @@
#include "miscadmin.h"
#include "optimizer/appendinfo.h"
+#include "optimizer/cost.h"
#include "optimizer/joininfo.h"
#include "optimizer/pathnode.h"
#include "optimizer/paths.h"
@@ -36,6 +37,9 @@ static bool has_legal_joinclause(PlannerInfo *root, RelOptInfo *rel);
static bool restriction_is_constant_false(List *restrictlist,
RelOptInfo *joinrel,
bool only_pushed_down);
+static void make_grouped_join_rel(PlannerInfo *root, RelOptInfo *rel1,
+ RelOptInfo *rel2, RelOptInfo *joinrel,
+ SpecialJoinInfo *sjinfo, List *restrictlist);
static void populate_joinrel_with_paths(PlannerInfo *root, RelOptInfo *rel1,
RelOptInfo *rel2, RelOptInfo *joinrel,
SpecialJoinInfo *sjinfo, List *restrictlist);
@@ -762,6 +766,10 @@ make_join_rel(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2)
return joinrel;
}
+ /* Build a grouped join relation for 'joinrel' if possible. */
+ make_grouped_join_rel(root, rel1, rel2, joinrel, sjinfo,
+ restrictlist);
+
/* Add paths to the join relation. */
populate_joinrel_with_paths(root, rel1, rel2, joinrel, sjinfo,
restrictlist);
@@ -874,6 +882,186 @@ add_outer_joins_to_relids(PlannerInfo *root, Relids input_relids,
}
/*
+ * make_grouped_join_rel
+ * Build a grouped join relation for the given "joinrel" if eager
+ * aggregation is applicable and the resulting grouped paths are considered
+ * useful.
+ *
+ * There are two strategies for generating grouped paths for a join relation:
+ *
+ * 1. Join a grouped (partially aggregated) input relation with a non-grouped
+ * input (e.g., AGG(B) JOIN A).
+ *
+ * 2. Apply partial aggregation (sorted or hashed) on top of existing
+ * non-grouped join paths (e.g., AGG(A JOIN B)).
+ *
+ * To limit planning effort and avoid an explosion of alternatives, we adopt a
+ * strategy where partial aggregation is only pushed to the lowest possible
+ * level in the join tree that is deemed useful. That is, if grouped paths can
+ * be built using the first strategy, we skip consideration of the second
+ * strategy for the same join level.
+ *
+ * Additionally, if there are multiple lowest useful levels where partial
+ * aggregation could be applied, such as in a join tree with relations A, B,
+ * and C where both "AGG(A JOIN B) JOIN C" and "A JOIN AGG(B JOIN C)" are valid
+ * placements, we choose only the first one encountered during join search.
+ * This avoids generating multiple versions of the same grouped relation based
+ * on different aggregation placements.
+ *
+ * These heuristics also ensure that all grouped paths for the same grouped
+ * relation produce the same set of rows, which is a basic assumption in the
+ * planner.
+ */
+static void
+make_grouped_join_rel(PlannerInfo *root, RelOptInfo *rel1,
+ RelOptInfo *rel2, RelOptInfo *joinrel,
+ SpecialJoinInfo *sjinfo, List *restrictlist)
+{
+ RelOptInfo *grouped_rel;
+ RelOptInfo *grouped_rel1;
+ RelOptInfo *grouped_rel2;
+ bool rel1_empty;
+ bool rel2_empty;
+ Relids agg_apply_at;
+
+ /*
+ * If there are no aggregate expressions or grouping expressions, eager
+ * aggregation is not possible.
+ */
+ if (root->agg_clause_list == NIL ||
+ root->group_expr_list == NIL)
+ return;
+
+ /* Retrieve the grouped relations for the two input rels */
+ grouped_rel1 = rel1->grouped_rel;
+ grouped_rel2 = rel2->grouped_rel;
+
+ rel1_empty = (grouped_rel1 == NULL || IS_DUMMY_REL(grouped_rel1));
+ rel2_empty = (grouped_rel2 == NULL || IS_DUMMY_REL(grouped_rel2));
+
+ /* Find or construct a grouped joinrel for this joinrel */
+ grouped_rel = joinrel->grouped_rel;
+ if (grouped_rel == NULL)
+ {
+ RelAggInfo *agg_info = NULL;
+
+ /*
+ * Prepare the information needed to create grouped paths for this
+ * join relation.
+ */
+ agg_info = create_rel_agg_info(root, joinrel, rel1_empty == rel2_empty);
+ if (agg_info == NULL)
+ return;
+
+ /*
+ * If grouped paths for the given join relation are not considered
+ * useful, and no grouped paths can be built by joining grouped input
+ * relations, skip building the grouped join relation.
+ */
+ if (!agg_info->agg_useful &&
+ (rel1_empty == rel2_empty))
+ return;
+
+ /* build the grouped relation */
+ grouped_rel = build_grouped_rel(root, joinrel);
+ grouped_rel->reltarget = agg_info->target;
+
+ if (rel1_empty != rel2_empty)
+ {
+ /*
+ * If there is exactly one grouped input relation, then we can
+ * build grouped paths by joining the input relations. Set size
+ * estimates for the grouped join relation based on the input
+ * relations, and update the set of relids where partial
+ * aggregation is applied to that of the grouped input relation.
+ */
+ set_joinrel_size_estimates(root, grouped_rel,
+ rel1_empty ? rel1 : grouped_rel1,
+ rel2_empty ? rel2 : grouped_rel2,
+ sjinfo, restrictlist);
+ agg_info->apply_at = rel1_empty ?
+ grouped_rel2->agg_info->apply_at :
+ grouped_rel1->agg_info->apply_at;
+ }
+ else
+ {
+ /*
+ * Otherwise, grouped paths can be built by applying partial
+ * aggregation on top of existing non-grouped join paths. Set
+ * size estimates for the grouped join relation based on the
+ * estimated number of groups, and track the set of relids where
+ * partial aggregation is applied. Note that these values may be
+ * updated later if it is determined that grouped paths can be
+ * constructed by joining other input relations.
+ */
+ grouped_rel->rows = agg_info->grouped_rows;
+ agg_info->apply_at = bms_copy(joinrel->relids);
+ }
+
+ grouped_rel->agg_info = agg_info;
+ joinrel->grouped_rel = grouped_rel;
+ }
+
+ Assert(IS_GROUPED_REL(grouped_rel));
+
+ /* We may have already proven this grouped join relation to be dummy. */
+ if (IS_DUMMY_REL(grouped_rel))
+ return;
+
+ /*
+ * Nothing to do if there's no grouped input relation. Also, joining two
+ * grouped relations is not currently supported.
+ */
+ if (rel1_empty == rel2_empty)
+ return;
+
+ /*
+ * Get the set of relids where partial aggregation is applied among the
+ * given input relations.
+ */
+ agg_apply_at = rel1_empty ?
+ grouped_rel2->agg_info->apply_at :
+ grouped_rel1->agg_info->apply_at;
+
+ /*
+ * If it's not the designated level, skip building grouped paths.
+ *
+ * One exception is when it is a subset of the previously recorded level.
+ * In that case, we need to update the designated level to this one, and
+ * adjust the size estimates for the grouped join relation accordingly.
+ * For example, suppose partial aggregation can be applied on top of (B
+ * JOIN C). If we first construct the join as ((A JOIN B) JOIN C), we'd
+ * record the designated level as including all three relations (A B C).
+ * Later, when we consider (A JOIN (B JOIN C)), we encounter the smaller
+ * (B C) join level directly. Since this is a subset of the previous
+ * level and still valid for partial aggregation, we update the designated
+ * level to (B C), and adjust the size estimates accordingly.
+ */
+ if (!bms_equal(agg_apply_at, grouped_rel->agg_info->apply_at))
+ {
+ if (bms_is_subset(agg_apply_at, grouped_rel->agg_info->apply_at))
+ {
+ /* Adjust the size estimates for the grouped join relation. */
+ set_joinrel_size_estimates(root, grouped_rel,
+ rel1_empty ? rel1 : grouped_rel1,
+ rel2_empty ? rel2 : grouped_rel2,
+ sjinfo, restrictlist);
+ grouped_rel->agg_info->apply_at = agg_apply_at;
+ }
+ else
+ return;
+ }
+
+ /* Make paths for the grouped join relation. */
+ populate_joinrel_with_paths(root,
+ rel1_empty ? rel1 : grouped_rel1,
+ rel2_empty ? rel2 : grouped_rel2,
+ grouped_rel,
+ sjinfo,
+ restrictlist);
+}
+
+/*
* populate_joinrel_with_paths
* Add paths to the given joinrel for given pair of joining relations. The
* SpecialJoinInfo provides details about the join and the restrictlist
@@ -1615,6 +1803,11 @@ try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2,
adjust_child_relids(joinrel->relids,
nappinfos, appinfos)));
+ /* Build a grouped join relation for 'child_joinrel' if possible */
+ make_grouped_join_rel(root, child_rel1, child_rel2,
+ child_joinrel, child_sjinfo,
+ child_restrictlist);
+
/* And make paths for the child join */
populate_joinrel_with_paths(root, child_rel1, child_rel2,
child_joinrel, child_sjinfo,
diff --git a/src/backend/optimizer/path/pathkeys.c b/src/backend/optimizer/path/pathkeys.c
index 8b04d40d36d..879dcb4608e 100644
--- a/src/backend/optimizer/path/pathkeys.c
+++ b/src/backend/optimizer/path/pathkeys.c
@@ -2154,14 +2154,31 @@ right_merge_direction(PlannerInfo *root, PathKey *pathkey)
* Because we the have the possibility of incremental sort, a prefix list of
* keys is potentially useful for improving the performance of the requested
* ordering. Thus we return 0, if no valuable keys are found, or the number
- * of leading keys shared by the list and the requested ordering..
+ * of leading keys shared by the list and the requested ordering.
*/
static int
pathkeys_useful_for_ordering(PlannerInfo *root, List *pathkeys)
{
int n_common_pathkeys;
- (void) pathkeys_count_contained_in(root->query_pathkeys, pathkeys,
+ (void) pathkeys_count_contained_in(root->sort_pathkeys, pathkeys,
+ &n_common_pathkeys);
+
+ return n_common_pathkeys;
+}
+
+/*
+ * pathkeys_useful_for_windowing
+ * Count the number of pathkeys that are useful for meeting the
+ * query's desired sort order for window function evaluation.
+ */
+static int
+pathkeys_useful_for_windowing(PlannerInfo *root, List *pathkeys)
+{
+ int n_common_pathkeys;
+
+ (void) pathkeys_count_contained_in(root->window_pathkeys,
+ pathkeys,
&n_common_pathkeys);
return n_common_pathkeys;
@@ -2278,6 +2295,9 @@ truncate_useless_pathkeys(PlannerInfo *root,
nuseful2 = pathkeys_useful_for_ordering(root, pathkeys);
if (nuseful2 > nuseful)
nuseful = nuseful2;
+ nuseful2 = pathkeys_useful_for_windowing(root, pathkeys);
+ if (nuseful2 > nuseful)
+ nuseful = nuseful2;
nuseful2 = pathkeys_useful_for_grouping(root, pathkeys);
if (nuseful2 > nuseful)
nuseful = nuseful2;