From 6a2510cc9efd163565d61e6fa511427a6784b729 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" 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