From 4cebf4059a7462b94e0ae8d0aeeb657e396d0363 Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Sat, 28 Feb 2026 23:05:09 +0530
Subject: [PATCH v53 3/3] 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       | 17 +++++++----
 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                |  6 ++--
 src/test/regress/expected/publication.out     | 28 +++++++++++-------
 src/test/regress/sql/publication.sql          |  3 +-
 .../t/037_rep_changes_except_table.pl         | 29 +++++++++++++++++++
 8 files changed, 69 insertions(+), 29 deletions(-)

diff --git a/doc/src/sgml/ref/alter_publication.sgml b/doc/src/sgml/ref/alter_publication.sgml
index 7c28391033e..da046be474b 100644
--- a/doc/src/sgml/ref/alter_publication.sgml
+++ b/doc/src/sgml/ref/alter_publication.sgml
@@ -41,7 +41,7 @@ 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> [ * ] [, ... ]
+    [ 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 +63,11 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
    publication.  The <literal>SET</literal> clause will replace the list of
    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
+   will be removed.  The <literal>ADD</literal> clauses will add one or more
+   tables/schemas to the publication.  The <literal>DROP</literal> clauses
+   will remove one or more except tables/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 +236,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 cedbdd38570..12e0d1a2574 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -20395,7 +20395,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 before attaching the table."));
+				errhint("Modify the publication's EXCEPT clause using ALTER PUBLICATION ... SET EXCEPT TABLE or DROP EXCEPT TABLE before attaching the table."));
 	}
 
 	list_free(exceptpuboids);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 0544908fcb9..37dece5ed98 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -11032,7 +11032,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;
@@ -11049,18 +11049,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 f45f3b76523..9ab11971c1d 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -2317,8 +2317,10 @@ 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", "TABLES IN SCHEMA", "TABLE");
+	else if (Matches("ALTER", "PUBLICATION", MatchAny, "DROP", "EXCEPT"))
+		COMPLETE_WITH("TABLE");
+	/* ALTER PUBLICATION <name> SET */
 	else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET"))
 		COMPLETE_WITH("(", "EXCEPT", "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 3c70c51ddec..1b23116db28 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -128,14 +128,15 @@ Tables from schemas:
 
 -- 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
+ERROR:  EXCEPT TABLE clause allowed only for SET/DROP 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
+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
@@ -225,14 +226,15 @@ Not-null constraints:
 
 -- 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
+ERROR:  EXCEPT TABLE clause allowed only for SET/DROP 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
+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
 ---------------------------------------------
@@ -285,12 +287,18 @@ ALTER PUBLICATION testpub_foralltables_excepttable SET EXCEPT TABLE testpub_tbl1
 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 
+--------------------------+------------+---------------+---------+---------+---------+-----------+-------------------+----------
+ 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;
@@ -380,7 +388,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.
+HINT:  Modify the publication's EXCEPT clause using ALTER PUBLICATION ... SET EXCEPT TABLE or DROP EXCEPT TABLE before attaching the table.
 RESET client_min_messages;
 DROP TABLE testpub_root, testpub_part1, testpub_part2, tab_main;
 DROP PUBLICATION testpub8;
diff --git a/src/test/regress/sql/publication.sql b/src/test/regress/sql/publication.sql
index 957b2d183f9..a62fa0ac84d 100644
--- a/src/test/regress/sql/publication.sql
+++ b/src/test/regress/sql/publication.sql
@@ -138,8 +138,9 @@ CREATE PUBLICATION testpub_foralltables_excepttable1 FOR ALL TABLES EXCEPT TABLE
 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_rep_changes_except_table.pl b/src/test/subscription/t/037_rep_changes_except_table.pl
index 786799ed260..a0c15184bd3 100644
--- a/src/test/subscription/t/037_rep_changes_except_table.pl
+++ b/src/test/subscription/t/037_rep_changes_except_table.pl
@@ -166,6 +166,35 @@ $result =
   $node_subscriber->safe_psql('postgres', "SELECT count(*) FROM sch1.child1");
 is($result, qq(10), 'check replicated inserts on subscriber');
 
+# Remove sch1.tab1 from the publication's EXCEPT list so that it becomes part
+# of the ALL TABLES publication.
+$node_publisher->safe_psql('postgres',
+	"ALTER PUBLICATION tap_pub_schema DROP EXCEPT TABLE sch1.tab1, sch1.parent, only sch1.parent1");
+
+# Refresh the subscription so the subscriber picks up the updated
+# publication definition and initiates table synchronization.
+$node_subscriber->safe_psql('postgres',
+	"ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+
+# Wait for initial table sync to finish
+$node_subscriber->wait_for_subscription_sync($node_publisher,
+	'tap_sub_schema');
+
+$result =
+  $node_subscriber->safe_psql('postgres', "SELECT count(*) FROM sch1.tab1");
+is($result, qq(20), 'check replicated inserts on subscriber');
+
+# Insert additional rows on the publisher after synchronization.
+$node_publisher->safe_psql('postgres',
+	"INSERT INTO sch1.tab1 VALUES(generate_series(21,30))");
+
+$node_publisher->wait_for_catchup('tap_sub_schema');
+
+# Verify that the new inserts are also replicated.
+$result =
+  $node_subscriber->safe_psql('postgres', "SELECT count(*) FROM sch1.tab1");
+is($result, qq(30), 'check replicated inserts on subscriber');
+
 # cleanup
 $node_subscriber->safe_psql('postgres', "DROP SUBSCRIPTION tap_sub_schema");
 $node_publisher->safe_psql(
-- 
2.43.0

