diff --git a/contrib/postgres_fdw/t/010_subscription.pl b/contrib/postgres_fdw/t/010_subscription.pl
index 163c788d209..c34b3d15b8d 100644
--- a/contrib/postgres_fdw/t/010_subscription.pl
+++ b/contrib/postgres_fdw/t/010_subscription.pl
@@ -99,4 +99,49 @@ $result =
is($result, qq(1073),
'check subscription after ALTER SUBSCRIPTION ... SERVER');
+# Check that REASSIGN OWNED processes a subscription only when run in the
+# database to which the subscription belongs.
+$node_subscriber->safe_psql(
+ 'postgres', q{
+ CREATE ROLE regress_sub_old;
+ CREATE ROLE regress_sub_new;
+ GRANT pg_create_subscription TO regress_sub_old;
+ CREATE DATABASE regress_sub_db;
+});
+
+$node_subscriber->safe_psql(
+ 'regress_sub_db', qq{
+ CREATE EXTENSION postgres_fdw;
+ CREATE SERVER regress_sub_server FOREIGN DATA WRAPPER postgres_fdw
+ OPTIONS (host '$publisher_host', port '$publisher_port', dbname 'postgres');
+ CREATE USER MAPPING FOR regress_sub_old SERVER regress_sub_server
+ OPTIONS (user 'unused', password 'secret');
+ CREATE USER MAPPING FOR regress_sub_new SERVER regress_sub_server
+ OPTIONS (user 'unused', password 'secret');
+ GRANT USAGE ON FOREIGN SERVER regress_sub_server
+ TO regress_sub_old, regress_sub_new;
+ GRANT CREATE ON DATABASE regress_sub_db TO regress_sub_old;
+ SET SESSION AUTHORIZATION regress_sub_old;
+ CREATE SUBSCRIPTION regress_sub SERVER regress_sub_server
+ PUBLICATION tap_pub WITH (connect = false, slot_name = NONE);
+});
+
+$node_subscriber->safe_psql('postgres',
+ 'REASSIGN OWNED BY regress_sub_old TO regress_sub_new');
+$result = $node_subscriber->safe_psql(
+ 'postgres',
+ q{SELECT subowner::regrole FROM pg_subscription
+ WHERE subname = 'regress_sub'});
+is($result, 'regress_sub_old',
+ 'REASSIGN OWNED in another database skips subscription');
+
+$node_subscriber->safe_psql('regress_sub_db',
+ 'REASSIGN OWNED BY regress_sub_old TO regress_sub_new');
+$result = $node_subscriber->safe_psql(
+ 'regress_sub_db',
+ q{SELECT subowner::regrole FROM pg_subscription
+ WHERE subname = 'regress_sub'});
+is($result, 'regress_sub_new',
+ 'REASSIGN OWNED in subscription database changes owner');
+
done_testing();
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 d68dd261012..1663794caec 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 b5cb301db88..d1a110f1ff3 100644
--- a/src/backend/catalog/pg_subscription.c
+++ b/src/backend/catalog/pg_subscription.c
@@ -114,6 +114,12 @@ GetSubscription(Oid subid, bool missing_ok, bool conninfo_needed,
subform = (Form_pg_subscription) GETSTRUCT(tup);
+ /*
+ * It's only safe to access subscriptions from another database from the
+ * launcher process, which does not call GetSubscription().
+ */
+ Assert(subform->subdbid == MyDatabaseId);
+
sub = palloc0_object(Subscription);
sub->cxt = cxt;
sub->oid = subid;
@@ -265,6 +271,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. */
@@ -707,6 +716,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 ee06a726f42..1284d410790 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2665,6 +2665,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;
@@ -2779,6 +2782,7 @@ AlterSubscriptionOwner_oid(Oid subid, Oid newOwnerId)
{
HeapTuple tup;
Relation rel;
+ Form_pg_subscription form;
rel = table_open(SubscriptionRelationId, RowExclusiveLock);
@@ -2789,7 +2793,15 @@ 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 which exist only in the database identified by subdbid.
+ */
+ if (form->subdbid == MyDatabaseId)
+ AlterSubscriptionOwner_internal(rel, tup, newOwnerId);
heap_freetuple(tup);