From 33d0cb5ed12bf5ebdd6f8fe3cd8bd28fc2706bc1 Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Wed, 4 Mar 2026 16:28:32 +0530
Subject: [PATCH v57 1/2] Support SET EXCEPT TABLE in ALTER PUBLICATION

Extend ALTER PUBLICATION to support SET EXCEPT TABLE for
publications defined with FOR ALL TABLES.
---
 doc/src/sgml/ref/alter_publication.sgml   | 29 ++++++++++-----
 src/backend/catalog/pg_publication.c      | 10 +++---
 src/backend/commands/publicationcmds.c    | 39 ++++++++++++++------
 src/backend/commands/tablecmds.c          |  3 +-
 src/backend/parser/gram.y                 | 44 +++++++++++++++++++----
 src/bin/psql/tab-complete.in.c            |  6 ++--
 src/include/catalog/pg_publication.h      |  3 +-
 src/test/regress/expected/publication.out | 37 +++++++++++++++++++
 src/test/regress/sql/publication.sql      | 24 +++++++++++++
 src/test/subscription/t/037_except.pl     | 38 ++++++++++++++++++--
 10 files changed, 198 insertions(+), 35 deletions(-)

diff --git a/doc/src/sgml/ref/alter_publication.sgml b/doc/src/sgml/ref/alter_publication.sgml
index 028770f2149..579dd4bfba4 100644
--- a/doc/src/sgml/ref/alter_publication.sgml
+++ b/doc/src/sgml/ref/alter_publication.sgml
@@ -21,18 +21,24 @@ PostgreSQL documentation
 
  <refsynopsisdiv>
 <synopsis>
-ALTER PUBLICATION <replaceable class="parameter">name</replaceable> ADD <replaceable class="parameter">publication_object</replaceable> [, ...]
-ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET <replaceable class="parameter">publication_object</replaceable> [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> ADD <replaceable class="parameter">publication_add_object</replaceable> [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET <replaceable class="parameter">publication_set_object</replaceable> [, ...]
 ALTER PUBLICATION <replaceable class="parameter">name</replaceable> DROP <replaceable class="parameter">publication_drop_object</replaceable> [, ...]
 ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )
 ALTER PUBLICATION <replaceable class="parameter">name</replaceable> OWNER TO { <replaceable>new_owner</replaceable> | CURRENT_ROLE | CURRENT_USER | SESSION_USER }
 ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <replaceable>new_name</replaceable>
 
-<phrase>where <replaceable class="parameter">publication_object</replaceable> is one of:</phrase>
+<phrase>where <replaceable class="parameter">publication_add_object</replaceable> is one of:</phrase>
 
     TABLE <replaceable class="parameter">table_and_columns</replaceable> [, ... ]
     TABLES IN SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ... ]
 
+<phrase>where <replaceable class="parameter">publication_set_object</replaceable> is one of:</phrase>
+
+    EXCEPT TABLE [ ONLY ] ( <replaceable class="parameter">table_name</replaceable> [, ... ] )
+    TABLE <replaceable class="parameter">table_and_columns</replaceable> [, ... ]
+    TABLES IN SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ... ]
+
 <phrase>and <replaceable class="parameter">publication_drop_object</replaceable> is one of:</phrase>
 
     TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ... ]
@@ -55,11 +61,12 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
   <para>
    The first three variants change which tables/schemas are part of the
    publication.  The <literal>SET</literal> clause will replace the list of
-   tables/schemas in the publication with the specified list; the existing
-   tables/schemas that were present in the publication will be removed.  The
-   <literal>ADD</literal> and <literal>DROP</literal> clauses will add and
-   remove one or more tables/schemas from the publication.  Note that adding
-   tables/schemas to a publication that is already subscribed to will require an
+   except tables/tables/schemas in the publication with the specified list; the
+   existing except tables/tables/schemas that were present in the publication
+   will be removed.  The <literal>ADD</literal> and <literal>DROP</literal>
+   clauses will add and remove one or more tables/schemas from the publication.
+   Note that adding tables/schemas to a publication that is already subscribed
+   to will require an
    <link linkend="sql-altersubscription-params-refresh-publication">
    <literal>ALTER SUBSCRIPTION ... REFRESH PUBLICATION</literal></link> action on the
    subscribing side in order to become effective. Note also that
