Factor some code out of create_grouping_paths.
authorRobert Haas <rhaas@postgresql.org>
Fri, 26 Jan 2018 20:03:12 +0000 (15:03 -0500)
committerRobert Haas <rhaas@postgresql.org>
Fri, 26 Jan 2018 20:03:12 +0000 (15:03 -0500)
This is preparatory refactoring to prepare the way for partition-wise
aggregate, which will reuse the new subroutines for child grouping
rels.  It also does not seem like a bad idea on general principle,
as the function was getting pretty long.

Jeevan Chalke.  The larger patch series of which this patch is a part
was reviewed and tested by Antonin Houska, Rajkumar Raghuwanshi,
Ashutosh Bapat, David Rowley, Dilip Kumar, Konstantin Knizhnik,
Pascal Legrand, and me.  Some cosmetic changes by me.

Discussion: http://postgr.es/m/CAM2+6=V64_xhstVHie0Rz=KPEQnLJMZt_e314P0jaT_oJ9MR8A@mail.gmail.com

src/backend/optimizer/plan/planner.c

index 53870432ea7b7ba0dbc245c5f5a938e858e00e3c..2a4e22b6c889a8d2ac15ebb324296d3b11b5fc51 100644 (file)
@@ -185,6 +185,26 @@ static PathTarget *make_sort_input_target(PlannerInfo *root,
                                           bool *have_postponed_srfs);
 static void adjust_paths_for_srfs(PlannerInfo *root, RelOptInfo *rel,
                                          List *targets, List *targets_contain_srfs);
+static void add_paths_to_grouping_rel(PlannerInfo *root, RelOptInfo *input_rel,
+                                                 RelOptInfo *grouped_rel, PathTarget *target,
+                                                 PathTarget *partial_grouping_target,
+                                                 const AggClauseCosts *agg_costs,
+                                                 const AggClauseCosts *agg_final_costs,
+                                                 grouping_sets_data *gd, bool can_sort, bool can_hash,
+                                                 double dNumGroups, List *havingQual);
+static void add_partial_paths_to_grouping_rel(PlannerInfo *root,
+                                                                 RelOptInfo *input_rel,
+                                                                 RelOptInfo *grouped_rel,
+                                                                 PathTarget *target,
+                                                                 PathTarget *partial_grouping_target,
+                                                                 AggClauseCosts *agg_partial_costs,
+                                                                 AggClauseCosts *agg_final_costs,
+                                                                 grouping_sets_data *gd,
+                                                                 bool can_sort,
+                                                                 bool can_hash,
+                                                                 List *havingQual);
+static bool can_parallel_agg(PlannerInfo *root, RelOptInfo *input_rel,
+                                RelOptInfo *grouped_rel, const AggClauseCosts *agg_costs);
 
 
 /*****************************************************************************
@@ -3610,15 +3630,11 @@ create_grouping_paths(PlannerInfo *root,
        PathTarget *partial_grouping_target = NULL;
        AggClauseCosts agg_partial_costs;       /* parallel only */
        AggClauseCosts agg_final_costs; /* parallel only */
-       Size            hashaggtablesize;
        double          dNumGroups;
-       double          dNumPartialGroups = 0;
        bool            can_hash;
        bool            can_sort;
        bool            try_parallel_aggregation;
 
-       ListCell   *lc;
-
        /* For now, do all work in the (GROUP_AGG, NULL) upperrel */
        grouped_rel = fetch_upper_rel(root, UPPERREL_GROUP_AGG, NULL);
 
@@ -3754,44 +3770,11 @@ create_grouping_paths(PlannerInfo *root,
                                (gd ? gd->any_hashable : grouping_is_hashable(parse->groupClause)));
 
        /*
-        * If grouped_rel->consider_parallel is true, then paths that we generate
-        * for this grouping relation could be run inside of a worker, but that
-        * doesn't mean we can actually use the PartialAggregate/FinalizeAggregate
-        * execution strategy.  Figure that out.
+        * Figure out whether a PartialAggregate/Finalize Aggregate execution
+        * strategy is viable.
         */
-       if (!grouped_rel->consider_parallel)
-       {
-               /* Not even parallel-safe. */
-               try_parallel_aggregation = false;
-       }
-       else if (input_rel->partial_pathlist == NIL)
-       {
-               /* Nothing to use as input for partial aggregate. */
-               try_parallel_aggregation = false;
-       }
-       else if (!parse->hasAggs && parse->groupClause == NIL)
-       {
-               /*
-                * We don't know how to do parallel aggregation unless we have either
-                * some aggregates or a grouping clause.
-                */
-               try_parallel_aggregation = false;
-       }
-       else if (parse->groupingSets)
-       {
-               /* We don't know how to do grouping sets in parallel. */
-               try_parallel_aggregation = false;
-       }
-       else if (agg_costs->hasNonPartial || agg_costs->hasNonSerial)
-       {
-               /* Insufficient support for partial mode. */
-               try_parallel_aggregation = false;
-       }
-       else
-       {
-               /* Everything looks good. */
-               try_parallel_aggregation = true;
-       }
+       try_parallel_aggregation = can_parallel_agg(root, input_rel, grouped_rel,
+                                                                                               agg_costs);
 
        /*
         * Before generating paths for grouped_rel, we first generate any possible
@@ -3803,8 +3786,6 @@ create_grouping_paths(PlannerInfo *root,
         */
        if (try_parallel_aggregation)
        {
-               Path       *cheapest_partial_path = linitial(input_rel->partial_pathlist);
-
                /*
                 * Build target list for partial aggregate paths.  These paths cannot
                 * just emit the same tlist as regular aggregate paths, because (1) we
@@ -3814,11 +3795,6 @@ create_grouping_paths(PlannerInfo *root,
                 */
                partial_grouping_target = make_partial_grouping_target(root, target);
 
-               /* Estimate number of partial groups. */
-               dNumPartialGroups = get_number_of_groups(root,
-                                                                                                cheapest_partial_path->rows,
-                                                                                                gd);
-
                /*
                 * Collect statistics about aggregates for estimating costs of
                 * performing aggregation in parallel.
@@ -3841,480 +3817,141 @@ create_grouping_paths(PlannerInfo *root,
                                                                 &agg_final_costs);
                }
 
-               if (can_sort)
-               {
-                       /* This was checked before setting try_parallel_aggregation */
-                       Assert(parse->hasAggs || parse->groupClause);
+               add_partial_paths_to_grouping_rel(root, input_rel, grouped_rel, target,
+                                                                                 partial_grouping_target,
+                                                                                 &agg_partial_costs, &agg_final_costs,
+                                                                                 gd, can_sort, can_hash,
+                                                                                 (List *) parse->havingQual);
+       }
 
