From 723d8106a6ae63667d76138b0dfb33824e01de80 Mon Sep 17 00:00:00 2001
From: Joshua-Shin <sjh910805@gmail.com>
Date: Mon, 9 Mar 2026 19:58:27 +0900
Subject: [PATCH] Make CREATE STATISTICS assign ownership to the relation owner

When a superuser creates extended statistics on another user's table,
the statistics object was owned by the current user (GetUserId()) rather
than the table owner.  This is inconsistent with CREATE INDEX, which
assigns ownership to the table owner unconditionally.

Fix by setting stxowner to the relation owner after the relation is
opened, while keeping the permission check against GetUserId().

Add a regression test that verifies the statistics owner matches the
table owner when created by a superuser.
---
 src/backend/commands/statscmds.c        |  3 ++-
 src/test/regress/expected/stats_ext.out | 14 ++++++++++++++
 src/test/regress/sql/stats_ext.sql      | 10 ++++++++++
 3 files changed, 26 insertions(+), 1 deletion(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index c1da79f36ba..74732f96eb9 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -142,7 +142,7 @@ CreateStatistics(CreateStatsStmt *stmt, bool check_rights)
 		 * different relation than a previous lookup by the caller, so we must
 		 * perform this check even when check_rights == false.
 		 */
-		if (!object_ownercheck(RelationRelationId, RelationGetRelid(rel), stxowner))
+		if (!object_ownercheck(RelationRelationId, RelationGetRelid(rel), GetUserId()))
 			aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(rel->rd_rel->relkind),
 						   RelationGetRelationName(rel));
 
@@ -156,6 +156,7 @@ CreateStatistics(CreateStatsStmt *stmt, bool check_rights)
 
 	Assert(rel);
 	relid = RelationGetRelid(rel);
+	stxowner = rel->rd_rel->relowner;
 
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
diff --git a/src/test/regress/expected/stats_ext.out b/src/test/regress/expected/stats_ext.out
index b6431d1ee95..8dbe94be5ce 100644
--- a/src/test/regress/expected/stats_ext.out
+++ b/src/test/regress/expected/stats_ext.out
@@ -3509,6 +3509,20 @@ REVOKE CREATE ON SCHEMA sts_sch1, sts_sch2 FROM regress_stats_user1;
 SET SESSION AUTHORIZATION regress_stats_user1;
 ALTER TABLE sts_sch1.tbl ALTER COLUMN a TYPE SMALLINT;
 ALTER TABLE sts_sch1.tbl ALTER COLUMN c SET EXPRESSION AS (a * 3);
+-- Test that statistics ownership follows the table owner when a superuser
+-- creates statistics on another user's table, consistent with CREATE INDEX.
+RESET SESSION AUTHORIZATION;
+CREATE TABLE stats_owner_test (a int, b int) WITH (autovacuum_enabled = off);
+ALTER TABLE stats_owner_test OWNER TO regress_stats_user1;
+CREATE STATISTICS stats_owner_test_stat ON a, b FROM stats_owner_test;
+SELECT pg_get_userbyid(stxowner) = 'regress_stats_user1' AS owner_is_table_owner
+FROM pg_statistic_ext WHERE stxname = 'stats_owner_test_stat';
+ owner_is_table_owner 
+----------------------
+ t
+(1 row)
+
+DROP TABLE stats_owner_test;
 -- Tidy up
 DROP OPERATOR <<< (int, int);
 DROP FUNCTION op_leak(int, int);
diff --git a/src/test/regress/sql/stats_ext.sql b/src/test/regress/sql/stats_ext.sql
index 9dcce3440c8..be14c82743e 100644
--- a/src/test/regress/sql/stats_ext.sql
+++ b/src/test/regress/sql/stats_ext.sql
@@ -1795,6 +1795,16 @@ SET SESSION AUTHORIZATION regress_stats_user1;
 ALTER TABLE sts_sch1.tbl ALTER COLUMN a TYPE SMALLINT;
 ALTER TABLE sts_sch1.tbl ALTER COLUMN c SET EXPRESSION AS (a * 3);
 
+-- Test that statistics ownership follows the table owner when a superuser
+-- creates statistics on another user's table, consistent with CREATE INDEX.
+RESET SESSION AUTHORIZATION;
+CREATE TABLE stats_owner_test (a int, b int) WITH (autovacuum_enabled = off);
+ALTER TABLE stats_owner_test OWNER TO regress_stats_user1;
+CREATE STATISTICS stats_owner_test_stat ON a, b FROM stats_owner_test;
+SELECT pg_get_userbyid(stxowner) = 'regress_stats_user1' AS owner_is_table_owner
+FROM pg_statistic_ext WHERE stxname = 'stats_owner_test_stat';
+DROP TABLE stats_owner_test;
+
 -- Tidy up
 DROP OPERATOR <<< (int, int);
 DROP FUNCTION op_leak(int, int);
-- 
2.52.0

