From 17d84c2b67f38cc3fc8b868c668297d59a299d97 Mon Sep 17 00:00:00 2001 From: Amit Kapila Date: Mon, 14 Sep 2026 09:35:02 +0530 Subject: [PATCH] Disallow SET UNLOGGED for tables in a publication's EXCEPT clause. Unlogged tables cannot be replicated, so they can neither be published nor be named in a publication's EXCEPT clause. ALTER TABLE ... SET UNLOGGED checked only whether the table was published, so a table in an EXCEPT clause could still be made unlogged, leaving a state that CREATE PUBLICATION would reject and that pg_dump could not restore. Author: Vignesh C Reviewed-by: Amit Kapila Reviewed-by: Chao Li Reviewed-by: shveta malik Reviewed-by: Hayato Kuroda Reviewed-by: Peter Smith Reviewed-by: Zhijie Hou Discussion: https://postgr.es/m/CALDaNm1r2MkGu6h8zgU1Kj1sX-FcMQ7wGTeLSnhx-5joiyXEvg@mail.gmail.com Backpatch-through: 19, where it was introduced --- src/backend/catalog/pg_publication.c | 19 +++++++++++++++++++ src/backend/commands/tablecmds.c | 13 +++++++------ src/include/catalog/pg_publication.h | 1 + src/test/regress/expected/publication.out | 10 ++++++++++ src/test/regress/sql/publication.sql | 8 ++++++++ 5 files changed, 45 insertions(+), 6 deletions(-) diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c index f9b31c9b15b..a480d7924d9 100644 --- a/src/backend/catalog/pg_publication.c +++ b/src/backend/catalog/pg_publication.c @@ -927,6 +927,25 @@ GetRelationExcludedPublications(Oid relid) return get_relation_publications(relid, true); } +/* + * Check whether the relation is referenced by any publication, either as a + * published relation or in a publication's EXCEPT clause. + */ +bool +RelationHasPublication(Oid relid) +{ + CatCList *pubrellist; + bool found; + + pubrellist = SearchSysCacheList1(PUBLICATIONRELMAP, + ObjectIdGetDatum(relid)); + found = (pubrellist->n_members > 0); + + ReleaseSysCacheList(pubrellist); + + return found; +} + /* * Internal function to get the list of relation oids for a publication. * diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 67829288ed4..e6cedb55337 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -19481,16 +19481,17 @@ ATPrepChangePersistence(AlteredTableInfo *tab, Relation rel, bool toLogged) } /* - * Check that the table is not part of any publication when changing to - * UNLOGGED, as UNLOGGED tables can't be published. + * UNLOGGED tables can neither be published nor be named in a + * publication's EXCEPT clause, so reject the change if the table is + * referenced by any publication. */ - if (!toLogged && - GetRelationIncludedPublications(RelationGetRelid(rel)) != NIL) + if (!toLogged && RelationHasPublication(RelationGetRelid(rel))) ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), - errmsg("cannot change table \"%s\" to unlogged because it is part of a publication", + errmsg("cannot change table \"%s\" to unlogged because it is referenced by a publication", RelationGetRelationName(rel)), - errdetail("Unlogged relations cannot be replicated."))); + errdetail("Unlogged relations cannot be published or excluded via an EXCEPT clause."), + errhint("Drop the table from the publication, or remove it from the publication's EXCEPT clause, first."))); /* * Check existing foreign key constraints to preserve the invariant that diff --git a/src/include/catalog/pg_publication.h b/src/include/catalog/pg_publication.h index 89b4bb14f62..5d1e6c54a85 100644 --- a/src/include/catalog/pg_publication.h +++ b/src/include/catalog/pg_publication.h @@ -157,6 +157,7 @@ extern Publication *GetPublication(Oid pubid); extern Publication *GetPublicationByName(const char *pubname, bool missing_ok); extern List *GetRelationIncludedPublications(Oid relid); extern List *GetRelationExcludedPublications(Oid relid); +extern bool RelationHasPublication(Oid relid); /*--------- * Expected values for pub_partopt parameter of diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out index 29e54b214a0..2405d37660d 100644 --- a/src/test/regress/expected/publication.out +++ b/src/test/regress/expected/publication.out @@ -270,6 +270,16 @@ CREATE PUBLICATION testpub_foralltables_excepttable2 FOR ALL TABLES EXCEPT (test ERROR: syntax error at or near "testpub_tbl1" LINE 1: ..._foralltables_excepttable2 FOR ALL TABLES EXCEPT (testpub_tb... ^ +-- A table in an EXCEPT clause cannot be changed to UNLOGGED. +CREATE TABLE testpub_exc_unlogged_tbl (a int); +CREATE PUBLICATION testpub_exc_unlogged FOR ALL TABLES EXCEPT (TABLE testpub_exc_unlogged_tbl); +-- fail - the table is referenced in a publication EXCEPT clause +ALTER TABLE testpub_exc_unlogged_tbl SET UNLOGGED; +ERROR: cannot change table "testpub_exc_unlogged_tbl" to unlogged because it is referenced by a publication +DETAIL: Unlogged relations cannot be published or excluded via an EXCEPT clause. +HINT: Drop the table from the publication, or remove it from the publication's EXCEPT clause, first. +DROP PUBLICATION testpub_exc_unlogged; +DROP TABLE testpub_exc_unlogged_tbl; --------------------------------------------- -- SET ALL TABLES/SEQUENCES --------------------------------------------- diff --git a/src/test/regress/sql/publication.sql b/src/test/regress/sql/publication.sql index 041e14a4de6..a87d8eb4252 100644 --- a/src/test/regress/sql/publication.sql +++ b/src/test/regress/sql/publication.sql @@ -124,6 +124,14 @@ CREATE PUBLICATION testpub_foralltables_excepttable1 FOR ALL TABLES EXCEPT (TABL -- fail - first table in the EXCEPT list should use TABLE keyword CREATE PUBLICATION testpub_foralltables_excepttable2 FOR ALL TABLES EXCEPT (testpub_tbl1, testpub_tbl2); +-- A table in an EXCEPT clause cannot be changed to UNLOGGED. +CREATE TABLE testpub_exc_unlogged_tbl (a int); +CREATE PUBLICATION testpub_exc_unlogged FOR ALL TABLES EXCEPT (TABLE testpub_exc_unlogged_tbl); +-- fail - the table is referenced in a publication EXCEPT clause +ALTER TABLE testpub_exc_unlogged_tbl SET UNLOGGED; +DROP PUBLICATION testpub_exc_unlogged; +DROP TABLE testpub_exc_unlogged_tbl; + --------------------------------------------- -- SET ALL TABLES/SEQUENCES ---------------------------------------------