-                       /*
-                        * Use any available suitably-sorted path as input, and also
-                        * consider sorting the cheapest partial path.
-                        */
-                       foreach(lc, input_rel->partial_pathlist)
-                       {
-                               Path       *path = (Path *) lfirst(lc);
-                               bool            is_sorted;
+       /* Build final grouping paths */
+       add_paths_to_grouping_rel(root, input_rel, grouped_rel, target,
+                                                         partial_grouping_target, agg_costs,
+                                                         &agg_final_costs, gd, can_sort, can_hash,
+                                                         dNumGroups, (List *) parse->havingQual);
 
-                               is_sorted = pathkeys_contained_in(root->group_pathkeys,
-                                                                                                 path->pathkeys);
-                               if (path == cheapest_partial_path || is_sorted)
-                               {
-                                       /* Sort the cheapest partial path, if it isn't already */
-                                       if (!is_sorted)
-                                               path = (Path *) create_sort_path(root,
-                                                                                                                grouped_rel,
-                                                                                                                path,
-                                                                                                                root->group_pathkeys,
-                                                                                                                -1.0);
+       /* Give a helpful error if we failed to find any implementation */
+       if (grouped_rel->pathlist == NIL)
+               ereport(ERROR,
+                               (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                errmsg("could not implement GROUP BY"),
+                                errdetail("Some of the datatypes only support hashing, while others only support sorting.")));
 
-                                       if (parse->hasAggs)
-                                               add_partial_path(grouped_rel, (Path *)
-                                                                                create_agg_path(root,
-                                                                                                                grouped_rel,
-                                                                                                                path,
-                                                                                                                partial_grouping_target,
-                                                                                                                parse->groupClause ? AGG_SORTED : AGG_PLAIN,
-                                                                                                                AGGSPLIT_INITIAL_SERIAL,
-                                                                                                                parse->groupClause,
-                                                                                                                NIL,
-                                                                                                                &agg_partial_costs,
-                                                                                                                dNumPartialGroups));
-                                       else
-                                               add_partial_path(grouped_rel, (Path *)
-                                                                                create_group_path(root,
-                                                                                                                  grouped_rel,
-                                                                                                                  path,
-                                                                                                                  partial_grouping_target,
-                                                                                                                  parse->groupClause,
-                                                                                                                  NIL,
-                                                                                                                  dNumPartialGroups));
-                               }
-                       }
-               }
+       /*
+        * If there is an FDW that's responsible for all baserels of the query,
+        * let it consider adding ForeignPaths.
+        */
+       if (grouped_rel->fdwroutine &&
+               grouped_rel->fdwroutine->GetForeignUpperPaths)
+               grouped_rel->fdwroutine->GetForeignUpperPaths(root, UPPERREL_GROUP_AGG,
+                                                                                                         input_rel, grouped_rel);
 
-               if (can_hash)
-               {
-                       /* Checked above */
-                       Assert(parse->hasAggs || parse->groupClause);
+       /* Let extensions possibly add some more paths */
+       if (create_upper_paths_hook)
+               (*create_upper_paths_hook) (root, UPPERREL_GROUP_AGG,
+                                                                       input_rel, grouped_rel);
+
+       /* Now choose the best path(s) */
+       set_cheapest(grouped_rel);
 
-                       hashaggtablesize =
-                               estimate_hashagg_tablesize(cheapest_partial_path,
-                                                                                  &agg_partial_costs,
-                                                                                  dNumPartialGroups);
+       /*
+        * We've been using the partial pathlist for the grouped relation to hold
+        * partially aggregated paths, but that's actually a little bit bogus
+        * because it's unsafe for later planning stages -- like ordered_rel ---
+        * to get the idea that they can use these partial paths as if they didn't
+        * need a FinalizeAggregate step.  Zap the partial pathlist at this stage
+        * so we don't get confused.
+        */
+       grouped_rel->partial_pathlist = NIL;
 
-                       /*
-                        * Tentatively produce a partial HashAgg Path, depending on if it
-                        * looks as if the hash table will fit in work_mem.
-                        */
-                       if (hashaggtablesize < work_mem * 1024L)
-                       {
-                               add_partial_path(grouped_rel, (Path *)
-                                                                create_agg_path(root,
-                                                                                                grouped_rel,
-                                                                                                cheapest_partial_path,
-                                                                                                partial_grouping_target,
-                                                                                                AGG_HASHED,
-                                                                                                AGGSPLIT_INITIAL_SERIAL,
-                                                                                                parse->groupClause,
-                                                                                                NIL,
-                                                                                                &agg_partial_costs,
-                                                                                                dNumPartialGroups));
-                       }
-               }
-       }
+       return grouped_rel;
+}
 
-       /* Build final grouping paths */
-       if (can_sort)
+
+/*
+ * For a given input path, consider the possible ways of doing grouping sets on
+ * it, by combinations of hashing and sorting.  This can be called multiple
+ * times, so it's important that it not scribble on input.  No result is
+ * returned, but any generated paths are added to grouped_rel.
+ */
+static void
+consider_groupingsets_paths(PlannerInfo *root,
+                                                       RelOptInfo *grouped_rel,
+                                                       Path *path,
+                                                       bool is_sorted,
+                                                       bool can_hash,
+                                                       PathTarget *target,
+                                                       grouping_sets_data *gd,
+                                                       const AggClauseCosts *agg_costs,
+                                                       double dNumGroups)
+{
+       Query      *parse = root->parse;
+
+       /*
+        * If we're not being offered sorted input, then only consider plans that
+        * can be done entirely by hashing.
+        *
+        * We can hash everything if it looks like it'll fit in work_mem. But if
+        * the input is actually sorted despite not being advertised as such, we
+        * prefer to make use of that in order to use less memory.
+        *
+        * If none of the grouping sets are sortable, then ignore the work_mem
+        * limit and generate a path anyway, since otherwise we'll just fail.
+        */
+       if (!is_sorted)
        {
-               /*
-                * Use any available suitably-sorted path as input, and also consider
-                * sorting the cheapest-total path.
-                */
-               foreach(lc, input_rel->pathlist)
-               {
-                       Path       *path = (Path *) lfirst(lc);
-                       bool            is_sorted;
+               List       *new_rollups = NIL;
+               RollupData *unhashed_rollup = NULL;
+               List       *sets_data;
+               List       *empty_sets_data = NIL;
+               List       *empty_sets = NIL;
+               ListCell   *lc;
+               ListCell   *l_start = list_head(gd->rollups);
+               AggStrategy strat = AGG_HASHED;
+               Size            hashsize;
+               double          exclude_groups = 0.0;
 
-                       is_sorted = pathkeys_contained_in(root->group_pathkeys,
-                                                                                         path->pathkeys);
-                       if (path == cheapest_path || is_sorted)
-                       {
-                               /* Sort the cheapest-total path if it isn't already sorted */
-                               if (!is_sorted)
-                                       path = (Path *) create_sort_path(root,
-                                                                                                        grouped_rel,
-                                                                                                        path,
-                                                                                                        root->group_pathkeys,
-                                                                                                        -1.0);
+               Assert(can_hash);
 
-                               /* Now decide what to stick atop it */
-                               if (parse->groupingSets)
-                               {
-                                       consider_groupingsets_paths(root, grouped_rel,
-                                                                                               path, true, can_hash, target,
-                                                                                               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,
-                                                                                        target,
-                                                                                        parse->groupClause ? AGG_SORTED : AGG_PLAIN,
-                                                                                        AGGSPLIT_SIMPLE,
-                                                                                        parse->groupClause,
-                                                                                        (List *) parse->havingQual,
-                                                                                        agg_costs,
-                                                                                        dNumGroups));
-                               }
-                               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,
-                                                                                          target,
-                                                                                          parse->groupClause,
-                                                                                          (List *) parse->havingQual,
-                                                                                          dNumGroups));
-                               }
-                               else
-                               {
-                                       /* Other cases should have been handled above */
-                                       Assert(false);
-                               }
-                       }
+               if (pathkeys_contained_in(root->group_pathkeys, path->pathkeys))
+               {
+                       unhashed_rollup = lfirst_node(RollupData, l_start);
+                       exclude_groups = unhashed_rollup->numGroups;
+                       l_start = lnext(l_start);
                }
 
