Determine grouping strategies in create_grouping_paths.
authorRobert Haas <rhaas@postgresql.org>
Tue, 20 Mar 2018 15:31:06 +0000 (11:31 -0400)
committerRobert Haas <rhaas@postgresql.org>
Tue, 20 Mar 2018 15:31:06 +0000 (11:31 -0400)
Partition-wise aggregate will call create_ordinary_grouping_paths
multiple times and we don't want to redo this work every time; have
the caller do it instead and pass the details down.

Patch by me, reviewed by Ashutosh Bapat.

Discussion: http://postgr.es/m/CA+TgmoY7VYYn9a7YHj1nJL6zj6BkHmt4K-un9LRmXkyqRZyynA@mail.gmail.com

src/backend/optimizer/plan/planner.c

index 59802423fc8e87ed2fac14df7c3372ed17d5d0e3..7b623496e30113c2052d016905c64d7eca9f7e93 100644 (file)
@@ -93,6 +93,25 @@ typedef struct
    List       *groupClause;    /* overrides parse->groupClause */
 } standard_qp_extra;
 
+/*
+ * Various flags indicating what kinds of grouping are possible.
+ *
+ * GROUPING_CAN_USE_SORT should be set if it's possible to perform
+ * sort-based implementations of grouping.  When grouping sets are in use,
+ * this will be true if sorting is potentially usable for any of the grouping
+ * sets, even if it's not usable for all of them.
+ *
+ * GROUPING_CAN_USE_HASH should be set if it's possible to perform
+ * hash-based implementations of grouping.
+ *
+ * GROUPING_CAN_PARTIAL_AGG should be set if the aggregation is of a type
+ * for which we support partial aggregation (not, for example, grouping sets).
+ * It says nothing about parallel-safety or the availability of suitable paths.
+ */
+#define GROUPING_CAN_USE_SORT       0x0001
+#define GROUPING_CAN_USE_HASH       0x0002
+#define GROUPING_CAN_PARTIAL_AGG   0x0004
+
 /*
  * Data specific to grouping sets
  */
@@ -149,7 +168,7 @@ static void create_ordinary_grouping_paths(PlannerInfo *root,
                               RelOptInfo *input_rel,
                               PathTarget *target, RelOptInfo *grouped_rel,
                               const AggClauseCosts *agg_costs,
-                              grouping_sets_data *gd);
+                              grouping_sets_data *gd, int flags);
 static void consider_groupingsets_paths(PlannerInfo *root,
                            RelOptInfo *grouped_rel,
                            Path *path,
@@ -215,8 +234,8 @@ static RelOptInfo *create_partial_grouping_paths(PlannerInfo *root,
                              bool can_hash,
                              AggClauseCosts *agg_final_costs);
 static void gather_grouping_paths(PlannerInfo *root, RelOptInfo *rel);
-static bool can_parallel_agg(PlannerInfo *root, RelOptInfo *input_rel,
-                RelOptInfo *grouped_rel, const AggClauseCosts *agg_costs);
+static bool can_partial_agg(PlannerInfo *root,
+               const AggClauseCosts *agg_costs);
 
 
 /*****************************************************************************
@@ -3720,8 +3739,58 @@ create_grouping_paths(PlannerInfo *root,
    if (is_degenerate_grouping(root))
        create_degenerate_grouping_paths(root, input_rel, target, grouped_rel);
    else
+   {
+       int         flags = 0;
+
+       /*
+        * Determine whether it's possible to perform sort-based
+        * implementations of grouping.  (Note that if groupClause is empty,
+        * grouping_is_sortable() is trivially true, and all the
+        * pathkeys_contained_in() tests will succeed too, so that we'll
+        * consider every surviving input path.)
+        *
+        * If we have grouping sets, we might be able to sort some but not all
+        * of them; in this case, we need can_sort to be true as long as we
+        * must consider any sorted-input plan.
+        */
+       if ((gd && gd->rollups != NIL)
+           || grouping_is_sortable(parse->groupClause))
+           flags |= GROUPING_CAN_USE_SORT;
+
+       /*
+        * Determine whether we should consider hash-based implementations of
+        * grouping.
+        *
+        * Hashed aggregation only applies if we're grouping. If we have
+        * grouping sets, some groups might be hashable but others not; in
+        * this case we set can_hash true as long as there is nothing globally
+        * preventing us from hashing (and we should therefore consider plans
+        * with hashes).
+        *
+        * Executor doesn't support hashed aggregation with DISTINCT or ORDER
+        * BY aggregates.  (Doing so would imply storing *all* the input
+        * values in the hash table, and/or running many sorts in parallel,
+        * either of which seems like a certain loser.)  We similarly don't
+        * support ordered-set aggregates in hashed aggregation, but that case
+        * is also included in the numOrderedAggs count.
+        *
+        * Note: grouping_is_hashable() is much more expensive to check than
+        * the other gating conditions, so we want to do it last.
+        */
+       if ((parse->groupClause != NIL &&
+            agg_costs->numOrderedAggs == 0 &&
+            (gd ? gd->any_hashable : grouping_is_hashable(parse->groupClause))))
+           flags |= GROUPING_CAN_USE_HASH;
+
+       /*
+        * Determine whether partial aggregation is possible.
+        */
+       if (can_partial_agg(root, agg_costs))
+           flags |= GROUPING_CAN_PARTIAL_AGG;
+
        create_ordinary_grouping_paths(root, input_rel, target, grouped_rel,
-                                      agg_costs, gd);
+                                      agg_costs, gd, flags);
+   }
 
    set_cheapest(grouped_rel);
    return grouped_rel;