@@ -222,6 +229,12 @@ ALTER PUBLICATION mypublication ADD TABLE users (user_id, firstname), department
    Change the set of columns published for a table:
 <programlisting>
 ALTER PUBLICATION mypublication SET TABLE users (user_id, firstname, lastname), TABLE departments;
+</programlisting></para>
+
+  <para>
+   Replace the publication's EXCEPT table list:
+<programlisting>
+ALTER PUBLICATION mypublication SET EXCEPT TABLE (users, departments);
 </programlisting></para>
 
   <para>
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index aadc7c202c6..facccbe74ea 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -440,7 +440,7 @@ attnumstoint2vector(Bitmapset *attrs)
  */
 ObjectAddress
 publication_add_relation(Oid pubid, PublicationRelInfo *pri,
-						 bool if_not_exists)
+						 bool if_not_exists, bool is_alter)
 {
 	Relation	rel;
 	HeapTuple	tup;
@@ -544,10 +544,12 @@ publication_add_relation(Oid pubid, PublicationRelInfo *pri,
 
 	/*
 	 * Relations excluded via the EXCEPT clause do not need explicit
-	 * invalidation as CreatePublication() function invalidates all relations
-	 * as part of defining a FOR ALL TABLES publication.
+	 * invalidation during CREATE PUBLICATION, as CreatePublication() function
+	 * invalidates all relations as part of defining a FOR ALL TABLES
+	 * publication. For ALTER PUBLICATION, explicit invalidation is still
+	 * required.
 	 */
-	if (!pri->except)
+	if (!pri->except || is_alter)
 	{
 		/*
 		 * Invalidate relcache so that publication info is rebuilt.
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index 6a3ca4751fa..166fe3b0c3d 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -1272,15 +1272,24 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
 		PublicationDropTables(pubid, rels, false);
 	else						/* AP_SetObjects */
 	{
-		List	   *oldrelids = GetIncludedPublicationRelations(pubid,
-																PUBLICATION_PART_ROOT);
+		bool		isexcept = pubform->puballtables;
+		List	   *oldrelids;
 		List	   *delrels = NIL;
 		ListCell   *oldlc;
 
-		TransformPubWhereClauses(rels, queryString, pubform->pubviaroot);
+		if (isexcept)
+			oldrelids = GetExcludedPublicationTables(pubid,
+													 PUBLICATION_PART_ROOT);
+		else
+		{
+			oldrelids = GetIncludedPublicationRelations(pubid,
+														PUBLICATION_PART_ROOT);
 
-		CheckPubRelationColumnList(stmt->pubname, rels, publish_schema,
-								   pubform->pubviaroot);
+			TransformPubWhereClauses(rels, queryString, pubform->pubviaroot);
+
+			CheckPubRelationColumnList(stmt->pubname, rels, publish_schema,
+									   pubform->pubviaroot);
+		}
 
 		/*
 		 * To recreate the relation list for the publication, look for
@@ -1488,7 +1497,7 @@ AlterPublicationSchemas(AlterPublicationStmt *stmt,
  */
 static void
 CheckAlterPublication(AlterPublicationStmt *stmt, HeapTuple tup,
-					  List *tables, List *schemaidlist)
+					  List *tables, List *excepttables, List *schemaidlist)
 {
 	Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
 
@@ -1546,6 +1555,14 @@ CheckAlterPublication(AlterPublicationStmt *stmt, HeapTuple tup,
 						   NameStr(pubform->pubname)),
 					errdetail("Tables or sequences cannot be added to or dropped from FOR ALL SEQUENCES publications."));
 	}
+
+	/* Check that user is allowed to manipulate the publication tables. */
+	if (excepttables && !pubform->puballtables)
+		ereport(ERROR,
+				errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				errmsg("publication \"%s\" is not defined as FOR ALL TABLES",
+					   NameStr(pubform->pubname)),
+				errdetail("EXCEPT Tables cannot be added to publications that are not defined as FOR ALL TABLES."));
 }
 
 /*
@@ -1591,10 +1608,8 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
 		ObjectsInPublicationToOids(stmt->pubobjects, pstate, &relations,
 								   &exceptrelations, &schemaidlist);
 
-		/* EXCEPT clause is not supported with ALTER PUBLICATION */
-		Assert(exceptrelations == NIL);
-
-		CheckAlterPublication(stmt, tup, relations, schemaidlist);
+		CheckAlterPublication(stmt, tup, relations, exceptrelations,
+							  schemaidlist);
 
 		heap_freetuple(tup);
 
@@ -1615,6 +1630,7 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
 					errmsg("publication \"%s\" does not exist",
 						   stmt->pubname));
 