+               hashsize = estimate_hashagg_tablesize(path,
+                                                                                         agg_costs,
+                                                                                         dNumGroups - exclude_groups);
+
                /*
-                * Now generate a complete GroupAgg Path atop of the cheapest partial
-                * path.  We can do this using either Gather or Gather Merge.
+                * gd->rollups is empty if we have only unsortable columns to work
+                * with.  Override work_mem in that case; otherwise, we'll rely on the
+                * sorted-input case to generate usable mixed paths.
                 */
-               if (grouped_rel->partial_pathlist)
-               {
-                       Path       *path = (Path *) linitial(grouped_rel->partial_pathlist);
-                       double          total_groups = path->rows * path->parallel_workers;
+               if (hashsize > work_mem * 1024L && gd->rollups)
+                       return;                         /* nope, won't fit */
 
-                       path = (Path *) create_gather_path(root,
-                                                                                          grouped_rel,
-                                                                                          path,
-                                                                                          partial_grouping_target,
-                                                                                          NULL,
-                                                                                          &total_groups);
+               /*
+                * We need to burst the existing rollups list into individual grouping
+                * sets and recompute a groupClause for each set.
+                */
+               sets_data = list_copy(gd->unsortable_sets);
+
+               for_each_cell(lc, l_start)
+               {
+                       RollupData *rollup = lfirst_node(RollupData, lc);
 
                        /*
-                        * Since Gather's output is always unsorted, we'll need to sort,
-                        * unless there's no GROUP BY clause or a degenerate (constant)
-                        * one, in which case there will only be a single group.
-                        */
-                       if (root->group_pathkeys)
-                               path = (Path *) create_sort_path(root,
-                                                                                                grouped_rel,
-                                                                                                path,
-                                                                                                root->group_pathkeys,
-                                                                                                -1.0);
-
-                       if (parse->hasAggs)
-                               add_path(grouped_rel, (Path *)
-                                                create_agg_path(root,
-                                                                                grouped_rel,
-                                                                                path,
-                                                                                target,
-                                                                                parse->groupClause ? AGG_SORTED : AGG_PLAIN,
-                                                                                AGGSPLIT_FINAL_DESERIAL,
-                                                                                parse->groupClause,
-                                                                                (List *) parse->havingQual,
-                                                                                &agg_final_costs,
-                                                                                dNumGroups));
-                       else
-                               add_path(grouped_rel, (Path *)
-                                                create_group_path(root,
-                                                                                  grouped_rel,
-                                                                                  path,
-                                                                                  target,
-                                                                                  parse->groupClause,
-                                                                                  (List *) parse->havingQual,
-                                                                                  dNumGroups));
-
-                       /*
-                        * The point of using Gather Merge rather than Gather is that it
-                        * can preserve the ordering of the input path, so there's no
-                        * reason to try it unless (1) it's possible to produce more than
-                        * one output row and (2) we want the output path to be ordered.
-                        */
-                       if (parse->groupClause != NIL && root->group_pathkeys != NIL)
-                       {
-                               foreach(lc, grouped_rel->partial_pathlist)
-                               {
-                                       Path       *subpath = (Path *) lfirst(lc);
-                                       Path       *gmpath;
-                                       double          total_groups;
-
-                                       /*
-                                        * It's useful to consider paths that are already properly
-                                        * ordered for Gather Merge, because those don't need a
-                                        * sort.  It's also useful to consider the cheapest path,
-                                        * because sorting it in parallel and then doing Gather
-                                        * Merge may be better than doing an unordered Gather
-                                        * followed by a sort.  But there's no point in
-                                        * considering non-cheapest paths that aren't already
-                                        * sorted correctly.
-                                        */
-                                       if (path != subpath &&
-                                               !pathkeys_contained_in(root->group_pathkeys,
-                                                                                          subpath->pathkeys))
-                                               continue;
-
-                                       total_groups = subpath->rows * subpath->parallel_workers;
-
-                                       gmpath = (Path *)
-                                               create_gather_merge_path(root,
-                                                                                                grouped_rel,
-                                                                                                subpath,
-                                                                                                partial_grouping_target,
-                                                                                                root->group_pathkeys,
-                                                                                                NULL,
-                                                                                                &total_groups);
-
-                                       if (parse->hasAggs)
-                                               add_path(grouped_rel, (Path *)
-                                                                create_agg_path(root,
-                                                                                                grouped_rel,
-                                                                                                gmpath,
-                                                                                                target,
-                                                                                                parse->groupClause ? AGG_SORTED : AGG_PLAIN,
-                                                                                                AGGSPLIT_FINAL_DESERIAL,
-                                                                                                parse->groupClause,
-                                                                                                (List *) parse->havingQual,
-                                                                                                &agg_final_costs,
-                                                                                                dNumGroups));
-                                       else
-                                               add_path(grouped_rel, (Path *)
-                                                                create_group_path(root,
-                                                                                                  grouped_rel,
-                                                                                                  gmpath,
-                                                                                                  target,
-                                                                                                  parse->groupClause,
-                                                                                                  (List *) parse->havingQual,
-                                                                                                  dNumGroups));
-                               }
-                       }
-               }
-       }
-
-       if (can_hash)
-       {
-               if (parse->groupingSets)
-               {
-                       /*
-                        * Try for a hash-only groupingsets path over unsorted input.
-                        */
-                       consider_groupingsets_paths(root, grouped_rel,
-                                                                               cheapest_path, false, true, target,
-                                                                               gd, agg_costs, dNumGroups);
-               }
-               else
-               {
-                       hashaggtablesize = estimate_hashagg_tablesize(cheapest_path,
-                                                                                                                 agg_costs,
-                                                                                                                 dNumGroups);
-
-                       /*
-                        * Provided that the estimated size of the hashtable does not
-                        * exceed work_mem, we'll generate a HashAgg Path, although if we
-                        * were unable to sort above, then we'd better generate a Path, so
-                        * that we at least have one.
-                        */
-                       if (hashaggtablesize < work_mem * 1024L ||
-                               grouped_rel->pathlist == NIL)
-                       {
-                               /*
-                                * We just need an Agg over the cheapest-total input path,
-                                * since input order won't matter.
-                                */
-                               add_path(grouped_rel, (Path *)
-                                                create_agg_path(root, grouped_rel,
-                                                                                cheapest_path,
-                                                                                target,
-                                                                                AGG_HASHED,
-                                                                                AGGSPLIT_SIMPLE,
-                                                                                parse->groupClause,
-                                                                                (List *) parse->havingQual,
-                                                                                agg_costs,
-                                                                                dNumGroups));
-                       }
-               }
-
-               /*
-                * Generate a HashAgg Path atop of the cheapest partial path. Once
-                * again, we'll only do this if it looks as though the hash table
-                * won't exceed work_mem.
-                */
-               if (grouped_rel->partial_pathlist)
-               {
-                       Path       *path = (Path *) linitial(grouped_rel->partial_pathlist);
-
-                       hashaggtablesize = estimate_hashagg_tablesize(path,
-                                                                                                                 &agg_final_costs,
-                                                                                                                 dNumGroups);
-
-                       if (hashaggtablesize < work_mem * 1024L)
-                       {
-                               double          total_groups = path->rows * path->parallel_workers;
-
-                               path = (Path *) create_gather_path(root,
-                                                                                                  grouped_rel,
-                                                                                                  path,
-                                                                                                  partial_grouping_target,
-                                                                                                  NULL,
-                                                                                                  &total_groups);
-
-                               add_path(grouped_rel, (Path *)
-                                                create_agg_path(root,
-                                                                                grouped_rel,
-                                                                                path,
-                                                                                target,
-                                                                                AGG_HASHED,
-                                                                                AGGSPLIT_FINAL_DESERIAL,
-                                                                                parse->groupClause,
-                                                                                (List *) parse->havingQual,
-                                                                                &agg_final_costs,
-                                                                                dNumGroups));
-                       }
-               }
-       }
-
-       /* Give a helpful error if we failed to find any implementation */
-       if (grouped_rel->pathlist == NIL)
-               ereport(ERROR,
-                               (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-                                errmsg("could not implement GROUP BY"),
-                                errdetail("Some of the datatypes only support hashing, while others only support sorting.")));
-
-       /*
-        * If there is an FDW that's responsible for all baserels of the query,
-        * let it consider adding ForeignPaths.
-        */
-       if (grouped_rel->fdwroutine &&
-               grouped_rel->fdwroutine->GetForeignUpperPaths)
-               grouped_rel->fdwroutine->GetForeignUpperPaths(root, UPPERREL_GROUP_AGG,
-                                                                                                         input_rel, grouped_rel);
-
-       /* Let extensions possibly add some more paths */
-       if (create_upper_paths_hook)
-               (*create_upper_paths_hook) (root, UPPERREL_GROUP_AGG,
-                                                                       input_rel, grouped_rel);
-
-       /* Now choose the best path(s) */
-       set_cheapest(grouped_rel);
-
-       /*
-        * We've been using the partial pathlist for the grouped relation to hold
-        * partially aggregated paths, but that's actually a little bit bogus
-        * because it's unsafe for later planning stages -- like ordered_rel ---
-        * to get the idea that they can use these partial paths as if they didn't
-        * need a FinalizeAggregate step.  Zap the partial pathlist at this stage
-        * so we don't get confused.
-        */
-       grouped_rel->partial_pathlist = NIL;
-
-       return grouped_rel;
-}
-
-
-/*
- * For a given input path, consider the possible ways of doing grouping sets on
- * it, by combinations of hashing and sorting.  This can be called multiple
- * times, so it's important that it not scribble on input.  No result is
- * returned, but any generated paths are added to grouped_rel.
- */
-static void
-consider_groupingsets_paths(PlannerInfo *root,
-                                                       RelOptInfo *grouped_rel,
-                                                       Path *path,
-                                                       bool is_sorted,
-                                                       bool can_hash,
-                                                       PathTarget *target,
-                                                       grouping_sets_data *gd,
-                                                       const AggClauseCosts *agg_costs,
-                                                       double dNumGroups)
-{
-       Query      *parse = root->parse;
-
-       /*
-        * If we're not being offered sorted input, then only consider plans that
-        * can be done entirely by hashing.
-        *
-        * We can hash everything if it looks like it'll fit in work_mem. But if
-        * the input is actually sorted despite not being advertised as such, we
-        * prefer to make use of that in order to use less memory.
-        *
-        * If none of the grouping sets are sortable, then ignore the work_mem
-        * limit and generate a path anyway, since otherwise we'll just fail.
-        */
-       if (!is_sorted)
-       {
-               List       *new_rollups = NIL;
-               RollupData *unhashed_rollup = NULL;
-               List       *sets_data;
-               List       *empty_sets_data = NIL;
-               List       *empty_sets = NIL;
-               ListCell   *lc;
-               ListCell   *l_start = list_head(gd->rollups);
-               AggStrategy strat = AGG_HASHED;
-               Size            hashsize;
-               double          exclude_groups = 0.0;
-
-               Assert(can_hash);
-
-               if (pathkeys_contained_in(root->group_pathkeys, path->pathkeys))
-               {
-                       unhashed_rollup = lfirst_node(RollupData, l_start);
-                       exclude_groups = unhashed_rollup->numGroups;
-                       l_start = lnext(l_start);
-               }
-
-               hashsize = estimate_hashagg_tablesize(path,
-                                                                                         agg_costs,
-                                                                                         dNumGroups - exclude_groups);
-
-               /*
-                * gd->rollups is empty if we have only unsortable columns to work
-                * with.  Override work_mem in that case; otherwise, we'll rely on the
-                * sorted-input case to generate usable mixed paths.
-                */
-               if (hashsize > work_mem * 1024L && gd->rollups)
-                       return;                         /* nope, won't fit */
-
-               /*
-                * We need to burst the existing rollups list into individual grouping
-                * sets and recompute a groupClause for each set.
-                */
-               sets_data = list_copy(gd->unsortable_sets);
-
-               for_each_cell(lc, l_start)
-               {
-                       RollupData *rollup = lfirst_node(RollupData, lc);
-
-                       /*
-                        * If we find an unhashable rollup that's not been skipped by the
-                        * "actually sorted" check above, we can't cope; we'd need sorted
-                        * input (with a different sort order) but we can't get that here.
-                        * So bail out; we'll get a valid path from the is_sorted case
-                        * instead.
-                        *
-                        * The mere presence of empty grouping sets doesn't make a rollup
-                        * unhashable (see preprocess_grouping_sets), we handle those
-                        * specially below.
+                        * If we find an unhashable rollup that's not been skipped by the
+                        * "actually sorted" check above, we can't cope; we'd need sorted
+                        * input (with a different sort order) but we can't get that here.
+                        * So bail out; we'll get a valid path from the is_sorted case
+                        * instead.
+                        *
+                        * The mere presence of empty grouping sets doesn't make a rollup
+                        * unhashable (see preprocess_grouping_sets), we handle those
+                        * specially below.
                         */
                        if (!rollup->hashable)
                                return;
@@ -5971,246 +5608,693 @@ adjust_paths_for_srfs(PlannerInfo *root, RelOptInfo *rel,
                        rel->cheapest_total_path = newpath;
        }
 
-       /* Likewise for partial paths, if any */
-       foreach(lc, rel->partial_pathlist)
+       /* Likewise for partial paths, if any */
+       foreach(lc, rel->partial_pathlist)
+       {
+               Path       *subpath = (Path *) lfirst(lc);
+               Path       *newpath = subpath;
+               ListCell   *lc1,
+                                  *lc2;
+
+               Assert(subpath->param_info == NULL);
+               forboth(lc1, targets, lc2, targets_contain_srfs)
+               {
+                       PathTarget *thistarget = lfirst_node(PathTarget, lc1);
+                       bool            contains_srfs = (bool) lfirst_int(lc2);
+
+                       /* If this level doesn't contain SRFs, do regular projection */
+                       if (contains_srfs)
+                               newpath = (Path *) create_set_projection_path(root,
+                                                                                                                         rel,
+                                                                                                                         newpath,
+                                                                                                                         thistarget);
+                       else
+                       {
+                               /* avoid apply_projection_to_path, in case of multiple refs */
+                               newpath = (Path *) create_projection_path(root,
+                                                                                                                 rel,
+                                                                                                                 newpath,
+                                                                                                                 thistarget);
+                       }
+               }
+               lfirst(lc) = newpath;
+       }
+}
+
+/*
+ * expression_planner
+ *             Perform planner's transformations on a standalone expression.
+ *
+ * Various utility commands need to evaluate expressions that are not part
+ * of a plannable query.  They can do so using the executor's regular
+ * expression-execution machinery, but first the expression has to be fed
+ * through here to transform it from parser output to something executable.
+ *
+ * Currently, we disallow sublinks in standalone expressions, so there's no
+ * real "planning" involved here.  (That might not always be true though.)
+ * What we must do is run eval_const_expressions to ensure that any function
+ * calls are converted to positional notation and function default arguments
+ * get inserted.  The fact that constant subexpressions get simplified is a
+ * side-effect that is useful when the expression will get evaluated more than
+ * once.  Also, we must fix operator function IDs.
+ *
+ * Note: this must not make any damaging changes to the passed-in expression
+ * tree.  (It would actually be okay to apply fix_opfuncids to it, but since
+ * we first do an expression_tree_mutator-based walk, what is returned will
+ * be a new node tree.)
+ */
+Expr *
+expression_planner(Expr *expr)
+{
+       Node       *result;
+
+       /*
+        * Convert named-argument function calls, insert default arguments and
+        * simplify constant subexprs
+        */
+       result = eval_const_expressions(NULL, (Node *) expr);
+
+       /* Fill in opfuncid values if missing */
+       fix_opfuncids(result);
+
+       return (Expr *) result;
+}
+
+
+/*
+ * plan_cluster_use_sort
+ *             Use the planner to decide how CLUSTER should implement sorting
+ *
+ * tableOid is the OID of a table to be clustered on its index indexOid
+ * (which is already known to be a btree index).  Decide whether it's
+ * cheaper to do an indexscan or a seqscan-plus-sort to execute the CLUSTER.
+ * Return true to use sorting, false to use an indexscan.
+ *
+ * Note: caller had better already hold some type of lock on the table.
+ */
+bool
+plan_cluster_use_sort(Oid tableOid, Oid indexOid)
+{
+       PlannerInfo *root;
+       Query      *query;
+       PlannerGlobal *glob;
+       RangeTblEntry *rte;
+       RelOptInfo *rel;
+       IndexOptInfo *indexInfo;
+       QualCost        indexExprCost;
+       Cost            comparisonCost;
+       Path       *seqScanPath;
+       Path            seqScanAndSortPath;
+       IndexPath  *indexScanPath;
+       ListCell   *lc;
+
+       /* We can short-circuit the cost comparison if indexscans are disabled */
+       if (!enable_indexscan)
+               return true;                    /* use sort */
+
+       /* Set up mostly-dummy planner state */
+       query = makeNode(Query);
+       query->commandType = CMD_SELECT;
+
+       glob = makeNode(PlannerGlobal);
+
+       root = makeNode(PlannerInfo);
+       root->parse = query;
+       root->glob = glob;
+       root->query_level = 1;
+       root->planner_cxt = CurrentMemoryContext;
+       root->wt_param_id = -1;
+
+       /* Build a minimal RTE for the rel */
+       rte = makeNode(RangeTblEntry);
+       rte->rtekind = RTE_RELATION;
+       rte->relid = tableOid;
+       rte->relkind = RELKIND_RELATION;        /* Don't be too picky. */
+       rte->lateral = false;
+       rte->inh = false;
+       rte->inFromCl = true;
+       query->rtable = list_make1(rte);
+
+       /* Set up RTE/RelOptInfo arrays */
+       setup_simple_rel_arrays(root);
+
+       /* Build RelOptInfo */
+       rel = build_simple_rel(root, 1, NULL);
+
+       /* Locate IndexOptInfo for the target index */
+       indexInfo = NULL;
+       foreach(lc, rel->indexlist)
+       {
+               indexInfo = lfirst_node(IndexOptInfo, lc);
+               if (indexInfo->indexoid == indexOid)
+                       break;
+       }
+
+       /*
+        * It's possible that get_relation_info did not generate an IndexOptInfo
+        * for the desired index; this could happen if it's not yet reached its
+        * indcheckxmin usability horizon, or if it's a system index and we're
+        * ignoring system indexes.  In such cases we should tell CLUSTER to not
+        * trust the index contents but use seqscan-and-sort.
+        */
+       if (lc == NULL)                         /* not in the list? */
+               return true;                    /* use sort */
+
+       /*
+        * Rather than doing all the pushups that would be needed to use
+        * set_baserel_size_estimates, just do a quick hack for rows and width.
+        */
+       rel->rows = rel->tuples;
+       rel->reltarget->width = get_relation_data_width(tableOid, NULL);
+
+       root->total_table_pages = rel->pages;
+
+       /*
+        * Determine eval cost of the index expressions, if any.  We need to
+        * charge twice that amount for each tuple comparison that happens during
+        * the sort, since tuplesort.c will have to re-evaluate the index
+        * expressions each time.  (XXX that's pretty inefficient...)
+        */
+       cost_qual_eval(&indexExprCost, indexInfo->indexprs, root);
+       comparisonCost = 2.0 * (indexExprCost.startup + indexExprCost.per_tuple);
+
+       /* Estimate the cost of seq scan + sort */
+       seqScanPath = create_seqscan_path(root, rel, NULL, 0);
+       cost_sort(&seqScanAndSortPath, root, NIL,
+                         seqScanPath->total_cost, rel->tuples, rel->reltarget->width,
+                         comparisonCost, maintenance_work_mem, -1.0);
+
+       /* Estimate the cost of index scan */
+       indexScanPath = create_index_path(root, indexInfo,
+                                                                         NIL, NIL, NIL, NIL, NIL,
+                                                                         ForwardScanDirection, false,
+                                                                         NULL, 1.0, false);
+
+       return (seqScanAndSortPath.total_cost < indexScanPath->path.total_cost);
+}
+
+/*
+ * get_partitioned_child_rels
+ *             Returns a list of the RT indexes of the partitioned child relations
+ *             with rti as the root parent RT index. Also sets
+ *             *part_cols_updated to true if any of the root rte's updated
+ *             columns is used in the partition key either of the relation whose RTI
+ *             is specified or of any child relation.
+ *
+ * Note: This function might get called even for range table entries that
+ * are not partitioned tables; in such a case, it will simply return NIL.
+ */
+List *
+get_partitioned_child_rels(PlannerInfo *root, Index rti,
+                                                  bool *part_cols_updated)
+{
+       List       *result = NIL;
+       ListCell   *l;
+
+       if (part_cols_updated)
+               *part_cols_updated = false;
+
+       foreach(l, root->pcinfo_list)
        {
-               Path       *subpath = (Path *) lfirst(lc);
-               Path       *newpath = subpath;
-               ListCell   *lc1,
-                                  *lc2;
+               PartitionedChildRelInfo *pc = lfirst_node(PartitionedChildRelInfo, l);
 
-               Assert(subpath->param_info == NULL);
-               forboth(lc1, targets, lc2, targets_contain_srfs)
+               if (pc->parent_relid == rti)
                {
-                       PathTarget *thistarget = lfirst_node(PathTarget, lc1);
-                       bool            contains_srfs = (bool) lfirst_int(lc2);
-
-                       /* If this level doesn't contain SRFs, do regular projection */
-                       if (contains_srfs)
-                               newpath = (Path *) create_set_projection_path(root,
-                                                                                                                         rel,
-                                                                                                                         newpath,
-                                                                                                                         thistarget);
-                       else
-                       {
-                               /* avoid apply_projection_to_path, in case of multiple refs */
-                               newpath = (Path *) create_projection_path(root,
-                                                                                                                 rel,
-                                                                                                                 newpath,
-                                                                                                                 thistarget);
-                       }
+                       result = pc->child_rels;
+                       if (part_cols_updated)
+                               *part_cols_updated = pc->part_cols_updated;
+                       break;
                }
-               lfirst(lc) = newpath;
        }
+
+       return result;
 }
 
 /*
- * expression_planner
- *             Perform planner's transformations on a standalone expression.
- *
- * Various utility commands need to evaluate expressions that are not part
- * of a plannable query.  They can do so using the executor's regular
- * expression-execution machinery, but first the expression has to be fed
- * through here to transform it from parser output to something executable.
- *
- * Currently, we disallow sublinks in standalone expressions, so there's no
- * real "planning" involved here.  (That might not always be true though.)
- * What we must do is run eval_const_expressions to ensure that any function
- * calls are converted to positional notation and function default arguments
- * get inserted.  The fact that constant subexpressions get simplified is a
- * side-effect that is useful when the expression will get evaluated more than
- * once.  Also, we must fix operator function IDs.
- *
- * Note: this must not make any damaging changes to the passed-in expression
- * tree.  (It would actually be okay to apply fix_opfuncids to it, but since
- * we first do an expression_tree_mutator-based walk, what is returned will
- * be a new node tree.)
+ * get_partitioned_child_rels_for_join
+ *             Build and return a list containing the RTI of every partitioned
+ *             relation which is a child of some rel included in the join.
  */
-Expr *
-expression_planner(Expr *expr)
+List *
+get_partitioned_child_rels_for_join(PlannerInfo *root, Relids join_relids)
 {
-       Node       *result;
+       List       *result = NIL;
+       ListCell   *l;
 
-       /*
-        * Convert named-argument function calls, insert default arguments and
-        * simplify constant subexprs
-        */
-       result = eval_const_expressions(NULL, (Node *) expr);
+       foreach(l, root->pcinfo_list)
+       {
+               PartitionedChildRelInfo *pc = lfirst(l);
 
-       /* Fill in opfuncid values if missing */
-       fix_opfuncids(result);
+               if (bms_is_member(pc->parent_relid, join_relids))
+                       result = list_concat(result, list_copy(pc->child_rels));
+       }
 
-       return (Expr *) result;
+       return result;
 }
 
-
 /*
- * plan_cluster_use_sort
- *             Use the planner to decide how CLUSTER should implement sorting
- *
- * tableOid is the OID of a table to be clustered on its index indexOid
- * (which is already known to be a btree index).  Decide whether it's
- * cheaper to do an indexscan or a seqscan-plus-sort to execute the CLUSTER.
- * Return true to use sorting, false to use an indexscan.
+ * add_paths_to_grouping_rel
  *
- * Note: caller had better already hold some type of lock on the table.
+ * Add non-partial paths to grouping relation.
  */
-bool
-plan_cluster_use_sort(Oid tableOid, Oid indexOid)
+static void
+add_paths_to_grouping_rel(PlannerInfo *root, RelOptInfo *input_rel,
+                                                 RelOptInfo *grouped_rel, PathTarget *target,
+                                                 PathTarget *partial_grouping_target,
+                                                 const AggClauseCosts *agg_costs,
+                                                 const AggClauseCosts *agg_final_costs,
+                                                 grouping_sets_data *gd, bool can_sort, bool can_hash,
+                                                 double dNumGroups, List *havingQual)
 {
-       PlannerInfo *root;
-       Query      *query;
-       PlannerGlobal *glob;
-       RangeTblEntry *rte;
-       RelOptInfo *rel;
-       IndexOptInfo *indexInfo;
-       QualCost        indexExprCost;
-       Cost            comparisonCost;
-       Path       *seqScanPath;
-       Path            seqScanAndSortPath;
-       IndexPath  *indexScanPath;
+       Query      *parse = root->parse;
+       Path       *cheapest_path = input_rel->cheapest_total_path;
        ListCell   *lc;
 
-       /* We can short-circuit the cost comparison if indexscans are disabled */
-       if (!enable_indexscan)
-               return true;                    /* use sort */
+       if (can_sort)
+       {
+               /*
+                * Use any available suitably-sorted path as input, and also consider
+                * sorting the cheapest-total path.
+                */
+               foreach(lc, input_rel->pathlist)
+               {
+                       Path       *path = (Path *) lfirst(lc);
+                       bool            is_sorted;
 
-       /* Set up mostly-dummy planner state */
-       query = makeNode(Query);
-       query->commandType = CMD_SELECT;
+                       is_sorted = pathkeys_contained_in(root->group_pathkeys,
+                                                                                         path->pathkeys);
+                       if (path == cheapest_path || is_sorted)
+                       {
+                               /* Sort the cheapest-total path if it isn't already sorted */
+                               if (!is_sorted)
+                                       path = (Path *) create_sort_path(root,
+                                                                                                        grouped_rel,
+                                                                                                        path,
+                                                                                                        root->group_pathkeys,
+                                                                                                        -1.0);
 
-       glob = makeNode(PlannerGlobal);
+                               /* Now decide what to stick atop it */
+                               if (parse->groupingSets)
+                               {
+                                       consider_groupingsets_paths(root, grouped_rel,
+                                                                                               path, true, can_hash, target,
+                                                                                               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,
+                                                                                        target,
+                                                                                        parse->groupClause ? AGG_SORTED : AGG_PLAIN,
+                                                                                        AGGSPLIT_SIMPLE,
+                                                                                        parse->groupClause,
+                                                                                        havingQual,
+                                                                                        agg_costs,
+                                                                                        dNumGroups));
+                               }
+                               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,
+                                                                                          target,
+                                                                                          parse->groupClause,
+                                                                                          havingQual,
+                                                                                          dNumGroups));
+                               }
+                               else
+                               {
+                                       /* Other cases should have been handled above */
+                                       Assert(false);
+                               }
+                       }
+               }
 
