From a26f6f8c1898cf212b30c33c10dfceeedd474c2c Mon Sep 17 00:00:00 2001
From: Sergey Soloviev <sergey.soloviev@tantorlabs.ru>
Date: Thu, 11 Dec 2025 16:06:01 +0300
Subject: [PATCH v4 5/5] fix tests for IndexAggregate

After adding IndexAggregate node some test output changed and tests
broke. This patch updates expected output.

Also it adds some IndexAggregate specific tests into aggregates.sql and
partition_aggregate.sql.
---
 .../postgres_fdw/expected/postgres_fdw.out    |  39 +-
 src/test/regress/expected/aggregates.out      | 291 +++++++++-
 .../regress/expected/collate.icu.utf8.out     |  16 +-
 src/test/regress/expected/eager_aggregate.out | 539 ++++++++++--------
 src/test/regress/expected/join.out            |  31 +-
 .../regress/expected/partition_aggregate.out  | 361 ++++++++----
 src/test/regress/expected/select_parallel.out |  27 +-
 src/test/regress/expected/sysviews.out        |   3 +-
 src/test/regress/sql/aggregates.sql           | 147 ++++-
 src/test/regress/sql/eager_aggregate.sql      |  41 ++
 src/test/regress/sql/join.sql                 |   2 +
 src/test/regress/sql/partition_aggregate.sql  |  31 +-
 src/test/regress/sql/select_parallel.sql      |   3 +
 13 files changed, 1096 insertions(+), 435 deletions(-)

diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index 6066510c7c0..0a03140a80b 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -3701,33 +3701,30 @@ select count(t1.c3) from ft2 t1 left join ft2 t2 on (t1.c1 = random() * t2.c2);
 -- Subquery in FROM clause having aggregate
 explain (verbose, costs off)
 select count(*), x.b from ft1, (select c2 a, sum(c1) b from ft1 group by c2) x where ft1.c2 = x.a group by x.b order by 1, 2;
-                                       QUERY PLAN                                        
------------------------------------------------------------------------------------------
+                                    QUERY PLAN                                     
+-----------------------------------------------------------------------------------
  Sort
    Output: (count(*)), (sum(ft1_1.c1))
    Sort Key: (count(*)), (sum(ft1_1.c1))
-   ->  Finalize GroupAggregate
+   ->  Finalize IndexAggregate
          Output: count(*), (sum(ft1_1.c1))
          Group Key: (sum(ft1_1.c1))
-         ->  Sort
+         ->  Hash Join
                Output: (sum(ft1_1.c1)), (PARTIAL count(*))
-               Sort Key: (sum(ft1_1.c1))
-               ->  Hash Join
-                     Output: (sum(ft1_1.c1)), (PARTIAL count(*))
-                     Hash Cond: (ft1_1.c2 = ft1.c2)
-                     ->  Foreign Scan
-                           Output: ft1_1.c2, (sum(ft1_1.c1))
-                           Relations: Aggregate on (public.ft1 ft1_1)
-                           Remote SQL: SELECT c2, sum("C 1") FROM "S 1"."T 1" GROUP BY 1
-                     ->  Hash
-                           Output: ft1.c2, (PARTIAL count(*))
-                           ->  Partial HashAggregate
-                                 Output: ft1.c2, PARTIAL count(*)
-                                 Group Key: ft1.c2
-                                 ->  Foreign Scan on public.ft1
-                                       Output: ft1.c2
-                                       Remote SQL: SELECT c2 FROM "S 1"."T 1"
-(24 rows)
+               Hash Cond: (ft1_1.c2 = ft1.c2)
+               ->  Foreign Scan
+                     Output: ft1_1.c2, (sum(ft1_1.c1))
+                     Relations: Aggregate on (public.ft1 ft1_1)
+                     Remote SQL: SELECT c2, sum("C 1") FROM "S 1"."T 1" GROUP BY 1
+               ->  Hash
+                     Output: ft1.c2, (PARTIAL count(*))
+                     ->  Partial HashAggregate
+                           Output: ft1.c2, PARTIAL count(*)
+                           Group Key: ft1.c2
+                           ->  Foreign Scan on public.ft1
+                                 Output: ft1.c2
+                                 Remote SQL: SELECT c2 FROM "S 1"."T 1"
+(21 rows)
 
 select count(*), x.b from ft1, (select c2 a, sum(c1) b from ft1 group by c2) x where ft1.c2 = x.a group by x.b order by 1, 2;
  count |   b   
diff --git a/src/test/regress/expected/aggregates.out b/src/test/regress/expected/aggregates.out
index cae8e7bca31..afe01f5da85 100644
--- a/src/test/regress/expected/aggregates.out
+++ b/src/test/regress/expected/aggregates.out
@@ -1533,7 +1533,7 @@ explain (costs off) select * from t1 group by a,b,c,d;
 explain (costs off) select * from only t1 group by a,b,c,d;
       QUERY PLAN      
 ----------------------
- HashAggregate
+ IndexAggregate
    Group Key: a, b
    ->  Seq Scan on t1
 (3 rows)
@@ -3270,6 +3270,7 @@ FROM generate_series(1, 100) AS i;
 CREATE INDEX btg_x_y_idx ON btg(x, y);
 ANALYZE btg;
 SET enable_hashagg = off;
+SET enable_indexagg = off;
 SET enable_seqscan = off;
 -- Utilize the ordering of index scan to avoid a Sort operation
 EXPLAIN (COSTS OFF)
@@ -3707,10 +3708,242 @@ select v||'a', case when v||'a' = 'aa' then 1 else 0 end, count(*)
  ba       |    0 |     1
 (2 rows)
 
+ 
+--
+-- Index Aggregation tests
+--
+set enable_hashagg = false;
+set enable_sort = false;
+set enable_indexagg = true;
+set enable_indexscan = false;
+-- require ordered output
+EXPLAIN (COSTS OFF, VERBOSE)
+SELECT unique1, SUM(two) FROM tenk1
+GROUP BY 1
+ORDER BY 1
+LIMIT 10;
+                                                                         QUERY PLAN                                                                          
+-------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Limit
+   Output: unique1, (sum(two))
+   ->  IndexAggregate
+         Output: unique1, sum(two)
+         Group Key: tenk1.unique1
+         ->  Seq Scan on public.tenk1
+               Output: unique1, unique2, two, four, ten, twenty, hundred, thousand, twothousand, fivethous, tenthous, odd, even, stringu1, stringu2, string4
+(7 rows)
+
+SELECT unique1, SUM(two) FROM tenk1
+GROUP BY 1
+ORDER BY 1
+LIMIT 10;
+ unique1 | sum 
+---------+-----
+       0 |   0
+       1 |   1
+       2 |   0
+       3 |   1
+       4 |   0
+       5 |   1
+       6 |   0
+       7 |   1
+       8 |   0
+       9 |   1
+(10 rows)
+
+EXPLAIN (COSTS OFF, VERBOSE)
+SELECT even, sum(two) FROM tenk1
+GROUP BY 1
+ORDER BY 1
+LIMIT 10;
+                                                                         QUERY PLAN                                                                          
+-------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Limit
+   Output: even, (sum(two))
+   ->  IndexAggregate
+         Output: even, sum(two)
+         Group Key: tenk1.even
+         ->  Seq Scan on public.tenk1
+               Output: unique1, unique2, two, four, ten, twenty, hundred, thousand, twothousand, fivethous, tenthous, odd, even, stringu1, stringu2, string4
+(7 rows)
+
+SELECT even, sum(two) FROM tenk1
+GROUP BY 1
+ORDER BY 1
+LIMIT 10;
+ even | sum 
+------+-----
+    1 |   0
+    3 | 100
+    5 |   0
+    7 | 100
+    9 |   0
+   11 | 100
+   13 |   0
+   15 | 100
+   17 |   0
+   19 | 100
+(10 rows)
+
+-- multiple grouping columns
+EXPLAIN (COSTS OFF, VERBOSE)
+SELECT even, odd, sum(unique1) FROM tenk1
+GROUP BY 1, 2
+ORDER BY 1, 2
+LIMIT 10;
+                                                                         QUERY PLAN                                                                          
+-------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Limit
+   Output: even, odd, (sum(unique1))
+   ->  IndexAggregate
+         Output: even, odd, sum(unique1)
+         Group Key: tenk1.even, tenk1.odd
+         ->  Seq Scan on public.tenk1
+               Output: unique1, unique2, two, four, ten, twenty, hundred, thousand, twothousand, fivethous, tenthous, odd, even, stringu1, stringu2, string4
+(7 rows)
+
+SELECT even, odd, sum(unique1) FROM tenk1
+GROUP BY 1, 2
+ORDER BY 1, 2
+LIMIT 10;
+ even | odd |  sum   
+------+-----+--------
+    1 |   0 | 495000
+    3 |   2 | 495100
+    5 |   4 | 495200
+    7 |   6 | 495300
+    9 |   8 | 495400
+   11 |  10 | 495500
+   13 |  12 | 495600
+   15 |  14 | 495700
+   17 |  16 | 495800
+   19 |  18 | 495900
+(10 rows)
+
+-- mixing columns between group by and order by
+begin;
+create temp table tmp(x int, y int);
+insert into tmp values (1, 8), (2, 7), (3, 6), (4, 5);
+EXPLAIN (COSTS OFF, VERBOSE)
+SELECT x, y, sum(x) FROM tmp
+GROUP BY 1, 2
+ORDER BY 1, 2;
+          QUERY PLAN           
+-------------------------------
+ IndexAggregate
+   Output: x, y, sum(x)
+   Group Key: tmp.x, tmp.y
+   ->  Seq Scan on pg_temp.tmp
+         Output: x, y
+(5 rows)
+
+SELECT x, y, sum(x) FROM tmp
+GROUP BY 1, 2
+ORDER BY 1, 2;
+ x | y | sum 
+---+---+-----
+ 1 | 8 |   1
+ 2 | 7 |   2
+ 3 | 6 |   3
+ 4 | 5 |   4
+(4 rows)
+
+EXPLAIN (COSTS OFF, VERBOSE)
+SELECT x, y, sum(x) FROM tmp
+GROUP BY 1, 2
+ORDER BY 2, 1;
+          QUERY PLAN           
+-------------------------------
+ IndexAggregate
+   Output: x, y, sum(x)
+   Group Key: tmp.y, tmp.x
+   ->  Seq Scan on pg_temp.tmp
+         Output: x, y
+(5 rows)
+
+SELECT x, y, sum(x) FROM tmp
+GROUP BY 1, 2
+ORDER BY 2, 1;
+ x | y | sum 
+---+---+-----
+ 4 | 5 |   4
+ 3 | 6 |   3
+ 2 | 7 |   2
+ 1 | 8 |   1
+(4 rows)
+
+--
+-- Index Aggregation Spill tests
+--
+set enable_indexagg = true;
+set enable_sort=false;
+set enable_hashagg = false;
+set work_mem='64kB';
+select unique1, count(*), sum(twothousand) from tenk1
+group by unique1
+having sum(fivethous) > 4975
+order by sum(twothousand);
+ unique1 | count | sum  
+---------+-------+------
+    4976 |     1 |  976
+    4977 |     1 |  977
+    4978 |     1 |  978
+    4979 |     1 |  979
+    4980 |     1 |  980
+    4981 |     1 |  981
+    4982 |     1 |  982
+    4983 |     1 |  983
+    4984 |     1 |  984
+    4985 |     1 |  985
+    4986 |     1 |  986
+    4987 |     1 |  987
+    4988 |     1 |  988
+    4989 |     1 |  989
+    4990 |     1 |  990
+    4991 |     1 |  991
+    4992 |     1 |  992
+    4993 |     1 |  993
+    4994 |     1 |  994
+    4995 |     1 |  995
+    4996 |     1 |  996
+    4997 |     1 |  997
+    4998 |     1 |  998
+    4999 |     1 |  999
+    9976 |     1 | 1976
+    9977 |     1 | 1977
+    9978 |     1 | 1978
+    9979 |     1 | 1979
+    9980 |     1 | 1980
+    9981 |     1 | 1981
+    9982 |     1 | 1982
+    9983 |     1 | 1983
+    9984 |     1 | 1984
+    9985 |     1 | 1985
+    9986 |     1 | 1986
+    9987 |     1 | 1987
+    9988 |     1 | 1988
+    9989 |     1 | 1989
+    9990 |     1 | 1990
+    9991 |     1 | 1991
+    9992 |     1 | 1992
+    9993 |     1 | 1993
+    9994 |     1 | 1994
+    9995 |     1 | 1995
+    9996 |     1 | 1996
+    9997 |     1 | 1997
+    9998 |     1 | 1998
+    9999 |     1 | 1999
+(48 rows)
+
+set work_mem to default;
+set enable_sort to default;
+set enable_hashagg to default;
+set enable_indexagg to default;
 --
 -- Hash Aggregation Spill tests
 --
 set enable_sort=false;
