From d484cbc3cb74c333ee6f6d184ddbcecce927bbd5 Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Fri, 6 Mar 2026 12:57:32 +0530
Subject: [PATCH v58 2/2] Support DROP EXCEPT TABLE in ALTER PUBLICATION

Extend ALTER PUBLICATION to support DROP EXCEPT TABLE for
publications defined with FOR ALL TABLES.
---
 doc/src/sgml/ref/alter_publication.sgml   | 18 +++++++++++-----
 src/backend/commands/publicationcmds.c    |  2 +-
 src/backend/commands/tablecmds.c          |  2 +-
 src/backend/parser/gram.y                 | 11 ++--------
 src/bin/psql/tab-complete.in.c            |  8 +++++--
 src/test/regress/expected/publication.out | 26 +++++++++++++++++------
 src/test/regress/sql/publication.sql      |  7 +++++-
 src/test/subscription/t/037_except.pl     | 21 ++++++++++++++++++
 8 files changed, 70 insertions(+), 25 deletions(-)

diff --git a/doc/src/sgml/ref/alter_publication.sgml b/doc/src/sgml/ref/alter_publication.sgml
index d13e128f21e..b27d92b42e5 100644
--- a/doc/src/sgml/ref/alter_publication.sgml
+++ b/doc/src/sgml/ref/alter_publication.sgml
@@ -41,7 +41,8 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
 
 <phrase>and <replaceable class="parameter">publication_drop_object</replaceable> is one of:</phrase>
 
-    TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ... ]
+    TABLE [ ONLY ] ( <replaceable class="parameter">table_name</replaceable> [ * ] [, ... ] )
+    EXCEPT TABLE [ ONLY ] ( <replaceable class="parameter">table_name</replaceable> [ * ] [, ... ] )
     TABLES IN SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ... ]
 
 <phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
@@ -63,10 +64,11 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
    publication.  The <literal>SET</literal> clause will replace the list of
    tables/except tables/schemas in the publication with the specified list; the
    existing tables/except 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
+   will be removed.  The <literal>ADD</literal> clause will add one or more
+   tables/schemas to the publication. The <literal>DROP</literal> clause will
+   remove one or more tables/except tables/schemas from the publication. Note
+   that adding tables/schemas or dropping except tables 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
@@ -235,6 +237,12 @@ ALTER PUBLICATION mypublication SET TABLE users (user_id, firstname, lastname),
    Replace the publication's EXCEPT table list:
 <programlisting>
 ALTER PUBLICATION mypublication SET EXCEPT TABLE (users, departments);
+</programlisting></para>
+
+  <para>
+   Remove tables from the publication's EXCEPT table list:
+<programlisting>
+ALTER PUBLICATION mypublication DROP EXCEPT TABLE (users, departments);
 </programlisting></para>
 
   <para>
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index 166fe3b0c3d..f43edd33098 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -1562,7 +1562,7 @@ CheckAlterPublication(AlterPublicationStmt *stmt, HeapTuple tup,
 				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."));
+				errdetail("EXCEPT Tables cannot be added to or dropped from publications that are not defined as FOR ALL TABLES."));
 }
 
 /*
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index ab77f11d7a4..e64d66ce9fe 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -20401,7 +20401,7 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd,
 							  RelationGetRelationName(attachrel),
 							  pubnames.data),
 				errdetail("The publication EXCEPT clause cannot contain tables that are partitions."),
-				errhint("Modify the publication's EXCEPT clause using ALTER PUBLICATION ... SET EXCEPT TABLE."));
+				errhint("Modify the publication's EXCEPT clause using ALTER PUBLICATION ... SET/DROP EXCEPT TABLE."));
 	}
 
 	list_free(exceptpuboids);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 7e99824b8b1..71f2e27816e 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -11031,7 +11031,7 @@ AlterPublicationStmt:
 					if (has_except_table)
 						ereport(ERROR,
 								errcode(ERRCODE_SYNTAX_ERROR),
-								errmsg("EXCEPT TABLE clause allowed only for SET clause"));
+								errmsg("EXCEPT TABLE clause allowed only for SET/DROP clause"));
 
 					n->action = AP_AddObjects;
 					$$ = (Node *) n;
@@ -11048,18 +11048,11 @@ AlterPublicationStmt:
 				}
 			| ALTER PUBLICATION name DROP pub_obj_list
 				{
-					bool has_except_table = false;
 					AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
 
 					n->pubname = $3;
 					n->pubobjects = $5;
-					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"));
-
+					preprocess_pubobj_list(n->pubobjects, yyscanner);
 					n->action = AP_DropObjects;
 					$$ = (Node *) n;
 				}
diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c
index 411e4d0cb7f..5f1fcd43b23 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -2317,8 +2317,12 @@ match_previous_words(int pattern_id,
 		COMPLETE_WITH(",");
 	/* ALTER PUBLICATION <name> DROP */
 	else if (Matches("ALTER", "PUBLICATION", MatchAny, "DROP"))