+		relations = list_concat(relations, exceptrelations);
 		AlterPublicationTables(stmt, tup, relations, pstate->p_sourcetext,
 							   schemaidlist != NIL);
 		AlterPublicationSchemas(stmt, tup, schemaidlist);
@@ -1953,7 +1969,8 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
 			aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(rel->rd_rel->relkind),
 						   RelationGetRelationName(rel));
 
-		obj = publication_add_relation(pubid, pub_rel, if_not_exists);
+		obj = publication_add_relation(pubid, pub_rel, if_not_exists,
+									   (stmt != NULL));
 		if (stmt)
 		{
 			EventTriggerCollectSimpleCommand(obj, InvalidObjectAddress,
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 85242dcc245..104b2d74d1d 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -20400,7 +20400,8 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd,
 							  list_length(exceptpuboids),
 							  RelationGetRelationName(attachrel),
 							  pubnames.data),
-				errdetail("The publication EXCEPT clause cannot contain tables that are partitions."));
+				errdetail("The publication EXCEPT clause cannot contain tables that are partitions."),
+				errhint("Modify the publication's EXCEPT clause using ALTER PUBLICATION ... SET EXCEPT TABLE before attaching the table."));
 	}
 
 	list_free(exceptpuboids);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 3c3e24324a8..7e99824b8b1 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -207,7 +207,7 @@ static void preprocess_pub_all_objtype_list(List *all_objects_list,
 											bool *all_tables,
 											bool *all_sequences,
 											core_yyscan_t yyscanner);
-static void preprocess_pubobj_list(List *pubobjspec_list,
+static bool preprocess_pubobj_list(List *pubobjspec_list,
 								   core_yyscan_t yyscanner);
 static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
@@ -10830,12 +10830,19 @@ CreatePublicationStmt:
 				}
 			| CREATE PUBLICATION name FOR pub_obj_list opt_definition
 				{
+					bool has_except_table;
 					CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
 
 					n->pubname = $3;
 					n->options = $6;
 					n->pubobjects = (List *) $5;
-					preprocess_pubobj_list(n->pubobjects, yyscanner);
+					has_except_table = preprocess_pubobj_list(n->pubobjects,
+															  yyscanner);
+					if (has_except_table)
+						ereport(ERROR,
+								errcode(ERRCODE_SYNTAX_ERROR),
+								errmsg("EXCEPT TABLE clause allowed only for ALL TABLES PUBLICATION"));
+
 					$$ = (Node *) n;
 				}
 		;
@@ -10933,8 +10940,12 @@ PublicationObjSpec:
 
 pub_obj_list:	PublicationObjSpec
 					{ $$ = list_make1($1); }
+			| EXCEPT TABLE '(' pub_except_obj_list ')'
+					{ $$ = $4; }
 			| pub_obj_list ',' PublicationObjSpec
 					{ $$ = lappend($1, $3); }
+			| pub_obj_list ',' EXCEPT TABLE '(' pub_except_obj_list ')'
+					{ $$ = list_concat($1,$6); }
 	;
 
 opt_pub_except_clause:
