Subscription objects are conceptually database-local objects, but
pg_subscription is a shared catalog so that the launcher process can
scan it.
Check readers of pg_subscription to ensure that, unless it's the
launcher process, it filters by MyDatabaseId. Most readers were
already doing so, but this commit fixes REASSIGN OWNED and adds guards
to catch other problems in the future. Also, clarify documentation.
Author: Dilip Kumar <dilipbalaut@gmail.com>
Reported-by: Noah Misch <noah@leadboat.com>
Reviewed-by: shveta malik <shveta.malik@gmail.com>
Discussion: https://postgr.es/m/
20260710192533.4f.noahmisch@microsoft.com
Backpatch-through: 19
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();
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
- <replaceable class="parameter">new_role</replaceable>.
+ <replaceable class="parameter">new_role</replaceable>. Subscriptions,
+ although stored in a shared catalog, belong to a specific database
+ and are reassigned only when <command>REASSIGN OWNED</command> is executed
+ in that database.
</para>
</listitem>
</varlistentry>
that contains objects owned by the role. (Note that the first
such <command>REASSIGN OWNED</command> 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
+ <command>REASSIGN OWNED</command> is executed in that database.)
</para>
<para>
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;
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. */
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. */
form = (Form_pg_subscription) GETSTRUCT(tup);
+ /* Must only alter subscriptions belonging to the current database. */
+ Assert(form->subdbid == MyDatabaseId);
+
if (form->subowner == newOwnerId)
return;
{
HeapTuple tup;
Relation rel;
+ Form_pg_subscription form;
rel = table_open(SubscriptionRelationId, RowExclusiveLock);
(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);