public inbox for [email protected]  
help / color / mirror / Atom feed
From: Shin Berg <[email protected]>
To: [email protected]
Subject: Re: Inconsistency in owner assignment between INDEX and STATISTICS
Date: Mon, 9 Mar 2026 20:02:56 +0900
Message-ID: <CACSdjfOyVWALYwb4QrLhAurqo5Ft9XU6zc6SSe=2FoG1DO0WWg@mail.gmail.com> (raw)
In-Reply-To: <CACSdjfPuNND8dn8zYame6mRwoDPKxMKRQvih=W1Nrq5urvwTsg@mail.gmail.com>
References: <CACSdjfMWcR7aJUDf6XNt_yaDi+riz3Ku4EQ-x3xA=Xx1209n2g@mail.gmail.com>
	<CACSdjfPuNND8dn8zYame6mRwoDPKxMKRQvih=W1Nrq5urvwTsg@mail.gmail.com>

Hi,

Following up on my earlier proposal — I've gone ahead and written a patch
rather than waiting for feedback.

The fix is in CreateStatistics(): after opening the relation, stxowner is
set to rel->rd_rel->relowner instead of GetUserId().  The permission check
is left using GetUserId() so that only the relation owner (or a superuser)
can create statistics, but the ownership recorded in pg_statistic_ext now
matches what CREATE INDEX does.

A regression test is included in stats_ext.sql to verify that the
statistics owner equals the table owner when a superuser creates the
statistics object.

Patch attached.

Thanks,
Joshua-Shin

On Thu, Feb 26, 2026 at 6:52 PM Shin Berg <[email protected]> wrote:

> Gentle ping on this thread — any thoughts or concerns about the
> proposed alignment?
>
> Thanks.
>
> On Sat, Feb 14, 2026 at 5:48 PM Shin Berg <[email protected]> wrote:
>
>> Hi,
>>
>> I'd like to raise a small consistency issue between how INDEX and
>> extended STATISTICS handle object ownership, and ask whether aligning them
>> would be desirable.
>>
>> Current behavior (tested on REL_17_STABLE):
>>
>> - When a superuser creates an INDEX on another user's table, the index is
>> owned by the *table owner* (see catalog/index.c: index relation's relowner
>> is set from the heap relation's relowner).
>> - When a superuser creates STATISTICS on another user's table, the
>> statistics object is owned by the *current user* (statscmds.c: stxowner =
>> GetUserId()).
>>
>> So in a scenario where a DBA creates both an index and extended
>> statistics on a user's table, the table owner can DROP the index (because
>> they own it) but cannot DROP the statistics object (they get "does not
>> exist" when lacking ownership, which hides the real permission issue). That
>> can cause operational friction in multi-tenant or shared-schema setups
>> (e.g. the table owner cannot drop the statistics to resolve dependency
>> issues before altering the table).
>>
>> Reproduction (as superuser, then as table owner):
>>
>>   CREATE SCHEMA shared_schema;
>>   CREATE USER bob;
>>   GRANT USAGE, CREATE ON SCHEMA shared_schema TO bob;
>>
>>   SET ROLE bob;
>>   CREATE TABLE shared_schema.bob_table (a int, b int);
>>   RESET ROLE;
>>
>>   CREATE INDEX idx_bob ON shared_schema.bob_table(a);
>>   CREATE STATISTICS stat_bob ON a, b FROM shared_schema.bob_table;
>>
>>   SELECT 'INDEX', c.relname, pg_get_userbyid(c.relowner) FROM pg_index i
>>     JOIN pg_class c ON c.oid = i.indexrelid
>>     WHERE indrelid = 'shared_schema.bob_table'::regclass
>>   UNION ALL
>>   SELECT 'STATISTICS', stxname, pg_get_userbyid(stxowner) FROM
>> pg_statistic_ext
>>     WHERE stxrelid = 'shared_schema.bob_table'::regclass;
>>   -- INDEX owner = bob, STATISTICS owner = superuser
>>
>>   SET ROLE bob;
>>   DROP INDEX shared_schema.idx_bob;        -- succeeds
>>   DROP STATISTICS shared_schema.stat_bob;  -- ERROR: statistics object
>> "..." does not exist
>>
>> I'm not sure if the current STATISTICS ownership behavior was
>> intentional. If it wasn't, would it make sense to assign the statistics
>> object's owner to the relation owner (same as INDEX) for consistency and to
>> avoid the above scenario?
>>
>> Thanks for your time.
>>
>


Attachments:

  [application/octet-stream] 0001-Make-CREATE-STATISTICS-assign-ownership-to-the-relat.patch (4.1K, 3-0001-Make-CREATE-STATISTICS-assign-ownership-to-the-relat.patch)
  download | inline diff:
From 723d8106a6ae63667d76138b0dfb33824e01de80 Mon Sep 17 00:00:00 2001
From: Joshua-Shin <[email protected]>
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



view thread (7+ messages)  latest in thread

reply

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Reply to all the recipients using the --to and --cc options:
  reply via email

  To: [email protected]
  Cc: [email protected], [email protected]
  Subject: Re: Inconsistency in owner assignment between INDEX and STATISTICS
  In-Reply-To: <CACSdjfOyVWALYwb4QrLhAurqo5Ft9XU6zc6SSe=2FoG1DO0WWg@mail.gmail.com>

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox