From e2d7a0d8c361fe6f7fe93706dc9501e8c5d8c6df Mon Sep 17 00:00:00 2001
From: Richard Guo <guofenglinux@gmail.com>
Date: Wed, 24 Jun 2026 18:09:06 +0900
Subject: [PATCH v4] Add an enable_groupagg GUC parameter

We've long had enable_hashagg to discourage hashed aggregation, but
there was no equivalent for grouping that works by reading presorted
input.  That's handy when investigating planner cost misestimates, and
as an escape hatch when a sorted grouping plan is chosen over a much
cheaper hashed one.

enable_groupagg (on by default) covers the GroupAggregate and Group
nodes, the sort-based Unique step used for DISTINCT and semijoin
unique-ification, and the sorted mode of SetOp.  It isn't a hard
switch; it only bumps disabled_nodes, so plans with no other choice
are still produced.

Several regression and module tests had been setting enable_sort off,
and one enable_indexscan off, only to force a hashed plan.  Use
enable_groupagg there instead, where that was the real intent.  In
union.sql this also lets us test the hashed UNION path, which we had
no way to reach before.
---
 contrib/hstore/expected/hstore.out            |  4 +-
 contrib/hstore/sql/hstore.sql                 |  4 +-
 contrib/ltree/expected/ltree.out              |  4 +-
 contrib/ltree/sql/ltree.sql                   |  4 +-
 doc/src/sgml/config.sgml                      | 14 +++++++
 src/backend/optimizer/path/costsize.c         | 33 ++++++++++++++---
 src/backend/optimizer/util/pathnode.c         | 20 ++++++++++
 src/backend/utils/misc/guc_parameters.dat     |  7 ++++
 src/backend/utils/misc/postgresql.conf.sample |  1 +
 src/include/optimizer/cost.h                  |  1 +
 .../injection_points/expected/hashagg.out     |  2 +-
 .../modules/injection_points/sql/hashagg.sql  |  2 +-
 src/test/regress/expected/aggregates.out      | 10 ++---
 src/test/regress/expected/groupingsets.out    |  8 ++--
 src/test/regress/expected/jsonb.out           |  6 +--
 src/test/regress/expected/multirangetypes.out |  4 +-
 src/test/regress/expected/rangetypes.out      |  4 +-
 src/test/regress/expected/select_distinct.out |  4 +-
 src/test/regress/expected/subselect.out       |  2 +-
 src/test/regress/expected/sysviews.out        |  3 +-
 src/test/regress/expected/union.out           | 37 ++++++++++++-------
 src/test/regress/sql/aggregates.sql           | 10 ++---
 src/test/regress/sql/groupingsets.sql         |  8 ++--
 src/test/regress/sql/jsonb.sql                |  6 +--
 src/test/regress/sql/multirangetypes.sql      |  4 +-
 src/test/regress/sql/rangetypes.sql           |  4 +-
 src/test/regress/sql/select_distinct.sql      |  4 +-
 src/test/regress/sql/subselect.sql            |  2 +-
 src/test/regress/sql/union.sql                | 16 ++++----
 29 files changed, 153 insertions(+), 75 deletions(-)