-		COMPLETE_WITH("TABLES IN SCHEMA", "TABLE");
-		/* ALTER PUBLICATION <name> SET */
+		COMPLETE_WITH("EXCEPT TABLE (", "TABLES IN SCHEMA", "TABLE");
+	else if (Matches("ALTER", "PUBLICATION", MatchAny, "DROP", "EXCEPT"))
+		COMPLETE_WITH("TABLE (");
+	else if (Matches("ALTER", "PUBLICATION", MatchAny, "DROP", "EXCEPT", "TABLE"))
+		COMPLETE_WITH("(");
+	/* ALTER PUBLICATION <name> SET */
 	else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET"))
 		COMPLETE_WITH("(", "EXCEPT TABLE (", "TABLES IN SCHEMA", "TABLE");
 	else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "EXCEPT"))
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index a50d66a2bd5..3347b59cfa2 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -126,10 +126,14 @@ ALTER PUBLICATION testpub_fortable SET TABLES IN SCHEMA pub_test;
 Tables from schemas:
     "pub_test"
 
+-- fail - can't drop an EXCEPT TABLE from 'FOR TABLE' publication
+ALTER PUBLICATION testpub_fortable DROP EXCEPT TABLE (testpub_tbl1);
+ERROR:  publication "testpub_fortable" is not defined as FOR ALL TABLES
+DETAIL:  EXCEPT Tables cannot be added to or dropped from publications that are not defined as FOR ALL TABLES.
 -- 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.
+DETAIL:  EXCEPT Tables cannot be added to or dropped from 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
@@ -217,10 +221,14 @@ Not-null constraints:
  regress_publication_user | t          | f             | t       | t       | f       | f         | none              | f        | 
 (1 row)
 
+-- fail - can't drop an EXCEPT TABLE from schema publication
+ALTER PUBLICATION testpub_forschema DROP EXCEPT TABLE (pub_test.testpub_nopk);
+ERROR:  publication "testpub_forschema" is not defined as FOR ALL TABLES
+DETAIL:  EXCEPT Tables cannot be added to or dropped from publications that are not defined as FOR ALL TABLES.
 -- 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.
+DETAIL:  EXCEPT Tables cannot be added to or dropped from publications that are not defined as FOR ALL TABLES.
 ---------------------------------------------
 -- EXCEPT TABLE tests for normal tables
 ---------------------------------------------
@@ -275,12 +283,18 @@ ALTER PUBLICATION testpub_foralltables_excepttable SET EXCEPT TABLE (testpub_tbl
 Except tables:
     "public.testpub_tbl1"
 
--- fail - Dropping EXCEPT table is not supported.
+-- Drop table from the EXCEPT list of a FOR ALL TABLES publication.
 ALTER PUBLICATION testpub_foralltables_excepttable DROP EXCEPT TABLE (testpub_tbl1);
-ERROR:  EXCEPT TABLE clause allowed only for SET clause
+ \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        | 
+(1 row)
+
 -- 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
+ERROR:  EXCEPT TABLE clause allowed only for SET/DROP 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;
@@ -388,7 +402,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.
+HINT:  Modify the publication's EXCEPT clause using ALTER PUBLICATION ... SET/DROP EXCEPT 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 ce0ece4abc0..42703ddac5d 100644
--- a/src/test/regress/sql/publication.sql
+++ b/src/test/regress/sql/publication.sql
@@ -74,6 +74,8 @@ 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 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);
 
@@ -108,6 +110,8 @@ SELECT pubname, puballtables FROM pg_publication WHERE pubname = 'testpub_forall
 \d+ testpub_tbl2
 \dRp+ testpub_foralltables
 
+-- 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);
 
@@ -132,8 +136,9 @@ CREATE PUBLICATION test_pub_forexcepttable FOR EXCEPT TABLE (testpub_tbl1);
 ALTER PUBLICATION testpub_foralltables_excepttable SET EXCEPT TABLE (testpub_tbl1);
 \dRp+ testpub_foralltables_excepttable
 
--- fail - Dropping EXCEPT table is not supported.
+-- Drop table from the EXCEPT list of a FOR ALL TABLES publication.
 ALTER PUBLICATION testpub_foralltables_excepttable DROP EXCEPT TABLE (testpub_tbl1);
+ \dRp+ testpub_foralltables_excepttable
 
 -- fail - Adding EXCEPT table is not supported.
 ALTER PUBLICATION testpub_foralltables_excepttable ADD EXCEPT TABLE (testpub_tbl1);
diff --git a/src/test/subscription/t/037_except.pl b/src/test/subscription/t/037_except.pl
index 647ae8433ad..45f3707d69e 100644
--- a/src/test/subscription/t/037_except.pl
+++ b/src/test/subscription/t/037_except.pl
@@ -186,6 +186,27 @@ $result =
 is($result, qq(20),
 	'check that the data is copied as the tab1 is removed from EXCEPT TABLE clause');
 
+# Remove tab2 from the publication's EXCEPT list so that it becomes part
+# of the ALL TABLES publication.
+$node_publisher->safe_psql('postgres',
+	"ALTER PUBLICATION tab_pub DROP 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 table synchronization occurs once tab2 is removed from the
+# EXCEPT TABLE clause via SET EXCEPT TABLE.
+$result =
+  $node_subscriber->safe_psql('postgres', "SELECT count(*) FROM tab2");
+is($result, qq(10), 
+	'check that the data is copied as the tab1 is removed from EXCEPT TABLE clause');
+
 # cleanup
 $node_subscriber->safe_psql(
 	'postgres', qq(
-- 
2.43.0

