From a1702c4d496fcda9419c9963acc0e9ed9d8de1c7 Mon Sep 17 00:00:00 2001
From: Fujii Masao <fujii@postgresql.org>
Date: Thu, 14 May 2026 22:25:02 +0900
Subject: [PATCH v1] postgres_fdw: Use foreign table owner's user mapping for
 restore_stats

Previously, ANALYZE on a foreign table used different user mappings
depending on the restore_stats option. When restore_stats was disabled,
it used the foreign table owner's user mapping to sample remote data.
But when restore_stats was enabled, it instead used the user mapping of
the role running ANALYZE to fetch remote statistics.

As a result, ANALYZE could fail with "user mapping not found" for users
allowed to analyze the foreign table but lacking their own user mapping,
even if the table owner had a valid mapping. Also using different
mappings depending on restore_stats was confusing and inconsistent.

This commit fixes the issue by making ANALYZE always use the foreign
table owner's user mapping, regardless of the restore_stats setting.

Also this commit adds a regression test covering both restore_stats
enabled and disabled cases.
---
 contrib/postgres_fdw/expected/postgres_fdw.out | 11 +++++++++++
 contrib/postgres_fdw/postgres_fdw.c            |  6 +++---
 contrib/postgres_fdw/sql/postgres_fdw.sql      | 12 ++++++++++++
 3 files changed, 26 insertions(+), 3 deletions(-)

diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index e90289e4ab1..e195003eae0 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -12917,6 +12917,16 @@ CREATE FOREIGN TABLE simport_fview (c1 int, c2 text)
 ALTER FOREIGN TABLE simport_fview OPTIONS (ADD restore_stats 'true');
 ANALYZE simport_fview;                    -- should fail
 WARNING:  could not import statistics for foreign table "public.simport_fview" --- remote table "public.simport_view" is of relkind "v" which cannot have statistics
+-- Ensure ANALYZE uses the table owner's user mapping
+CREATE ROLE regress_simport_maintain NOSUPERUSER;
+GRANT MAINTAIN ON TABLE simport_ftable TO regress_simport_maintain;
+SET ROLE regress_simport_maintain;
+ANALYZE simport_ftable;           -- should work
+RESET ROLE;
+ALTER FOREIGN TABLE simport_ftable OPTIONS (SET restore_stats 'false');
+SET ROLE regress_simport_maintain;
+ANALYZE simport_ftable;           -- should work
+RESET ROLE;
 -- This tests build_remattrmap()'s deparsing of column names that include
 -- single quotes or backslashes
 CREATE TABLE dtest_table ("col'quote" int, "col\backslash" int);
@@ -12932,6 +12942,7 @@ DROP FOREIGN TABLE simport_ftable;
 DROP FOREIGN TABLE simport_fview;
 DROP VIEW simport_view;
 DROP TABLE simport_table;
+DROP ROLE regress_simport_maintain;
 DROP FOREIGN TABLE dtest_ftable;
 DROP TABLE dtest_table;
 -- ===================================================================
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 85be46eb2a2..4e50ecbfba5 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -5720,10 +5720,10 @@ fetch_remote_statistics(Relation relation,
 	}
 
 	/*
-	 * Get connection to the foreign server.  Connection manager will
-	 * establish new connection if necessary.
+	 * Get the connection to use.  We do the remote access as the table's
+	 * owner, even if the ANALYZE was started by some other user.
 	 */
-	user = GetUserMapping(GetUserId(), table->serverid);
+	user = GetUserMapping(relation->rd_rel->relowner, table->serverid);
 	conn = GetConnection(user, false, NULL);
 	remstats->server_version_num = server_version_num = PQserverVersion(conn);
 
diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql
index dfc58beb0d2..80fbfe9892d 100644
--- a/contrib/postgres_fdw/sql/postgres_fdw.sql
+++ b/contrib/postgres_fdw/sql/postgres_fdw.sql
@@ -4573,6 +4573,17 @@ ALTER FOREIGN TABLE simport_fview OPTIONS (ADD restore_stats 'true');
 
 ANALYZE simport_fview;                    -- should fail
 
+-- Ensure ANALYZE uses the table owner's user mapping
+CREATE ROLE regress_simport_maintain NOSUPERUSER;
+GRANT MAINTAIN ON TABLE simport_ftable TO regress_simport_maintain;
+SET ROLE regress_simport_maintain;
+ANALYZE simport_ftable;           -- should work
+RESET ROLE;
+ALTER FOREIGN TABLE simport_ftable OPTIONS (SET restore_stats 'false');
+SET ROLE regress_simport_maintain;
+ANALYZE simport_ftable;           -- should work
+RESET ROLE;
+
 -- This tests build_remattrmap()'s deparsing of column names that include
 -- single quotes or backslashes
 CREATE TABLE dtest_table ("col'quote" int, "col\backslash" int);
@@ -4589,6 +4600,7 @@ DROP FOREIGN TABLE simport_ftable;
 DROP FOREIGN TABLE simport_fview;
 DROP VIEW simport_view;
 DROP TABLE simport_table;
+DROP ROLE regress_simport_maintain;
 DROP FOREIGN TABLE dtest_ftable;
 DROP TABLE dtest_table;
 
-- 
2.53.0