+set enable_indexagg = false;
 set work_mem='64kB';
 select unique1, count(*), sum(twothousand) from tenk1
 group by unique1
@@ -3783,6 +4016,7 @@ select g from generate_series(0, 19999) g;
 analyze agg_data_20k;
 -- Produce results with sorting.
 set enable_hashagg = false;
+set enable_indexagg = false;
 set jit_above_cost = 0;
 explain (costs off)
 select g%10000 as c1, sum(g::numeric) as c2, count(*) as c3
@@ -3852,31 +4086,74 @@ 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 work_mem to default;
+-- Produce results with index aggregation
+set enable_sort = false;
+set enable_hashagg = false;
+set enable_indexagg = true;
+set jit_above_cost = 0;
+explain (costs off)
+select g%10000 as c1, sum(g::numeric) as c2, count(*) as c3
+  from agg_data_20k group by g%10000;
+           QUERY PLAN           
+--------------------------------
+ IndexAggregate
+   Group Key: (g % 10000)
+   ->  Seq Scan on agg_data_20k
+(3 rows)
+
+create table agg_index_1 as
+select g%10000 as c1, sum(g::numeric) as c2, count(*) as c3
+  from agg_data_20k group by g%10000;
+create table agg_index_2 as
+select * from
+  (values (100), (300), (500)) as r(a),
+  lateral (
+    select (g/2)::numeric as c1,
+           array_agg(g::numeric) as c2,
+	   count(*) as c3
+    from agg_data_2k
+    where g < r.a
+    group by g/2) as s;
+set jit_above_cost to default;
+create table agg_index_3 as
+select (g/2)::numeric as c1, sum(7::int4) as c2, count(*) as c3
+  from agg_data_2k group by g/2;
+create table agg_index_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;
 -- Compare group aggregation results to hash aggregation results
 (select * from agg_hash_1 except select * from agg_group_1)
   union all
-(select * from agg_group_1 except select * from agg_hash_1);
+(select * from agg_group_1 except select * from agg_hash_1)
+  union all
+(select * from agg_index_1 except select * from agg_group_1);
  c1 | c2 | c3 
 ----+----+----
 (0 rows)
 
 (select * from agg_hash_2 except select * from agg_group_2)
   union all
-(select * from agg_group_2 except select * from agg_hash_2);
+(select * from agg_group_2 except select * from agg_hash_2)
+  union all
+(select * from agg_index_2 except select * from agg_group_2);
  a | c1 | c2 | c3 
 ---+----+----+----
 (0 rows)
 
 (select * from agg_hash_3 except select * from agg_group_3)
   union all
-(select * from agg_group_3 except select * from agg_hash_3);
+(select * from agg_group_3 except select * from agg_hash_3)
+  union all
+(select * from agg_index_3 except select * from agg_group_3);
  c1 | c2 | c3 
 ----+----+----
 (0 rows)
 
 (select * from agg_hash_4 except select * from agg_group_4)
   union all
-(select * from agg_group_4 except select * from agg_hash_4);
+(select * from agg_group_4 except select * from agg_hash_4)
+  union all
+(select * from agg_index_4 except select * from agg_group_4);
  c1 | c2 | c3 
 ----+----+----
 (0 rows)
@@ -3889,3 +4166,7 @@ drop table agg_hash_1;
 drop table agg_hash_2;
 drop table agg_hash_3;
 drop table agg_hash_4;
+drop table agg_index_1;
+drop table agg_index_2;
+drop table agg_index_3;
+drop table agg_index_4;
diff --git a/src/test/regress/expected/collate.icu.utf8.out b/src/test/regress/expected/collate.icu.utf8.out
index 8023014fe63..c62e312175c 100644
--- a/src/test/regress/expected/collate.icu.utf8.out
+++ b/src/test/regress/expected/collate.icu.utf8.out
@@ -2395,8 +2395,8 @@ SELECT upper(c collate case_insensitive), count(c) FROM pagg_tab3 GROUP BY c col
 --------------------------------------------------------------
  Sort
    Sort Key: (upper(pagg_tab3.c)) COLLATE case_insensitive
-   ->  Finalize HashAggregate
-         Group Key: pagg_tab3.c
+   ->  Finalize IndexAggregate
+         Group Key: pagg_tab3.c COLLATE case_insensitive
          ->  Append
                ->  Partial HashAggregate
                      Group Key: pagg_tab3.c