-       root = makeNode(PlannerInfo);
-       root->parse = query;
-       root->glob = glob;
-       root->query_level = 1;
-       root->planner_cxt = CurrentMemoryContext;
-       root->wt_param_id = -1;
+               /*
+                * Now generate a complete GroupAgg Path atop of the cheapest partial
+                * path.  We can do this using either Gather or Gather Merge.
+                */
+               if (grouped_rel->partial_pathlist)
+               {
+                       Path       *path = (Path *) linitial(grouped_rel->partial_pathlist);
+                       double          total_groups = path->rows * path->parallel_workers;
+
+                       path = (Path *) create_gather_path(root,
+                                                                                          grouped_rel,
+                                                                                          path,
+                                                                                          partial_grouping_target,
+                                                                                          NULL,
+                                                                                          &total_groups);
+
+                       /*
+                        * Since Gather's output is always unsorted, we'll need to sort,
+                        * unless there's no GROUP BY clause or a degenerate (constant)
+                        * one, in which case there will only be a single group.
+                        */
+                       if (root->group_pathkeys)
+                               path = (Path *) create_sort_path(root,
+                                                                                                grouped_rel,
+                                                                                                path,
+                                                                                                root->group_pathkeys,
+                                                                                                -1.0);
+
+                       if (parse->hasAggs)
+                               add_path(grouped_rel, (Path *)
+                                                create_agg_path(root,
+                                                                                grouped_rel,
+                                                                                path,
+                                                                                target,
+                                                                                parse->groupClause ? AGG_SORTED : AGG_PLAIN,
+                                                                                AGGSPLIT_FINAL_DESERIAL,
+                                                                                parse->groupClause,
+                                                                                havingQual,
+                                                                                agg_final_costs,
+                                                                                dNumGroups));
+                       else
+                               add_path(grouped_rel, (Path *)
+                                                create_group_path(root,
+                                                                                  grouped_rel,
+                                                                                  path,
+                                                                                  target,
+                                                                                  parse->groupClause,
+                                                                                  havingQual,
+                                                                                  dNumGroups));
+
+                       /*
+                        * The point of using Gather Merge rather than Gather is that it
+                        * can preserve the ordering of the input path, so there's no
+                        * reason to try it unless (1) it's possible to produce more than
+                        * one output row and (2) we want the output path to be ordered.
+                        */
+                       if (parse->groupClause != NIL && root->group_pathkeys != NIL)
+                       {
+                               foreach(lc, grouped_rel->partial_pathlist)
+                               {
+                                       Path       *subpath = (Path *) lfirst(lc);
+                                       Path       *gmpath;
+                                       double          total_groups;
+
+                                       /*
+                                        * It's useful to consider paths that are already properly
+                                        * ordered for Gather Merge, because those don't need a
+                                        * sort. It's also useful to consider the cheapest path,
+                                        * because sorting it in parallel and then doing Gather
+                                        * Merge may be better than doing an unordered Gather
+                                        * followed by a sort. But there's no point in considering
+                                        * non-cheapest paths that aren't already sorted
+                                        * correctly.
+                                        */
+                                       if (path != subpath &&
+                                               !pathkeys_contained_in(root->group_pathkeys,
+                                                                                          subpath->pathkeys))
+                                               continue;
 
-       /* Build a minimal RTE for the rel */
-       rte = makeNode(RangeTblEntry);
-       rte->rtekind = RTE_RELATION;
-       rte->relid = tableOid;
-       rte->relkind = RELKIND_RELATION;        /* Don't be too picky. */
-       rte->lateral = false;
-       rte->inh = false;
-       rte->inFromCl = true;
-       query->rtable = list_make1(rte);
+                                       total_groups = subpath->rows * subpath->parallel_workers;
 
-       /* Set up RTE/RelOptInfo arrays */
-       setup_simple_rel_arrays(root);
+                                       gmpath = (Path *)
+                                               create_gather_merge_path(root,
+                                                                                                grouped_rel,
+                                                                                                subpath,
+                                                                                                partial_grouping_target,
+                                                                                                root->group_pathkeys,
+                                                                                                NULL,
+                                                                                                &total_groups);
 
-       /* Build RelOptInfo */
-       rel = build_simple_rel(root, 1, NULL);
+                                       if (parse->hasAggs)
+                                               add_path(grouped_rel, (Path *)
+                                                                create_agg_path(root,
+                                                                                                grouped_rel,
+                                                                                                gmpath,
+                                                                                                target,
+                                                                                                parse->groupClause ? AGG_SORTED : AGG_PLAIN,
+                                                                                                AGGSPLIT_FINAL_DESERIAL,
+                                                                                                parse->groupClause,
+                                                                                                havingQual,
+                                                                                                agg_final_costs,
+                                                                                                dNumGroups));
+                                       else
+                                               add_path(grouped_rel, (Path *)
+                                                                create_group_path(root,
+                                                                                                  grouped_rel,
+                                                                                                  gmpath,
+                                                                                                  target,
+                                                                                                  parse->groupClause,
+                                                                                                  havingQual,
+                                                                                                  dNumGroups));
+                               }
+                       }
+               }
+       }
 