@@ -11010,11 +11021,18 @@ AlterPublicationStmt:
 				}
 			| ALTER PUBLICATION name ADD_P pub_obj_list
 				{
+					bool has_except_table = false;
 					AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
 
 					n->pubname = $3;
 					n->pubobjects = $5;
-					preprocess_pubobj_list(n->pubobjects, yyscanner);
+					has_except_table = preprocess_pubobj_list(n->pubobjects,
+															  yyscanner);
+					if (has_except_table)
+						ereport(ERROR,
+								errcode(ERRCODE_SYNTAX_ERROR),
+								errmsg("EXCEPT TABLE clause allowed only for SET clause"));
+
 					n->action = AP_AddObjects;
 					$$ = (Node *) n;
 				}
@@ -11030,11 +11048,18 @@ AlterPublicationStmt:
 				}
 			| ALTER PUBLICATION name DROP pub_obj_list
 				{
+					bool has_except_table = false;
 					AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
 
 					n->pubname = $3;
 					n->pubobjects = $5;
-					preprocess_pubobj_list(n->pubobjects, yyscanner);
+					has_except_table = preprocess_pubobj_list(n->pubobjects,
+															  yyscanner);
+					if (has_except_table)
+						ereport(ERROR,
+								errcode(ERRCODE_SYNTAX_ERROR),
+								errmsg("EXCEPT TABLE clause allowed only for SET clause"));
+
 					n->action = AP_DropObjects;
 					$$ = (Node *) n;
 				}
@@ -19880,16 +19905,19 @@ preprocess_pub_all_objtype_list(List *all_objects_list, List **pubobjects,
 /*
  * Process pubobjspec_list to check for errors in any of the objects and
  * convert PUBLICATIONOBJ_CONTINUATION into appropriate PublicationObjSpecType.
+ *
+ * Return true if an EXCEPT table is found.
  */
-static void
+static bool
 preprocess_pubobj_list(List *pubobjspec_list, core_yyscan_t yyscanner)
 {
 	ListCell   *cell;
 	PublicationObjSpec *pubobj;
 	PublicationObjSpecType prevobjtype = PUBLICATIONOBJ_CONTINUATION;
+	bool		foundexcepttable = false;
 
 	if (!pubobjspec_list)
-		return;
+		return false;
 
 	pubobj = (PublicationObjSpec *) linitial(pubobjspec_list);
 	if (pubobj->pubobjtype == PUBLICATIONOBJ_CONTINUATION)
@@ -19926,6 +19954,8 @@ preprocess_pubobj_list(List *pubobjspec_list, core_yyscan_t yyscanner)
 				pubobj->name = NULL;
 			}
 		}
+		else if (pubobj->pubobjtype == PUBLICATIONOBJ_EXCEPT_TABLE)
+			foundexcepttable = true;
 		else if (pubobj->pubobjtype == PUBLICATIONOBJ_TABLES_IN_SCHEMA ||
 				 pubobj->pubobjtype == PUBLICATIONOBJ_TABLES_IN_CUR_SCHEMA)
 		{
@@ -19960,6 +19990,8 @@ preprocess_pubobj_list(List *pubobjspec_list, core_yyscan_t yyscanner)
 
 		prevobjtype = pubobj->pubobjtype;
 	}
+
+	return foundexcepttable;
 }
 
 /*----------
diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c
index f8c0865ca89..b0710a55422 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -2318,9 +2318,11 @@ match_previous_words(int pattern_id,
 	/* ALTER PUBLICATION <name> DROP */
 	else if (Matches("ALTER", "PUBLICATION", MatchAny, "DROP"))
 		COMPLETE_WITH("TABLES IN SCHEMA", "TABLE");
-	/* ALTER PUBLICATION <name> SET */
+		/* ALTER PUBLICATION <name> SET */
 	else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET"))
