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;