Hi,

On Mon, Jul 06, 2026 at 02:53:39PM +0000, Bertrand Drouvot wrote:
> Hi,
> 
> On Mon, Jul 06, 2026 at 03:07:24PM +0530, Amit Kapila wrote:
> 
> DROP SUBSCRIPTION however has its own dedicated code path and does not go 
> through
> get_object_address(): 0003 adds the retry loop for it. And if DROP already 
> uses
> the retry loop then ALTER should probably use it too (also done in 0003 and 
> 0004).

Mandatory rebase attached.

Regards,

-- 
Bertrand Drouvot
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com
>From 4ff620ab3401573443fec13488386f84c896533b Mon Sep 17 00:00:00 2001
From: Bertrand Drouvot <[email protected]>
Date: Fri, 3 Jul 2026 12:28:42 +0000
Subject: [PATCH v5 1/4] Re-read subscription state after lock in
 AlterSubscription

AlterSubscription() reads the subscription's catalog state via GetSubscription()
before acquiring AccessExclusiveLock on the subscription object. A concurrent
session that commits a DROP or ALTER between the read and the lock acquisition
leaves the other session acting with stale information once it unblocks.

Fix by moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.

Remark:

The ownership check is intentionally not re-done after the lock because
AlterSubscriptionOwner() does not take AccessExclusiveLock on the subscription
object: it only takes RowExclusiveLock on the pg_subscription catalog table.
This means ownership can change regardless of our lock, making a re-check after
lock acquisition pointless. The existing "tuple concurrently updated" error from
CatalogTupleUpdate() already provides a protection if ownership changes
concurrently.

Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
 src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
 1 file changed, 23 insertions(+), 7 deletions(-)
 100.0% src/backend/commands/

diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index d3eef2a3efa..a2973aa229e 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1728,6 +1728,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
 			orig_conninfo_needed = false;
 	}
 
+	heap_freetuple(tup);
+
+	/* Lock the subscription so nobody else can do anything with it. */
+	LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+
+	/*
+	 * Re-read the subscription tuple after acquiring the lock. A concurrent
+	 * DROP or ALTER may have committed before we acquired the lock.
+	 */
+	tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+	if (!HeapTupleIsValid(tup))
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_OBJECT),
+				 errmsg("subscription \"%s\" does not exist",
+						stmt->subname)));
+
+	form = (Form_pg_subscription) GETSTRUCT(tup);
+
 	/*
 	 * Skip ACL checks on the subscription's foreign server, if any. If
 	 * changing the server (or replacing it with a raw connection), then the
@@ -1737,11 +1756,6 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
 	 */
 	sub = GetSubscription(subid, false, orig_conninfo_needed, false);
 
-	retain_dead_tuples = sub->retaindeadtuples;
-	origin = sub->origin;
-	max_retention = sub->maxretention;
-	retention_active = sub->retentionactive;
-
 	/*
 	 * Don't allow non-superuser modification of a subscription with
 	 * password_required=false.
@@ -1752,8 +1766,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
 				 errmsg("password_required=false is superuser-only"),
 				 errhint("Subscriptions with the password_required option set to false may only be created or modified by the superuser.")));
 
-	/* Lock the subscription so nobody else can do anything with it. */
-	LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+	retain_dead_tuples = sub->retaindeadtuples;
+	origin = sub->origin;
+	max_retention = sub->maxretention;
+	retention_active = sub->retentionactive;
 
 	/* Form a new tuple. */
 	memset(values, 0, sizeof(values));
-- 
2.34.1

>From 483d4cc028dab24cdfb46fdec76a1e12d78bf1e7 Mon Sep 17 00:00:00 2001
From: Bertrand Drouvot <[email protected]>
Date: Fri, 3 Jul 2026 11:54:29 +0000
Subject: [PATCH v5 2/4] Re-read subscription state after lock in
 DropSubscription

Similarly to what has been done for AlterSubscription() in XXXX, re-read the
subscription tuple after LockSharedObject() in DropSubscription().

A concurrent DROP or ALTER may have committed while we were waiting for the lock.
Without a re-read, DropSubscription would deal with invalid data, which currently
produces a confusing "tuple concurrently updated" elog() from CatalogTupleDelete().

Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
 src/backend/commands/subscriptioncmds.c | 40 ++++++++++++++++++-------
 1 file changed, 29 insertions(+), 11 deletions(-)
 100.0% src/backend/commands/

diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index a2973aa229e..9bb9fa90503 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2601,25 +2601,15 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
 		return;
 	}
 
-	datum = SysCacheGetAttr(SUBSCRIPTIONOID, tup,
-							Anum_pg_subscription_subconninfo, &isnull);
-	if (!isnull)
-		subconninfo = TextDatumGetCString(datum);
-
 	form = (Form_pg_subscription) GETSTRUCT(tup);
 	subid = form->oid;
-	subowner = form->subowner;
-	subserver = form->subserver;
-	subconflictlogrelid = form->subconflictlogrelid;
-	must_use_password = !superuser_arg(subowner) && form->subpasswordrequired;
 
 	/* must be owner */
 	if (!object_ownercheck(SubscriptionRelationId, subid, GetUserId()))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SUBSCRIPTION,
 					   stmt->subname);
 
-	/* DROP hook for the subscription being removed */
-	InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+	ReleaseSysCache(tup);
 
 	/*
 	 * Lock the subscription so nobody else can do anything with it (including
@@ -2627,6 +2617,34 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
 	 */
 	LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
 
+	/* DROP hook for the subscription being removed */
+	InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+
+	/*
+	 * Re-read the subscription tuple after acquiring the lock. A concurrent
+	 * ALTER or DROP may have committed before we acquired the lock.
+	 */
+	tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+	if (!HeapTupleIsValid(tup))
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_OBJECT),
+				 errmsg("subscription \"%s\" does not exist",
+						stmt->subname)));
+
+	form = (Form_pg_subscription) GETSTRUCT(tup);
+	subowner = form->subowner;
+	subserver = form->subserver;
+	subconflictlogrelid = form->subconflictlogrelid;
+	must_use_password = !superuser_arg(subowner) && form->subpasswordrequired;
+
+	datum = SysCacheGetAttr(SUBSCRIPTIONOID, tup,
+							Anum_pg_subscription_subconninfo, &isnull);
+	if (!isnull)
+		subconninfo = TextDatumGetCString(datum);
+	else
+		subconninfo = NULL;
+
 	/* Get subname */
 	datum = SysCacheGetAttrNotNull(SUBSCRIPTIONOID, tup,
 								   Anum_pg_subscription_subname);
-- 
2.34.1

>From cc7334ab73d69e9b289f69b905a416b8caf30c75 Mon Sep 17 00:00:00 2001
From: Bertrand Drouvot <[email protected]>
Date: Mon, 6 Jul 2026 04:44:13 +0000
Subject: [PATCH v5 3/4] Add invalidation-based retry loop for Alter/Drop
 Subscription

Following the approach of RangeVarGetRelidExtended() for relations, add a
retry loop that includes name resolution, ownership check, and lock
acquisition in AlterSubscription() and DropSubscription().

The loop records SharedInvalidMessageCounter, resolves the subscription name
to an OID, checks ownership, then locks the subscription. If the invalidation
counter changed (indicating concurrent DDL), we save the current OID and
retry. On the next iteration, if the name still resolves to the same OID,
we're done (already holding the correct lock). If it resolves to a different
OID, we release the old lock and acquire the new one.

This mirrors RangeVarGetRelidExtended()'s behavior: the lock is kept across
retries to avoid a window where another session could have committed concurrent
DDL modifying the ownership and/or the name resolution.

Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
 src/backend/commands/subscriptioncmds.c | 189 +++++++++++++++++-------
 1 file changed, 134 insertions(+), 55 deletions(-)
 100.0% src/backend/commands/

diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 9bb9fa90503..4acefbdcf7a 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -51,6 +51,7 @@
 #include "replication/worker_internal.h"
 #include "storage/lmgr.h"
 #include "storage/lock.h"
+#include "storage/sinval.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/guc.h"
@@ -1634,23 +1635,67 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
 
 	rel = table_open(SubscriptionRelationId, RowExclusiveLock);
 