-		COMPLETE_WITH("(", "TABLES IN SCHEMA", "TABLE");
+		COMPLETE_WITH("(", "EXCEPT", "TABLES IN SCHEMA", "TABLE");
+	else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "EXCEPT"))
+		COMPLETE_WITH("TABLE");
 	else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD|DROP|SET", "TABLES", "IN", "SCHEMA"))
 		COMPLETE_WITH_QUERY_PLUS(Query_for_list_of_schemas
 								 " AND nspname NOT LIKE E'pg\\\\_%%'",
diff --git a/src/include/catalog/pg_publication.h b/src/include/catalog/pg_publication.h
index e25228713e7..f40c0fe2f95 100644
--- a/src/include/catalog/pg_publication.h
+++ b/src/include/catalog/pg_publication.h
@@ -198,7 +198,8 @@ extern bool is_schema_publication(Oid pubid);
 extern bool check_and_fetch_column_list(Publication *pub, Oid relid,
 										MemoryContext mcxt, Bitmapset **cols);
 extern ObjectAddress publication_add_relation(Oid pubid, PublicationRelInfo *pri,
-											  bool if_not_exists);
+											  bool if_not_exists,
+											  bool is_alter);
 extern Bitmapset *pub_collist_validate(Relation targetrel, List *columns);
 extern ObjectAddress publication_add_schema(Oid pubid, Oid schemaid,
 											bool if_not_exists);
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 681d2564ed5..72480eecf71 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -126,6 +126,16 @@ ALTER PUBLICATION testpub_fortable SET TABLES IN SCHEMA pub_test;
 Tables from schemas:
     "pub_test"
 
+-- fail - can't add an EXCEPT TABLE to 'FOR TABLE' publication
+ALTER PUBLICATION testpub_fortable ADD EXCEPT TABLE (testpub_tbl1);
+ERROR:  EXCEPT TABLE clause allowed only for SET clause
+-- fail - can't drop an EXCEPT TABLE from 'FOR TABLE' publication
+ALTER PUBLICATION testpub_fortable DROP EXCEPT TABLE (testpub_tbl1);
+ERROR:  EXCEPT TABLE clause allowed only for SET clause
+-- fail - can't set an EXCEPT TABLE to 'FOR TABLE' publication
+ALTER PUBLICATION testpub_fortable SET EXCEPT TABLE (testpub_tbl1);
+ERROR:  publication "testpub_fortable" is not defined as FOR ALL TABLES
+DETAIL:  EXCEPT Tables cannot be added to publications that are not defined as FOR ALL TABLES.
 SET client_min_messages = 'ERROR';
 CREATE PUBLICATION testpub_forschema FOR TABLES IN SCHEMA pub_test;
 -- should be able to create publication with schema and table of the same
@@ -213,6 +223,16 @@ Not-null constraints:
  regress_publication_user | t          | f             | t       | t       | f       | f         | none              | f        | 
 (1 row)
 
+-- fail - can't add an EXCEPT TABLE to schema publication
+ALTER PUBLICATION testpub_forschema ADD EXCEPT TABLE (pub_test.testpub_nopk);
+ERROR:  EXCEPT TABLE clause allowed only for SET clause
+-- fail - can't drop an EXCEPT TABLE from schema publication
+ALTER PUBLICATION testpub_forschema DROP EXCEPT TABLE (pub_test.testpub_nopk);
+ERROR:  EXCEPT TABLE clause allowed only for SET clause
+-- fail - can't set an EXCEPT TABLE to schema publication
+ALTER PUBLICATION testpub_forschema SET EXCEPT TABLE (pub_test.testpub_nopk);
+ERROR:  publication "testpub_forschema" is not defined as FOR ALL TABLES
+DETAIL:  EXCEPT Tables cannot be added to publications that are not defined as FOR ALL TABLES.
 ---------------------------------------------
 -- EXCEPT TABLE tests for normal tables
 ---------------------------------------------
@@ -254,6 +274,22 @@ Except Publications:
     "testpub_foralltables_excepttable"
     "testpub_foralltables_excepttable1"
 