-       /* Locate IndexOptInfo for the target index */
-       indexInfo = NULL;
-       foreach(lc, rel->indexlist)
+       if (can_hash)
        {
-               indexInfo = lfirst_node(IndexOptInfo, lc);
-               if (indexInfo->indexoid == indexOid)
-                       break;
-       }
+               Size            hashaggtablesize;
 
-       /*
-        * It's possible that get_relation_info did not generate an IndexOptInfo
-        * for the desired index; this could happen if it's not yet reached its
-        * indcheckxmin usability horizon, or if it's a system index and we're
-        * ignoring system indexes.  In such cases we should tell CLUSTER to not
-        * trust the index contents but use seqscan-and-sort.
-        */
-       if (lc == NULL)                         /* not in the list? */
-               return true;                    /* use sort */
+               if (parse->groupingSets)
+               {
+                       /*
+                        * Try for a hash-only groupingsets path over unsorted input.
+                        */
+                       consider_groupingsets_paths(root, grouped_rel,
+                                                                               cheapest_path, false, true, target,
+                                                                               gd, agg_costs, dNumGroups);
+               }
+               else
+               {
+                       hashaggtablesize = estimate_hashagg_tablesize(cheapest_path,
+                                                                                                                 agg_costs,
+                                                                                                                 dNumGroups);
 
-       /*
-        * Rather than doing all the pushups that would be needed to use
-        * set_baserel_size_estimates, just do a quick hack for rows and width.
-        */
-       rel->rows = rel->tuples;
-       rel->reltarget->width = get_relation_data_width(tableOid, NULL);
+                       /*
+                        * Provided that the estimated size of the hashtable does not
+                        * exceed work_mem, we'll generate a HashAgg Path, although if we
+                        * were unable to sort above, then we'd better generate a Path, so
+                        * that we at least have one.
+                        */
+                       if (hashaggtablesize < work_mem * 1024L ||
+                               grouped_rel->pathlist == NIL)
+                       {
+                               /*
+                                * We just need an Agg over the cheapest-total input path,
+                                * since input order won't matter.
+                                */
+                               add_path(grouped_rel, (Path *)
+                                                create_agg_path(root, grouped_rel,
+                                                                                cheapest_path,
+                                                                                target,
+                                                                                AGG_HASHED,
+                                                                                AGGSPLIT_SIMPLE,
+                                                                                parse->groupClause,
+                                                                                havingQual,
+                                                                                agg_costs,
+                                                                                dNumGroups));
+                       }
+               }
 
