From dd10498637c63cf07b47deb05f5b7c17ba7c497f Mon Sep 17 00:00:00 2001 From: Sami Imseih Date: Fri, 21 Aug 2026 02:19:48 +0000 Subject: [PATCH v1 1/1] postgres_fdw: Fix flaky push down FUNCTION RTE test The unnest() test introduced in 0ee83dd4a99 did not produce a determinstic EXPLAIN plan output. The two sides of this test's nested loop join cost about the same, so which one ended up as the outer wasn't stable and could flip between platforms. Use an equality predicate on ft1.c3 instead of a range one. That restricts ft1 to a single row, so it's clearly the outer of the nested loop and the Materialize on the inner side goes away, giving a stable plan. --- .../postgres_fdw/expected/postgres_fdw.out | 24 +++++++++---------- contrib/postgres_fdw/sql/postgres_fdw.sql | 8 +++---- 2 files changed, 15 insertions(+), 17 deletions(-) diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out index 7c09c52b8ea..c3a3fe57376 100644 --- a/contrib/postgres_fdw/expected/postgres_fdw.out +++ b/contrib/postgres_fdw/expected/postgres_fdw.out @@ -3128,28 +3128,26 @@ WHERE t1.c1 = u.id AND t2.c1 = u.id; Remote SQL: SELECT c1 FROM "S 1"."T 4" (12 rows) --- Selective predicate on ft1.c3 (not in the eqclass) shrinks ft1 to a --- handful of remote rows; now ft6 is effectively the bigger side and the --- function is absorbed into ft6 instead. +-- Equality on ft1.c3 (not in the eqclass) restricts ft1 to a single remote row; +-- now ft6 is effectively the bigger side and the function is absorbed into +-- ft6 instead. EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.c1, t2.c1 FROM ft1 t1, ft6 t2, unnest(ARRAY[3, 6, 9, 12, 15, 18]::int[]) AS u(id) -WHERE t1.c1 = u.id AND t2.c1 = u.id AND t1.c3 < '00010'; - QUERY PLAN ----------------------------------------------------------------------------------------------------------------------------------------------------- +WHERE t1.c1 = u.id AND t2.c1 = u.id AND t1.c3 = '00010'; + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------------------- Nested Loop Output: t1.c1, t2.c1 Join Filter: (t1.c1 = u.id) -> Foreign Scan on public.ft1 t1 Output: t1.c1 - Remote SQL: SELECT "C 1" FROM "S 1"."T 1" WHERE ((c3 < '00010')) - -> Materialize + Remote SQL: SELECT "C 1" FROM "S 1"."T 1" WHERE ((c3 = '00010')) + -> Foreign Scan Output: t2.c1, u.id - -> Foreign Scan - Output: t2.c1, u.id - Relations: (public.ft6 t2) INNER JOIN (pg_catalog.unnest() u) - Remote SQL: SELECT r2.c1, f3.c1 FROM ("S 1"."T 4" r2 INNER JOIN unnest('{3,6,9,12,15,18}'::integer[]) f3(c1) ON (((r2.c1 = f3.c1)))) -(12 rows) + Relations: (public.ft6 t2) INNER JOIN (pg_catalog.unnest() u) + Remote SQL: SELECT r2.c1, f3.c1 FROM ("S 1"."T 4" r2 INNER JOIN unnest('{3,6,9,12,15,18}'::integer[]) f3(c1) ON (((r2.c1 = f3.c1)))) +(10 rows) -- The remaining scenarios reuse a dedicated foreign table to cover the -- corner cases of FUNCTION RTE push-down: function-first FROM, record diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql index 6571a18ba0c..7d46fd3dfbb 100644 --- a/contrib/postgres_fdw/sql/postgres_fdw.sql +++ b/contrib/postgres_fdw/sql/postgres_fdw.sql @@ -883,13 +883,13 @@ EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.c1, t2.c1 FROM ft1 t1, ft6 t2, unnest(ARRAY[3, 6, 9, 12, 15, 18]::int[]) AS u(id) WHERE t1.c1 = u.id AND t2.c1 = u.id; --- Selective predicate on ft1.c3 (not in the eqclass) shrinks ft1 to a --- handful of remote rows; now ft6 is effectively the bigger side and the --- function is absorbed into ft6 instead. +-- Equality on ft1.c3 (not in the eqclass) restricts ft1 to a single remote row; +-- now ft6 is effectively the bigger side and the function is absorbed into +-- ft6 instead. EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.c1, t2.c1 FROM ft1 t1, ft6 t2, unnest(ARRAY[3, 6, 9, 12, 15, 18]::int[]) AS u(id) -WHERE t1.c1 = u.id AND t2.c1 = u.id AND t1.c3 < '00010'; +WHERE t1.c1 = u.id AND t2.c1 = u.id AND t1.c3 = '00010'; -- The remaining scenarios reuse a dedicated foreign table to cover the -- corner cases of FUNCTION RTE push-down: function-first FROM, record -- 2.47.3