From 446c5e0d8c09dffb4b1090f0d205ffcb9c0f29a5 Mon Sep 17 00:00:00 2001 From: Dilip Kumar Date: Mon, 13 Jul 2026 16:19:00 +0530 Subject: [PATCH v2] Do not reassign subscriptions belonging to other databases in REASSIGN OWNED. Subscription objects are fundamentally database-local in scope. Subscription DDL commands already operate on a per-database basis: CREATE SUBSCRIPTION stores subdbid = MyDatabaseId, and ALTER/DROP SUBSCRIPTION commands (including ALTER SUBSCRIPTION ... OWNER TO) look subscriptions up by (MyDatabaseId, subname) via the unique index on (subdbid, subname), acting strictly on subscriptions in the current database. Furthermore, subscription fields like subserver (commit 8185bb5) and subconflictlogrelid (commit a5918fd) reference database-local objects (!relisshared) in that database. However, because pg_subscription is stored as a shared catalog table (relisshared == true), shared dependency entries for subscriptions in pg_shdepend have dbid = InvalidOid. As a result, REASSIGN OWNED attempted to reassign subscriptions belonging to any database in the cluster. When executed in a database other than subdbid, this caused AlterSubscriptionOwner_internal() to resolve database-local references (such as subserver and subconflictlogrelid) against the current database (MyDatabaseId), resulting in errors or wrongly altering unrelated objects. Fix this by skipping subscription ownership reassignment in AlterSubscriptionOwner_internal() if form->subdbid != MyDatabaseId. Like other database-local objects, subscriptions will now be reassigned when REASSIGN OWNED is executed in the database to which they belong. --- doc/src/sgml/ref/reassign_owned.sgml | 5 ++++- doc/src/sgml/user-manag.sgml | 4 +++- src/backend/catalog/pg_subscription.c | 13 +++++++++++++ src/backend/commands/subscriptioncmds.c | 15 ++++++++++++++- 4 files changed, 34 insertions(+), 3 deletions(-) diff --git a/doc/src/sgml/ref/reassign_owned.sgml b/doc/src/sgml/ref/reassign_owned.sgml index ab692bd0690..5fca2329de2 100644 --- a/doc/src/sgml/ref/reassign_owned.sgml +++ b/doc/src/sgml/ref/reassign_owned.sgml @@ -48,7 +48,10 @@ REASSIGN OWNED BY { old_role | CURR The name of a role. The ownership of all the objects within the current database, and of all shared objects (databases, tablespaces), owned by this role will be reassigned to - new_role. + new_role. Subscriptions, + although stored in a shared catalog, belong to a specific database + and are reassigned only when REASSIGN OWNED is executed + in that database. diff --git a/doc/src/sgml/user-manag.sgml b/doc/src/sgml/user-manag.sgml index 0ec32700bd4..f0b4ca069ec 100644 --- a/doc/src/sgml/user-manag.sgml +++ b/doc/src/sgml/user-manag.sgml @@ -530,7 +530,9 @@ ALTER TABLE bobs_table OWNER TO alice; that contains objects owned by the role. (Note that the first such REASSIGN OWNED will change the ownership of any shared-across-databases objects, that is databases or tablespaces, that - are owned by the role-to-be-dropped.) + are owned by the role-to-be-dropped. Subscriptions, while stored in a + shared catalog, belong to a specific database and are reassigned only when + REASSIGN OWNED is executed in that database.) diff --git a/src/backend/catalog/pg_subscription.c b/src/backend/catalog/pg_subscription.c index 2068e03c571..387365f7ee4 100644 --- a/src/backend/catalog/pg_subscription.c +++ b/src/backend/catalog/pg_subscription.c @@ -25,6 +25,7 @@ #include "catalog/pg_type.h" #include "foreign/foreign.h" #include "miscadmin.h" +#include "replication/logicallauncher.h" #include "storage/lmgr.h" #include "storage/lock.h" #include "utils/acl.h" @@ -114,6 +115,12 @@ GetSubscription(Oid subid, bool missing_ok, bool conninfo_needed, subform = (Form_pg_subscription) GETSTRUCT(tup); + /* + * Outside the launcher process, subscription lookups must only access + * subscriptions belonging to the current database. + */ + Assert(IsLogicalLauncher() || subform->subdbid == MyDatabaseId); + sub = palloc0_object(Subscription); sub->cxt = cxt; sub->oid = subid; @@ -272,6 +279,9 @@ DisableSubscription(Oid subid) if (!HeapTupleIsValid(tup)) elog(ERROR, "cache lookup failed for subscription %u", subid); + /* Must only modify subscriptions belonging to the current database. */ + Assert(((Form_pg_subscription) GETSTRUCT(tup))->subdbid == MyDatabaseId); + LockSharedObject(SubscriptionRelationId, subid, 0, AccessShareLock); /* Form a new tuple. */ @@ -714,6 +724,9 @@ UpdateDeadTupleRetentionStatus(Oid subid, bool active) if (!HeapTupleIsValid(tup)) elog(ERROR, "cache lookup failed for subscription %u", subid); + /* Must only modify subscriptions belonging to the current database. */ + Assert(((Form_pg_subscription) GETSTRUCT(tup))->subdbid == MyDatabaseId); + LockSharedObject(SubscriptionRelationId, subid, 0, AccessShareLock); /* Form a new tuple. */ diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c index 4292e7fb8f4..c40c5def1a3 100644 --- a/src/backend/commands/subscriptioncmds.c +++ b/src/backend/commands/subscriptioncmds.c @@ -2868,6 +2868,9 @@ AlterSubscriptionOwner_internal(Relation rel, HeapTuple tup, Oid newOwnerId) form = (Form_pg_subscription) GETSTRUCT(tup); + /* Must only alter subscriptions belonging to the current database. */ + Assert(form->subdbid == MyDatabaseId); + if (form->subowner == newOwnerId) return; @@ -2987,6 +2990,7 @@ AlterSubscriptionOwner_oid(Oid subid, Oid newOwnerId) { HeapTuple tup; Relation rel; + Form_pg_subscription form; rel = table_open(SubscriptionRelationId, RowExclusiveLock); @@ -2997,7 +3001,16 @@ AlterSubscriptionOwner_oid(Oid subid, Oid newOwnerId) (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("subscription with OID %u does not exist", subid))); - AlterSubscriptionOwner_internal(rel, tup, newOwnerId); + form = (Form_pg_subscription) GETSTRUCT(tup); + + /* + * Don't process subscriptions belonging to other databases. While + * pg_subscription is a shared catalog, subscriptions refer to db-local + * objects like subserver and subconflictlogrelid which exist only in the + * database identified by subdbid. + */ + if (form->subdbid == MyDatabaseId) + AlterSubscriptionOwner_internal(rel, tup, newOwnerId); heap_freetuple(tup); -- 2.49.0