-       root->total_table_pages = rel->pages;
+               /*
+                * Generate a HashAgg Path atop of the cheapest partial path. Once
+                * again, we'll only do this if it looks as though the hash table
+                * won't exceed work_mem.
+                */
+               if (grouped_rel->partial_pathlist)
+               {
+                       Path       *path = (Path *) linitial(grouped_rel->partial_pathlist);
 
-       /*
-        * Determine eval cost of the index expressions, if any.  We need to
-        * charge twice that amount for each tuple comparison that happens during
-        * the sort, since tuplesort.c will have to re-evaluate the index
-        * expressions each time.  (XXX that's pretty inefficient...)
-        */
-       cost_qual_eval(&indexExprCost, indexInfo->indexprs, root);
-       comparisonCost = 2.0 * (indexExprCost.startup + indexExprCost.per_tuple);
+                       hashaggtablesize = estimate_hashagg_tablesize(path,
+                                                                                                                 agg_final_costs,
+                                                                                                                 dNumGroups);
 
-       /* Estimate the cost of seq scan + sort */
-       seqScanPath = create_seqscan_path(root, rel, NULL, 0);
-       cost_sort(&seqScanAndSortPath, root, NIL,
-                         seqScanPath->total_cost, rel->tuples, rel->reltarget->width,
-                         comparisonCost, maintenance_work_mem, -1.0);
+                       if (hashaggtablesize < work_mem * 1024L)
+                       {
+                               double          total_groups = path->rows * path->parallel_workers;
 
-       /* Estimate the cost of index scan */
-       indexScanPath = create_index_path(root, indexInfo,
-                                                                         NIL, NIL, NIL, NIL, NIL,
-                                                                         ForwardScanDirection, false,
-                                                                         NULL, 1.0, false);
+                               path = (Path *) create_gather_path(root,
+                                                                                                  grouped_rel,
+                                                                                                  path,
+                                                                                                  partial_grouping_target,
+                                                                                                  NULL,
+                                                                                                  &total_groups);
 
-       return (seqScanAndSortPath.total_cost < indexScanPath->path.total_cost);
+                               add_path(grouped_rel, (Path *)
+                                                create_agg_path(root,
+                                                                                grouped_rel,
+                                                                                path,
+                                                                                target,
+                                                                                AGG_HASHED,
+                                                                                AGGSPLIT_FINAL_DESERIAL,
+                                                                                parse->groupClause,
+                                                                                havingQual,
+                                                                                agg_final_costs,
+                                                                                dNumGroups));
+                       }
+               }
+       }
 }
 
 /*
- * get_partitioned_child_rels
- *             Returns a list of the RT indexes of the partitioned child relations
- *             with rti as the root parent RT index. Also sets
- *             *part_cols_updated to true if any of the root rte's updated
- *             columns is used in the partition key either of the relation whose RTI
- *             is specified or of any child relation.
+ * add_partial_paths_to_grouping_rel
  *
- * Note: This function might get called even for range table entries that
- * are not partitioned tables; in such a case, it will simply return NIL.
+ * Add partial paths to grouping relation.  These paths are not fully
+ * aggregated; a FinalizeAggregate step is still required.
  */
-List *
-get_partitioned_child_rels(PlannerInfo *root, Index rti,
-                                                  bool *part_cols_updated)
+static void
+add_partial_paths_to_grouping_rel(PlannerInfo *root,
+                                                                 RelOptInfo *input_rel,
+                                                                 RelOptInfo *grouped_rel,
+                                                                 PathTarget *target,
+                                                                 PathTarget *partial_grouping_target,
+                                                                 AggClauseCosts *agg_partial_costs,
+                                                                 AggClauseCosts *agg_final_costs,
+                                                                 grouping_sets_data *gd,
+                                                                 bool can_sort,
+                                                                 bool can_hash,
+                                                                 List *havingQual)
 {
-       List       *result = NIL;
-       ListCell   *l;
+       Query      *parse = root->parse;
+       Path       *cheapest_partial_path = linitial(input_rel->partial_pathlist);
+       Size            hashaggtablesize;
+       double          dNumPartialGroups = 0;
+       ListCell   *lc;
 
-       if (part_cols_updated)
-               *part_cols_updated = false;
+       /* Estimate number of partial groups. */
+       dNumPartialGroups = get_number_of_groups(root,
+                                                                                        cheapest_partial_path->rows,
+                                                                                        gd);
 
-       foreach(l, root->pcinfo_list)
+       if (can_sort)
        {
-               PartitionedChildRelInfo *pc = lfirst_node(PartitionedChildRelInfo, l);
+               /* This should have been checked previously */
+               Assert(parse->hasAggs || parse->groupClause);
 
-               if (pc->parent_relid == rti)
+               /*
+                * Use any available suitably-sorted path as input, and also consider
+                * sorting the cheapest partial path.
+                */
+               foreach(lc, input_rel->partial_pathlist)
                {
-                       result = pc->child_rels;
-                       if (part_cols_updated)
-                               *part_cols_updated = pc->part_cols_updated;
-                       break;
+                       Path       *path = (Path *) lfirst(lc);
+                       bool            is_sorted;
+
+                       is_sorted = pathkeys_contained_in(root->group_pathkeys,
+                                                                                         path->pathkeys);
+                       if (path == cheapest_partial_path || is_sorted)
+                       {
+                               /* Sort the cheapest partial path, if it isn't already */
+                               if (!is_sorted)
+                                       path = (Path *) create_sort_path(root,
+                                                                                                        grouped_rel,
+                                                                                                        path,
+                                                                                                        root->group_pathkeys,
+                                                                                                        -1.0);
+
+                               if (parse->hasAggs)
+                                       add_partial_path(grouped_rel, (Path *)
+                                                                        create_agg_path(root,
+                                                                                                        grouped_rel,
+                                                                                                        path,
+                                                                                                        partial_grouping_target,
+                                                                                                        parse->groupClause ? AGG_SORTED : AGG_PLAIN,
+                                                                                                        AGGSPLIT_INITIAL_SERIAL,
+                                                                                                        parse->groupClause,
+                                                                                                        NIL,
+                                                                                                        agg_partial_costs,
+                                                                                                        dNumPartialGroups));
+                               else
+                                       add_partial_path(grouped_rel, (Path *)
+                                                                        create_group_path(root,
+                                                                                                          grouped_rel,
+                                                                                                          path,
+                                                                                                          partial_grouping_target,
+                                                                                                          parse->groupClause,
+                                                                                                          NIL,
+                                                                                                          dNumPartialGroups));
+                       }
                }
        }
 
-       return result;
+       if (can_hash)
+       {
+               /* Checked above */
+               Assert(parse->hasAggs || parse->groupClause);
+
+               hashaggtablesize =
+                       estimate_hashagg_tablesize(cheapest_partial_path,
+                                                                          agg_partial_costs,
+                                                                          dNumPartialGroups);
+
+               /*
+                * Tentatively produce a partial HashAgg Path, depending on if it
+                * looks as if the hash table will fit in work_mem.
+                */
+               if (hashaggtablesize < work_mem * 1024L)
+               {
+                       add_partial_path(grouped_rel, (Path *)
+                                                        create_agg_path(root,
+                                                                                        grouped_rel,
+                                                                                        cheapest_partial_path,
+                                                                                        partial_grouping_target,
+                                                                                        AGG_HASHED,
+                                                                                        AGGSPLIT_INITIAL_SERIAL,
+                                                                                        parse->groupClause,
+                                                                                        NIL,
+                                                                                        agg_partial_costs,
+                                                                                        dNumPartialGroups));
+               }
+       }
 }
 
 /*
- * get_partitioned_child_rels_for_join
- *             Build and return a list containing the RTI of every partitioned
- *             relation which is a child of some rel included in the join.
+ * can_parallel_agg
+ *
+ * Determines whether or not parallel grouping and/or aggregation is possible.
+ * Returns true when possible, false otherwise.
  */