@@ -3820,15 +3889,15 @@ static void
 create_ordinary_grouping_paths(PlannerInfo *root, RelOptInfo *input_rel,
                               PathTarget *target, RelOptInfo *grouped_rel,
                               const AggClauseCosts *agg_costs,
-                              grouping_sets_data *gd)
+                              grouping_sets_data *gd, int flags)
 {
    Query      *parse = root->parse;
    Path       *cheapest_path = input_rel->cheapest_total_path;
    RelOptInfo *partially_grouped_rel = NULL;
    AggClauseCosts agg_final_costs; /* parallel only */
    double      dNumGroups;
-   bool        can_hash;
-   bool        can_sort;
+   bool        can_hash = (flags & GROUPING_CAN_USE_HASH) != 0;
+   bool        can_sort = (flags & GROUPING_CAN_USE_SORT) != 0;
 
    /*
     * Estimate number of groups.
@@ -3838,50 +3907,14 @@ create_ordinary_grouping_paths(PlannerInfo *root, RelOptInfo *input_rel,
                                      gd,
                                      parse->targetList);
 
-   /*
-    * Determine whether it's possible to perform sort-based implementations
-    * of grouping.  (Note that if groupClause is empty,
-    * grouping_is_sortable() is trivially true, and all the
-    * pathkeys_contained_in() tests will succeed too, so that we'll consider
-    * every surviving input path.)
-    *
-    * If we have grouping sets, we might be able to sort some but not all of
-    * them; in this case, we need can_sort to be true as long as we must
-    * consider any sorted-input plan.
-    */
-   can_sort = (gd && gd->rollups != NIL)
-       || grouping_is_sortable(parse->groupClause);
-
-   /*
-    * Determine whether we should consider hash-based implementations of
-    * grouping.
-    *
-    * Hashed aggregation only applies if we're grouping. If we have grouping
-    * sets, some groups might be hashable but others not; in this case we set
-    * can_hash true as long as there is nothing globally preventing us from
-    * hashing (and we should therefore consider plans with hashes).
-    *
-    * Executor doesn't support hashed aggregation with DISTINCT or ORDER BY
-    * aggregates.  (Doing so would imply storing *all* the input values in
-    * the hash table, and/or running many sorts in parallel, either of which
-    * seems like a certain loser.)  We similarly don't support ordered-set
-    * aggregates in hashed aggregation, but that case is also included in the
-    * numOrderedAggs count.
-    *
-    * Note: grouping_is_hashable() is much more expensive to check than the
-    * other gating conditions, so we want to do it last.
-    */
-   can_hash = (parse->groupClause != NIL &&
-               agg_costs->numOrderedAggs == 0 &&
-               (gd ? gd->any_hashable : grouping_is_hashable(parse->groupClause)));
-
    /*
     * Before generating paths for grouped_rel, we first generate any possible
     * partially grouped paths; that way, later code can easily consider both
     * parallel and non-parallel approaches to grouping.
     */
    MemSet(&agg_final_costs, 0, sizeof(AggClauseCosts));
-   if (can_parallel_agg(root, input_rel, grouped_rel, agg_costs))
+   if (grouped_rel->consider_parallel && input_rel->partial_pathlist != NIL
+       && (flags & GROUPING_CAN_PARTIAL_AGG) != 0)
    {
        partially_grouped_rel =
            create_partial_grouping_paths(root,
@@ -6490,28 +6523,17 @@ gather_grouping_paths(PlannerInfo *root, RelOptInfo *rel)
 }
 
 /*
- * can_parallel_agg
+ * can_partial_agg
  *
- * Determines whether or not parallel grouping and/or aggregation is possible.
+ * Determines whether or not partial grouping and/or aggregation is possible.
  * Returns true when possible, false otherwise.
  */
 static bool
-can_parallel_agg(PlannerInfo *root, RelOptInfo *input_rel,
-                RelOptInfo *grouped_rel, const AggClauseCosts *agg_costs)
+can_partial_agg(PlannerInfo *root, const AggClauseCosts *agg_costs)
 {
    Query      *parse = root->parse;
 
-   if (!grouped_rel->consider_parallel)
-   {
-       /* 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)
+   if (!parse->hasAggs && parse->groupClause == NIL)
    {
        /*
         * We don't know how to do parallel aggregation unless we have either