Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Fix assorted missing logic for GroupingFunc nodes.
authorTom Lane <tgl@sss.pgh.pa.us>
Mon, 21 Mar 2022 21:44:29 +0000 (17:44 -0400)
committerTom Lane <tgl@sss.pgh.pa.us>
Mon, 21 Mar 2022 21:44:29 +0000 (17:44 -0400)
The planner needs to treat GroupingFunc like Aggref for many purposes,
in particular with respect to processing of the argument expressions,
which are not to be evaluated at runtime.  A few places hadn't gotten
that memo, notably including subselect.c's processing of outer-level
aggregates.  This resulted in assertion failures or wrong plans for
cases in which a GROUPING() construct references an outer aggregation
level.

Also fix missing special cases for GroupingFunc in cost_qual_eval
(resulting in wrong cost estimates for GROUPING(), although it's
not clear that that would affect plan shapes in practice) and in
ruleutils.c (resulting in excess parentheses in pretty-print mode).

Per bug #17088 from Yaoguang Chen.  Back-patch to all supported
branches.

Richard Guo, Tom Lane

Discussion: https://postgr.es/m/17088-e33882b387de7f5c@postgresql.org

src/backend/nodes/nodeFuncs.c
src/backend/optimizer/path/costsize.c
src/backend/optimizer/plan/subselect.c
src/backend/utils/adt/ruleutils.c
src/test/regress/expected/groupingsets.out
src/test/regress/sql/groupingsets.sql

index 1dc873ed255f85a6f51769c4e2c1ffc570861562..cbf15aa48d668d8a43fbd6fcb0671321d39c8844 100644 (file)
@@ -745,6 +745,8 @@ expression_returns_set_walker(Node *node, void *context)
    /* Avoid recursion for some cases that parser checks not to return a set */
    if (IsA(node, Aggref))
        return false;
+   if (IsA(node, GroupingFunc))
+       return false;
    if (IsA(node, WindowFunc))
        return false;
 
index 92b5223fee8de5b1bf76c6c69487ff3b5df80f2e..4edc859cb57c7845f984bb03e58bc1882ecb4991 100644 (file)
@@ -4213,6 +4213,12 @@ cost_qual_eval_walker(Node *node, cost_qual_eval_context *context)
         */
        return false;           /* don't recurse into children */
    }
+   else if (IsA(node, GroupingFunc))
+   {
+       /* Treat this as having cost 1 */
+       context->total.per_tuple += cpu_operator_cost;
+       return false;           /* don't recurse into children */
+   }
    else if (IsA(node, CoerceViaIO))
    {
        CoerceViaIO *iocoerce = (CoerceViaIO *) node;
index 760865a863602f9a5726b1d7a1bed858bdaba980..11e29dd1536de3ab271b0efe6294381096546132 100644 (file)
@@ -357,15 +357,17 @@ build_subplan(PlannerInfo *root, Plan *plan, PlannerInfo *subroot,
        Node       *arg = pitem->item;
 
        /*
-        * The Var, PlaceHolderVar, or Aggref has already been adjusted to
-        * have the correct varlevelsup, phlevelsup, or agglevelsup.
+        * The Var, PlaceHolderVar, Aggref or GroupingFunc has already been
+        * adjusted to have the correct varlevelsup, phlevelsup, or
+        * agglevelsup.
         *
-        * If it's a PlaceHolderVar or Aggref, its arguments might contain
-        * SubLinks, which have not yet been processed (see the comments for
-        * SS_replace_correlation_vars).  Do that now.
+        * If it's a PlaceHolderVar, Aggref or GroupingFunc, its arguments
+        * might contain SubLinks, which have not yet been processed (see the
+        * comments for SS_replace_correlation_vars).  Do that now.
         */
        if (IsA(arg, PlaceHolderVar) ||
-           IsA(arg, Aggref))
+           IsA(arg, Aggref) ||
+           IsA(arg, GroupingFunc))
            arg = SS_process_sublinks(root, arg, false);
 
        splan->parParam = lappend_int(splan->parParam, pitem->paramId);
@@ -1929,10 +1931,11 @@ process_sublinks_mutator(Node *node, process_sublinks_context *context)
    }
 
    /*
-    * Don't recurse into the arguments of an outer PHV or aggregate here. Any
-    * SubLinks in the arguments have to be dealt with at the outer query
-    * level; they'll be handled when build_subplan collects the PHV or Aggref
-    * into the arguments to be passed down to the current subplan.
+    * Don't recurse into the arguments of an outer PHV, Aggref or
+    * GroupingFunc here.  Any SubLinks in the arguments have to be dealt with
+    * at the outer query level; they'll be handled when build_subplan
+    * collects the PHV, Aggref or GroupingFunc into the arguments to be
+    * passed down to the current subplan.
     */
    if (IsA(node, PlaceHolderVar))
    {
@@ -1944,6 +1947,11 @@ process_sublinks_mutator(Node *node, process_sublinks_context *context)
        if (((Aggref *) node)->agglevelsup > 0)
            return node;
    }