+-- Replace the publication EXCEPT table list with a specific EXCEPT table.
+ALTER PUBLICATION testpub_foralltables_excepttable SET EXCEPT TABLE (testpub_tbl1);
+\dRp+ testpub_foralltables_excepttable
+                                                 Publication testpub_foralltables_excepttable
+          Owner           | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Generated columns | Via root | Description 
+--------------------------+------------+---------------+---------+---------+---------+-----------+-------------------+----------+-------------
+ regress_publication_user | t          | f             | t       | t       | t       | t         | none              | f        | 
+Except tables:
+    "public.testpub_tbl1"
+
+-- fail - Dropping EXCEPT table is not supported.
+ALTER PUBLICATION testpub_foralltables_excepttable DROP EXCEPT TABLE (testpub_tbl1);
+ERROR:  EXCEPT TABLE clause allowed only for SET clause
+-- fail - Adding EXCEPT table is not supported.
+ALTER PUBLICATION testpub_foralltables_excepttable ADD EXCEPT TABLE (testpub_tbl1);
+ERROR:  EXCEPT TABLE clause allowed only for SET clause
 RESET client_min_messages;
 DROP TABLE testpub_tbl2;
 DROP PUBLICATION testpub_foralltables, testpub_fortable, testpub_forschema, testpub_for_tbl_schema, testpub_foralltables_excepttable, testpub_foralltables_excepttable1;
@@ -361,6 +397,7 @@ CREATE TABLE tab_main (a int) PARTITION BY RANGE(a);
 ALTER TABLE tab_main ATTACH PARTITION testpub_root FOR VALUES FROM (0) TO (200);
 ERROR:  cannot attach table "testpub_root" as partition because it is referenced in publication "testpub8" EXCEPT clause
 DETAIL:  The publication EXCEPT clause cannot contain tables that are partitions.
+HINT:  Modify the publication's EXCEPT clause using ALTER PUBLICATION ... SET EXCEPT TABLE before attaching the table.
 RESET client_min_messages;
 DROP TABLE testpub_root, testpub_part1, tab_main;
 DROP PUBLICATION testpub8;
diff --git a/src/test/regress/sql/publication.sql b/src/test/regress/sql/publication.sql
index 405579dad52..3fa86987a34 100644
--- a/src/test/regress/sql/publication.sql
+++ b/src/test/regress/sql/publication.sql
@@ -74,6 +74,13 @@ ALTER PUBLICATION testpub_fortable DROP TABLES IN SCHEMA pub_test;
 ALTER PUBLICATION testpub_fortable SET TABLES IN SCHEMA pub_test;
 \dRp+ testpub_fortable
 
+-- fail - can't add an EXCEPT TABLE to 'FOR TABLE' publication
+ALTER PUBLICATION testpub_fortable ADD EXCEPT TABLE (testpub_tbl1);
+-- fail - can't drop an EXCEPT TABLE from 'FOR TABLE' publication
+ALTER PUBLICATION testpub_fortable DROP EXCEPT TABLE (testpub_tbl1);
+-- fail - can't set an EXCEPT TABLE to 'FOR TABLE' publication
+ALTER PUBLICATION testpub_fortable SET EXCEPT TABLE (testpub_tbl1);
+
 SET client_min_messages = 'ERROR';
 CREATE PUBLICATION testpub_forschema FOR TABLES IN SCHEMA pub_test;
 -- should be able to create publication with schema and table of the same
@@ -105,6 +112,13 @@ SELECT pubname, puballtables FROM pg_publication WHERE pubname = 'testpub_forall
 \d+ testpub_tbl2
 \dRp+ testpub_foralltables
 
+-- fail - can't add an EXCEPT TABLE to schema publication
+ALTER PUBLICATION testpub_forschema ADD EXCEPT TABLE (pub_test.testpub_nopk);
+-- fail - can't drop an EXCEPT TABLE from schema publication
+ALTER PUBLICATION testpub_forschema DROP EXCEPT TABLE (pub_test.testpub_nopk);
+-- fail - can't set an EXCEPT TABLE to schema publication
+ALTER PUBLICATION testpub_forschema SET EXCEPT TABLE (pub_test.testpub_nopk);
+
 ---------------------------------------------
 -- EXCEPT TABLE tests for normal tables
 ---------------------------------------------