-	/* Fetch the existing tuple. */
-	tup = SearchSysCacheCopy2(SUBSCRIPTIONNAME, ObjectIdGetDatum(MyDatabaseId),
-							  CStringGetDatum(stmt->subname));
+	/*
+	 * Lock the subscription so nobody else can do anything with it.
+	 *
+	 * Like RangeVarGetRelidExtended() does for relations, we resolve the
+	 * name, check ownership, and lock inside a loop. If invalidation messages
+	 * arrive (indicating concurrent DDL), we retry. We keep the lock held
+	 * across retries and only release it if the name resolves to a different
+	 * OID on the next iteration.
+	 */
+	{
+		Oid			oldSubId = InvalidOid;
+		bool		retry = false;
 
-	if (!HeapTupleIsValid(tup))
-		ereport(ERROR,
-				(errcode(ERRCODE_UNDEFINED_OBJECT),
-				 errmsg("subscription \"%s\" does not exist",
-						stmt->subname)));
+		for (;;)
+		{
+			uint64		inval_count = SharedInvalidMessageCounter;
 
-	form = (Form_pg_subscription) GETSTRUCT(tup);
-	subid = form->oid;
+			tup = SearchSysCacheCopy2(SUBSCRIPTIONNAME,
+									  ObjectIdGetDatum(MyDatabaseId),
+									  CStringGetDatum(stmt->subname));
 
-	/* must be owner */
-	if (!object_ownercheck(SubscriptionRelationId, subid, GetUserId()))
-		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SUBSCRIPTION,
-					   stmt->subname);
+			if (!HeapTupleIsValid(tup))
+				ereport(ERROR,
+						(errcode(ERRCODE_UNDEFINED_OBJECT),
+						 errmsg("subscription \"%s\" does not exist",
+								stmt->subname)));
+
+			form = (Form_pg_subscription) GETSTRUCT(tup);
+			subid = form->oid;
+
+			if (!object_ownercheck(SubscriptionRelationId, subid,
+								   GetUserId()))
+				aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SUBSCRIPTION,
+							   stmt->subname);
+
+			/*
+			 * If upon retry we get the same OID, the invalidation messages
+			 * did not change the final answer. So we're done. If we got a
+			 * different OID, unlock the old one and lock the new one below.
+			 */
+			if (retry)
+			{
+				if (subid == oldSubId)
+					break;
+				UnlockSharedObject(SubscriptionRelationId, oldSubId, 0,
+								   AccessExclusiveLock);
+			}
+
+			LockSharedObject(SubscriptionRelationId, subid, 0,
+							 AccessExclusiveLock);
+
+			/* If no invalidation messages, we're done. */
+			if (inval_count == SharedInvalidMessageCounter)
+				break;
+
+			/* Something may have changed, retry. */
+			retry = true;
+			oldSubId = subid;
+			heap_freetuple(tup);
+		}
+	}
 
 	/* parse and check options */
 	switch (stmt->kind)
@@ -1728,25 +1773,6 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
 			orig_conninfo_needed = false;
 	}
 
-	heap_freetuple(tup);
-
-	/* Lock the subscription so nobody else can do anything with it. */
-	LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
-
-	/*
-	 * Re-read the subscription tuple after acquiring the lock. A concurrent
-	 * DROP or ALTER may have committed before we acquired the lock.
-	 */
-	tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
-
-	if (!HeapTupleIsValid(tup))
-		ereport(ERROR,
-				(errcode(ERRCODE_UNDEFINED_OBJECT),
-				 errmsg("subscription \"%s\" does not exist",
-						stmt->subname)));
-
-	form = (Form_pg_subscription) GETSTRUCT(tup);
-
 	/*
 	 * Skip ACL checks on the subscription's foreign server, if any. If
 	 * changing the server (or replacing it with a raw connection), then the
@@ -2601,38 +2627,91 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
 		return;
 	}
 
-	form = (Form_pg_subscription) GETSTRUCT(tup);
-	subid = form->oid;
-
-	/* must be owner */
-	if (!object_ownercheck(SubscriptionRelationId, subid, GetUserId()))
-		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SUBSCRIPTION,
-					   stmt->subname);
-
 	ReleaseSysCache(tup);
 
 	/*
 	 * Lock the subscription so nobody else can do anything with it (including
 	 * the replication workers).
+	 *
+	 * Like RangeVarGetRelidExtended() does for relations, we resolve the
+	 * name, check ownership, and lock inside a loop. If invalidation messages
+	 * arrive (indicating concurrent DDL), we retry. We keep the lock held
+	 * across retries and only release it if the name resolves to a different
+	 * OID on the next iteration.
 	 */
