This is an automated email from the ASF dual-hosted git repository.

tuhaihe pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/cloudberry.git


The following commit(s) were added to refs/heads/main by this push:
     new 268e9b05b21 Fix constant GROUP BY on empty input
268e9b05b21 is described below

commit 268e9b05b21662b77f5391ccac94efb47267dece
Author: krylosov-aa <[email protected]>
AuthorDate: Fri Oct 2 16:02:44 2026 +0300

    Fix constant GROUP BY on empty input
    
    With optimizer=off and multi-phase aggregation, a query grouped only
    by constants, such as SELECT count(*) FROM t GROUP BY 'x'::text,
    returned one row for empty input instead of none. The planner removes
    constant grouping keys, and cdbgroupingpaths.c then chose AGG_PLAIN as
    if there were no GROUP BY.
    
    Choose the aggregation strategy from the original GROUP BY clause.
    This also fixes an assertion failure for constant GROUP BY without
    aggregates. Skip the TupleSplit plan in this case when every DISTINCT
    aggregate has a FILTER, since it could lose the only group.
    
    See: Issue#2025 <https://github.com/apache/cloudberry/issues/2025>
---
 .../src/test/regress/expected/bfv_aggregate.out    |   2 +-
 src/backend/cdb/cdbgroupingpaths.c                 |  74 +++++-
 src/test/regress/expected/bfv_aggregate.out        |   2 +-
 src/test/regress/expected/gp_group_by_constant.out | 295 +++++++++++++++++++++
 src/test/regress/greenplum_schedule                |   1 +
 src/test/regress/sql/gp_group_by_constant.sql      | 114 ++++++++
 6 files changed, 473 insertions(+), 15 deletions(-)

diff --git a/contrib/pax_storage/src/test/regress/expected/bfv_aggregate.out 
b/contrib/pax_storage/src/test/regress/expected/bfv_aggregate.out
index 78807ff4085..9d00af5ae60 100644
--- a/contrib/pax_storage/src/test/regress/expected/bfv_aggregate.out
+++ b/contrib/pax_storage/src/test/regress/expected/bfv_aggregate.out
@@ -1777,7 +1777,7 @@ explain (costs off)
 select 1, sum(col1) from group_by_const group by 1;
                    QUERY PLAN                   
 ------------------------------------------------
- Finalize Aggregate
+ Finalize GroupAggregate
    ->  Gather Motion 3:1  (slice1; segments: 3)
          ->  Partial GroupAggregate
                ->  Seq Scan on group_by_const
diff --git a/src/backend/cdb/cdbgroupingpaths.c 
b/src/backend/cdb/cdbgroupingpaths.c
index 2ebe136d9f0..1db2d15e8d4 100644
--- a/src/backend/cdb/cdbgroupingpaths.c
+++ b/src/backend/cdb/cdbgroupingpaths.c
@@ -514,6 +514,37 @@ cdb_create_multistage_grouping_paths(PlannerInfo *root,
                        break;
                case MULTI_DQAS:
                        {
+                               ListCell   *lc;
+
+                               /*
+                                * If all aggregate FILTER conditions are 
false, TupleSplit
+                                * returns no rows even though the input is 
nonempty. A
+                                * constant GROUP BY must still return one 
group in this
+                                * case, but the GroupAggregate nodes in this 
plan would
+                                * return none.
+                                *
+                                * Do not build this plan when every DQA has a 
FILTER. An
+                                * unfiltered DQA ensures that TupleSplit 
produces rows for
+                                * nonempty input.
+                                */
+                               if (ctx.parseGroupClause && !ctx.groupClause)
+                               {
+                                       bool            has_unfiltered_agg = 
false;
+
+                                       foreach(lc, agg_costs->distinctAggrefs)
+                                       {
+                                               Aggref     *aggref = 
lfirst_node(Aggref, lc);
+
+                                               if (!aggref->aggfilter)
+                                               {
+                                                       has_unfiltered_agg = 
true;
+                                                       break;
+                                               }
+                                       }
+                                       if (!has_unfiltered_agg)
+                                               break;
+                               }
+
                                fetch_multi_dqas_info(root, cheapest_path, 
&ctx, &info);
                                /*
                                 * GPDB_14_MERGE_FIXME: We have done some copy 
job in
@@ -530,7 +561,6 @@ cdb_create_multistage_grouping_paths(PlannerInfo *root,
                                 * removing the origin plan's aggfilter can 
work around
                                 * this problem. We'll look at it again later.
                                 */
-                               ListCell   *lc;
                                foreach(lc, root->agginfos)
                                {
                                        AggInfo    *agginfo = (AggInfo *) 
lfirst(lc);
@@ -1102,7 +1132,7 @@ add_first_stage_group_agg_path(PlannerInfo *root,
                                                                                
          ctx->agg_partial_costs);
                add_path(ctx->partial_rel, first_stage_agg_path, root);
        }
-       else if (ctx->hasAggs || ctx->groupClause || ctx->hasDistinctOn)
+       else if (ctx->hasAggs || ctx->parseGroupClause || ctx->hasDistinctOn)
        {
                add_path(ctx->partial_rel,
                        (Path *) create_agg_path(root,
@@ -1141,10 +1171,19 @@ add_second_stage_group_agg_path(PlannerInfo *root,
        CdbPathLocus singleQE_locus;
        CdbPathLocus group_locus;
        bool            need_redistribute;
+       AggStrategy aggstrategy;
 
        /* The input should be distributed, otherwise no point in a two-stage 
Agg. */
        Assert(CdbPathLocus_IsPartitioned(initial_agg_path->locus));
 
+       /*
+        * GROUP BY must return no rows for empty input, even if all grouping
+        * keys were removed as redundant. Use AGG_SORTED to preserve this
+        * behavior; AGG_PLAIN would produce one row.
+        */
+       aggstrategy = (ctx->parseGroupClause != NIL ||
+                                  ctx->final_groupClause != NIL) ? AGG_SORTED 
: AGG_PLAIN;
+
        group_locus = choose_grouping_locus(root,
                                                                                
initial_agg_path,
                                                                                
ctx->final_group_tles,
@@ -1189,7 +1228,7 @@ add_second_stage_group_agg_path(PlannerInfo *root,
                                                                                
output_rel,
                                                                                
path,
                                                                                
ctx->target,
-                                                                               
(ctx->final_groupClause ? AGG_SORTED : AGG_PLAIN),
+                                                                               
aggstrategy,
                                                                                
ctx->hasAggs ? AGGSPLIT_FINAL_DESERIAL : AGGSPLIT_SIMPLE,
                                                                                
false, /* streaming */
                                                                                
ctx->final_groupClause,
@@ -1227,7 +1266,7 @@ add_second_stage_group_agg_path(PlannerInfo *root,
                                                                output_rel,
                                                                path,
                                                                ctx->target,
-                                                               
(ctx->final_groupClause ? AGG_SORTED : AGG_PLAIN),
+                                                               aggstrategy,
                                                                ctx->hasAggs ? 
AGGSPLIT_FINAL_DESERIAL : AGGSPLIT_SIMPLE,
                                                                false, /* 
streaming */
                                                                
ctx->final_groupClause,
@@ -1440,6 +1479,7 @@ static void 
add_single_mixed_dqa_hash_agg_path(PlannerInfo *root,
        CdbPathLocus distinct_locus;
        bool            distinct_need_redistribute;
        CdbPathLocus singleQE_locus;
+       AggStrategy aggstrategy;
 
        if (!gp_enable_agg_distinct)
                return;
@@ -1447,6 +1487,8 @@ static void 
add_single_mixed_dqa_hash_agg_path(PlannerInfo *root,
        if (ctx->groupClause)
                return;
 
+       aggstrategy = ctx->parseGroupClause ? AGG_SORTED : AGG_PLAIN;
+
        /*
         * If subpath is projection capable, we do not want to generate a
         * projection plan. The reason is that the projection plan does not
@@ -1471,7 +1513,7 @@ static void 
add_single_mixed_dqa_hash_agg_path(PlannerInfo *root,
                                                                        
output_rel,
                                                                        path,
                                                                        
ctx->partial_grouping_target,
-                                                                       
AGG_PLAIN,
+                                                                       
aggstrategy,
                                                                        
AGGSPLIT_INITIAL_SERIAL,
                                                                        false, 
/* streaming */
                                                                        
ctx->groupClause,
@@ -1487,7 +1529,7 @@ static void 
add_single_mixed_dqa_hash_agg_path(PlannerInfo *root,
                                                                        
output_rel,
                                                                        path,
                                                                        
ctx->target,
-                                                                       
AGG_PLAIN,
+                                                                       
aggstrategy,
                                                                        
AGGSPLIT_FINAL_DESERIAL,
                                                                        false, 
/* streaming */
                                                                        
ctx->groupClause,
@@ -1517,10 +1559,16 @@ add_single_dqa_hash_agg_path(PlannerInfo *root,
        bool            group_need_redistribute;
        CdbPathLocus distinct_locus;
        bool            distinct_need_redistribute;
+       AggStrategy aggstrategy;
 
        if (!gp_enable_agg_distinct)
                return;
 
+       if (ctx->groupClause)
+               aggstrategy = AGG_HASHED;
+       else
+               aggstrategy = ctx->parseGroupClause ? AGG_SORTED : AGG_PLAIN;
+
        /*
         * If subpath is projection capable, we do not want to generate a
         * projection plan. The reason is that the projection plan does not
@@ -1598,7 +1646,7 @@ add_single_dqa_hash_agg_path(PlannerInfo *root,
                                                                                
output_rel,
                                                                                
path,
                                                                                
ctx->target,
-                                                                               
ctx->groupClause ? AGG_HASHED : AGG_PLAIN,
+                                                                               
aggstrategy,
                                                                                
AGGSPLIT_DEDUPLICATED,
                                                                                
false, /* streaming */
                                                                                
ctx->groupClause,
@@ -1630,7 +1678,7 @@ add_single_dqa_hash_agg_path(PlannerInfo *root,
                                                                                
output_rel,
                                                                                
path,
                                                                                
strip_aggdistinct(ctx->partial_grouping_target),
-                                                                               
ctx->groupClause ? AGG_HASHED : AGG_PLAIN,
+                                                                               
aggstrategy,
                                                                                
AGGSPLIT_INITIAL_SERIAL | AGGSPLITOP_DEDUPLICATED,
                                                                                
false, /* streaming */
                                                                                
ctx->groupClause,
@@ -1645,7 +1693,7 @@ add_single_dqa_hash_agg_path(PlannerInfo *root,
                                                                                
output_rel,
                                                                                
path,
                                                                                
ctx->target,
-                                                                               
ctx->groupClause ? AGG_HASHED : AGG_PLAIN,
+                                                                               
aggstrategy,
                                                                                
AGGSPLIT_FINAL_DESERIAL | AGGSPLITOP_DEDUPLICATED,
                                                                                
false, /* streaming */
                                                                                
ctx->groupClause,
@@ -1714,7 +1762,7 @@ add_single_dqa_hash_agg_path(PlannerInfo *root,
                                                                                
output_rel,
                                                                                
path,
                                                                                
ctx->target,
-                                                                               
ctx->groupClause ? AGG_HASHED : AGG_PLAIN,
+                                                                               
aggstrategy,
                                                                                
AGGSPLIT_DEDUPLICATED,
                                                                                
false, /* streaming */
                                                                                
ctx->groupClause,
@@ -1767,7 +1815,7 @@ add_single_dqa_hash_agg_path(PlannerInfo *root,
                                                                                
output_rel,
                                                                                
path,
                                                                                
strip_aggdistinct(ctx->partial_grouping_target),
-                                                                               
ctx->groupClause ? AGG_HASHED : AGG_PLAIN,
+                                                                               
aggstrategy,
                                                                                
AGGSPLIT_INITIAL_SERIAL | AGGSPLITOP_DEDUPLICATED,
                                                                                
false, /* streaming */
                                                                                
ctx->groupClause,
@@ -1781,7 +1829,7 @@ add_single_dqa_hash_agg_path(PlannerInfo *root,
                                                                                
output_rel,
                                                                                
path,
                                                                                
ctx->target,
-                                                                               
ctx->groupClause ? AGG_HASHED : AGG_PLAIN,
+                                                                               
aggstrategy,
                                                                                
AGGSPLIT_FINAL_DESERIAL | AGGSPLITOP_DEDUPLICATED,
                                                                                
false, /* streaming */
                                                                                
ctx->groupClause,
@@ -1899,7 +1947,7 @@ add_multi_dqas_hash_agg_path(PlannerInfo *root,
                path = cdbpath_create_motion_path(root, path, NIL, false,
                                                                                
  distinct_locus);
 
-       AggStrategy split = AGG_PLAIN;
+       AggStrategy split = ctx->parseGroupClause ? AGG_SORTED : AGG_PLAIN;
        unsigned long DEDUPLICATED_FLAG = 0;
        PathTarget *partial_target = info->partial_target;
        double          input_rows = path->rows;
diff --git a/src/test/regress/expected/bfv_aggregate.out 
b/src/test/regress/expected/bfv_aggregate.out
index d192e787b48..5289633b0cf 100644
--- a/src/test/regress/expected/bfv_aggregate.out
+++ b/src/test/regress/expected/bfv_aggregate.out
@@ -1777,7 +1777,7 @@ explain (costs off)
 select 1, sum(col1) from group_by_const group by 1;
                    QUERY PLAN                   
 ------------------------------------------------
- Finalize Aggregate
+ Finalize GroupAggregate
    ->  Gather Motion 3:1  (slice1; segments: 3)
          ->  Partial GroupAggregate
                ->  Seq Scan on group_by_const
diff --git a/src/test/regress/expected/gp_group_by_constant.out 
b/src/test/regress/expected/gp_group_by_constant.out
new file mode 100644
index 00000000000..148f94ff76a
--- /dev/null
+++ b/src/test/regress/expected/gp_group_by_constant.out
@@ -0,0 +1,295 @@
+-- Licensed to the Apache Software Foundation (ASF) under one
+-- or more contributor license agreements.  See the NOTICE file
+-- distributed with this work for additional information
+-- regarding copyright ownership.  The ASF licenses this file
+-- to you under the Apache License, Version 2.0 (the
+-- "License"); you may not use this file except in compliance
+-- with the License.  You may obtain a copy of the License at
+--
+-- http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing,
+-- software distributed under the License is distributed on an
+-- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+-- KIND, either express or implied.  See the License for the
+-- specific language governing permissions and limitations
+-- under the License.
+BEGIN;
+SET LOCAL optimizer = off;
+SET LOCAL enable_parallel = off;
+SET LOCAL gp_enable_multiphase_agg = on;
+CREATE TABLE group_by_constant_empty (n int, c0 boolean)
+    WITH (parallel_workers = 2) DISTRIBUTED BY (n);
+CREATE TABLE group_by_constant_data (n int, c0 boolean)
+    WITH (parallel_workers = 2) DISTRIBUTED BY (n);
+INSERT INTO group_by_constant_data
+    SELECT n, false FROM generate_series(1, 10000) n;
+ANALYZE group_by_constant_data;
+SELECT count(DISTINCT gp_segment_id) > 1 AS multiple_segments
+    FROM group_by_constant_data;
+ multiple_segments 
+-------------------
+ t
+(1 row)
+
+-- Removing all physical grouping keys must not create a group on empty input.
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM group_by_constant_empty GROUP BY 'x'::text;
+                      QUERY PLAN                       
+-------------------------------------------------------
+ Finalize GroupAggregate
+   ->  Gather Motion 3:1  (slice1; segments: 3)
+         ->  Partial GroupAggregate
+               ->  Seq Scan on group_by_constant_empty
+ Optimizer: Postgres query optimizer
+(5 rows)
+
+SELECT count(*) FROM group_by_constant_empty GROUP BY 'x'::text;
+ count 
+-------
+(0 rows)
+
+SELECT 1 FROM group_by_constant_empty
+    GROUP BY (0.25)::money HAVING count(*) = 0;
+ ?column? 
+----------
+(0 rows)
+
+-- Input can also become empty after a WHERE clause on a nonempty table.
+SELECT count(*) FROM group_by_constant_data WHERE n % 10000 = -1
+    GROUP BY 'x'::text;
+ count 
+-------
+(0 rows)
+
+-- The grouping key is not a literal, but WHERE n % 2 = 0 makes it constant.
+SELECT count(*) FROM group_by_constant_empty
+    WHERE n % 2 = 0 GROUP BY n % 2;
+ count 
+-------
+(0 rows)
+
+-- All segments contribute; then only one segment contributes.
+SELECT count(*), sum(n) FROM group_by_constant_data GROUP BY 'x'::text;
+ count |   sum    
+-------+----------
+ 10000 | 50005000
+(1 row)
+
+SELECT count(*), sum(n) FROM group_by_constant_data WHERE n % 10000 = 1
+    GROUP BY 'x'::text;
+ count | sum 
+-------+-----
+     1 |   1
+(1 row)
+
+-- Global aggregates and empty grouping sets still produce a row.
+SELECT count(*) FROM group_by_constant_empty;
+ count 
+-------
+     0
+(1 row)
+
+SELECT count(*) FROM group_by_constant_empty GROUP BY GROUPING SETS ((), ());
+ count 
+-------
+     0
+     0
+(2 rows)
+
+SELECT 'x'::text FROM group_by_constant_empty GROUP BY 'x'::text;
+ text 
+------
+(0 rows)
+
+SELECT DISTINCT 'x'::text FROM group_by_constant_empty;
+ text 
+------
+(0 rows)
+
+SELECT 'x'::text FROM group_by_constant_data GROUP BY 'x'::text;
+ text 
+------
+ x
+(1 row)
+
+-- DISTINCT arguments may require redistribution before aggregation.
+EXPLAIN (COSTS OFF)
+SELECT count(DISTINCT n) FROM group_by_constant_empty GROUP BY 'x'::text;
+                         QUERY PLAN                          
+-------------------------------------------------------------
+ Finalize GroupAggregate
+   ->  Gather Motion 3:1  (slice1; segments: 3)
+         ->  Partial GroupAggregate
+               ->  HashAggregate
+                     Group Key: n
+                     ->  Seq Scan on group_by_constant_empty
+ Optimizer: Postgres query optimizer
+(7 rows)
+
+SELECT count(DISTINCT n) FROM group_by_constant_empty GROUP BY 'x'::text;
+ count 
+-------
+(0 rows)
+
+SELECT count(DISTINCT c0) FROM group_by_constant_empty GROUP BY 'x'::text;
+ count 
+-------
+(0 rows)
+
+SELECT count(DISTINCT c0), sum(n) FROM group_by_constant_empty
+    GROUP BY 'x'::text;
+ count | sum 
+-------+-----
+(0 rows)
+
+SELECT count(DISTINCT n), count(DISTINCT c0) FROM group_by_constant_empty
+    GROUP BY 'x'::text;
+ count | count 
+-------+-------
+(0 rows)
+
+SELECT count(DISTINCT n) FROM group_by_constant_data GROUP BY 'x'::text;
+ count 
+-------
+ 10000
+(1 row)
+
+SELECT count(DISTINCT c0) FROM group_by_constant_data GROUP BY 'x'::text;
+ count 
+-------
+     1
+(1 row)
+
+SELECT count(DISTINCT c0), sum(n) FROM group_by_constant_data
+    GROUP BY 'x'::text;
+ count |   sum    
+-------+----------
+     1 | 50005000
+(1 row)
+
+SELECT count(DISTINCT n), count(DISTINCT c0) FROM group_by_constant_data
+    GROUP BY 'x'::text;
+ count | count 
+-------+-------
+ 10000 |     1
+(1 row)
+
+SELECT count(DISTINCT n) FROM group_by_constant_empty;
+ count 
+-------
+     0
+(1 row)
+
+-- Filtering all aggregate arguments must not remove an existing group.
+SELECT count(DISTINCT n) FILTER (WHERE n < 0),
+       count(DISTINCT c0) FILTER (WHERE n < 0)
+    FROM group_by_constant_data GROUP BY 'x'::text;
+ count | count 
+-------+-------
+     0 |     0
+(1 row)
+
+-- TupleSplit is still used when one DQA has no FILTER.
+EXPLAIN (COSTS OFF)
+SELECT count(DISTINCT n) FILTER (WHERE n < 0), count(DISTINCT c0)
+    FROM group_by_constant_data GROUP BY 'x'::text;
+                                   QUERY PLAN                                  
 
+--------------------------------------------------------------------------------
+ Finalize GroupAggregate
+   ->  Gather Motion 3:1  (slice1; segments: 3)
+         ->  Partial GroupAggregate
+               ->  Redistribute Motion 3:3  (slice2; segments: 3)
+                     Hash Key: n, c0, (AggExprId)
+                     ->  Streaming HashAggregate
+                           Group Key: AggExprId, n, c0
+                           ->  TupleSplit
+                                 Split by Col: (n) FILTER (WHERE (n < 0)), (c0)
+                                 ->  Seq Scan on group_by_constant_data
+ Optimizer: Postgres query optimizer
+(11 rows)
+
+SELECT count(DISTINCT n) FILTER (WHERE n < 0), count(DISTINCT c0)
+    FROM group_by_constant_data GROUP BY 'x'::text;
+ count | count 
+-------+-------
+     0 |     1
+(1 row)
+
+-- Compare with single-phase aggregation and ORCA.
+SET LOCAL gp_enable_multiphase_agg = off;
+SELECT count(*) FROM group_by_constant_empty GROUP BY 'x'::text;
+ count 
+-------
+(0 rows)
+
+SET LOCAL optimizer = on;
+SET LOCAL gp_enable_multiphase_agg = on;
+SELECT count(*) FROM group_by_constant_empty GROUP BY 'x'::text;
+ count 
+-------
+(0 rows)
+
+-- Exercise worker partial aggregation as well as the final MPP stage.
+SET LOCAL optimizer = off;
+SET LOCAL enable_parallel = on;
+SET LOCAL min_parallel_table_scan_size = 0;
+SET LOCAL max_parallel_workers_per_gather = 2;
+SET LOCAL parallel_setup_cost = 0;
+SET LOCAL parallel_tuple_cost = 0;
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM group_by_constant_data WHERE n % 10000 = -1
+    GROUP BY 'x'::text;
+                          QUERY PLAN                           
+---------------------------------------------------------------
+ Finalize GroupAggregate
+   ->  Gather Motion 6:1  (slice1; segments: 6)
+         ->  Partial GroupAggregate
+               ->  Parallel Seq Scan on group_by_constant_data
+                     Filter: ((n % 10000) = '-1'::integer)
+ Optimizer: Postgres query optimizer
+(6 rows)
+
+SELECT count(*) FROM group_by_constant_data WHERE n % 10000 = -1
+    GROUP BY 'x'::text;
+ count 
+-------
+(0 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM group_by_constant_empty
+    WHERE n % 2 = 0 GROUP BY n % 2;
+                           QUERY PLAN                           
+----------------------------------------------------------------
+ Finalize GroupAggregate
+   ->  Gather Motion 6:1  (slice1; segments: 6)
+         ->  Partial GroupAggregate
+               ->  Parallel Seq Scan on group_by_constant_empty
+                     Filter: ((n % 2) = 0)
+ Optimizer: Postgres query optimizer
+(6 rows)
+
+SELECT count(*) FROM group_by_constant_empty
+    WHERE n % 2 = 0 GROUP BY n % 2;
+ count 
+-------
+(0 rows)
+
+SELECT count(*), sum(n) FROM group_by_constant_data GROUP BY 'x'::text;
+ count |   sum    
+-------+----------
+ 10000 | 50005000
+(1 row)
+
+SELECT 'x'::text FROM group_by_constant_empty GROUP BY 'x'::text;
+ text 
+------
+(0 rows)
+
+SELECT 'x'::text FROM group_by_constant_data GROUP BY 'x'::text;
+ text 
+------
+ x
+(1 row)
+
+COMMIT;
diff --git a/src/test/regress/greenplum_schedule 
b/src/test/regress/greenplum_schedule
index 84e8766844b..5e2a83ee5a5 100755
--- a/src/test/regress/greenplum_schedule
+++ b/src/test/regress/greenplum_schedule
@@ -51,6 +51,7 @@ test: instr_in_shmem
 
 test: createdb
 test: gp_aggregates gp_aggregates_costs gp_metadata variadic_parameters 
default_parameters function_extensions spi gp_xml shared_scan update_gp 
triggers_gp returning_gp resource_queue_with_rule gp_types gp_index cluster_gp 
combocid_gp gp_sort gp_prepared_xacts gp_backend_info gp_locale
+test: gp_group_by_constant
 test: foreign_key_gp
 test: spi_processed64bit
 test: gp_tablespace_with_faults
diff --git a/src/test/regress/sql/gp_group_by_constant.sql 
b/src/test/regress/sql/gp_group_by_constant.sql
new file mode 100644
index 00000000000..5aa5b15c371
--- /dev/null
+++ b/src/test/regress/sql/gp_group_by_constant.sql
@@ -0,0 +1,114 @@
+-- Licensed to the Apache Software Foundation (ASF) under one
+-- or more contributor license agreements.  See the NOTICE file
+-- distributed with this work for additional information
+-- regarding copyright ownership.  The ASF licenses this file
+-- to you under the Apache License, Version 2.0 (the
+-- "License"); you may not use this file except in compliance
+-- with the License.  You may obtain a copy of the License at
+--
+-- http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing,
+-- software distributed under the License is distributed on an
+-- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+-- KIND, either express or implied.  See the License for the
+-- specific language governing permissions and limitations
+-- under the License.
+
+BEGIN;
+SET LOCAL optimizer = off;
+SET LOCAL enable_parallel = off;
+SET LOCAL gp_enable_multiphase_agg = on;
+
+CREATE TABLE group_by_constant_empty (n int, c0 boolean)
+    WITH (parallel_workers = 2) DISTRIBUTED BY (n);
+CREATE TABLE group_by_constant_data (n int, c0 boolean)
+    WITH (parallel_workers = 2) DISTRIBUTED BY (n);
+INSERT INTO group_by_constant_data
+    SELECT n, false FROM generate_series(1, 10000) n;
+ANALYZE group_by_constant_data;
+SELECT count(DISTINCT gp_segment_id) > 1 AS multiple_segments
+    FROM group_by_constant_data;
+
+-- Removing all physical grouping keys must not create a group on empty input.
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM group_by_constant_empty GROUP BY 'x'::text;
+SELECT count(*) FROM group_by_constant_empty GROUP BY 'x'::text;
+SELECT 1 FROM group_by_constant_empty
+    GROUP BY (0.25)::money HAVING count(*) = 0;
+-- Input can also become empty after a WHERE clause on a nonempty table.
+SELECT count(*) FROM group_by_constant_data WHERE n % 10000 = -1
+    GROUP BY 'x'::text;
+-- The grouping key is not a literal, but WHERE n % 2 = 0 makes it constant.
+SELECT count(*) FROM group_by_constant_empty
+    WHERE n % 2 = 0 GROUP BY n % 2;
+
+-- All segments contribute; then only one segment contributes.
+SELECT count(*), sum(n) FROM group_by_constant_data GROUP BY 'x'::text;
+SELECT count(*), sum(n) FROM group_by_constant_data WHERE n % 10000 = 1
+    GROUP BY 'x'::text;
+
+-- Global aggregates and empty grouping sets still produce a row.
+SELECT count(*) FROM group_by_constant_empty;
+SELECT count(*) FROM group_by_constant_empty GROUP BY GROUPING SETS ((), ());
+
+SELECT 'x'::text FROM group_by_constant_empty GROUP BY 'x'::text;
+SELECT DISTINCT 'x'::text FROM group_by_constant_empty;
+SELECT 'x'::text FROM group_by_constant_data GROUP BY 'x'::text;
+
+-- DISTINCT arguments may require redistribution before aggregation.
+EXPLAIN (COSTS OFF)
+SELECT count(DISTINCT n) FROM group_by_constant_empty GROUP BY 'x'::text;
+SELECT count(DISTINCT n) FROM group_by_constant_empty GROUP BY 'x'::text;
+SELECT count(DISTINCT c0) FROM group_by_constant_empty GROUP BY 'x'::text;
+SELECT count(DISTINCT c0), sum(n) FROM group_by_constant_empty
+    GROUP BY 'x'::text;
+SELECT count(DISTINCT n), count(DISTINCT c0) FROM group_by_constant_empty
+    GROUP BY 'x'::text;
+SELECT count(DISTINCT n) FROM group_by_constant_data GROUP BY 'x'::text;
+SELECT count(DISTINCT c0) FROM group_by_constant_data GROUP BY 'x'::text;
+SELECT count(DISTINCT c0), sum(n) FROM group_by_constant_data
+    GROUP BY 'x'::text;
+SELECT count(DISTINCT n), count(DISTINCT c0) FROM group_by_constant_data
+    GROUP BY 'x'::text;
+SELECT count(DISTINCT n) FROM group_by_constant_empty;
+
+-- Filtering all aggregate arguments must not remove an existing group.
+SELECT count(DISTINCT n) FILTER (WHERE n < 0),
+       count(DISTINCT c0) FILTER (WHERE n < 0)
+    FROM group_by_constant_data GROUP BY 'x'::text;
+-- TupleSplit is still used when one DQA has no FILTER.
+EXPLAIN (COSTS OFF)
+SELECT count(DISTINCT n) FILTER (WHERE n < 0), count(DISTINCT c0)
+    FROM group_by_constant_data GROUP BY 'x'::text;
+SELECT count(DISTINCT n) FILTER (WHERE n < 0), count(DISTINCT c0)
+    FROM group_by_constant_data GROUP BY 'x'::text;
+
+-- Compare with single-phase aggregation and ORCA.
+SET LOCAL gp_enable_multiphase_agg = off;
+SELECT count(*) FROM group_by_constant_empty GROUP BY 'x'::text;
+SET LOCAL optimizer = on;
+SET LOCAL gp_enable_multiphase_agg = on;
+SELECT count(*) FROM group_by_constant_empty GROUP BY 'x'::text;
+
+-- Exercise worker partial aggregation as well as the final MPP stage.
+SET LOCAL optimizer = off;
+SET LOCAL enable_parallel = on;
+SET LOCAL min_parallel_table_scan_size = 0;
+SET LOCAL max_parallel_workers_per_gather = 2;
+SET LOCAL parallel_setup_cost = 0;
+SET LOCAL parallel_tuple_cost = 0;
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM group_by_constant_data WHERE n % 10000 = -1
+    GROUP BY 'x'::text;
+SELECT count(*) FROM group_by_constant_data WHERE n % 10000 = -1
+    GROUP BY 'x'::text;
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM group_by_constant_empty
+    WHERE n % 2 = 0 GROUP BY n % 2;
+SELECT count(*) FROM group_by_constant_empty
+    WHERE n % 2 = 0 GROUP BY n % 2;
+SELECT count(*), sum(n) FROM group_by_constant_data GROUP BY 'x'::text;
+SELECT 'x'::text FROM group_by_constant_empty GROUP BY 'x'::text;
+SELECT 'x'::text FROM group_by_constant_data GROUP BY 'x'::text;
+COMMIT;


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to