@@ -119,6 +133,16 @@ CREATE PUBLICATION testpub_foralltables_excepttable1 FOR ALL TABLES EXCEPT TABLE
 -- in the EXCEPT TABLE clause
 \d testpub_tbl1
 
+-- Replace the publication EXCEPT table list with a specific EXCEPT table.
+ALTER PUBLICATION testpub_foralltables_excepttable SET EXCEPT TABLE (testpub_tbl1);
+\dRp+ testpub_foralltables_excepttable
+
+-- fail - Dropping EXCEPT table is not supported.
+ALTER PUBLICATION testpub_foralltables_excepttable DROP EXCEPT TABLE (testpub_tbl1);
+
+-- fail - Adding EXCEPT table is not supported.
+ALTER PUBLICATION testpub_foralltables_excepttable ADD EXCEPT TABLE (testpub_tbl1);
+
 RESET client_min_messages;
 DROP TABLE testpub_tbl2;
 DROP PUBLICATION testpub_foralltables, testpub_fortable, testpub_forschema, testpub_for_tbl_schema, testpub_foralltables_excepttable, testpub_foralltables_excepttable1;
diff --git a/src/test/subscription/t/037_except.pl b/src/test/subscription/t/037_except.pl
index 2729df4d5c0..647ae8433ad 100644
--- a/src/test/subscription/t/037_except.pl
+++ b/src/test/subscription/t/037_except.pl
@@ -152,18 +152,52 @@ $result =
   $node_subscriber->safe_psql('postgres', "SELECT count(*) FROM child1");
 is($result, qq(10), 'check replicated inserts on subscriber');
 
+$node_publisher->safe_psql('postgres',
+	"CREATE TABLE tab2 AS SELECT generate_series(1,10) AS a"
+);
+$node_subscriber->safe_psql('postgres',
+	"CREATE TABLE tab2 (a int)"
+);
+
+# Replace the EXCEPT TABLE list so that only tab2 is excluded.
+$node_publisher->safe_psql('postgres',
+       "ALTER PUBLICATION tab_pub SET EXCEPT TABLE (tab2)");
+
+# Refresh the subscription so the subscriber picks up the updated
+# publication definition and initiates table synchronization.
+$node_subscriber->safe_psql('postgres',
+       "ALTER SUBSCRIPTION tab_sub REFRESH PUBLICATION");
+
+# Wait for initial table sync to finish
+$node_subscriber->wait_for_subscription_sync($node_publisher,
+       'tab_sub');
+
+# Verify that initial table synchronization does not occur for tables
+# listed in the EXCEPT TABLE clause.
+$result =
+  $node_subscriber->safe_psql('postgres', "SELECT count(*) FROM tab2");
+is($result, qq(0),
+	'check there is no initial data copied for the tables specified in the EXCEPT TABLE clause');
+
+# Verify that table synchronization occurs once tab1 is removed from the
+# EXCEPT TABLE clause via SET EXCEPT TABLE.
+$result =
+  $node_subscriber->safe_psql('postgres', "SELECT count(*) FROM tab1");
+is($result, qq(20),
+	'check that the data is copied as the tab1 is removed from EXCEPT TABLE clause');
+
 # cleanup
 $node_subscriber->safe_psql(
 	'postgres', qq(
 	DROP SUBSCRIPTION tab_sub;
 	TRUNCATE TABLE tab1;
-	DROP TABLE parent, parent1, child, child1;
+	DROP TABLE parent, parent1, child, child1, tab2;
 ));
 $node_publisher->safe_psql(
 	'postgres', qq(
 	DROP PUBLICATION tab_pub;
 	TRUNCATE TABLE tab1;
-    DROP TABLE parent, parent1, child, child1;
+    DROP TABLE parent, parent1, child, child1, tab2;
 ));
 
 # ============================================
-- 
2.43.0