-	LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+	{
+		Oid			oldSubId = InvalidOid;
+		bool		retry = false;
 
-	/* DROP hook for the subscription being removed */
-	InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+		for (;;)
+		{
+			uint64		inval_count = SharedInvalidMessageCounter;
 
-	/*
-	 * Re-read the subscription tuple after acquiring the lock. A concurrent
-	 * ALTER or DROP may have committed before we acquired the lock.
-	 */
-	tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+			tup = SearchSysCache2(SUBSCRIPTIONNAME,
+								  ObjectIdGetDatum(MyDatabaseId),
+								  CStringGetDatum(stmt->subname));
 
-	if (!HeapTupleIsValid(tup))
-		ereport(ERROR,
-				(errcode(ERRCODE_UNDEFINED_OBJECT),
-				 errmsg("subscription \"%s\" does not exist",
-						stmt->subname)));
+			if (!HeapTupleIsValid(tup))
+			{
+				if (retry)
+					UnlockSharedObject(SubscriptionRelationId, oldSubId, 0,
+									   AccessExclusiveLock);
+				table_close(rel, NoLock);
+
+				if (!stmt->missing_ok)
+					ereport(ERROR,
+							(errcode(ERRCODE_UNDEFINED_OBJECT),
+							 errmsg("subscription \"%s\" does not exist",
+									stmt->subname)));
+				else
+					ereport(NOTICE,
+							(errmsg("subscription \"%s\" does not exist, skipping",
+									stmt->subname)));
+
+				return;
+			}
+
+			form = (Form_pg_subscription) GETSTRUCT(tup);
+			subid = form->oid;
+
+			if (!object_ownercheck(SubscriptionRelationId, subid,
+								   GetUserId()))
+			{
+				ReleaseSysCache(tup);
+				aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SUBSCRIPTION,
+							   stmt->subname);
+			}
+
+			/*
+			 * If upon retry we get the same OID, the invalidation messages
+			 * did not change the final answer.  So we're done.  If we got a
+			 * different OID, unlock the old one and lock the new one below.
+			 */
+			if (retry)
+			{
+				if (subid == oldSubId)
+					break;
+				UnlockSharedObject(SubscriptionRelationId, oldSubId, 0,
+								   AccessExclusiveLock);
+			}
+
+			LockSharedObject(SubscriptionRelationId, subid, 0,
+							 AccessExclusiveLock);
+
+			/* If no invalidation messages, we're done. */
+			if (inval_count == SharedInvalidMessageCounter)
+				break;
+
+			/* Something may have changed, retry. */
+			retry = true;
+			oldSubId = subid;
+			ReleaseSysCache(tup);
+		}
+	}
+
+	/* DROP hook for the subscription being removed */
+	InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
 
-	form = (Form_pg_subscription) GETSTRUCT(tup);
 	subowner = form->subowner;
 	subserver = form->subserver;
 	subconflictlogrelid = form->subconflictlogrelid;
-- 
2.34.1

>From c84584a8d74f00b3acaf249b5f8e666d2c24075f Mon Sep 17 00:00:00 2001
From: Bertrand Drouvot <[email protected]>
Date: Fri, 3 Jul 2026 14:46:42 +0000
Subject: [PATCH v5 4/4] Add invalidation-based retry loop for AlterPublication

Apply the same RangeVarGetRelidExtended() style retry loop to
AlterPublication()'s tables/schemas branch that was added for subscriptions
in commit XXXX.

Previously, this branch resolved the publication name and checked ownership
at the top of AlterPublication(), then locked and re-read by OID. This left a
window where concurrent DDL could have modified the ownership and/or the name
resolution

Now the tables/schemas branch has its own complete retry loop: name
resolution, ownership check, and lock acquisition all inside the loop.

Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
 src/backend/commands/publicationcmds.c | 105 +++++++++++++++++--------
 1 file changed, 72 insertions(+), 33 deletions(-)
 100.0% src/backend/commands/

diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index 440adb356ad..dfd707bc7d7 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -39,6 +39,7 @@
 #include "parser/parse_relation.h"
 #include "rewrite/rewriteHandler.h"
 #include "storage/lmgr.h"
+#include "storage/sinval.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/inval.h"
@@ -1662,54 +1663,92 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
 
 	rel = table_open(PublicationRelationId, RowExclusiveLock);
 
