From d64e025081722a1fe53641b9ab38cfa81e766695 Mon Sep 17 00:00:00 2001 From: Vignesh C Date: Wed, 9 Sep 2026 09:40:32 +0530 Subject: [PATCH v2] Fix ALTER PUBLICATION race with concurrent SET ALL TABLES AlterPublicationOptions() uses the puballtables value from a publication tuple fetched before acquiring the publication lock. A concurrent ALTER PUBLICATION ... SET ALL TABLES can change the publication state while the command waits for the lock, leaving the value stale. Use the current publication state after acquiring the lock when deciding whether to check for explicitly added relations. --- src/backend/commands/publicationcmds.c | 43 ++++++++++++++++++++------ 1 file changed, 33 insertions(+), 10 deletions(-) diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c index 96838730fe1..091797db402 100644 --- a/src/backend/commands/publicationcmds.c +++ b/src/backend/commands/publicationcmds.c @@ -1057,15 +1057,6 @@ AlterPublicationOptions(ParseState *pstate, AlterPublicationStmt *stmt, if (!pubform->puballtables && publish_via_partition_root_given && !publish_via_partition_root) { - /* - * Lock the publication so nobody else can do anything with it. This - * prevents concurrent alter to add partitioned table(s) with WHERE - * clause(s) and/or column lists which we don't allow when not - * publishing via root. - */ - LockDatabaseObject(PublicationRelationId, pubform->oid, 0, - AccessShareLock); - root_relids = GetIncludedPublicationRelations(pubform->oid, PUBLICATION_PART_ROOT); @@ -1659,6 +1650,7 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt) Relation rel; HeapTuple tup; Form_pg_publication pubform; + Oid pubid; rel = table_open(PublicationRelationId, RowExclusiveLock); @@ -1678,14 +1670,45 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt) aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_PUBLICATION, stmt->pubname); + pubid = pubform->oid; + if (stmt->options) + { + /* + * Lock the publication so nobody else can change it while we validate + * and update it. This prevents a concurrent alter from adding + * partitioned table(s) with WHERE clause(s) and/or column lists, + * which we don't allow when not publishing via root, and it also + * prevents the publication definition from changing (for example, via + * SET ALL TABLES) between the validation performed by + * AlterPublicationOptions() and the subsequent catalog update. + */ + LockDatabaseObject(PublicationRelationId, pubid, 0, + AccessShareLock); + + heap_freetuple(tup); + + /* + * 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)); + AlterPublicationOptions(pstate, stmt, rel, tup); + } else { List *relations = NIL; List *exceptrelations = NIL; List *schemaidlist = NIL; - Oid pubid = pubform->oid; ObjectsInPublicationToOids(stmt->pubobjects, pstate, &relations, &exceptrelations, &schemaidlist); -- 2.55.0