Avoid making a separate pass over the query to check for partializability.
authorTom Lane <[email protected]>
Sun, 26 Jun 2016 19:55:01 +0000 (15:55 -0400)
committerTom Lane <[email protected]>
Sun, 26 Jun 2016 19:55:01 +0000 (15:55 -0400)
It's rather silly to make a separate pass over the tlist + HAVING qual,
and a separate set of visits to the syscache, when get_agg_clause_costs
already has all the required information in hand.  This nets out as less
code as well as fewer cycles.

src/backend/optimizer/plan/planner.c
src/backend/optimizer/util/clauses.c
src/include/nodes/relation.h
src/include/optimizer/clauses.h

index cc208a6a9bb3f568675c5f91d9c5e231a0f2e1b0..070ad316eb7ca694d92f70c928346211c34ee1a3 100644 (file)
@@ -110,8 +110,10 @@ static double get_number_of_groups(PlannerInfo *root,
                     List *rollup_groupclauses);
 static void set_grouped_rel_consider_parallel(PlannerInfo *root,
                                  RelOptInfo *grouped_rel,
-                                 PathTarget *target);
-static Size estimate_hashagg_tablesize(Path *path, AggClauseCosts *agg_costs,
+                                 PathTarget *target,
+                                 const AggClauseCosts *agg_costs);
+static Size estimate_hashagg_tablesize(Path *path,
+                          const AggClauseCosts *agg_costs,
                           double dNumGroups);
 static RelOptInfo *create_grouping_paths(PlannerInfo *root,
                      RelOptInfo *input_rel,
@@ -3207,7 +3209,8 @@ get_number_of_groups(PlannerInfo *root,
  */
 static void
 set_grouped_rel_consider_parallel(PlannerInfo *root, RelOptInfo *grouped_rel,
-                                 PathTarget *target)
+                                 PathTarget *target,
+                                 const AggClauseCosts *agg_costs)
 {
    Query      *parse = root->parse;
 
@@ -3240,15 +3243,14 @@ set_grouped_rel_consider_parallel(PlannerInfo *root, RelOptInfo *grouped_rel,
        return;
 
    /*
-    * All that's left to check now is to make sure all aggregate functions
-    * support partial mode. If there's no aggregates then we can skip
-    * checking that.
+    * If we have any non-partial-capable aggregates, or if any of them can't
+    * be serialized, we can't go parallel.
     */
-   if (!parse->hasAggs)
-       grouped_rel->consider_parallel = true;
-   else if (aggregates_allow_partial((Node *) target->exprs) == PAT_ANY &&
-            aggregates_allow_partial(root->parse->havingQual) == PAT_ANY)
-       grouped_rel->consider_parallel = true;
+   if (agg_costs->hasNonPartial || agg_costs->hasNonSerial)
+       return;
+
+   /* OK, consider parallelization */
+   grouped_rel->consider_parallel = true;
 }
 
 /*
@@ -3257,7 +3259,7 @@ set_grouped_rel_consider_parallel(PlannerInfo *root, RelOptInfo *grouped_rel,
  *   require based on the agg_costs, path width and dNumGroups.
  */
 static Size
-estimate_hashagg_tablesize(Path *path, AggClauseCosts *agg_costs,
+estimate_hashagg_tablesize(Path *path, const AggClauseCosts *agg_costs,
                           double dNumGroups)
 {
    Size        hashentrysize;
@@ -3411,7 +3413,8 @@ create_grouping_paths(PlannerInfo *root,
     * going to be safe to do so.
     */
    if (input_rel->partial_pathlist != NIL)
-       set_grouped_rel_consider_parallel(root, grouped_rel, target);
+       set_grouped_rel_consider_parallel(root, grouped_rel,
+                                         target, &agg_costs);
 
    /*
     * Determine whether it's possible to perform sort-based implementations
index 40c39772649503a3175c1fe669fd728639d49c3b..1435f052fa82d947271672d67cf567e16ecf3d37 100644 (file)
 #include "utils/syscache.h"
 #include "utils/typcache.h"
 
-typedef struct
-{
-   PartialAggType allowedtype;
-} partial_agg_context;
 
 typedef struct
 {
@@ -98,8 +94,6 @@ typedef struct
    bool        allow_restricted;
 } has_parallel_hazard_arg;
 
-static bool aggregates_allow_partial_walker(Node *node,
-                               partial_agg_context *context);
 static bool contain_agg_clause_walker(Node *node, void *context);
 static bool get_agg_clause_costs_walker(Node *node,
                            get_agg_clause_costs_context *context);
@@ -403,81 +397,6 @@ make_ands_implicit(Expr *clause)
  *     Aggregate-function clause manipulation
  *****************************************************************************/
 
-/*
- * aggregates_allow_partial
- *     Recursively search for Aggref clauses and determine the maximum
- *     level of partial aggregation which can be supported.
- */
-PartialAggType
-aggregates_allow_partial(Node *clause)
-{
-   partial_agg_context context;
-
-   /* initially any type is okay, until we find Aggrefs which say otherwise */
-   context.allowedtype = PAT_ANY;
-
-   if (!aggregates_allow_partial_walker(clause, &context))
-       return context.allowedtype;
-   return context.allowedtype;
-}
-
-static bool
-aggregates_allow_partial_walker(Node *node, partial_agg_context *context)
-{
-   if (node == NULL)
-       return false;
-   if (IsA(node, Aggref))
-   {
-       Aggref     *aggref = (Aggref *) node;
-       HeapTuple   aggTuple;
-       Form_pg_aggregate aggform;
-
-       Assert(aggref->agglevelsup == 0);
-
-       /*
-        * We can't perform partial aggregation with Aggrefs containing a
-        * DISTINCT or ORDER BY clause.
-        */
-       if (aggref->aggdistinct || aggref->aggorder)
-       {
-           context->allowedtype = PAT_DISABLED;
-           return true;        /* abort search */
-       }
-       aggTuple = SearchSysCache1(AGGFNOID,
-                                  ObjectIdGetDatum(aggref->aggfnoid));
-       if (!HeapTupleIsValid(aggTuple))
-           elog(ERROR, "cache lookup failed for aggregate %u",
-                aggref->aggfnoid);
-       aggform = (Form_pg_aggregate) GETSTRUCT(aggTuple);
-
-       /*
-        * If there is no combine function, then partial aggregation is not
-        * possible.
-        */
-       if (!OidIsValid(aggform->aggcombinefn))
-       {
-           ReleaseSysCache(aggTuple);
-           context->allowedtype = PAT_DISABLED;
-           return true;        /* abort search */
-       }
-
-       /*
-        * If we find any aggs with an internal transtype then we must check
-        * whether these have serialization/deserialization functions;
-        * otherwise, we set the maximum allowed type to PAT_INTERNAL_ONLY.
-        */
-       if (aggform->aggtranstype == INTERNALOID &&
-           (!OidIsValid(aggform->aggserialfn) ||
-            !OidIsValid(aggform->aggdeserialfn)))
-           context->allowedtype = PAT_INTERNAL_ONLY;
-
-       ReleaseSysCache(aggTuple);
-       return false;           /* continue searching */
-   }
-   return expression_tree_walker(node, aggregates_allow_partial_walker,
-                                 (void *) context);
-}
-
 /*
  * contain_agg_clause
  *   Recursively search for Aggref/GroupingFunc nodes within a clause.
@@ -529,8 +448,9 @@ contain_agg_clause_walker(Node *node, void *context)
  *
  * We count the nodes, estimate their execution costs, and estimate the total
  * space needed for their transition state values if all are evaluated in
- * parallel (as would be done in a HashAgg plan).  See AggClauseCosts for
- * the exact set of statistics collected.
+ * parallel (as would be done in a HashAgg plan).  Also, we check whether
+ * partial aggregation is feasible.  See AggClauseCosts for the exact set
+ * of statistics collected.
  *
  * In addition, we mark Aggref nodes with the correct aggtranstype, so
  * that that doesn't need to be done repeatedly.  (That makes this function's
@@ -616,10 +536,40 @@ get_agg_clause_costs_walker(Node *node, get_agg_clause_costs_context *context)
            aggref->aggtranstype = aggtranstype;
        }
 
-       /* count it; note ordered-set aggs always have nonempty aggorder */
+       /*
+        * Count it, and check for cases requiring ordered input.  Note that
+        * ordered-set aggs always have nonempty aggorder.  Any ordered-input
+        * case also defeats partial aggregation.
+        */
        costs->numAggs++;
        if (aggref->aggorder != NIL || aggref->aggdistinct != NIL)
+       {
            costs->numOrderedAggs++;
+           costs->hasNonPartial = true;
+       }
+
+       /*
+        * Check whether partial aggregation is feasible, unless we already
+        * found out that we can't do it.
+        */
+       if (!costs->hasNonPartial)
+       {
+           /*
+            * If there is no combine function, then partial aggregation is
+            * not possible.
+            */
+           if (!OidIsValid(aggcombinefn))
+               costs->hasNonPartial = true;
+
+           /*
+            * If we have any aggs with transtype INTERNAL then we must check
+            * whether they have serialization/deserialization functions; if
+            * not, we can't serialize partial-aggregation results.
+            */
+           else if (aggtranstype == INTERNALOID &&
+                    (!OidIsValid(aggserialfn) || !OidIsValid(aggdeserialfn)))
+               costs->hasNonSerial = true;
+       }
 
        /*
         * Add the appropriate component function execution costs to
index b5f96839755896a3a1cef14808843476df9c65eb..0b5cb9e8682c218b49c9c945de7a55a5b70ad0a3 100644 (file)
@@ -50,12 +50,15 @@ typedef struct QualCost
  * Costing aggregate function execution requires these statistics about
  * the aggregates to be executed by a given Agg node.  Note that the costs
  * include the execution costs of the aggregates' argument expressions as
- * well as the aggregate functions themselves.
+ * well as the aggregate functions themselves.  Also, the fields must be
+ * defined so that initializing the struct to zeroes with memset is correct.
  */
 typedef struct AggClauseCosts
 {
    int         numAggs;        /* total number of aggregate functions */
    int         numOrderedAggs; /* number w/ DISTINCT/ORDER BY/WITHIN GROUP */
+   bool        hasNonPartial;  /* does any agg not support partial mode? */
+   bool        hasNonSerial;   /* is any partial agg non-serializable? */
    QualCost    transCost;      /* total per-input-row execution costs */
    Cost        finalCost;      /* total per-aggregated-row costs */
    Size        transitionSpace;    /* space for pass-by-ref transition data */
index 526126df6fc8cf67a78975c92a250e8582ab8b5c..be7c639f7b93a6b21de532748f98b886960474d8 100644 (file)
@@ -27,25 +27,6 @@ typedef struct
    List      **windowFuncs;    /* lists of WindowFuncs for each winref */
 } WindowFuncLists;
 
-/*
- * PartialAggType
- * PartialAggType stores whether partial aggregation is allowed and
- * which context it is allowed in. We require three states here as there are
- * two different contexts in which partial aggregation is safe. For aggregates
- * which have an 'stype' of INTERNAL, it is okay to pass a pointer to the
- * aggregate state within a single process, since the datum is just a
- * pointer. In cases where the aggregate state must be passed between
- * different processes, for example during parallel aggregation, passing
- * pointers directly is not going to work.
- */
-typedef enum
-{
-   PAT_ANY = 0,                /* Any type of partial aggregation is okay. */
-   PAT_INTERNAL_ONLY,          /* Some aggregates support only internal mode. */
-   PAT_DISABLED                /* Some aggregates don't support partial mode
-                                * at all */
-} PartialAggType;
-
 extern Expr *make_opclause(Oid opno, Oid opresulttype, bool opretset,
              Expr *leftop, Expr *rightop,
              Oid opcollid, Oid inputcollid);
@@ -65,7 +46,6 @@ extern Node *make_and_qual(Node *qual1, Node *qual2);
 extern Expr *make_ands_explicit(List *andclauses);
 extern List *make_ands_implicit(Expr *clause);
 
-extern PartialAggType aggregates_allow_partial(Node *clause);
 extern bool contain_agg_clause(Node *clause);
 extern void get_agg_clause_costs(PlannerInfo *root, Node *clause,
                     AggSplit aggsplit, AggClauseCosts *costs);