@@ -2613,20 +2613,20 @@ INSERT INTO pagg_tab6 (b, c) SELECT substr('cdCD', (i % 4) + 1 , 1), substr('cdC
 ANALYZE pagg_tab6;
 EXPLAIN (COSTS OFF)
 SELECT t1.c, count(t2.c) FROM pagg_tab5 t1 JOIN pagg_tab6 t2 ON t1.c = t2.c AND t1.c = t2.b GROUP BY 1 ORDER BY t1.c COLLATE "C";
-                      QUERY PLAN                       
--------------------------------------------------------
+                        QUERY PLAN                        
+----------------------------------------------------------
  Sort
    Sort Key: t1.c COLLATE "C"
    ->  Append
-         ->  HashAggregate
-               Group Key: t1.c
+         ->  IndexAggregate
+               Group Key: t1.c COLLATE case_insensitive
                ->  Nested Loop
                      Join Filter: (t1.c = t2.c)
                      ->  Seq Scan on pagg_tab6_p1 t2
                            Filter: (c = b)
                      ->  Seq Scan on pagg_tab5_p1 t1
-         ->  HashAggregate
-               Group Key: t1_1.c
+         ->  IndexAggregate
+               Group Key: t1_1.c COLLATE case_insensitive
                ->  Nested Loop
                      Join Filter: (t1_1.c = t2_1.c)
                      ->  Seq Scan on pagg_tab6_p2 t2_1
diff --git a/src/test/regress/expected/eager_aggregate.out b/src/test/regress/expected/eager_aggregate.out
index 5ac966186f7..0d4468fa686 100644
--- a/src/test/regress/expected/eager_aggregate.out
+++ b/src/test/regress/expected/eager_aggregate.out
@@ -21,27 +21,24 @@ SELECT t1.a, avg(t2.c)
   FROM eager_agg_t1 t1
   JOIN eager_agg_t2 t2 ON t1.b = t2.b
 GROUP BY t1.a ORDER BY t1.a;
-                            QUERY PLAN                            
-------------------------------------------------------------------
- Finalize GroupAggregate
+                         QUERY PLAN                         
+------------------------------------------------------------
+ Finalize IndexAggregate
    Output: t1.a, avg(t2.c)
    Group Key: t1.a
-   ->  Sort
+   ->  Hash Join
          Output: t1.a, (PARTIAL avg(t2.c))
-         Sort Key: t1.a
-         ->  Hash Join
-               Output: t1.a, (PARTIAL avg(t2.c))
-               Hash Cond: (t1.b = t2.b)
-               ->  Seq Scan on public.eager_agg_t1 t1
-                     Output: t1.a, t1.b, t1.c
-               ->  Hash
-                     Output: t2.b, (PARTIAL avg(t2.c))
-                     ->  Partial HashAggregate
-                           Output: t2.b, PARTIAL avg(t2.c)
-                           Group Key: t2.b
-                           ->  Seq Scan on public.eager_agg_t2 t2
-                                 Output: t2.a, t2.b, t2.c
-(18 rows)
+         Hash Cond: (t1.b = t2.b)
+         ->  Seq Scan on public.eager_agg_t1 t1
+               Output: t1.a, t1.b, t1.c
+         ->  Hash
+               Output: t2.b, (PARTIAL avg(t2.c))
+               ->  Partial HashAggregate
+                     Output: t2.b, PARTIAL avg(t2.c)
+                     Group Key: t2.b
+                     ->  Seq Scan on public.eager_agg_t2 t2
+                           Output: t2.a, t2.b, t2.c
+(15 rows)
 
 SELECT t1.a, avg(t2.c)
   FROM eager_agg_t1 t1
@@ -62,6 +59,7 @@ GROUP BY t1.a ORDER BY t1.a;
 
 -- Produce results with sorting aggregation
 SET enable_hashagg TO off;
+SET enable_indexagg TO off;
 EXPLAIN (VERBOSE, COSTS OFF)
 SELECT t1.a, avg(t2.c)
   FROM eager_agg_t1 t1
@@ -110,6 +108,53 @@ GROUP BY t1.a ORDER BY t1.a;
 (9 rows)
 
 RESET enable_hashagg;
+RESET enable_indexagg;
+-- Produce results with index aggregation
+SET enable_hashagg TO off;
+SET enable_sort TO off;
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.a, avg(t2.c)
+  FROM eager_agg_t1 t1
+  JOIN eager_agg_t2 t2 ON t1.b = t2.b
+GROUP BY t1.a ORDER BY t1.a;
+                         QUERY PLAN                         
+------------------------------------------------------------
+ Finalize IndexAggregate
+   Output: t1.a, avg(t2.c)
+   Group Key: t1.a
+   ->  Hash Join
+         Output: t1.a, (PARTIAL avg(t2.c))
+         Hash Cond: (t1.b = t2.b)
+         ->  Seq Scan on public.eager_agg_t1 t1
+               Output: t1.a, t1.b, t1.c
+         ->  Hash
+               Output: t2.b, (PARTIAL avg(t2.c))
+               ->  Partial IndexAggregate
+                     Output: t2.b, PARTIAL avg(t2.c)
+                     Group Key: t2.b
+                     ->  Seq Scan on public.eager_agg_t2 t2
+                           Output: t2.a, t2.b, t2.c
+(15 rows)
+
+SELECT t1.a, avg(t2.c)
+  FROM eager_agg_t1 t1
+  JOIN eager_agg_t2 t2 ON t1.b = t2.b
+GROUP BY t1.a ORDER BY t1.a;
+ a | avg 
+---+-----
+ 1 | 496
+ 2 | 497
+ 3 | 498
+ 4 | 499
+ 5 | 500
+ 6 | 501
+ 7 | 502
+ 8 | 503
+ 9 | 504
+(9 rows)
+
+RESET enable_hashagg;
+RESET enable_sort;
 --
 -- Test eager aggregation over join rel
 --
@@ -121,34 +166,31 @@ SELECT t1.a, avg(t2.c + t3.c)
   JOIN eager_agg_t2 t2 ON t1.b = t2.b
   JOIN eager_agg_t3 t3 ON t2.a = t3.a
 GROUP BY t1.a ORDER BY t1.a;
-                                  QUERY PLAN                                  
-------------------------------------------------------------------------------
- Finalize GroupAggregate
+                               QUERY PLAN                               
+------------------------------------------------------------------------
+ Finalize IndexAggregate
    Output: t1.a, avg((t2.c + t3.c))
    Group Key: t1.a
-   ->  Sort
+   ->  Hash Join
          Output: t1.a, (PARTIAL avg((t2.c + t3.c)))
-         Sort Key: t1.a
-         ->  Hash Join
-               Output: t1.a, (PARTIAL avg((t2.c + t3.c)))
-               Hash Cond: (t1.b = t2.b)
-               ->  Seq Scan on public.eager_agg_t1 t1
-                     Output: t1.a, t1.b, t1.c
-               ->  Hash
-                     Output: t2.b, (PARTIAL avg((t2.c + t3.c)))
-                     ->  Partial HashAggregate
-                           Output: t2.b, PARTIAL avg((t2.c + t3.c))
-                           Group Key: t2.b
-                           ->  Hash Join
-                                 Output: t2.c, t2.b, t3.c
-                                 Hash Cond: (t3.a = t2.a)
-                                 ->  Seq Scan on public.eager_agg_t3 t3
-                                       Output: t3.a, t3.b, t3.c
-                                 ->  Hash
+         Hash Cond: (t1.b = t2.b)
+         ->  Seq Scan on public.eager_agg_t1 t1
+               Output: t1.a, t1.b, t1.c
+         ->  Hash
+               Output: t2.b, (PARTIAL avg((t2.c + t3.c)))
+               ->  Partial HashAggregate
+                     Output: t2.b, PARTIAL avg((t2.c + t3.c))
+                     Group Key: t2.b
+                     ->  Hash Join
+                           Output: t2.c, t2.b, t3.c
+                           Hash Cond: (t3.a = t2.a)
+                           ->  Seq Scan on public.eager_agg_t3 t3
+                                 Output: t3.a, t3.b, t3.c
+                           ->  Hash
+                                 Output: t2.c, t2.b, t2.a
+                                 ->  Seq Scan on public.eager_agg_t2 t2
                                        Output: t2.c, t2.b, t2.a
-                                       ->  Seq Scan on public.eager_agg_t2 t2
-                                             Output: t2.c, t2.b, t2.a
-(25 rows)
+(22 rows)
 
 SELECT t1.a, avg(t2.c + t3.c)
   FROM eager_agg_t1 t1
@@ -170,6 +212,7 @@ GROUP BY t1.a ORDER BY t1.a;
 
 -- Produce results with sorting aggregation
 SET enable_hashagg TO off;
+SET enable_indexagg TO off;
 EXPLAIN (VERBOSE, COSTS OFF)
 SELECT t1.a, avg(t2.c + t3.c)
   FROM eager_agg_t1 t1
@@ -227,6 +270,62 @@ GROUP BY t1.a ORDER BY t1.a;
 (9 rows)
 
 RESET enable_hashagg;
+RESET enable_indexagg;
+-- Produce results with index aggregation
+SET enable_hashagg TO off;
+SET enable_sort TO off;
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.a, avg(t2.c + t3.c)
+  FROM eager_agg_t1 t1
+  JOIN eager_agg_t2 t2 ON t1.b = t2.b
+  JOIN eager_agg_t3 t3 ON t2.a = t3.a
+GROUP BY t1.a ORDER BY t1.a;
+                               QUERY PLAN                               
+------------------------------------------------------------------------
+ Finalize IndexAggregate
+   Output: t1.a, avg((t2.c + t3.c))
+   Group Key: t1.a
+   ->  Hash Join
+         Output: t1.a, (PARTIAL avg((t2.c + t3.c)))
+         Hash Cond: (t1.b = t2.b)
+         ->  Seq Scan on public.eager_agg_t1 t1
+               Output: t1.a, t1.b, t1.c
+         ->  Hash
+               Output: t2.b, (PARTIAL avg((t2.c + t3.c)))
+               ->  Partial IndexAggregate
+                     Output: t2.b, PARTIAL avg((t2.c + t3.c))
+                     Group Key: t2.b
+                     ->  Hash Join
+                           Output: t2.c, t2.b, t3.c
+                           Hash Cond: (t3.a = t2.a)
+                           ->  Seq Scan on public.eager_agg_t3 t3
+                                 Output: t3.a, t3.b, t3.c
+                           ->  Hash
+                                 Output: t2.c, t2.b, t2.a
+                                 ->  Seq Scan on public.eager_agg_t2 t2
+                                       Output: t2.c, t2.b, t2.a
+(22 rows)
+
+SELECT t1.a, avg(t2.c + t3.c)
+  FROM eager_agg_t1 t1
+  JOIN eager_agg_t2 t2 ON t1.b = t2.b
+  JOIN eager_agg_t3 t3 ON t2.a = t3.a
+GROUP BY t1.a ORDER BY t1.a;
+ a | avg 
+---+-----
+ 1 | 497
+ 2 | 499
+ 3 | 501
+ 4 | 503
+ 5 | 505
+ 6 | 507
+ 7 | 509
+ 8 | 511
+ 9 | 513
+(9 rows)
+
+RESET enable_hashagg;
+RESET enable_sort;
 --
 -- Test that eager aggregation works for outer join
 --
@@ -236,27 +335,24 @@ SELECT t1.a, avg(t2.c)
   FROM eager_agg_t1 t1
   RIGHT JOIN eager_agg_t2 t2 ON t1.b = t2.b
 GROUP BY t1.a ORDER BY t1.a;
-                            QUERY PLAN                            
-------------------------------------------------------------------
- Finalize GroupAggregate
+                         QUERY PLAN                         
+------------------------------------------------------------
+ Finalize IndexAggregate
    Output: t1.a, avg(t2.c)
    Group Key: t1.a
-   ->  Sort
+   ->  Hash Right Join
          Output: t1.a, (PARTIAL avg(t2.c))
-         Sort Key: t1.a
-         ->  Hash Right Join
-               Output: t1.a, (PARTIAL avg(t2.c))
-               Hash Cond: (t1.b = t2.b)
-               ->  Seq Scan on public.eager_agg_t1 t1
-                     Output: t1.a, t1.b, t1.c
-               ->  Hash
-                     Output: t2.b, (PARTIAL avg(t2.c))
-                     ->  Partial HashAggregate
-                           Output: t2.b, PARTIAL avg(t2.c)
-                           Group Key: t2.b
-                           ->  Seq Scan on public.eager_agg_t2 t2
-                                 Output: t2.a, t2.b, t2.c
-(18 rows)
+         Hash Cond: (t1.b = t2.b)
+         ->  Seq Scan on public.eager_agg_t1 t1
+               Output: t1.a, t1.b, t1.c
+         ->  Hash
+               Output: t2.b, (PARTIAL avg(t2.c))
+               ->  Partial HashAggregate
+                     Output: t2.b, PARTIAL avg(t2.c)
+                     Group Key: t2.b
+                     ->  Seq Scan on public.eager_agg_t2 t2
+                           Output: t2.a, t2.b, t2.c
+(15 rows)
 
 SELECT t1.a, avg(t2.c)
   FROM eager_agg_t1 t1
@@ -331,30 +427,27 @@ SELECT t1.a, avg(t2.c)
   FROM eager_agg_t1 t1
   JOIN eager_agg_t2 t2 ON t1.b = t2.b
 GROUP BY t1.a ORDER BY t1.a;
-                                   QUERY PLAN                                    
----------------------------------------------------------------------------------
- Finalize GroupAggregate
+                                QUERY PLAN                                 
+---------------------------------------------------------------------------
+ Finalize IndexAggregate
    Output: t1.a, avg(t2.c)
    Group Key: t1.a
-   ->  Gather Merge
+   ->  Gather
          Output: t1.a, (PARTIAL avg(t2.c))
          Workers Planned: 2
-         ->  Sort
+         ->  Parallel Hash Join
                Output: t1.a, (PARTIAL avg(t2.c))
-               Sort Key: t1.a
-               ->  Parallel Hash Join
-                     Output: t1.a, (PARTIAL avg(t2.c))
-                     Hash Cond: (t1.b = t2.b)
-                     ->  Parallel Seq Scan on public.eager_agg_t1 t1
-                           Output: t1.a, t1.b, t1.c
-                     ->  Parallel Hash
-                           Output: t2.b, (PARTIAL avg(t2.c))
-                           ->  Partial HashAggregate
-                                 Output: t2.b, PARTIAL avg(t2.c)
-                                 Group Key: t2.b
-                                 ->  Parallel Seq Scan on public.eager_agg_t2 t2
-                                       Output: t2.a, t2.b, t2.c
-(21 rows)
+               Hash Cond: (t1.b = t2.b)
+               ->  Parallel Seq Scan on public.eager_agg_t1 t1
+                     Output: t1.a, t1.b, t1.c
+               ->  Parallel Hash
+                     Output: t2.b, (PARTIAL avg(t2.c))
+                     ->  Partial HashAggregate
+                           Output: t2.b, PARTIAL avg(t2.c)
+                           Group Key: t2.b
+                           ->  Parallel Seq Scan on public.eager_agg_t2 t2
+                                 Output: t2.a, t2.b, t2.c
+(18 rows)
 
 SELECT t1.a, avg(t2.c)
   FROM eager_agg_t1 t1
@@ -387,27 +480,24 @@ SELECT t1.a, avg(t2.c)
   FROM eager_agg_t1 t1
   JOIN eager_agg_t2 t2 ON t1.b = t2.b
 GROUP BY t1.a ORDER BY t1.a;
-                            QUERY PLAN                            
-------------------------------------------------------------------
- Finalize GroupAggregate
+                         QUERY PLAN                         
+------------------------------------------------------------
+ Finalize IndexAggregate
    Output: t1.a, avg(t2.c)
    Group Key: t1.a
-   ->  Sort
+   ->  Hash Join
          Output: t1.a, (PARTIAL avg(t2.c))
-         Sort Key: t1.a
-         ->  Hash Join
-               Output: t1.a, (PARTIAL avg(t2.c))
-               Hash Cond: (t1.b = t2.b)
-               ->  Seq Scan on public.eager_agg_t1 t1
-                     Output: t1.a, t1.b, t1.c
-               ->  Hash
-                     Output: t2.b, (PARTIAL avg(t2.c))
-                     ->  Partial HashAggregate
-                           Output: t2.b, PARTIAL avg(t2.c)
-                           Group Key: t2.b
-                           ->  Seq Scan on public.eager_agg_t2 t2
-                                 Output: t2.a, t2.b, t2.c
-(18 rows)
+         Hash Cond: (t1.b = t2.b)
+         ->  Seq Scan on public.eager_agg_t1 t1
+               Output: t1.a, t1.b, t1.c
+         ->  Hash
+               Output: t2.b, (PARTIAL avg(t2.c))
+               ->  Partial HashAggregate
+                     Output: t2.b, PARTIAL avg(t2.c)
+                     Group Key: t2.b
+                     ->  Seq Scan on public.eager_agg_t2 t2
+                           Output: t2.a, t2.b, t2.c
+(15 rows)
 
 SELECT t1.a, avg(t2.c)
   FROM eager_agg_t1 t1
@@ -696,79 +786,77 @@ SELECT t1.x, sum(t2.y + t3.y)
   JOIN eager_agg_tab1 t2 ON t1.x = t2.x
   JOIN eager_agg_tab1 t3 ON t2.x = t3.x
 GROUP BY t1.x ORDER BY t1.x;
-                                        QUERY PLAN                                         
--------------------------------------------------------------------------------------------
- Sort
-   Output: t1.x, (sum((t2.y + t3.y)))
+                                     QUERY PLAN                                      
+-------------------------------------------------------------------------------------
+ Merge Append
    Sort Key: t1.x
-   ->  Append
-         ->  Finalize HashAggregate
-               Output: t1.x, sum((t2.y + t3.y))
-               Group Key: t1.x
-               ->  Hash Join
-                     Output: t1.x, (PARTIAL sum((t2.y + t3.y)))
-                     Hash Cond: (t1.x = t2.x)
-                     ->  Seq Scan on public.eager_agg_tab1_p1 t1
-                           Output: t1.x
-                     ->  Hash
-                           Output: t2.x, t3.x, (PARTIAL sum((t2.y + t3.y)))
-                           ->  Partial HashAggregate
-                                 Output: t2.x, t3.x, PARTIAL sum((t2.y + t3.y))
-                                 Group Key: t2.x
-                                 ->  Hash Join
-                                       Output: t2.y, t2.x, t3.y, t3.x
-                                       Hash Cond: (t2.x = t3.x)
-                                       ->  Seq Scan on public.eager_agg_tab1_p1 t2
-                                             Output: t2.y, t2.x
-                                       ->  Hash
+   ->  Finalize IndexAggregate
+         Output: t1.x, sum((t2.y + t3.y))
+         Group Key: t1.x
+         ->  Hash Join
+               Output: t1.x, (PARTIAL sum((t2.y + t3.y)))
+               Hash Cond: (t1.x = t2.x)
+               ->  Seq Scan on public.eager_agg_tab1_p1 t1
+                     Output: t1.x
+               ->  Hash
+                     Output: t2.x, t3.x, (PARTIAL sum((t2.y + t3.y)))
+                     ->  Partial HashAggregate
+                           Output: t2.x, t3.x, PARTIAL sum((t2.y + t3.y))
+                           Group Key: t2.x
+                           ->  Hash Join
+                                 Output: t2.y, t2.x, t3.y, t3.x
+                                 Hash Cond: (t2.x = t3.x)
+                                 ->  Seq Scan on public.eager_agg_tab1_p1 t2
+                                       Output: t2.y, t2.x
+                                 ->  Hash
+                                       Output: t3.y, t3.x
+                                       ->  Seq Scan on public.eager_agg_tab1_p1 t3
                                              Output: t3.y, t3.x
-                                             ->  Seq Scan on public.eager_agg_tab1_p1 t3
-                                                   Output: t3.y, t3.x
-         ->  Finalize HashAggregate
-               Output: t1_1.x, sum((t2_1.y + t3_1.y))
-               Group Key: t1_1.x
-               ->  Hash Join
-                     Output: t1_1.x, (PARTIAL sum((t2_1.y + t3_1.y)))
-                     Hash Cond: (t1_1.x = t2_1.x)
-                     ->  Seq Scan on public.eager_agg_tab1_p2 t1_1
-                           Output: t1_1.x
-                     ->  Hash
-                           Output: t2_1.x, t3_1.x, (PARTIAL sum((t2_1.y + t3_1.y)))
-                           ->  Partial HashAggregate
-                                 Output: t2_1.x, t3_1.x, PARTIAL sum((t2_1.y + t3_1.y))
-                                 Group Key: t2_1.x
-                                 ->  Hash Join
-                                       Output: t2_1.y, t2_1.x, t3_1.y, t3_1.x
-                                       Hash Cond: (t2_1.x = t3_1.x)
-                                       ->  Seq Scan on public.eager_agg_tab1_p2 t2_1
-                                             Output: t2_1.y, t2_1.x
-                                       ->  Hash
+   ->  Finalize IndexAggregate
+         Output: t1_1.x, sum((t2_1.y + t3_1.y))
+         Group Key: t1_1.x
+         ->  Hash Join
+               Output: t1_1.x, (PARTIAL sum((t2_1.y + t3_1.y)))
+               Hash Cond: (t1_1.x = t2_1.x)
+               ->  Seq Scan on public.eager_agg_tab1_p2 t1_1
+                     Output: t1_1.x
+               ->  Hash
+                     Output: t2_1.x, t3_1.x, (PARTIAL sum((t2_1.y + t3_1.y)))
+                     ->  Partial HashAggregate
+                           Output: t2_1.x, t3_1.x, PARTIAL sum((t2_1.y + t3_1.y))
+                           Group Key: t2_1.x
+                           ->  Hash Join
+                                 Output: t2_1.y, t2_1.x, t3_1.y, t3_1.x
+                                 Hash Cond: (t2_1.x = t3_1.x)
+                                 ->  Seq Scan on public.eager_agg_tab1_p2 t2_1
+                                       Output: t2_1.y, t2_1.x
+                                 ->  Hash
+                                       Output: t3_1.y, t3_1.x
+                                       ->  Seq Scan on public.eager_agg_tab1_p2 t3_1
                                              Output: t3_1.y, t3_1.x
-                                             ->  Seq Scan on public.eager_agg_tab1_p2 t3_1
-                                                   Output: t3_1.y, t3_1.x
-         ->  Finalize HashAggregate
-               Output: t1_2.x, sum((t2_2.y + t3_2.y))
-               Group Key: t1_2.x
-               ->  Hash Join
-                     Output: t1_2.x, (PARTIAL sum((t2_2.y + t3_2.y)))
-                     Hash Cond: (t1_2.x = t2_2.x)
-                     ->  Seq Scan on public.eager_agg_tab1_p3 t1_2
-                           Output: t1_2.x
-                     ->  Hash
-                           Output: t2_2.x, t3_2.x, (PARTIAL sum((t2_2.y + t3_2.y)))
-                           ->  Partial HashAggregate
-                                 Output: t2_2.x, t3_2.x, PARTIAL sum((t2_2.y + t3_2.y))
-                                 Group Key: t2_2.x
-                                 ->  Hash Join
-                                       Output: t2_2.y, t2_2.x, t3_2.y, t3_2.x
-                                       Hash Cond: (t2_2.x = t3_2.x)
-                                       ->  Seq Scan on public.eager_agg_tab1_p3 t2_2
-                                             Output: t2_2.y, t2_2.x
-                                       ->  Hash
+   ->  Finalize IndexAggregate
+         Output: t1_2.x, sum((t2_2.y + t3_2.y))
+         Group Key: t1_2.x
+         ->  Hash Join
+               Output: t1_2.x, (PARTIAL sum((t2_2.y + t3_2.y)))
+               Hash Cond: (t1_2.x = t2_2.x)
+               ->  Seq Scan on public.eager_agg_tab1_p3 t1_2
+                     Output: t1_2.x
+               ->  Hash
+                     Output: t2_2.x, t3_2.x, (PARTIAL sum((t2_2.y + t3_2.y)))
+                     ->  Partial HashAggregate
+                           Output: t2_2.x, t3_2.x, PARTIAL sum((t2_2.y + t3_2.y))
+                           Group Key: t2_2.x
+                           ->  Hash Join
+                                 Output: t2_2.y, t2_2.x, t3_2.y, t3_2.x
+                                 Hash Cond: (t2_2.x = t3_2.x)
+                                 ->  Seq Scan on public.eager_agg_tab1_p3 t2_2
+                                       Output: t2_2.y, t2_2.x
+                                 ->  Hash
+                                       Output: t3_2.y, t3_2.x
+                                       ->  Seq Scan on public.eager_agg_tab1_p3 t3_2
                                              Output: t3_2.y, t3_2.x
-                                             ->  Seq Scan on public.eager_agg_tab1_p3 t3_2
-                                                   Output: t3_2.y, t3_2.x
-(70 rows)
+(68 rows)
 
 SELECT t1.x, sum(t2.y + t3.y)
   FROM eager_agg_tab1 t1
@@ -803,97 +891,46 @@ SELECT t3.y, sum(t2.y + t3.y)
   JOIN eager_agg_tab1 t2 ON t1.x = t2.x
   JOIN eager_agg_tab1 t3 ON t2.x = t3.x
 GROUP BY t3.y ORDER BY t3.y;
-                                        QUERY PLAN                                         
--------------------------------------------------------------------------------------------
- Finalize GroupAggregate
+                                     QUERY PLAN                                      
+-------------------------------------------------------------------------------------
+ Finalize IndexAggregate
    Output: t3.y, sum((t2.y + t3.y))
    Group Key: t3.y
-   ->  Sort
+   ->  Hash Join
          Output: t3.y, (PARTIAL sum((t2.y + t3.y)))
-         Sort Key: t3.y
+         Hash Cond: (t1.x = t2.x)
          ->  Append
-               ->  Hash Join
-                     Output: t3.y, (PARTIAL sum((t2.y + t3.y)))
-                     Hash Cond: (t2.x = t1.x)
-                     ->  Partial GroupAggregate
-                           Output: t2.x, t3.y, t3.x, PARTIAL sum((t2.y + t3.y))
-                           Group Key: t2.x, t3.y, t3.x
-                           ->  Incremental Sort
-                                 Output: t2.y, t2.x, t3.y, t3.x
-                                 Sort Key: t2.x, t3.y
-                                 Presorted Key: t2.x
-                                 ->  Merge Join
-                                       Output: t2.y, t2.x, t3.y, t3.x
-                                       Merge Cond: (t2.x = t3.x)
-                                       ->  Sort
-                                             Output: t2.y, t2.x
-                                             Sort Key: t2.x
-                                             ->  Seq Scan on public.eager_agg_tab1_p1 t2
-                                                   Output: t2.y, t2.x
-                                       ->  Sort
-                                             Output: t3.y, t3.x
-                                             Sort Key: t3.x
-                                             ->  Seq Scan on public.eager_agg_tab1_p1 t3
-                                                   Output: t3.y, t3.x
-                     ->  Hash
-                           Output: t1.x
-                           ->  Seq Scan on public.eager_agg_tab1_p1 t1
-                                 Output: t1.x
-               ->  Hash Join
-                     Output: t3_1.y, (PARTIAL sum((t2_1.y + t3_1.y)))
-                     Hash Cond: (t2_1.x = t1_1.x)
-                     ->  Partial GroupAggregate
-                           Output: t2_1.x, t3_1.y, t3_1.x, PARTIAL sum((t2_1.y + t3_1.y))
-                           Group Key: t2_1.x, t3_1.y, t3_1.x
-                           ->  Incremental Sort
-                                 Output: t2_1.y, t2_1.x, t3_1.y, t3_1.x
-                                 Sort Key: t2_1.x, t3_1.y
-                                 Presorted Key: t2_1.x
-                                 ->  Merge Join
-                                       Output: t2_1.y, t2_1.x, t3_1.y, t3_1.x
-                                       Merge Cond: (t2_1.x = t3_1.x)
-                                       ->  Sort
-                                             Output: t2_1.y, t2_1.x
-                                             Sort Key: t2_1.x
-                                             ->  Seq Scan on public.eager_agg_tab1_p2 t2_1
-                                                   Output: t2_1.y, t2_1.x
-                                       ->  Sort
+               ->  Seq Scan on public.eager_agg_tab1_p1 t1_1
+                     Output: t1_1.x
+               ->  Seq Scan on public.eager_agg_tab1_p2 t1_2
+                     Output: t1_2.x
+               ->  Seq Scan on public.eager_agg_tab1_p3 t1_3
+                     Output: t1_3.x
+         ->  Hash
+               Output: t2.x, t3.y, t3.x, (PARTIAL sum((t2.y + t3.y)))
+               ->  Partial IndexAggregate
+                     Output: t2.x, t3.y, t3.x, PARTIAL sum((t2.y + t3.y))
+                     Group Key: t2.x, t3.y, t3.x
+                     ->  Hash Join
+                           Output: t2.y, t2.x, t3.y, t3.x
+                           Hash Cond: (t2.x = t3.x)
+                           ->  Append
+                                 ->  Seq Scan on public.eager_agg_tab1_p1 t2_1
+                                       Output: t2_1.y, t2_1.x
+                                 ->  Seq Scan on public.eager_agg_tab1_p2 t2_2
+                                       Output: t2_2.y, t2_2.x
+                                 ->  Seq Scan on public.eager_agg_tab1_p3 t2_3
+                                       Output: t2_3.y, t2_3.x
+                           ->  Hash
+                                 Output: t3.y, t3.x
+                                 ->  Append
+                                       ->  Seq Scan on public.eager_agg_tab1_p1 t3_1
                                              Output: t3_1.y, t3_1.x
-                                             Sort Key: t3_1.x
-                                             ->  Seq Scan on public.eager_agg_tab1_p2 t3_1
-                                                   Output: t3_1.y, t3_1.x
-                     ->  Hash
-                           Output: t1_1.x
-                           ->  Seq Scan on public.eager_agg_tab1_p2 t1_1
-                                 Output: t1_1.x
-               ->  Hash Join
-                     Output: t3_2.y, (PARTIAL sum((t2_2.y + t3_2.y)))
-                     Hash Cond: (t2_2.x = t1_2.x)
-                     ->  Partial GroupAggregate
-                           Output: t2_2.x, t3_2.y, t3_2.x, PARTIAL sum((t2_2.y + t3_2.y))
-                           Group Key: t2_2.x, t3_2.y, t3_2.x
-                           ->  Incremental Sort
-                                 Output: t2_2.y, t2_2.x, t3_2.y, t3_2.x
-                                 Sort Key: t2_2.x, t3_2.y
-                                 Presorted Key: t2_2.x
-                                 ->  Merge Join
-                                       Output: t2_2.y, t2_2.x, t3_2.y, t3_2.x
-                                       Merge Cond: (t2_2.x = t3_2.x)
-                                       ->  Sort
-                                             Output: t2_2.y, t2_2.x
-                                             Sort Key: t2_2.x
-                                             ->  Seq Scan on public.eager_agg_tab1_p3 t2_2
-                                                   Output: t2_2.y, t2_2.x
-                                       ->  Sort
+                                       ->  Seq Scan on public.eager_agg_tab1_p2 t3_2
                                              Output: t3_2.y, t3_2.x
-                                             Sort Key: t3_2.x
-                                             ->  Seq Scan on public.eager_agg_tab1_p3 t3_2
-                                                   Output: t3_2.y, t3_2.x
-                     ->  Hash
-                           Output: t1_2.x
-                           ->  Seq Scan on public.eager_agg_tab1_p3 t1_2
-                                 Output: t1_2.x
-(88 rows)
+                                       ->  Seq Scan on public.eager_agg_tab1_p3 t3_3
+                                             Output: t3_3.y, t3_3.x
+(37 rows)
 
 SELECT t3.y, sum(t2.y + t3.y)
   FROM eager_agg_tab1 t1
diff --git a/src/test/regress/expected/join.out b/src/test/regress/expected/join.out
index d05a0ca0373..57f3af295d1 100644
--- a/src/test/regress/expected/join.out
+++ b/src/test/regress/expected/join.out
@@ -2830,6 +2830,7 @@ select count(*) from
 set enable_hashjoin = 0;
 set enable_nestloop = 0;
 set enable_hashagg = 0;
+set enable_indexagg = 0;
 --
 -- Check that we use the pathkeys from a prefix of the group by / order by
 -- clause for the join pathkeys when that prefix covers all join quals.  We
@@ -2857,6 +2858,7 @@ order by x.thousand desc, x.twothousand;
                      ->  Seq Scan on tenk1 x
 (13 rows)
 
+reset enable_indexagg;
 reset enable_hashagg;
 reset enable_nestloop;
 reset enable_hashjoin;
@@ -9537,23 +9539,20 @@ inner join (select distinct id from j3) j3 on j1.id = j3.id;
 explain (verbose, costs off)
 select * from j1
 inner join (select id from j3 group by id) j3 on j1.id = j3.id;
-               QUERY PLAN                
------------------------------------------
+            QUERY PLAN             
+-----------------------------------
  Nested Loop
    Output: j1.id, j3.id
    Inner Unique: true
    Join Filter: (j1.id = j3.id)
-   ->  Group
+   ->  IndexAggregate
          Output: j3.id
          Group Key: j3.id
-         ->  Sort
+         ->  Seq Scan on public.j3
                Output: j3.id
-               Sort Key: j3.id
-               ->  Seq Scan on public.j3
-                     Output: j3.id
    ->  Seq Scan on public.j1
          Output: j1.id
-(14 rows)
+(11 rows)
 
 drop table j1;
 drop table j2;
@@ -9870,16 +9869,14 @@ EXPLAIN (COSTS OFF)
 SELECT 1 FROM group_tbl t1
     LEFT JOIN (SELECT a c1, COALESCE(a, a) c2 FROM group_tbl t2) s ON TRUE
 GROUP BY s.c1, s.c2;
-                   QUERY PLAN                   
-------------------------------------------------
- Group
+                QUERY PLAN                 
+-------------------------------------------
+ IndexAggregate
    Group Key: t2.a, (COALESCE(t2.a, t2.a))
-   ->  Sort
-         Sort Key: t2.a, (COALESCE(t2.a, t2.a))
-         ->  Nested Loop Left Join
-               ->  Seq Scan on group_tbl t1
-               ->  Seq Scan on group_tbl t2
-(7 rows)
+   ->  Nested Loop Left Join
+         ->  Seq Scan on group_tbl t1
+         ->  Seq Scan on group_tbl t2
+(5 rows)
 
 DROP TABLE group_tbl;
 -- Test that we ignore PlaceHolderVars when looking up statistics
diff --git a/src/test/regress/expected/partition_aggregate.out b/src/test/regress/expected/partition_aggregate.out
index c30304b99c7..fce941ae1f0 100644
--- a/src/test/regress/expected/partition_aggregate.out
+++ b/src/test/regress/expected/partition_aggregate.out
@@ -150,7 +150,7 @@ EXPLAIN (COSTS OFF)
 SELECT c, sum(a) FROM pagg_tab WHERE 1 = 2 GROUP BY c;
              QUERY PLAN             
 ------------------------------------
- HashAggregate
+ IndexAggregate
    Group Key: c
    ->  Result
          Replaces: Scan on pagg_tab
@@ -177,8 +177,9 @@ SELECT c, sum(a) FROM pagg_tab WHERE c = 'x' GROUP BY c;
 ---+-----
 (0 rows)
 
--- Test GroupAggregate paths by disabling hash aggregates.
+-- Test GroupAggregate paths by disabling hash and index aggregates.
 SET enable_hashagg TO false;
+SET enable_indexagg TO false;
 -- When GROUP BY clause matches full aggregation is performed for each partition.
 EXPLAIN (COSTS OFF)
 SELECT c, sum(a), avg(b), count(*) FROM pagg_tab GROUP BY 1 HAVING avg(d) < 15 ORDER BY 1, 2, 3;
@@ -370,6 +371,150 @@ SELECT count(*) FROM pagg_tab GROUP BY c ORDER BY c LIMIT 1;
    250
 (1 row)
 
+RESET enable_hashagg;
+RESET enable_indexagg;
+-- Test IndexAggregate paths by disabling hash and group aggregates.
+SET enable_sort TO false;
+SET enable_hashagg TO false;
+-- When GROUP BY clause matches full aggregation is performed for each partition.
+EXPLAIN (COSTS OFF)
+SELECT c, sum(a), avg(b), count(*) FROM pagg_tab GROUP BY 1 HAVING avg(d) < 15 ORDER BY 1, 2, 3;
+                          QUERY PLAN                          
+--------------------------------------------------------------
+ Sort
+   Disabled: true
+   Sort Key: pagg_tab.c, (sum(pagg_tab.a)), (avg(pagg_tab.b))
+   ->  Append
+         ->  IndexAggregate
+               Group Key: pagg_tab.c
+               Filter: (avg(pagg_tab.d) < '15'::numeric)
+               ->  Seq Scan on pagg_tab_p1 pagg_tab
+         ->  IndexAggregate
+               Group Key: pagg_tab_1.c
+               Filter: (avg(pagg_tab_1.d) < '15'::numeric)
+               ->  Seq Scan on pagg_tab_p2 pagg_tab_1
+         ->  IndexAggregate
+               Group Key: pagg_tab_2.c
+               Filter: (avg(pagg_tab_2.d) < '15'::numeric)
+               ->  Seq Scan on pagg_tab_p3 pagg_tab_2
+(16 rows)
+
+SELECT c, sum(a), avg(b), count(*) FROM pagg_tab GROUP BY 1 HAVING avg(d) < 15 ORDER BY 1, 2, 3;
+  c   | sum  |         avg         | count 
+------+------+---------------------+-------
+ 0000 | 2000 | 12.0000000000000000 |   250
+ 0001 | 2250 | 13.0000000000000000 |   250
+ 0002 | 2500 | 14.0000000000000000 |   250
+ 0006 | 2500 | 12.0000000000000000 |   250
+ 0007 | 2750 | 13.0000000000000000 |   250
+ 0008 | 2000 | 14.0000000000000000 |   250
+(6 rows)
+
+-- When GROUP BY clause does not match; top finalize node is required
+EXPLAIN (COSTS OFF)
+SELECT a, sum(b), avg(b), count(*) FROM pagg_tab GROUP BY 1 HAVING avg(d) < 15 ORDER BY 1, 2, 3;
+                          QUERY PLAN                          
+--------------------------------------------------------------
+ Sort
+   Disabled: true
+   Sort Key: pagg_tab.a, (sum(pagg_tab.b)), (avg(pagg_tab.b))
+   ->  Finalize GroupAggregate
+         Group Key: pagg_tab.a
+         Filter: (avg(pagg_tab.d) < '15'::numeric)
+         ->  Merge Append
+               Sort Key: pagg_tab.a
+               ->  Partial IndexAggregate
+                     Group Key: pagg_tab.a
+                     ->  Seq Scan on pagg_tab_p1 pagg_tab
+               ->  Partial IndexAggregate
+                     Group Key: pagg_tab_1.a
+                     ->  Seq Scan on pagg_tab_p2 pagg_tab_1
+               ->  Partial IndexAggregate
+                     Group Key: pagg_tab_2.a
+                     ->  Seq Scan on pagg_tab_p3 pagg_tab_2
+(17 rows)
+
+SELECT a, sum(b), avg(b), count(*) FROM pagg_tab GROUP BY 1 HAVING avg(d) < 15 ORDER BY 1, 2, 3;
+ a  | sum  |         avg         | count 
+----+------+---------------------+-------
+  0 | 1500 | 10.0000000000000000 |   150
+  1 | 1650 | 11.0000000000000000 |   150
+  2 | 1800 | 12.0000000000000000 |   150
+  3 | 1950 | 13.0000000000000000 |   150
+  4 | 2100 | 14.0000000000000000 |   150
+ 10 | 1500 | 10.0000000000000000 |   150
+ 11 | 1650 | 11.0000000000000000 |   150
+ 12 | 1800 | 12.0000000000000000 |   150
+ 13 | 1950 | 13.0000000000000000 |   150
+ 14 | 2100 | 14.0000000000000000 |   150
+(10 rows)
+
+-- Test partitionwise grouping without any aggregates
+EXPLAIN (COSTS OFF)
+SELECT c FROM pagg_tab GROUP BY c ORDER BY 1;
+                   QUERY PLAN                   
+------------------------------------------------
+ Merge Append
+   Sort Key: pagg_tab.c
+   ->  IndexAggregate
+         Group Key: pagg_tab.c
+         ->  Seq Scan on pagg_tab_p1 pagg_tab
+   ->  IndexAggregate
+         Group Key: pagg_tab_1.c
+         ->  Seq Scan on pagg_tab_p2 pagg_tab_1
+   ->  IndexAggregate
+         Group Key: pagg_tab_2.c
+         ->  Seq Scan on pagg_tab_p3 pagg_tab_2
+(11 rows)
+
+SELECT c FROM pagg_tab GROUP BY c ORDER BY 1;
+  c   
+------
+ 0000
+ 0001
+ 0002
+ 0003
+ 0004
+ 0005
+ 0006
+ 0007
+ 0008
+ 0009
+ 0010
+ 0011
+(12 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT a FROM pagg_tab WHERE a < 3 GROUP BY a ORDER BY 1;
+                      QUERY PLAN                      
+------------------------------------------------------
+ Group
+   Group Key: pagg_tab.a
+   ->  Merge Append
+         Sort Key: pagg_tab.a
+         ->  Partial IndexAggregate
+               Group Key: pagg_tab.a
+               ->  Seq Scan on pagg_tab_p1 pagg_tab
+                     Filter: (a < 3)
+         ->  Partial IndexAggregate
+               Group Key: pagg_tab_1.a
+               ->  Seq Scan on pagg_tab_p2 pagg_tab_1
+                     Filter: (a < 3)
+         ->  Partial IndexAggregate
+               Group Key: pagg_tab_2.a
+               ->  Seq Scan on pagg_tab_p3 pagg_tab_2
+                     Filter: (a < 3)
+(16 rows)
+
+SELECT a FROM pagg_tab WHERE a < 3 GROUP BY a ORDER BY 1;
+ a 
+---
+ 0
+ 1
+ 2
+(3 rows)
+
+RESET enable_sort;
 RESET enable_hashagg;
 -- ROLLUP, partitionwise aggregation does not apply
 EXPLAIN (COSTS OFF)
@@ -554,6 +699,7 @@ SELECT t2.y, sum(t1.y), count(*) FROM pagg_tab1 t1, pagg_tab2 t2 WHERE t1.x = t2
 -- When GROUP BY clause does not match; partial aggregation is performed for each partition.
 -- Also test GroupAggregate paths by disabling hash aggregates.
 SET enable_hashagg TO false;
+SET enable_indexagg TO false;
 EXPLAIN (COSTS OFF)
 SELECT t1.y, sum(t1.x), count(*) FROM pagg_tab1 t1, pagg_tab2 t2 WHERE t1.x = t2.y GROUP BY t1.y HAVING avg(t1.x) > 10 ORDER BY 1, 2, 3;
                                QUERY PLAN                                
@@ -606,41 +752,40 @@ SELECT t1.y, sum(t1.x), count(*) FROM pagg_tab1 t1, pagg_tab2 t2 WHERE t1.x = t2
 (6 rows)
 
 RESET enable_hashagg;
+RESET enable_indexagg;
 -- Check with LEFT/RIGHT/FULL OUTER JOINs which produces NULL values for
 -- aggregation
 -- LEFT JOIN, should produce partial partitionwise aggregation plan as
 -- GROUP BY is on nullable column
 EXPLAIN (COSTS OFF)
 SELECT b.y, sum(a.y) FROM pagg_tab1 a LEFT JOIN pagg_tab2 b ON a.x = b.y GROUP BY b.y ORDER BY 1 NULLS LAST;
-                            QUERY PLAN                            
-------------------------------------------------------------------
- Finalize GroupAggregate
+                         QUERY PLAN                         
+------------------------------------------------------------
+ Finalize IndexAggregate
    Group Key: b.y
-   ->  Sort
-         Sort Key: b.y
-         ->  Append
-               ->  Partial HashAggregate
-                     Group Key: b.y
-                     ->  Hash Left Join
-                           Hash Cond: (a.x = b.y)
-                           ->  Seq Scan on pagg_tab1_p1 a
-                           ->  Hash
-                                 ->  Seq Scan on pagg_tab2_p1 b
-               ->  Partial HashAggregate
-                     Group Key: b_1.y
-                     ->  Hash Left Join
-                           Hash Cond: (a_1.x = b_1.y)
-                           ->  Seq Scan on pagg_tab1_p2 a_1
-                           ->  Hash
-                                 ->  Seq Scan on pagg_tab2_p2 b_1
-               ->  Partial HashAggregate
-                     Group Key: b_2.y
-                     ->  Hash Right Join
-                           Hash Cond: (b_2.y = a_2.x)
-                           ->  Seq Scan on pagg_tab2_p3 b_2
-                           ->  Hash
-                                 ->  Seq Scan on pagg_tab1_p3 a_2
-(26 rows)
+   ->  Append
+         ->  Partial HashAggregate
+               Group Key: b.y
+               ->  Hash Left Join
+                     Hash Cond: (a.x = b.y)
+                     ->  Seq Scan on pagg_tab1_p1 a
+                     ->  Hash
+                           ->  Seq Scan on pagg_tab2_p1 b
+         ->  Partial HashAggregate
+               Group Key: b_1.y
+               ->  Hash Left Join
+                     Hash Cond: (a_1.x = b_1.y)
+                     ->  Seq Scan on pagg_tab1_p2 a_1
+                     ->  Hash
+                           ->  Seq Scan on pagg_tab2_p2 b_1
+         ->  Partial HashAggregate
+               Group Key: b_2.y
+               ->  Hash Right Join
+                     Hash Cond: (b_2.y = a_2.x)
+                     ->  Seq Scan on pagg_tab2_p3 b_2
+                     ->  Hash
+                           ->  Seq Scan on pagg_tab1_p3 a_2
+(24 rows)
 
 SELECT b.y, sum(a.y) FROM pagg_tab1 a LEFT JOIN pagg_tab2 b ON a.x = b.y GROUP BY b.y ORDER BY 1 NULLS LAST;
  y  | sum  
@@ -704,35 +849,33 @@ SELECT b.y, sum(a.y) FROM pagg_tab1 a RIGHT JOIN pagg_tab2 b ON a.x = b.y GROUP
 -- GROUP BY is on nullable column
 EXPLAIN (COSTS OFF)
 SELECT a.x, sum(b.x) FROM pagg_tab1 a FULL OUTER JOIN pagg_tab2 b ON a.x = b.y GROUP BY a.x ORDER BY 1 NULLS LAST;
-                            QUERY PLAN                            
-------------------------------------------------------------------
- Finalize GroupAggregate
+                         QUERY PLAN                         
+------------------------------------------------------------
+ Finalize IndexAggregate
    Group Key: a.x
-   ->  Sort
-         Sort Key: a.x
-         ->  Append
-               ->  Partial HashAggregate
-                     Group Key: a.x
-                     ->  Hash Full Join
-                           Hash Cond: (a.x = b.y)
-                           ->  Seq Scan on pagg_tab1_p1 a
-                           ->  Hash
-                                 ->  Seq Scan on pagg_tab2_p1 b
-               ->  Partial HashAggregate
-                     Group Key: a_1.x
-                     ->  Hash Full Join
-                           Hash Cond: (a_1.x = b_1.y)
-                           ->  Seq Scan on pagg_tab1_p2 a_1
-                           ->  Hash
-                                 ->  Seq Scan on pagg_tab2_p2 b_1
-               ->  Partial HashAggregate
-                     Group Key: a_2.x
-                     ->  Hash Full Join
-                           Hash Cond: (b_2.y = a_2.x)
-                           ->  Seq Scan on pagg_tab2_p3 b_2
-                           ->  Hash
-                                 ->  Seq Scan on pagg_tab1_p3 a_2
-(26 rows)
+   ->  Append
+         ->  Partial HashAggregate
+               Group Key: a.x
+               ->  Hash Full Join
+                     Hash Cond: (a.x = b.y)
+                     ->  Seq Scan on pagg_tab1_p1 a
+                     ->  Hash
+                           ->  Seq Scan on pagg_tab2_p1 b
+         ->  Partial HashAggregate
+               Group Key: a_1.x
+               ->  Hash Full Join
+                     Hash Cond: (a_1.x = b_1.y)
+                     ->  Seq Scan on pagg_tab1_p2 a_1
+                     ->  Hash
+                           ->  Seq Scan on pagg_tab2_p2 b_1
+         ->  Partial HashAggregate
+               Group Key: a_2.x
+               ->  Hash Full Join
+                     Hash Cond: (b_2.y = a_2.x)
+                     ->  Seq Scan on pagg_tab2_p3 b_2
+                     ->  Hash
+                           ->  Seq Scan on pagg_tab1_p3 a_2
+(24 rows)
 
 SELECT a.x, sum(b.x) FROM pagg_tab1 a FULL OUTER JOIN pagg_tab2 b ON a.x = b.y GROUP BY a.x ORDER BY 1 NULLS LAST;
  x  | sum  
@@ -839,16 +982,14 @@ SELECT a.x, b.y, count(*) FROM (SELECT * FROM pagg_tab1 WHERE x < 20) a FULL JOI
 -- Empty join relation because of empty outer side, no partitionwise agg plan
 EXPLAIN (COSTS OFF)
 SELECT a.x, a.y, count(*) FROM (SELECT * FROM pagg_tab1 WHERE x = 1 AND x = 2) a LEFT JOIN pagg_tab2 b ON a.x = b.y GROUP BY a.x, a.y ORDER BY 1, 2;
-                  QUERY PLAN                  
-----------------------------------------------
- GroupAggregate
+               QUERY PLAN               
+----------------------------------------
+ IndexAggregate
    Group Key: pagg_tab1.y
-   ->  Sort
-         Sort Key: pagg_tab1.y
-         ->  Result
-               Replaces: Join on b, pagg_tab1
-               One-Time Filter: false
-(7 rows)
+   ->  Result
+         Replaces: Join on b, pagg_tab1
+         One-Time Filter: false
+(5 rows)
 
 SELECT a.x, a.y, count(*) FROM (SELECT * FROM pagg_tab1 WHERE x = 1 AND x = 2) a LEFT JOIN pagg_tab2 b ON a.x = b.y GROUP BY a.x, a.y ORDER BY 1, 2;
  x | y | count 
@@ -869,7 +1010,7 @@ SELECT a, sum(b), avg(c), count(*) FROM pagg_tab_m GROUP BY a HAVING avg(c) < 22
 --------------------------------------------------------------------
  Sort
    Sort Key: pagg_tab_m.a, (sum(pagg_tab_m.b)), (avg(pagg_tab_m.c))
-   ->  Finalize HashAggregate
+   ->  Finalize IndexAggregate
          Group Key: pagg_tab_m.a
          Filter: (avg(pagg_tab_m.c) < '22'::numeric)
          ->  Append
@@ -1067,8 +1208,8 @@ RESET parallel_setup_cost;
 -- PARTITION KEY, thus we will have a partial aggregation for them.
 EXPLAIN (COSTS OFF)
 SELECT a, sum(b), count(*) FROM pagg_tab_ml GROUP BY a HAVING avg(b) < 3 ORDER BY 1, 2, 3;
-                                   QUERY PLAN                                    
----------------------------------------------------------------------------------
+                                QUERY PLAN                                 
+---------------------------------------------------------------------------
  Sort
    Sort Key: pagg_tab_ml.a, (sum(pagg_tab_ml.b)), (count(*))
    ->  Append
@@ -1076,31 +1217,27 @@ SELECT a, sum(b), count(*) FROM pagg_tab_ml GROUP BY a HAVING avg(b) < 3 ORDER B
                Group Key: pagg_tab_ml.a
                Filter: (avg(pagg_tab_ml.b) < '3'::numeric)
                ->  Seq Scan on pagg_tab_ml_p1 pagg_tab_ml
-         ->  Finalize GroupAggregate
+         ->  Finalize IndexAggregate
                Group Key: pagg_tab_ml_2.a
                Filter: (avg(pagg_tab_ml_2.b) < '3'::numeric)
-               ->  Sort
-                     Sort Key: pagg_tab_ml_2.a
-                     ->  Append
-                           ->  Partial HashAggregate
-                                 Group Key: pagg_tab_ml_2.a
-                                 ->  Seq Scan on pagg_tab_ml_p2_s1 pagg_tab_ml_2
-                           ->  Partial HashAggregate
-                                 Group Key: pagg_tab_ml_3.a
-                                 ->  Seq Scan on pagg_tab_ml_p2_s2 pagg_tab_ml_3
-         ->  Finalize GroupAggregate
+               ->  Append
+                     ->  Partial HashAggregate
+                           Group Key: pagg_tab_ml_2.a
+                           ->  Seq Scan on pagg_tab_ml_p2_s1 pagg_tab_ml_2
+                     ->  Partial HashAggregate
+                           Group Key: pagg_tab_ml_3.a
+                           ->  Seq Scan on pagg_tab_ml_p2_s2 pagg_tab_ml_3
+         ->  Finalize IndexAggregate
                Group Key: pagg_tab_ml_5.a
                Filter: (avg(pagg_tab_ml_5.b) < '3'::numeric)
-               ->  Sort
-                     Sort Key: pagg_tab_ml_5.a
-                     ->  Append
-                           ->  Partial HashAggregate
-                                 Group Key: pagg_tab_ml_5.a
-                                 ->  Seq Scan on pagg_tab_ml_p3_s1 pagg_tab_ml_5
-                           ->  Partial HashAggregate
-                                 Group Key: pagg_tab_ml_6.a
-                                 ->  Seq Scan on pagg_tab_ml_p3_s2 pagg_tab_ml_6
-(31 rows)
+               ->  Append
+                     ->  Partial HashAggregate
+                           Group Key: pagg_tab_ml_5.a
+                           ->  Seq Scan on pagg_tab_ml_p3_s1 pagg_tab_ml_5
+                     ->  Partial HashAggregate
+                           Group Key: pagg_tab_ml_6.a
+                           ->  Seq Scan on pagg_tab_ml_p3_s2 pagg_tab_ml_6
+(27 rows)
 
 SELECT a, sum(b), count(*) FROM pagg_tab_ml GROUP BY a HAVING avg(b) < 3 ORDER BY 1, 2, 3;
  a  | sum  | count 
@@ -1120,31 +1257,29 @@ SELECT a, sum(b), count(*) FROM pagg_tab_ml GROUP BY a HAVING avg(b) < 3 ORDER B
 -- PARTITION KEY
 EXPLAIN (COSTS OFF)
 SELECT b, sum(a), count(*) FROM pagg_tab_ml GROUP BY b ORDER BY 1, 2, 3;
-                                QUERY PLAN                                 
----------------------------------------------------------------------------
+                             QUERY PLAN                              
+---------------------------------------------------------------------
  Sort
    Sort Key: pagg_tab_ml.b, (sum(pagg_tab_ml.a)), (count(*))
-   ->  Finalize GroupAggregate
+   ->  Finalize IndexAggregate
          Group Key: pagg_tab_ml.b
-         ->  Sort
-               Sort Key: pagg_tab_ml.b
-               ->  Append
-                     ->  Partial HashAggregate
-                           Group Key: pagg_tab_ml.b
-                           ->  Seq Scan on pagg_tab_ml_p1 pagg_tab_ml
-                     ->  Partial HashAggregate
-                           Group Key: pagg_tab_ml_1.b
-                           ->  Seq Scan on pagg_tab_ml_p2_s1 pagg_tab_ml_1
-                     ->  Partial HashAggregate
-                           Group Key: pagg_tab_ml_2.b
-                           ->  Seq Scan on pagg_tab_ml_p2_s2 pagg_tab_ml_2
-                     ->  Partial HashAggregate
-                           Group Key: pagg_tab_ml_3.b
-                           ->  Seq Scan on pagg_tab_ml_p3_s1 pagg_tab_ml_3
-                     ->  Partial HashAggregate
-                           Group Key: pagg_tab_ml_4.b
-                           ->  Seq Scan on pagg_tab_ml_p3_s2 pagg_tab_ml_4
-(22 rows)
+         ->  Append
+               ->  Partial HashAggregate
+                     Group Key: pagg_tab_ml.b
+                     ->  Seq Scan on pagg_tab_ml_p1 pagg_tab_ml
+               ->  Partial HashAggregate
+                     Group Key: pagg_tab_ml_1.b
+                     ->  Seq Scan on pagg_tab_ml_p2_s1 pagg_tab_ml_1
+               ->  Partial HashAggregate
+                     Group Key: pagg_tab_ml_2.b
+                     ->  Seq Scan on pagg_tab_ml_p2_s2 pagg_tab_ml_2
+               ->  Partial HashAggregate
+                     Group Key: pagg_tab_ml_3.b
+                     ->  Seq Scan on pagg_tab_ml_p3_s1 pagg_tab_ml_3
+               ->  Partial HashAggregate
+                     Group Key: pagg_tab_ml_4.b
+                     ->  Seq Scan on pagg_tab_ml_p3_s2 pagg_tab_ml_4
+(20 rows)
 
 SELECT b, sum(a), count(*) FROM pagg_tab_ml GROUP BY b HAVING avg(a) < 15 ORDER BY 1, 2, 3;
  b |  sum  | count 
diff --git a/src/test/regress/expected/select_parallel.out b/src/test/regress/expected/select_parallel.out
index 933921d1860..0318863bf1f 100644
--- a/src/test/regress/expected/select_parallel.out
+++ b/src/test/regress/expected/select_parallel.out
@@ -706,18 +706,16 @@ alter table tenk2 reset (parallel_workers);
 set enable_hashagg = false;
 explain (costs off)
    select count(*) from tenk1 group by twenty;
-                     QUERY PLAN                     
-----------------------------------------------------
+                  QUERY PLAN                  
+----------------------------------------------
  Finalize GroupAggregate
    Group Key: twenty
    ->  Gather Merge
          Workers Planned: 4
-         ->  Partial GroupAggregate
+         ->  Partial IndexAggregate
                Group Key: twenty
-               ->  Sort
-                     Sort Key: twenty
-                     ->  Parallel Seq Scan on tenk1
-(9 rows)
+               ->  Parallel Seq Scan on tenk1
+(7 rows)
 
 select count(*) from tenk1 group by twenty;
  count 
@@ -772,19 +770,17 @@ drop function sp_simple_func(integer);
 -- test handling of SRFs in targetlist (bug in 10.0)
 explain (costs off)
    select count(*), generate_series(1,2) from tenk1 group by twenty;
-                        QUERY PLAN                        
-----------------------------------------------------------
+                     QUERY PLAN                     
+----------------------------------------------------
  ProjectSet
    ->  Finalize GroupAggregate
          Group Key: twenty
          ->  Gather Merge
                Workers Planned: 4
-               ->  Partial GroupAggregate
+               ->  Partial IndexAggregate
                      Group Key: twenty
-                     ->  Sort
-                           Sort Key: twenty
-                           ->  Parallel Seq Scan on tenk1
-(10 rows)
+                     ->  Parallel Seq Scan on tenk1
+(8 rows)
 
 select count(*), generate_series(1,2) from tenk1 group by twenty;
  count | generate_series 
@@ -833,6 +829,7 @@ select count(*), generate_series(1,2) from tenk1 group by twenty;
 
 -- test gather merge with parallel leader participation disabled
 set parallel_leader_participation = off;
+set enable_indexagg = off;
 explain (costs off)
    select count(*) from tenk1 group by twenty;
                      QUERY PLAN                     
@@ -876,6 +873,7 @@ select count(*) from tenk1 group by twenty;
 reset parallel_leader_participation;
 --test rescan behavior of gather merge
 set enable_material = false;
+set enable_indexagg = false;
 explain (costs off)
 select * from
   (select string4, count(unique2)
@@ -917,6 +915,7 @@ select * from
 (12 rows)
 
 reset enable_material;
+reset enable_indexagg;
 reset enable_hashagg;
 -- check parallelized int8 aggregate (bug #14897)
 explain (costs off)
diff --git a/src/test/regress/expected/sysviews.out b/src/test/regress/expected/sysviews.out
index 0411db832f1..d32bec316d3 100644
--- a/src/test/regress/expected/sysviews.out
+++ b/src/test/regress/expected/sysviews.out
@@ -157,6 +157,7 @@ select name, setting from pg_settings where name like 'enable%';
  enable_hashagg                 | on
  enable_hashjoin                | on
  enable_incremental_sort        | on
+ enable_indexagg                | on
  enable_indexonlyscan           | on
  enable_indexscan               | on
  enable_material                | on
@@ -173,7 +174,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/sql/aggregates.sql b/src/test/regress/sql/aggregates.sql
index 850f5a5787f..f72eb367112 100644
--- a/src/test/regress/sql/aggregates.sql
+++ b/src/test/regress/sql/aggregates.sql
@@ -1392,6 +1392,7 @@ CREATE INDEX btg_x_y_idx ON btg(x, y);
 ANALYZE btg;
 
 SET enable_hashagg = off;
+SET enable_indexagg = off;
 SET enable_seqscan = off;
 
 -- Utilize the ordering of index scan to avoid a Sort operation
@@ -1623,12 +1624,100 @@ select v||'a', case v||'a' when 'aa' then 1 else 0 end, count(*)
 select v||'a', case when v||'a' = 'aa' then 1 else 0 end, count(*)
   from unnest(array['a','b']) u(v)
  group by v||'a' order by 1;
+ 
+--
+-- Index Aggregation tests
+--
+
+set enable_hashagg = false;
+set enable_sort = false;
+set enable_indexagg = true;
+set enable_indexscan = false;
+
+-- require ordered output
+EXPLAIN (COSTS OFF, VERBOSE)
+SELECT unique1, SUM(two) FROM tenk1
+GROUP BY 1
+ORDER BY 1
+LIMIT 10;
+
+SELECT unique1, SUM(two) FROM tenk1
+GROUP BY 1
+ORDER BY 1
+LIMIT 10;
+
+EXPLAIN (COSTS OFF, VERBOSE)
+SELECT even, sum(two) FROM tenk1
+GROUP BY 1
+ORDER BY 1
+LIMIT 10;
+
+SELECT even, sum(two) FROM tenk1
+GROUP BY 1
+ORDER BY 1
+LIMIT 10;
+
+-- multiple grouping columns
+EXPLAIN (COSTS OFF, VERBOSE)
+SELECT even, odd, sum(unique1) FROM tenk1
+GROUP BY 1, 2
+ORDER BY 1, 2
+LIMIT 10;
+
+SELECT even, odd, sum(unique1) FROM tenk1
+GROUP BY 1, 2
+ORDER BY 1, 2
+LIMIT 10;
+
+-- mixing columns between group by and order by
+begin;
+
+create temp table tmp(x int, y int);
+insert into tmp values (1, 8), (2, 7), (3, 6), (4, 5);
+
+EXPLAIN (COSTS OFF, VERBOSE)
+SELECT x, y, sum(x) FROM tmp
+GROUP BY 1, 2
+ORDER BY 1, 2;
+
+SELECT x, y, sum(x) FROM tmp
+GROUP BY 1, 2
+ORDER BY 1, 2;
+
+EXPLAIN (COSTS OFF, VERBOSE)
+SELECT x, y, sum(x) FROM tmp
+GROUP BY 1, 2
+ORDER BY 2, 1;
+
+SELECT x, y, sum(x) FROM tmp
+GROUP BY 1, 2
+ORDER BY 2, 1;
+
+--
+-- Index Aggregation Spill tests
+--
+
+set enable_indexagg = true;
+set enable_sort=false;
+set enable_hashagg = false;
+set work_mem='64kB';
+
+select unique1, count(*), sum(twothousand) from tenk1
+group by unique1
+having sum(fivethous) > 4975
+order by sum(twothousand);
+
+set work_mem to default;
+set enable_sort to default;
+set enable_hashagg to default;
+set enable_indexagg to default;
 
 --
 -- Hash Aggregation Spill tests
 --
 
 set enable_sort=false;
+set enable_indexagg = false;
 set work_mem='64kB';
 
 select unique1, count(*), sum(twothousand) from tenk1
@@ -1657,6 +1746,7 @@ analyze agg_data_20k;
 -- Produce results with sorting.
 
 set enable_hashagg = false;
+set enable_indexagg = false;
 
 set jit_above_cost = 0;
 
@@ -1728,23 +1818,68 @@ select (g/2)::numeric as c1, array_agg(g::numeric) as c2, count(*) as c3
 set enable_sort = true;
 set work_mem to default;
 
+-- Produce results with index aggregation
+
+set enable_sort = false;
+set enable_hashagg = false;
+set enable_indexagg = true;
+
+set jit_above_cost = 0;
+
+explain (costs off)
+select g%10000 as c1, sum(g::numeric) as c2, count(*) as c3
+  from agg_data_20k group by g%10000;
+
+create table agg_index_1 as
+select g%10000 as c1, sum(g::numeric) as c2, count(*) as c3
+  from agg_data_20k group by g%10000;
+
+create table agg_index_2 as
+select * from
+  (values (100), (300), (500)) as r(a),
+  lateral (
+    select (g/2)::numeric as c1,
+           array_agg(g::numeric) as c2,
+	   count(*) as c3
+    from agg_data_2k
+    where g < r.a
+    group by g/2) as s;
+
+set jit_above_cost to default;
+
+create table agg_index_3 as
+select (g/2)::numeric as c1, sum(7::int4) as c2, count(*) as c3
+  from agg_data_2k group by g/2;
+
+create table agg_index_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;
+
 -- Compare group aggregation results to hash aggregation results
 
 (select * from agg_hash_1 except select * from agg_group_1)
   union all
-(select * from agg_group_1 except select * from agg_hash_1);
+(select * from agg_group_1 except select * from agg_hash_1)
+  union all
+(select * from agg_index_1 except select * from agg_group_1);
 
 (select * from agg_hash_2 except select * from agg_group_2)
   union all
-(select * from agg_group_2 except select * from agg_hash_2);
+(select * from agg_group_2 except select * from agg_hash_2)
+  union all
+(select * from agg_index_2 except select * from agg_group_2);
 
 (select * from agg_hash_3 except select * from agg_group_3)
   union all
-(select * from agg_group_3 except select * from agg_hash_3);
+(select * from agg_group_3 except select * from agg_hash_3)
+  union all
+(select * from agg_index_3 except select * from agg_group_3);
 
 (select * from agg_hash_4 except select * from agg_group_4)
   union all
-(select * from agg_group_4 except select * from agg_hash_4);
+(select * from agg_group_4 except select * from agg_hash_4)
+  union all
+(select * from agg_index_4 except select * from agg_group_4);
 
 drop table agg_group_1;
 drop table agg_group_2;
@@ -1754,3 +1889,7 @@ drop table agg_hash_1;
 drop table agg_hash_2;
 drop table agg_hash_3;
 drop table agg_hash_4;
+drop table agg_index_1;
+drop table agg_index_2;
+drop table agg_index_3;
+drop table agg_index_4;
diff --git a/src/test/regress/sql/eager_aggregate.sql b/src/test/regress/sql/eager_aggregate.sql
index abe6d6ae09f..f9f4b5dcebd 100644
--- a/src/test/regress/sql/eager_aggregate.sql
+++ b/src/test/regress/sql/eager_aggregate.sql
@@ -35,6 +35,7 @@ GROUP BY t1.a ORDER BY t1.a;
 
 -- Produce results with sorting aggregation
 SET enable_hashagg TO off;
+SET enable_indexagg TO off;
 
 EXPLAIN (VERBOSE, COSTS OFF)
 SELECT t1.a, avg(t2.c)
@@ -48,6 +49,25 @@ SELECT t1.a, avg(t2.c)
 GROUP BY t1.a ORDER BY t1.a;
 
 RESET enable_hashagg;
+RESET enable_indexagg;
+
+-- Produce results with index aggregation
+SET enable_hashagg TO off;
+SET enable_sort TO off;
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.a, avg(t2.c)
+  FROM eager_agg_t1 t1
+  JOIN eager_agg_t2 t2 ON t1.b = t2.b
+GROUP BY t1.a ORDER BY t1.a;
+
+SELECT t1.a, avg(t2.c)
+  FROM eager_agg_t1 t1
+  JOIN eager_agg_t2 t2 ON t1.b = t2.b
+GROUP BY t1.a ORDER BY t1.a;
+
+RESET enable_hashagg;
+RESET enable_sort;
 
 
 --
@@ -71,6 +91,7 @@ GROUP BY t1.a ORDER BY t1.a;
 
 -- Produce results with sorting aggregation
 SET enable_hashagg TO off;
+SET enable_indexagg TO off;
 
 EXPLAIN (VERBOSE, COSTS OFF)
 SELECT t1.a, avg(t2.c + t3.c)
@@ -86,7 +107,27 @@ SELECT t1.a, avg(t2.c + t3.c)
 GROUP BY t1.a ORDER BY t1.a;
 
 RESET enable_hashagg;
+RESET enable_indexagg;
 
+-- Produce results with index aggregation
+SET enable_hashagg TO off;
+SET enable_sort TO off;
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.a, avg(t2.c + t3.c)
+  FROM eager_agg_t1 t1
+  JOIN eager_agg_t2 t2 ON t1.b = t2.b
+  JOIN eager_agg_t3 t3 ON t2.a = t3.a
+GROUP BY t1.a ORDER BY t1.a;
+
+SELECT t1.a, avg(t2.c + t3.c)
+  FROM eager_agg_t1 t1
+  JOIN eager_agg_t2 t2 ON t1.b = t2.b
+  JOIN eager_agg_t3 t3 ON t2.a = t3.a
+GROUP BY t1.a ORDER BY t1.a;
+
+RESET enable_hashagg;
+RESET enable_sort;
 
 --
 -- Test that eager aggregation works for outer join
diff --git a/src/test/regress/sql/join.sql b/src/test/regress/sql/join.sql
index b91fb7574df..4250b366b1a 100644
--- a/src/test/regress/sql/join.sql
+++ b/src/test/regress/sql/join.sql
@@ -605,6 +605,7 @@ select count(*) from
 set enable_hashjoin = 0;
 set enable_nestloop = 0;
 set enable_hashagg = 0;
+set enable_indexagg = 0;
 
 --
 -- Check that we use the pathkeys from a prefix of the group by / order by
@@ -617,6 +618,7 @@ from tenk1 x inner join tenk1 y on x.thousand = y.thousand
 group by x.thousand, x.twothousand
 order by x.thousand desc, x.twothousand;
 
+reset enable_indexagg;
 reset enable_hashagg;
 reset enable_nestloop;
 reset enable_hashjoin;
diff --git a/src/test/regress/sql/partition_aggregate.sql b/src/test/regress/sql/partition_aggregate.sql
index 7c725e2663a..570aac38fc5 100644
--- a/src/test/regress/sql/partition_aggregate.sql
+++ b/src/test/regress/sql/partition_aggregate.sql
@@ -55,8 +55,9 @@ EXPLAIN (COSTS OFF)
 SELECT c, sum(a) FROM pagg_tab WHERE c = 'x' GROUP BY c;
 SELECT c, sum(a) FROM pagg_tab WHERE c = 'x' GROUP BY c;
 
--- Test GroupAggregate paths by disabling hash aggregates.
+-- Test GroupAggregate paths by disabling hash and index aggregates.
 SET enable_hashagg TO false;
+SET enable_indexagg TO false;
 
 -- When GROUP BY clause matches full aggregation is performed for each partition.
 EXPLAIN (COSTS OFF)
@@ -81,6 +82,32 @@ EXPLAIN (COSTS OFF)
 SELECT count(*) FROM pagg_tab GROUP BY c ORDER BY c LIMIT 1;
 SELECT count(*) FROM pagg_tab GROUP BY c ORDER BY c LIMIT 1;
 
+RESET enable_hashagg;
+RESET enable_indexagg;
+
+-- Test IndexAggregate paths by disabling hash and group aggregates.
+SET enable_sort TO false;
+SET enable_hashagg TO false;
+
+-- When GROUP BY clause matches full aggregation is performed for each partition.
+EXPLAIN (COSTS OFF)
+SELECT c, sum(a), avg(b), count(*) FROM pagg_tab GROUP BY 1 HAVING avg(d) < 15 ORDER BY 1, 2, 3;
+SELECT c, sum(a), avg(b), count(*) FROM pagg_tab GROUP BY 1 HAVING avg(d) < 15 ORDER BY 1, 2, 3;
+
+-- When GROUP BY clause does not match; top finalize node is required
+EXPLAIN (COSTS OFF)
+SELECT a, sum(b), avg(b), count(*) FROM pagg_tab GROUP BY 1 HAVING avg(d) < 15 ORDER BY 1, 2, 3;
+SELECT a, sum(b), avg(b), count(*) FROM pagg_tab GROUP BY 1 HAVING avg(d) < 15 ORDER BY 1, 2, 3;
+
+-- Test partitionwise grouping without any aggregates
+EXPLAIN (COSTS OFF)
+SELECT c FROM pagg_tab GROUP BY c ORDER BY 1;
+SELECT c FROM pagg_tab GROUP BY c ORDER BY 1;
+EXPLAIN (COSTS OFF)
+SELECT a FROM pagg_tab WHERE a < 3 GROUP BY a ORDER BY 1;
+SELECT a FROM pagg_tab WHERE a < 3 GROUP BY a ORDER BY 1;
+
+RESET enable_sort;
 RESET enable_hashagg;
 
 -- ROLLUP, partitionwise aggregation does not apply
@@ -135,10 +162,12 @@ SELECT t2.y, sum(t1.y), count(*) FROM pagg_tab1 t1, pagg_tab2 t2 WHERE t1.x = t2
 -- When GROUP BY clause does not match; partial aggregation is performed for each partition.
 -- Also test GroupAggregate paths by disabling hash aggregates.
 SET enable_hashagg TO false;
+SET enable_indexagg TO false;
 EXPLAIN (COSTS OFF)
 SELECT t1.y, sum(t1.x), count(*) FROM pagg_tab1 t1, pagg_tab2 t2 WHERE t1.x = t2.y GROUP BY t1.y HAVING avg(t1.x) > 10 ORDER BY 1, 2, 3;
 SELECT t1.y, sum(t1.x), count(*) FROM pagg_tab1 t1, pagg_tab2 t2 WHERE t1.x = t2.y GROUP BY t1.y HAVING avg(t1.x) > 10 ORDER BY 1, 2, 3;
 RESET enable_hashagg;
+RESET enable_indexagg;
 
 -- Check with LEFT/RIGHT/FULL OUTER JOINs which produces NULL values for
 -- aggregation
diff --git a/src/test/regress/sql/select_parallel.sql b/src/test/regress/sql/select_parallel.sql
index 71a75bc86ea..5f398219166 100644
--- a/src/test/regress/sql/select_parallel.sql
+++ b/src/test/regress/sql/select_parallel.sql
@@ -318,6 +318,7 @@ select count(*), generate_series(1,2) from tenk1 group by twenty;
 
 -- test gather merge with parallel leader participation disabled
 set parallel_leader_participation = off;
+set enable_indexagg = off;
 
 explain (costs off)
    select count(*) from tenk1 group by twenty;
@@ -328,6 +329,7 @@ reset parallel_leader_participation;
 
 --test rescan behavior of gather merge
 set enable_material = false;
+set enable_indexagg = false;
 
 explain (costs off)
 select * from
@@ -341,6 +343,7 @@ select * from
   right join (values (1),(2),(3)) v(x) on true;
 
 reset enable_material;
+reset enable_indexagg;
 
 reset enable_hashagg;
 
-- 
2.43.0