-	tup = SearchSysCacheCopy1(PUBLICATIONNAME,
-							  CStringGetDatum(stmt->pubname));
+	if (stmt->options)
+	{
+		tup = SearchSysCacheCopy1(PUBLICATIONNAME,
+								  CStringGetDatum(stmt->pubname));
 
-	if (!HeapTupleIsValid(tup))
-		ereport(ERROR,
-				(errcode(ERRCODE_UNDEFINED_OBJECT),
-				 errmsg("publication \"%s\" does not exist",
-						stmt->pubname)));
+		if (!HeapTupleIsValid(tup))
+			ereport(ERROR,
+					(errcode(ERRCODE_UNDEFINED_OBJECT),
+					 errmsg("publication \"%s\" does not exist",
+							stmt->pubname)));
 
-	pubform = (Form_pg_publication) GETSTRUCT(tup);
+		pubform = (Form_pg_publication) GETSTRUCT(tup);
 
-	/* must be owner */
-	if (!object_ownercheck(PublicationRelationId, pubform->oid, GetUserId()))
-		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_PUBLICATION,
-					   stmt->pubname);
+		/* must be owner */
+		if (!object_ownercheck(PublicationRelationId, pubform->oid,
+							   GetUserId()))
+			aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_PUBLICATION,
+						   stmt->pubname);
 
-	if (stmt->options)
 		AlterPublicationOptions(pstate, stmt, rel, tup);
+	}
 	else
 	{
 		List	   *relations = NIL;
 		List	   *exceptrelations = NIL;
 		List	   *schemaidlist = NIL;
-		Oid			pubid = pubform->oid;
+		Oid			pubid;
 
-		ObjectsInPublicationToOids(stmt->pubobjects, pstate, &relations,
-								   &exceptrelations, &schemaidlist);
+		/*
+		 * Lock the publication so nobody else can do anything with it.
+		 *
+		 * Like RangeVarGetRelidExtended() does for relations, we resolve the
+		 * name, check ownership, and lock inside a loop. If invalidation
+		 * messages arrive (indicating concurrent DDL), we retry. We keep the
+		 * lock held across retries and only release it if the name resolves
+		 * to a different OID on the next iteration.
+		 */
+		{
+			Oid			oldPubId = InvalidOid;
+			bool		retry = false;
 
-		CheckAlterPublication(stmt, tup, relations, schemaidlist);
+			for (;;)
+			{
+				uint64		inval_count = SharedInvalidMessageCounter;
 
-		heap_freetuple(tup);
+				tup = SearchSysCacheCopy1(PUBLICATIONNAME,
+										  CStringGetDatum(stmt->pubname));
 
-		/* Lock the publication so nobody else can do anything with it. */
-		LockDatabaseObject(PublicationRelationId, pubid, 0,
-						   AccessExclusiveLock);
+				if (!HeapTupleIsValid(tup))
+					ereport(ERROR,
+							(errcode(ERRCODE_UNDEFINED_OBJECT),
+							 errmsg("publication \"%s\" does not exist",
+									stmt->pubname)));
 
-		/*
-		 * It is possible that by the time we acquire the lock on publication,
-		 * concurrent DDL has removed it. We can test this by checking the
-		 * existence of publication. We get the tuple again to avoid the risk
-		 * of any publication option getting changed.
-		 */
-		tup = SearchSysCacheCopy1(PUBLICATIONOID, ObjectIdGetDatum(pubid));
-		if (!HeapTupleIsValid(tup))
-			ereport(ERROR,
-					errcode(ERRCODE_UNDEFINED_OBJECT),
-					errmsg("publication \"%s\" does not exist",
-						   stmt->pubname));
+				pubform = (Form_pg_publication) GETSTRUCT(tup);
+				pubid = pubform->oid;
+
+				if (!object_ownercheck(PublicationRelationId, pubid,
+									   GetUserId()))
+					aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_PUBLICATION,
+								   stmt->pubname);
+
+				if (retry)
+				{
+					if (pubid == oldPubId)
+						break;
+					UnlockDatabaseObject(PublicationRelationId, oldPubId, 0,
+										 AccessExclusiveLock);
+				}
+
+				LockDatabaseObject(PublicationRelationId, pubid, 0,
+								   AccessExclusiveLock);
+
+				if (inval_count == SharedInvalidMessageCounter)
+					break;
+
+				retry = true;
+				oldPubId = pubid;
+				heap_freetuple(tup);
+			}
+		}
+
+		ObjectsInPublicationToOids(stmt->pubobjects, pstate, &relations,
+								   &exceptrelations, &schemaidlist);
+
+		CheckAlterPublication(stmt, tup, relations, schemaidlist);
 
 		relations = list_concat(relations, exceptrelations);
 		AlterPublicationTables(stmt, tup, relations, pstate->p_sourcetext,
-- 
2.34.1

Reply via email to