-List *
-get_partitioned_child_rels_for_join(PlannerInfo *root, Relids join_relids)
+static bool
+can_parallel_agg(PlannerInfo *root, RelOptInfo *input_rel,
+                                RelOptInfo *grouped_rel, const AggClauseCosts *agg_costs)
 {
-       List       *result = NIL;
-       ListCell   *l;
+       Query      *parse = root->parse;
 
-       foreach(l, root->pcinfo_list)
+       if (!grouped_rel->consider_parallel)
        {
-               PartitionedChildRelInfo *pc = lfirst(l);
-
-               if (bms_is_member(pc->parent_relid, join_relids))
-                       result = list_concat(result, list_copy(pc->child_rels));
+               /* Not even parallel-safe. */
+               return false;
+       }
+       else if (input_rel->partial_pathlist == NIL)
+       {
+               /* Nothing to use as input for partial aggregate. */
+               return false;
+       }
+       else if (!parse->hasAggs && parse->groupClause == NIL)
+       {
+               /*
+                * We don't know how to do parallel aggregation unless we have either
+                * some aggregates or a grouping clause.
+                */
+               return false;
+       }
+       else if (parse->groupingSets)
+       {
+               /* We don't know how to do grouping sets in parallel. */
+               return false;
+       }
+       else if (agg_costs->hasNonPartial || agg_costs->hasNonSerial)
+       {
+               /* Insufficient support for partial mode. */
+               return false;
        }
 
-       return result;
+       /* Everything looks good. */
+       return true;
 }