+   else if (IsA(node, GroupingFunc))
+   {
+       if (((GroupingFunc *) node)->agglevelsup > 0)
+           return node;
+   }
 
    /*
     * We should never see a SubPlan expression in the input (since this is
@@ -2056,7 +2064,7 @@ SS_identify_outer_params(PlannerInfo *root)
    outer_params = NULL;
    for (proot = root->parent_root; proot != NULL; proot = proot->parent_root)
    {
-       /* Include ordinary Var/PHV/Aggref params */
+       /* Include ordinary Var/PHV/Aggref/GroupingFunc params */
        foreach(l, proot->plan_params)
        {
            PlannerParamItem *pitem = (PlannerParamItem *) lfirst(l);
index 36d7b53c142ae24d941b6a5eed2c5ed32d1cc04a..92536f0e13f1972b327e562cd104fe131070add3 100644 (file)
@@ -7541,12 +7541,13 @@ get_parameter(Param *param, deparse_context *context)
        context->varprefix = true;
 
        /*
-        * A Param's expansion is typically a Var, Aggref, or upper-level
-        * Param, which wouldn't need extra parentheses.  Otherwise, insert
-        * parens to ensure the expression looks atomic.
+        * A Param's expansion is typically a Var, Aggref, GroupingFunc, or
+        * upper-level Param, which wouldn't need extra parentheses.
+        * Otherwise, insert parens to ensure the expression looks atomic.
         */
        need_paren = !(IsA(expr, Var) ||
                       IsA(expr, Aggref) ||
+                      IsA(expr, GroupingFunc) ||
                       IsA(expr, Param));
        if (need_paren)
            appendStringInfoChar(context->buf, '(');
@@ -7628,6 +7629,7 @@ isSimpleNode(Node *node, Node *parentNode, int prettyFlags)
        case T_NextValueExpr:
        case T_NullIfExpr:
        case T_Aggref:
+       case T_GroupingFunc:
        case T_WindowFunc:
        case T_FuncExpr:
            /* function-like: name(..) or name[..] */
@@ -7744,6 +7746,7 @@ isSimpleNode(Node *node, Node *parentNode, int prettyFlags)
                case T_XmlExpr: /* own parentheses */
                case T_NullIfExpr:  /* other separators */
                case T_Aggref:  /* own parentheses */
+               case T_GroupingFunc:    /* own parentheses */
                case T_WindowFunc:  /* own parentheses */
                case T_CaseExpr:    /* other separators */
                    return true;
@@ -7794,6 +7797,7 @@ isSimpleNode(Node *node, Node *parentNode, int prettyFlags)
                case T_XmlExpr: /* own parentheses */
                case T_NullIfExpr:  /* other separators */
                case T_Aggref:  /* own parentheses */
+               case T_GroupingFunc:    /* own parentheses */
                case T_WindowFunc:  /* own parentheses */
                case T_CaseExpr:    /* other separators */
                    return true;
index 7c844c6e09e890894388ced26a728edd32648760..1ed034ca06f95bcb2c15eeecc8705dd0f0367413 100644 (file)
@@ -1929,4 +1929,49 @@ set work_mem to default;
 
 drop table gs_group_1;
 drop table gs_hash_1;
+-- test handling of outer GroupingFunc within subqueries
+explain (costs off)
+select (select grouping(v1)) from (values ((select 1))) v(v1) group by cube(v1);
+        QUERY PLAN         
+---------------------------
+ MixedAggregate
+   Hash Key: $2
+   Group Key: ()
+   InitPlan 1 (returns $1)
+     ->  Result
+   InitPlan 3 (returns $2)
+     ->  Result
+   ->  Result
+   SubPlan 2
+     ->  Result
+(10 rows)
+
+select (select grouping(v1)) from (values ((select 1))) v(v1) group by cube(v1);
+ grouping 
+----------
+        1
+        0
+(2 rows)
+
+explain (costs off)
+select (select grouping(v1)) from (values ((select 1))) v(v1) group by v1;
+        QUERY PLAN         
+---------------------------
+ GroupAggregate
+   Group Key: $2
+   InitPlan 1 (returns $1)
+     ->  Result
+   InitPlan 3 (returns $2)
+     ->  Result
+   ->  Result
+   SubPlan 2
+     ->  Result
+(9 rows)
+
+select (select grouping(v1)) from (values ((select 1))) v(v1) group by v1;
+ grouping 
+----------
+        0
+(1 row)
+
 -- end
index 18ae803e9debdf87db0ebf42859a15662c0b4a07..d83ca2aa8287deba66bc973d937e427e9ad753d3 100644 (file)
@@ -529,4 +529,13 @@ set work_mem to default;
 drop table gs_group_1;
 drop table gs_hash_1;
 
+-- test handling of outer GroupingFunc within subqueries
+explain (costs off)
+select (select grouping(v1)) from (values ((select 1))) v(v1) group by cube(v1);
+select (select grouping(v1)) from (values ((select 1))) v(v1) group by cube(v1);
+
+explain (costs off)
+select (select grouping(v1)) from (values ((select 1))) v(v1) group by v1;
+select (select grouping(v1)) from (values ((select 1))) v(v1) group by v1;
+
 -- end