From 6a2510cc9efd163565d61e6fa511427a6784b729 Mon Sep 17 00:00:00 2001
From: "David E. Wheeler" <david@justatheory.com>
Date: Mon, 5 Jan 2026 18:53:11 -0500
Subject: [PATCH v1] Disable MIN/MAX optimization for foreign tables

Without this patch, the postgres_fdw tests case emits this plan:

```
                                    QUERY PLAN
-----------------------------------------------------------------------------------
 Result
   Output: (InitPlan minmax_1).col1
   Replaces: MinMaxAggregate
   InitPlan minmax_1
     ->  Limit
           Output: ft4.c2
           ->  Foreign Scan on public.ft4
                 Output: ft4.c2
                 Remote SQL: SELECT c2 FROM "S 1"."T 3" ORDER BY c2 ASC NULLS LAST
(9 rows)
```

Which seems sub-optimal, since it performs a table scan and fetches all
the rows back from the remote server. It would be useful to push down
the `LIMIT`, but better is to let the remote server handle the
optimization, especially since non-Postgres FDWs may not be able to take
advantage of the optimization.

So disable the min/max indexscan optimization for for foreign tables.
---
 .../postgres_fdw/expected/postgres_fdw.out    | 33 +++++++++++++++++++
 contrib/postgres_fdw/sql/postgres_fdw.sql     |  9 +++++
 src/backend/optimizer/plan/planagg.c          |  8 +++++
 3 files changed, 50 insertions(+)

diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index 6066510c7c0..3a0c4e2048a 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -2940,6 +2940,39 @@ select count(c6), sum(c1), avg(c1), min(c2), max(c1), stddev(c2), sum(c1) * (ran
    100 | 49600 | 496.0000000000000000 |   1 | 991 |      0 | 49600
 (1 row)
 
+-- Simple MIN and MAX aggregates should push down
+explain (verbose, costs off)
+select min(c2) from ft4;
+                  QUERY PLAN                   
+-----------------------------------------------
+ Foreign Scan
+   Output: (min(c2))
+   Relations: Aggregate on (public.ft4)
+   Remote SQL: SELECT min(c2) FROM "S 1"."T 3"
+(4 rows)
+
+select min(c2) from ft4;
+ min 
+-----
+   3
+(1 row)
+
+explain (verbose, costs off)
+select max(c2) from ft4;
+                  QUERY PLAN                   
+-----------------------------------------------
+ Foreign Scan
+   Output: (max(c2))
+   Relations: Aggregate on (public.ft4)
+   Remote SQL: SELECT max(c2) FROM "S 1"."T 3"
+(4 rows)
+
+select max(c2) from ft4;
+ max 
+-----
+ 101
+(1 row)
+
 -- Aggregate is not pushed down as aggregation contains random()
 explain (verbose, costs off)
 select sum(c1 * (random() <= 1)::int) as sum, avg(c1) from ft1;
diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql
index 4f7ab2ed0ac..7535c51f296 100644
--- a/contrib/postgres_fdw/sql/postgres_fdw.sql
+++ b/contrib/postgres_fdw/sql/postgres_fdw.sql
@@ -801,6 +801,15 @@ explain (verbose, costs off)
 select count(c6), sum(c1), avg(c1), min(c2), max(c1), stddev(c2), sum(c1) * (random() <= 1)::int as sum2 from ft1 where c2 < 5 group by c2 order by 1, 2 limit 1;
 select count(c6), sum(c1), avg(c1), min(c2), max(c1), stddev(c2), sum(c1) * (random() <= 1)::int as sum2 from ft1 where c2 < 5 group by c2 order by 1, 2 limit 1;
 
+-- Simple MIN and MAX aggregates should push down
+explain (verbose, costs off)
+select min(c2) from ft4;
+select min(c2) from ft4;
+
+explain (verbose, costs off)
+select max(c2) from ft4;
+select max(c2) from ft4;
+
 -- Aggregate is not pushed down as aggregation contains random()
 explain (verbose, costs off)
 select sum(c1 * (random() <= 1)::int) as sum, avg(c1) from ft1;
diff --git a/src/backend/optimizer/plan/planagg.c b/src/backend/optimizer/plan/planagg.c
index 09b38b2c378..9c27771e2b1 100644
--- a/src/backend/optimizer/plan/planagg.c
+++ b/src/backend/optimizer/plan/planagg.c
@@ -28,6 +28,7 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_class.h"
 #include "access/htup_details.h"
 #include "catalog/pg_aggregate.h"
 #include "catalog/pg_type.h"
@@ -137,6 +138,13 @@ preprocess_minmax_aggregates(PlannerInfo *root)
 	else
 		return;
 
+	/*
+	 * Reject foreign tables. They have their own optimizations, so just let
+	 * them have it.
+	 */
+	if (rte->relkind == RELKIND_FOREIGN_TABLE)
+		return;
+
 	/*
 	 * Examine all the aggregates and verify all are MIN/MAX aggregates.  Stop
 	 * as soon as we find one that isn't.
-- 
2.51.0