diff --git a/contrib/hstore/expected/hstore.out b/contrib/hstore/expected/hstore.out
index acea8806ba4..9713372b7a4 100644
--- a/contrib/hstore/expected/hstore.out
+++ b/contrib/hstore/expected/hstore.out
@@ -1509,7 +1509,7 @@ select count(*) from (select h from (select * from testhstore union all select *
 (1 row)
 
 set enable_hashagg = true;
-set enable_sort = false;
+set enable_groupagg = false;
 select count(*) from (select h from (select * from testhstore union all select * from testhstore) hs group by h) hs2;
  count 
 -------
@@ -1522,7 +1522,7 @@ select distinct * from (values (hstore '' || ''),('')) v(h);
  
 (1 row)
 
-set enable_sort = true;
+set enable_groupagg = true;
 -- btree
 drop index hidx;
 create index hidx on testhstore using btree (h);
diff --git a/contrib/hstore/sql/hstore.sql b/contrib/hstore/sql/hstore.sql
index 8ae95e8a510..ac929e07509 100644
--- a/contrib/hstore/sql/hstore.sql
+++ b/contrib/hstore/sql/hstore.sql
@@ -345,10 +345,10 @@ select count(distinct h) from testhstore;
 set enable_hashagg = false;
 select count(*) from (select h from (select * from testhstore union all select * from testhstore) hs group by h) hs2;
 set enable_hashagg = true;
-set enable_sort = false;
+set enable_groupagg = false;
 select count(*) from (select h from (select * from testhstore union all select * from testhstore) hs group by h) hs2;
 select distinct * from (values (hstore '' || ''),('')) v(h);
-set enable_sort = true;
+set enable_groupagg = true;
 
 -- btree
 drop index hidx;
diff --git a/contrib/ltree/expected/ltree.out b/contrib/ltree/expected/ltree.out
index f1d0eb37b81..0ab623cc204 100644
--- a/contrib/ltree/expected/ltree.out
+++ b/contrib/ltree/expected/ltree.out
@@ -7891,7 +7891,7 @@ reset enable_seqscan;
 reset enable_bitmapscan;
 -- test hash aggregate
 set enable_hashagg=on;
-set enable_sort=off;
+set enable_groupagg=off;
 EXPLAIN (COSTS OFF)
 SELECT count(*) FROM (
 SELECT t FROM (SELECT * FROM ltreetest UNION ALL SELECT * FROM ltreetest) t1 GROUP BY t
@@ -7915,7 +7915,7 @@ SELECT t FROM (SELECT * FROM ltreetest UNION ALL SELECT * FROM ltreetest) t1 GRO
 (1 row)
 
 reset enable_hashagg;
-reset enable_sort;
+reset enable_groupagg;
 drop index tstidx;
 -- test gist index
 create index tstidx on ltreetest using gist (t gist_ltree_ops(siglen=0));
diff --git a/contrib/ltree/sql/ltree.sql b/contrib/ltree/sql/ltree.sql
index 833091dc6bb..5c324160afd 100644
--- a/contrib/ltree/sql/ltree.sql
+++ b/contrib/ltree/sql/ltree.sql
@@ -372,7 +372,7 @@ reset enable_bitmapscan;
 -- test hash aggregate
 
 set enable_hashagg=on;
-set enable_sort=off;
+set enable_groupagg=off;
 
 EXPLAIN (COSTS OFF)
 SELECT count(*) FROM (
@@ -384,7 +384,7 @@ SELECT t FROM (SELECT * FROM ltreetest UNION ALL SELECT * FROM ltreetest) t1 GRO
 ) t2;
 
 reset enable_hashagg;
-reset enable_sort;
+reset enable_groupagg;
 
 drop index tstidx;
 
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index fa566c9e553..b395221cad1 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -5861,6 +5861,20 @@ ANY <replaceable class="parameter">num_sync</replaceable> ( <replaceable class="
       </listitem>
      </varlistentry>
 
+     <varlistentry id="guc-enable-groupagg" xreflabel="enable_groupagg">
+      <term><varname>enable_groupagg</varname> (<type>boolean</type>)
+      <indexterm>
+       <primary><varname>enable_groupagg</varname> configuration parameter</primary>
+      </indexterm>
+      </term>
+      <listitem>
+       <para>
+        Enables or disables the query planner's use of sort-based grouping
+        and aggregation plan types. The default is <literal>on</literal>.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry id="guc-enable-hashagg" xreflabel="enable_hashagg">
       <term><varname>enable_hashagg</varname> (<type>boolean</type>)
       <indexterm>
diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
index 1c575e56ff6..ac523ecf9a8 100644
--- a/src/backend/optimizer/path/costsize.c
+++ b/src/backend/optimizer/path/costsize.c
@@ -151,6 +151,7 @@ bool		enable_tidscan = true;
 bool		enable_sort = true;
 bool		enable_incremental_sort = true;
 bool		enable_hashagg = true;
+bool		enable_groupagg = true;
 bool		enable_nestloop = true;
 bool		enable_material = true;
 bool		enable_memoize = true;
@@ -2837,14 +2838,14 @@ cost_agg(Path *path, PlannerInfo *root,
 		/* we aren't grouping */
 		total_cost = startup_cost + cpu_tuple_cost;
 		output_tuples = 1;
+
+		/* AGG_PLAIN neither hashes nor sorts, so neither switch disables it */
 	}
 	else if (aggstrategy == AGG_SORTED || aggstrategy == AGG_MIXED)
 	{
 		/* Here we are able to deliver output on-the-fly */
 		startup_cost = input_startup_cost;
 		total_cost = input_total_cost;
-		if (aggstrategy == AGG_MIXED && !enable_hashagg)
-			++disabled_nodes;
 		/* calcs phrased this way to match HASHED case, see note above */
 		total_cost += aggcosts->transCost.startup;
 		total_cost += aggcosts->transCost.per_tuple * input_tuples;
@@ -2853,13 +2854,31 @@ cost_agg(Path *path, PlannerInfo *root,
 		total_cost += aggcosts->finalCost.per_tuple * numGroups;
 		total_cost += cpu_tuple_cost * numGroups;
 		output_tuples = numGroups;
+
+		/*
+		 * AGG_MIXED hashes at least one grouping set, so it is disabled when
+		 * enable_hashagg is off.  Any sorted grouping it also performs is
+		 * costed separately, since create_groupingsets_path() calls
+		 * cost_agg() once per rollup and the non-hashed rollups come through
+		 * as AGG_SORTED.
+		 *
+		 * AGG_SORTED is disabled when enable_groupagg is off, but only when
+		 * there are grouping columns.  The empty grouping set arrives with
+		 * numGroupCols == 0 and is computed like AGG_PLAIN, with no hashing
+		 * or sorting, so it isn't disabled.
+		 */
+		if (aggstrategy == AGG_MIXED)
+		{
+			if (!enable_hashagg)
+				++disabled_nodes;
+		}
+		else if (numGroupCols > 0 && !enable_groupagg)	/* AGG_SORTED */
+			++disabled_nodes;
 	}
 	else
 	{
 		/* must be AGG_HASHED */
 		startup_cost = input_total_cost;
-		if (!enable_hashagg)
-			++disabled_nodes;
 		startup_cost += aggcosts->transCost.startup;
 		startup_cost += aggcosts->transCost.per_tuple * input_tuples;
 		/* cost of computing hash value */
@@ -2871,6 +2890,10 @@ cost_agg(Path *path, PlannerInfo *root,
 		/* cost of retrieving from hash table */
 		total_cost += cpu_tuple_cost * numGroups;
 		output_tuples = numGroups;
+
+		/* AGG_HASHED is disabled when enable_hashagg is off */
+		if (!enable_hashagg)
+			++disabled_nodes;
 	}
 
 	/*
@@ -3340,7 +3363,7 @@ cost_group(Path *path, PlannerInfo *root,
 	}
 
 	path->rows = output_tuples;
-	path->disabled_nodes = input_disabled_nodes;
+	path->disabled_nodes = input_disabled_nodes + (enable_groupagg ? 0 : 1);
 	path->startup_cost = startup_cost;
 	path->total_cost = total_cost;
 }
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index 73518c8f870..3875e3dd571 100644
--- a/src/backend/optimizer/util/pathnode.c
+++ b/src/backend/optimizer/util/pathnode.c
@@ -3036,6 +3036,16 @@ create_unique_path(PlannerInfo *root,
 		cpu_operator_cost * subpath->rows * numCols;
 	pathnode->path.rows = numGroups;
 
+	/*
+	 * Mark the path as disabled if enable_groupagg is off.  While this isn't
+	 * a grouping Agg node, it is the sort-based way of removing duplicates
+	 * and so is the natural counterpart to the AGG_HASHED path that
+	 * enable_hashagg controls; it seems close enough to justify letting that
+	 * switch control it.
+	 */
+	if (!enable_groupagg)
+		pathnode->path.disabled_nodes++;
+
 	return pathnode;
 }
 
@@ -3522,6 +3532,16 @@ create_setop_path(PlannerInfo *root,
 		 * qual-checking or projection.
 		 */
 		pathnode->path.total_cost += cpu_operator_cost * outputRows;
+
+		/*
+		 * Mark the path as disabled if enable_groupagg is off.  While this
+		 * isn't a grouping Agg node, it is the sort-based implementation and
+		 * so is the natural counterpart to the SETOP_HASHED path that
+		 * enable_hashagg controls; it seems close enough to justify letting
+		 * that switch control it.
+		 */
+		if (!enable_groupagg)
+			pathnode->path.disabled_nodes++;
 	}
 	else
 	{
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 7b1eb6e61bc..3f9dc3fc5a6 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -905,6 +905,13 @@
   boot_val => 'true',
 },
 
+{ name => 'enable_groupagg', type => 'bool', context => 'PGC_USERSET', group => 'QUERY_TUNING_METHOD',
+  short_desc => 'Enables the planner\'s use of sort-based grouping and aggregation plans.',
+  flags => 'GUC_EXPLAIN',
+  variable => 'enable_groupagg',
+  boot_val => 'true',
+},
+
 { name => 'enable_hashagg', type => 'bool', context => 'PGC_USERSET', group => 'QUERY_TUNING_METHOD',
   short_desc => 'Enables the planner\'s use of hashed aggregation plans.',
   flags => 'GUC_EXPLAIN',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index ac38cddaaf9..2490f276019 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -426,6 +426,7 @@
 #enable_async_append = on
 #enable_bitmapscan = on
 #enable_gathermerge = on
+#enable_groupagg = on
 #enable_hashagg = on
 #enable_hashjoin = on
 #enable_incremental_sort = on
diff --git a/src/include/optimizer/cost.h b/src/include/optimizer/cost.h
index f2fd5d31507..bda3f1690c0 100644
--- a/src/include/optimizer/cost.h
+++ b/src/include/optimizer/cost.h
@@ -57,6 +57,7 @@ extern PGDLLIMPORT bool enable_tidscan;
 extern PGDLLIMPORT bool enable_sort;
 extern PGDLLIMPORT bool enable_incremental_sort;
 extern PGDLLIMPORT bool enable_hashagg;
+extern PGDLLIMPORT bool enable_groupagg;
 extern PGDLLIMPORT bool enable_nestloop;
 extern PGDLLIMPORT bool enable_material;
 extern PGDLLIMPORT bool enable_memoize;
diff --git a/src/test/modules/injection_points/expected/hashagg.out b/src/test/modules/injection_points/expected/hashagg.out
index cc4247af97d..2f75e9be8b7 100644
--- a/src/test/modules/injection_points/expected/hashagg.out
+++ b/src/test/modules/injection_points/expected/hashagg.out
@@ -36,7 +36,7 @@ CREATE TABLE hashagg_ij(x INTEGER);
 INSERT INTO hashagg_ij SELECT g FROM generate_series(1,5100) g;
 SET max_parallel_workers=0;
 SET max_parallel_workers_per_gather=0;
-SET enable_sort=FALSE;
+SET enable_groupagg=FALSE;
 SET work_mem='4MB';
 SELECT COUNT(*) FROM (SELECT DISTINCT x FROM hashagg_ij) s;
 NOTICE:  notice triggered for injection point hash-aggregate-spill-1000
diff --git a/src/test/modules/injection_points/sql/hashagg.sql b/src/test/modules/injection_points/sql/hashagg.sql
index 51d814623fc..5d580f8e425 100644
--- a/src/test/modules/injection_points/sql/hashagg.sql
+++ b/src/test/modules/injection_points/sql/hashagg.sql
@@ -17,7 +17,7 @@ INSERT INTO hashagg_ij SELECT g FROM generate_series(1,5100) g;
 
 SET max_parallel_workers=0;
 SET max_parallel_workers_per_gather=0;
-SET enable_sort=FALSE;
+SET enable_groupagg=FALSE;
 SET work_mem='4MB';
 
 SELECT COUNT(*) FROM (SELECT DISTINCT x FROM hashagg_ij) s;
diff --git a/src/test/regress/expected/aggregates.out b/src/test/regress/expected/aggregates.out
index 89e051ee824..de6f94a9e19 100644
--- a/src/test/regress/expected/aggregates.out
+++ b/src/test/regress/expected/aggregates.out
@@ -1459,7 +1459,7 @@ drop cascades to table minmaxtest2
 drop cascades to table minmaxtest3
 -- DISTINCT can also trigger wrong answers with hash aggregation (bug #18465)
 begin;
-set local enable_sort = off;
+set local enable_groupagg = off;
 explain (costs off)
   select f1, (select distinct min(t1.f1) from int4_tbl t1 where t1.f1 = t0.f1)
   from int4_tbl t0;
@@ -3804,7 +3804,7 @@ select v||'a', case when v||'a' = 'aa' then 1 else 0 end, count(*)
 --
 -- Hash Aggregation Spill tests
 --
-set enable_sort=false;
+set enable_groupagg=false;
 set work_mem='64kB';
 select unique1, count(*), sum(twothousand) from tenk1
 group by unique1
@@ -3863,7 +3863,7 @@ order by sum(twothousand);
 (48 rows)
 
 set work_mem to default;
-set enable_sort to default;
+set enable_groupagg to default;
 --
 -- Compare results between plans using sorting and plans using hash
 -- aggregation. Force spilling in both cases by setting work_mem low.
@@ -3912,7 +3912,7 @@ select (g/2)::numeric as c1, array_agg(g::numeric) as c2, count(*) as c3
   from agg_data_2k group by g/2;
 -- Produce results with hash aggregation
 set enable_hashagg = true;
-set enable_sort = false;
+set enable_groupagg = false;
 set jit_above_cost = 0;
 explain (costs off)
 select g%10000 as c1, sum(g::numeric) as c2, count(*) as c3
@@ -3944,7 +3944,7 @@ select (g/2)::numeric as c1, sum(7::int4) as c2, count(*) as c3
 create table agg_hash_4 as
 select (g/2)::numeric as c1, array_agg(g::numeric) as c2, count(*) as c3
   from agg_data_2k group by g/2;
-set enable_sort = true;
+set enable_groupagg = true;
 set work_mem to default;
 -- Compare group aggregation results to hash aggregation results
 (select * from agg_hash_1 except select * from agg_group_1)
diff --git a/src/test/regress/expected/groupingsets.out b/src/test/regress/expected/groupingsets.out
index b08083ec54c..c3f00771f6e 100644
--- a/src/test/regress/expected/groupingsets.out
+++ b/src/test/regress/expected/groupingsets.out
@@ -2010,7 +2010,7 @@ alter table bug_16784 set (autovacuum_enabled = 'false');
 update pg_class set reltuples = 10 where relname='bug_16784';
 insert into bug_16784 select g/10, g from generate_series(1,40) g;
 set work_mem='64kB';
-set enable_sort = false;
+set enable_groupagg = false;
 select * from
   (values (1),(2)) v(a),
   lateral (select a, i, j, count(*) from
@@ -2205,7 +2205,7 @@ alter table gs_data_1 set (autovacuum_enabled = 'false');
 update pg_class set reltuples = 10 where relname='gs_data_1';
 set work_mem='64kB';
 -- Produce results with sorting.
-set enable_sort = true;
+set enable_groupagg = true;
 set enable_hashagg = false;
 set jit_above_cost = 0;
 explain (costs off)
@@ -2234,7 +2234,7 @@ select g100, g10, sum(g::numeric), count(*), max(g::text)
 from gs_data_1 group by cube (g1000, g100,g10);
 -- Produce results with hash aggregation.
 set enable_hashagg = true;
-set enable_sort = false;
+set enable_groupagg = false;
 explain (costs off)
 select g100, g10, sum(g::numeric), count(*), max(g::text)
 from gs_data_1 group by cube (g1000, g100,g10);
@@ -2255,7 +2255,7 @@ from gs_data_1 group by cube (g1000, g100,g10);
 create table gs_hash_1 as
 select g100, g10, sum(g::numeric), count(*), max(g::text)
 from gs_data_1 group by cube (g1000, g100,g10);
-set enable_sort = true;
+set enable_groupagg = true;
 set work_mem to default;
 set hash_mem_multiplier to default;
 -- Compare results
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index 4e2467852db..b1d2ce5f03a 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -3473,7 +3473,7 @@ SELECT count(*) FROM (SELECT j FROM (SELECT * FROM testjsonb UNION ALL SELECT *
 (1 row)
 
 SET enable_hashagg = on;
-SET enable_sort = off;
+SET enable_groupagg = off;
 SELECT count(*) FROM (SELECT j FROM (SELECT * FROM testjsonb UNION ALL SELECT * FROM testjsonb) js GROUP BY j) js2;
  count 
 -------
@@ -3486,9 +3486,9 @@ SELECT distinct * FROM (values (jsonb '{}' || ''::text),('{}')) v(j);
  {}
 (1 row)
 
-SET enable_sort = on;
+SET enable_groupagg = on;
 RESET enable_hashagg;
-RESET enable_sort;
+RESET enable_groupagg;
 DROP INDEX jidx;
 DROP INDEX jidx_array;
 -- btree
diff --git a/src/test/regress/expected/multirangetypes.out b/src/test/regress/expected/multirangetypes.out
index 6006aede31d..d47ce4b6d6a 100644
--- a/src/test/regress/expected/multirangetypes.out
+++ b/src/test/regress/expected/multirangetypes.out
@@ -3442,14 +3442,14 @@ NOTICE:  drop cascades to type two_ints_range
 --
 -- Check behavior when subtype lacks a hash function
 --
-set enable_sort = off;  -- try to make it pick a hash setop implementation
+set enable_groupagg = off;  -- try to make it pick a hash setop implementation
 select '{(01,10)}'::varbitmultirange except select '{(10,11)}'::varbitmultirange;
  varbitmultirange 
 ------------------
  {(01,10)}
 (1 row)
 
-reset enable_sort;
+reset enable_groupagg;
 --
 -- OUT/INOUT/TABLE functions
 --
diff --git a/src/test/regress/expected/rangetypes.out b/src/test/regress/expected/rangetypes.out
index e062a4e5c2c..ba630e6f215 100644
--- a/src/test/regress/expected/rangetypes.out
+++ b/src/test/regress/expected/rangetypes.out
@@ -1819,14 +1819,14 @@ NOTICE:  drop cascades to type two_ints_range
 -- Check behavior when subtype lacks a hash function
 --
 create type varbitrange as range (subtype = varbit);
-set enable_sort = off;  -- try to make it pick a hash setop implementation
+set enable_groupagg = off;  -- try to make it pick a hash setop implementation
 select '(01,10)'::varbitrange except select '(10,11)'::varbitrange;
  varbitrange 
 -------------
  (01,10)
 (1 row)
 
-reset enable_sort;
+reset enable_groupagg;
 --
 -- OUT/INOUT/TABLE functions
 --
diff --git a/src/test/regress/expected/select_distinct.out b/src/test/regress/expected/select_distinct.out
index 379ba0bc9fa..741f7238742 100644
--- a/src/test/regress/expected/select_distinct.out
+++ b/src/test/regress/expected/select_distinct.out
@@ -187,7 +187,7 @@ SELECT DISTINCT hundred, two FROM tenk1;
 RESET enable_seqscan;
 SET enable_hashagg=TRUE;
 -- Produce results with hash aggregation.
-SET enable_sort=FALSE;
+SET enable_groupagg=FALSE;
 SET jit_above_cost=0;
 EXPLAIN (costs off)
 SELECT DISTINCT g%1000 FROM generate_series(0,9999) g;
@@ -203,7 +203,7 @@ SELECT DISTINCT g%1000 FROM generate_series(0,9999) g;
 SET jit_above_cost TO DEFAULT;
 CREATE TABLE distinct_hash_2 AS
 SELECT DISTINCT (g%1000)::text FROM generate_series(0,9999) g;
-SET enable_sort=TRUE;
+SET enable_groupagg=TRUE;
 SET work_mem TO DEFAULT;
 -- Compare results
 (SELECT * FROM distinct_hash_1 EXCEPT SELECT * FROM distinct_group_1)
diff --git a/src/test/regress/expected/subselect.out b/src/test/regress/expected/subselect.out
index a3778c23c34..a75c4ef3d3f 100644
--- a/src/test/regress/expected/subselect.out
+++ b/src/test/regress/expected/subselect.out
@@ -1454,7 +1454,7 @@ where o.ten = 0;
 -- Test rescan of a hashed SetOp node
 --
 begin;
-set local enable_sort = off;
+set local enable_groupagg = off;
 explain (costs off)
 select count(*) from
   onek o cross join lateral (
diff --git a/src/test/regress/expected/sysviews.out b/src/test/regress/expected/sysviews.out
index 132b56a5864..1e327c2afa4 100644
--- a/src/test/regress/expected/sysviews.out
+++ b/src/test/regress/expected/sysviews.out
@@ -161,6 +161,7 @@ select name, setting from pg_settings where name like 'enable%';
  enable_eager_aggregate         | on
  enable_gathermerge             | on
  enable_group_by_reordering     | on
+ enable_groupagg                | on
  enable_hashagg                 | on
  enable_hashjoin                | on
  enable_incremental_sort        | on
@@ -180,7 +181,7 @@ select name, setting from pg_settings where name like 'enable%';
  enable_seqscan                 | on
  enable_sort                    | on
  enable_tidscan                 | on
-(25 rows)
+(26 rows)
 
 -- There are always wait event descriptions for various types.  InjectionPoint
 -- may be present or absent, depending on history since last postmaster start.
diff --git a/src/test/regress/expected/union.out b/src/test/regress/expected/union.out
index 3a49b354058..62065953a84 100644
--- a/src/test/regress/expected/union.out
+++ b/src/test/regress/expected/union.out
@@ -386,14 +386,14 @@ select count(*) from
 (1 row)
 
 -- this query will prefer a sorted setop unless we force it.
-set enable_indexscan to off;
+set enable_groupagg to off;
 explain (costs off)
 select unique1 from tenk1 except select unique2 from tenk1 where unique2 != 10;
-           QUERY PLAN            
----------------------------------
+                         QUERY PLAN                         
+------------------------------------------------------------
  HashSetOp Except
-   ->  Seq Scan on tenk1
-   ->  Seq Scan on tenk1 tenk1_1
+   ->  Index Only Scan using tenk1_unique1 on tenk1
+   ->  Index Only Scan using tenk1_unique2 on tenk1 tenk1_1
          Filter: (unique2 <> 10)
 (4 rows)
 
@@ -403,7 +403,7 @@ select unique1 from tenk1 except select unique2 from tenk1 where unique2 != 10;
       10
 (1 row)
 
-reset enable_indexscan;
+reset enable_groupagg;
 -- the hashed implementation is sensitive to child plans' tuple slot types
 explain (costs off)
 select * from int8_tbl intersect select q2, q1 from int8_tbl order by 1, 2;
@@ -981,19 +981,30 @@ select except select;
 
 -- check hashed implementation
 set enable_hashagg = true;
-set enable_sort = false;
--- We've no way to check hashed UNION as the empty pathkeys in the Append are
--- fine to make use of Unique, which is cheaper than HashAggregate and we've
--- no means to disable Unique.
+set enable_groupagg = false;
+explain (costs off)
+select from generate_series(1,5) union select from generate_series(1,3);
+                           QUERY PLAN                           
+----------------------------------------------------------------
+ HashAggregate
+   ->  Append
+         ->  Function Scan on generate_series
+         ->  Function Scan on generate_series generate_series_1
+(4 rows)
+
 explain (costs off)
 select from generate_series(1,5) intersect select from generate_series(1,3);
                         QUERY PLAN                        
 ----------------------------------------------------------
- SetOp Intersect
+ HashSetOp Intersect
    ->  Function Scan on generate_series
    ->  Function Scan on generate_series generate_series_1
 (3 rows)
 
+select from generate_series(1,5) union select from generate_series(1,3);
+--
+(1 row)
+
 select from generate_series(1,5) union all select from generate_series(1,3);
 --
 (8 rows)
@@ -1016,7 +1027,7 @@ select from generate_series(1,5) except all select from generate_series(1,3);
 
 -- check sorted implementation
 set enable_hashagg = false;
-set enable_sort = true;
+set enable_groupagg = true;
 explain (costs off)
 select from generate_series(1,5) union select from generate_series(1,3);
                            QUERY PLAN                           
@@ -1075,7 +1086,7 @@ select from cte union select from cte;
 (1 row)
 
 reset enable_hashagg;
-reset enable_sort;
+reset enable_groupagg;
 --
 -- Check handling of a case with unknown constants.  We don't guarantee
 -- an undecorated constant will work in all cases, but historically this
diff --git a/src/test/regress/sql/aggregates.sql b/src/test/regress/sql/aggregates.sql
index 916383db927..b66dad5660e 100644
--- a/src/test/regress/sql/aggregates.sql
+++ b/src/test/regress/sql/aggregates.sql
@@ -493,7 +493,7 @@ drop table minmaxtest cascade;
 
 -- DISTINCT can also trigger wrong answers with hash aggregation (bug #18465)
 begin;
-set local enable_sort = off;
+set local enable_groupagg = off;
 explain (costs off)
   select f1, (select distinct min(t1.f1) from int4_tbl t1 where t1.f1 = t0.f1)
   from int4_tbl t0;
@@ -1672,7 +1672,7 @@ select v||'a', case when v||'a' = 'aa' then 1 else 0 end, count(*)
 -- Hash Aggregation Spill tests
 --
 
-set enable_sort=false;
+set enable_groupagg=false;
 set work_mem='64kB';
 
 select unique1, count(*), sum(twothousand) from tenk1
@@ -1681,7 +1681,7 @@ having sum(fivethous) > 4975
 order by sum(twothousand);
 
 set work_mem to default;
-set enable_sort to default;
+set enable_groupagg to default;
 
 --
 -- Compare results between plans using sorting and plans using hash
@@ -1736,7 +1736,7 @@ select (g/2)::numeric as c1, array_agg(g::numeric) as c2, count(*) as c3
 -- Produce results with hash aggregation
 
 set enable_hashagg = true;
-set enable_sort = false;
+set enable_groupagg = false;
 
 set jit_above_cost = 0;
 
@@ -1769,7 +1769,7 @@ create table agg_hash_4 as
 select (g/2)::numeric as c1, array_agg(g::numeric) as c2, count(*) as c3
   from agg_data_2k group by g/2;
 
-set enable_sort = true;
+set enable_groupagg = true;
 set work_mem to default;
 
 -- Compare group aggregation results to hash aggregation results
diff --git a/src/test/regress/sql/groupingsets.sql b/src/test/regress/sql/groupingsets.sql
index a594449b697..c5d4ed2eb57 100644
--- a/src/test/regress/sql/groupingsets.sql
+++ b/src/test/regress/sql/groupingsets.sql
@@ -583,7 +583,7 @@ update pg_class set reltuples = 10 where relname='bug_16784';
 insert into bug_16784 select g/10, g from generate_series(1,40) g;
 
 set work_mem='64kB';
-set enable_sort = false;
+set enable_groupagg = false;
 
 select * from
   (values (1),(2)) v(a),
@@ -609,7 +609,7 @@ set work_mem='64kB';
 
 -- Produce results with sorting.
 
-set enable_sort = true;
+set enable_groupagg = true;
 set enable_hashagg = false;
 set jit_above_cost = 0;
 
@@ -624,7 +624,7 @@ from gs_data_1 group by cube (g1000, g100,g10);
 -- Produce results with hash aggregation.
 
 set enable_hashagg = true;
-set enable_sort = false;
+set enable_groupagg = false;
 
 explain (costs off)
 select g100, g10, sum(g::numeric), count(*), max(g::text)
@@ -634,7 +634,7 @@ create table gs_hash_1 as
 select g100, g10, sum(g::numeric), count(*), max(g::text)
 from gs_data_1 group by cube (g1000, g100,g10);
 
-set enable_sort = true;
+set enable_groupagg = true;
 set work_mem to default;
 set hash_mem_multiplier to default;
 
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index d28ed1c1e85..d4e8d7d8c9e 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -927,13 +927,13 @@ SELECT count(distinct j) FROM testjsonb;
 SET enable_hashagg = off;
 SELECT count(*) FROM (SELECT j FROM (SELECT * FROM testjsonb UNION ALL SELECT * FROM testjsonb) js GROUP BY j) js2;
 SET enable_hashagg = on;
-SET enable_sort = off;
+SET enable_groupagg = off;
 SELECT count(*) FROM (SELECT j FROM (SELECT * FROM testjsonb UNION ALL SELECT * FROM testjsonb) js GROUP BY j) js2;
 SELECT distinct * FROM (values (jsonb '{}' || ''::text),('{}')) v(j);
-SET enable_sort = on;
+SET enable_groupagg = on;
 
 RESET enable_hashagg;
-RESET enable_sort;
+RESET enable_groupagg;
 
 DROP INDEX jidx;
 DROP INDEX jidx_array;
diff --git a/src/test/regress/sql/multirangetypes.sql b/src/test/regress/sql/multirangetypes.sql
index ddff722b28c..cf0fff6fddd 100644
--- a/src/test/regress/sql/multirangetypes.sql
+++ b/src/test/regress/sql/multirangetypes.sql
@@ -862,11 +862,11 @@ drop type two_ints cascade;
 -- Check behavior when subtype lacks a hash function
 --
 
-set enable_sort = off;  -- try to make it pick a hash setop implementation
+set enable_groupagg = off;  -- try to make it pick a hash setop implementation
 
 select '{(01,10)}'::varbitmultirange except select '{(10,11)}'::varbitmultirange;
 
-reset enable_sort;
+reset enable_groupagg;
 
 --
 -- OUT/INOUT/TABLE functions
diff --git a/src/test/regress/sql/rangetypes.sql b/src/test/regress/sql/rangetypes.sql
index 5c4b0337b7a..dbfe0d049da 100644
--- a/src/test/regress/sql/rangetypes.sql
+++ b/src/test/regress/sql/rangetypes.sql
@@ -587,11 +587,11 @@ drop type two_ints cascade;
 
 create type varbitrange as range (subtype = varbit);
 
-set enable_sort = off;  -- try to make it pick a hash setop implementation
+set enable_groupagg = off;  -- try to make it pick a hash setop implementation
 
 select '(01,10)'::varbitrange except select '(10,11)'::varbitrange;
 
-reset enable_sort;
+reset enable_groupagg;
 
 --
 -- OUT/INOUT/TABLE functions
diff --git a/src/test/regress/sql/select_distinct.sql b/src/test/regress/sql/select_distinct.sql
index 50ac7dde396..2ed1616b098 100644
--- a/src/test/regress/sql/select_distinct.sql
+++ b/src/test/regress/sql/select_distinct.sql
@@ -81,7 +81,7 @@ SET enable_hashagg=TRUE;
 
 -- Produce results with hash aggregation.
 
-SET enable_sort=FALSE;
+SET enable_groupagg=FALSE;
 
 SET jit_above_cost=0;
 
@@ -96,7 +96,7 @@ SET jit_above_cost TO DEFAULT;
 CREATE TABLE distinct_hash_2 AS
 SELECT DISTINCT (g%1000)::text FROM generate_series(0,9999) g;
 
-SET enable_sort=TRUE;
+SET enable_groupagg=TRUE;
 
 SET work_mem TO DEFAULT;
 
diff --git a/src/test/regress/sql/subselect.sql b/src/test/regress/sql/subselect.sql
index 1a02c3f86c0..5c8e24eb6db 100644
--- a/src/test/regress/sql/subselect.sql
+++ b/src/test/regress/sql/subselect.sql
@@ -730,7 +730,7 @@ where o.ten = 0;
 -- Test rescan of a hashed SetOp node
 --
 begin;
-set local enable_sort = off;
+set local enable_groupagg = off;
 
 explain (costs off)
 select count(*) from
diff --git a/src/test/regress/sql/union.sql b/src/test/regress/sql/union.sql
index 078c858429f..f814ade45a7 100644
--- a/src/test/regress/sql/union.sql
+++ b/src/test/regress/sql/union.sql
@@ -135,13 +135,13 @@ select count(*) from
   ( select unique1 from tenk1 intersect select fivethous from tenk1 ) ss;
 
 -- this query will prefer a sorted setop unless we force it.
-set enable_indexscan to off;
+set enable_groupagg to off;
 
 explain (costs off)
 select unique1 from tenk1 except select unique2 from tenk1 where unique2 != 10;
 select unique1 from tenk1 except select unique2 from tenk1 where unique2 != 10;
 
-reset enable_indexscan;
+reset enable_groupagg;
 
 -- the hashed implementation is sensitive to child plans' tuple slot types
 explain (costs off)
@@ -321,14 +321,14 @@ select except select;
 
 -- check hashed implementation
 set enable_hashagg = true;
-set enable_sort = false;
+set enable_groupagg = false;
 
--- We've no way to check hashed UNION as the empty pathkeys in the Append are
--- fine to make use of Unique, which is cheaper than HashAggregate and we've
--- no means to disable Unique.
+explain (costs off)
+select from generate_series(1,5) union select from generate_series(1,3);
 explain (costs off)
 select from generate_series(1,5) intersect select from generate_series(1,3);
 
+select from generate_series(1,5) union select from generate_series(1,3);
 select from generate_series(1,5) union all select from generate_series(1,3);
 select from generate_series(1,5) intersect select from generate_series(1,3);
 select from generate_series(1,5) intersect all select from generate_series(1,3);
@@ -337,7 +337,7 @@ select from generate_series(1,5) except all select from generate_series(1,3);
 
 -- check sorted implementation
 set enable_hashagg = false;
-set enable_sort = true;
+set enable_groupagg = true;
 
 explain (costs off)
 select from generate_series(1,5) union select from generate_series(1,3);
@@ -363,7 +363,7 @@ with cte as not materialized (select s from generate_series(1,5) s)
 select from cte union select from cte;
 
 reset enable_hashagg;
-reset enable_sort;
+reset enable_groupagg;
 
 --
 -- Check handling of a case with unknown constants.  We don't guarantee
-- 
2.39.5 (Apple Git-154)

