public inbox for [email protected]  
help / color / mirror / Atom feed
From: Shlok Kyal <[email protected]>
To: Peter Smith <[email protected]>
Cc: vignesh C <[email protected]>
Cc: Amit Kapila <[email protected]>
Cc: Dilip Kumar <[email protected]>
Cc: shveta malik <[email protected]>
Cc: Masahiko Sawada <[email protected]>
Cc: Hayato Kuroda (Fujitsu) <[email protected]>
Cc: Nisha Moond <[email protected]>
Cc: Ashutosh Sharma <[email protected]>
Cc: David G. Johnston <[email protected]>
Cc: Zhijie Hou (Fujitsu) <[email protected]>
Cc: YeXiu <[email protected]>
Cc: Ian Lawrence Barwick <[email protected]>
Cc: Bharath Rupireddy <[email protected]>
Cc: PostgreSQL Hackers <[email protected]>
Subject: Re: Skipping schema changes in publication
Date: Fri, 27 Mar 2026 13:20:14 +0530
Message-ID: <CANhcyEVLp5kbaVR4=nh1jR4YWqv7YpVx_SnYoshbnOrnY79_fg@mail.gmail.com> (raw)
In-Reply-To: <CAHut+PthJx_gZJNgF=mWSpkWjQJ58KyhrZ7D7CkX_TVq12wv7A@mail.gmail.com>
References: <CAJpy0uB20MhJJEaPJdm31t4fykJ+fChA_76jU2P9HX5knbJvAA@mail.gmail.com>
	<CAD21AoCC8XuwfX62qKBSfHUAoww_XB3_84HjswgL9jxQy696yw@mail.gmail.com>
	<OS9PR01MB12149EA0C749BC29C7C949E32F544A@OS9PR01MB12149.jpnprd01.prod.outlook.com>
	<CAD21AoBbZEshyaK0PeiF_J4_S75EfF=Gcs=C+X-osoVoUnawuQ@mail.gmail.com>
	<CAHut+PssG+sHeV+Xo0g=S7xBb9FgDPjHYDR4iSuOdYXDq-Psng@mail.gmail.com>
	<CAA4eK1LaSfAG7UAuy1xpnkWKM_YtrPuhbgAxYBFY3Sp_v_KqoQ@mail.gmail.com>
	<CAD21AoAb8E8krN63cY_U7RQs9v-zkqUZyKT_UVKDwKfExtvTBg@mail.gmail.com>
	<CAA4eK1K1GLR7DXSABayQE+pWM=v1ODD6haPYxuDhAYwJN5gjzg@mail.gmail.com>
	<CALDaNm2kvFahDDvdgCNo=Nv-COz_N5Xw8YmzQBN2bd3g=N81fQ@mail.gmail.com>
	<CAHut+PsCqTR_kQu5M1TqBjnE6KM5cO22aH8boHfpMa_gSJBmWg@mail.gmail.com>
	<CALDaNm2OOgmNOPpABUU+AXzHhfrLG9HMfSd3jfNe=t3dc-kp1Q@mail.gmail.com>
	<CAJpy0uCN4gfP7fSt__KdW5wYQ82650Z6L4YLnjRHZTQ1yir1mg@mail.gmail.com>
	<CALDaNm32+c6RTE5xR6sJ=MZGgwEtzjkxpov_Hu70MXfbvmN+=Q@mail.gmail.com>
	<CAHut+PtQbK9USLepyzArXFoNuLok1MsBu_Jg4UT=koZocombFw@mail.gmail.com>
	<CALDaNm1tKuU479T=winBqoMb3MzO3Mta2juk8W3t2R5ps0_zyg@mail.gmail.com>
	<CALDaNm3jpYs7ALcU6m5=Li=udidjZoW5dMpyCFs8QHGaf0S8+A@mail.gmail.com>
	<CAJpy0uCWS=ybBKG-kRAfdWEe1VBNj+VqpAUUoT8MPaNS7EggiA@mail.gmail.com>
	<CAA4eK1LMM-P4NatbkjG-96B7hHC7KYrJ8XTsCZQy0jLO9Qj4Bw@mail.gmail.com>
	<CAJpy0uAyf71QSYitBf4WbCYq22HDR6LPdxB12TpTgTRpczwphw@mail.gmail.com>
	<CAFiTN-s5PW121mBGKin20YEQpZkWefMehmP=v+0onzEaMQpwdw@mail.gmail.com>
	<CAA4eK1LBf5asit18HcqcFinOkdCjD6Lk2Eid9PDhtH6acwYb8w@mail.gmail.com>
	<CALDaNm3cdoT58E3QtYCwBbzyxYJjoS2k7Q0EgzR9ta6fyDGHSg@mail.gmail.com>
	<CAHut+PthJx_gZJNgF=mWSpkWjQJ58KyhrZ7D7CkX_TVq12wv7A@mail.gmail.com>

On Fri, 27 Mar 2026 at 07:06, Peter Smith <[email protected]> wrote:
>
> On Fri, Mar 27, 2026 at 2:24 AM vignesh C <[email protected]> wrote:
> >
> ...
> > > By now multiple people (Dilip Kumar, Peter Smith, Sawada Masahiko)
> > > have preferred the alternate syntax, to move TABLE inside () to make
> > > specifying inclusion and exclusion list in a similar way. Unless we
> > > have more feedback, I think we can change it now.
> >
> > Attached patch has the changes for the same.
> >
>
> Hi Vignesh.
>
> Here are some review comments for the new syntax patch 0001.
>
> ======
> doc/src/sgml/ref/create_publication.sgml
>
> 1.
> -    ALL TABLES [ EXCEPT TABLE ( <replaceable
> class="parameter">except_table_object</replaceable> [, ... ] ) ]
> +    ALL TABLES [ EXCEPT ( TABLE  <replaceable
> class="parameter">except_table_object</replaceable> [, ... ] ) ]
>
> I don't think this is correct. To have the same flexibility as the
> TABLE inclusion lists, the TABLE keyword needs to be inside the
> <except_table_object>, and there need to be more ellipses there too.
>
> e.g. Like this:
>
> and publication_all_object is one of:
>     ALL TABLES [ EXCEPT ( except_table_object [, ... ] ) ]
>     ALL SEQUENCES
>
> and except_table_object is:
>     TABLE { [ ONLY ] table_name [ * ] } [, ...]
>
> ~~~
>
> 2.
> The CREATE PUBLICATION page still refers to "EXCEPT TABLE" in multiple places.
>
> Perhaps now it should be called "EXCEPT (TABLE) clause" or just
> "EXCEPT clause" or something else.
>
> ======
> doc/src/sgml/ref/alter_publication.sgml
>
> 3.
> -    ALL TABLES [ EXCEPT TABLE ( <replaceable
> class="parameter">except_table_object</replaceable> [, ... ] ) ]
> +    ALL TABLES [ EXCEPT ( TABLE  <replaceable
> class="parameter">except_table_object</replaceable> [, ... ] ) ]
>
> This ALTER PUBLICATION synopsis should have the same/similar changes
> as in the above review comment above for the CREATE PUBLICATION
> synopsis.
>
> e.g.
>
> and publication_all_object is one of:
>     ALL TABLES [ EXCEPT ( except_table_object [, ... ] ) ]
>     ALL SEQUENCES
>
> and except_table_object is:
>     TABLE { [ ONLY ] table_name [ * ] } [, ...]
>
> ~~~
>
> 4.
> Also, similar to the earlier review comment, this ALTER PUBLICATION
> page still refers to "EXCEPT TABLE" in multiple places.
>
> Perhaps now it should be called "EXCEPT (TABLE) clause" or just "EXCEPT clause".
>
> ======
> src/backend/parser/gram.y
>
> 5.
> - EXCEPT TABLE '(' pub_except_obj_list ')' { $$ = $4; }
> + EXCEPT '(' TABLE pub_except_obj_list ')' { $$ = $4; }
>
> Same review comment as my above synopsis comments.
>
> This patch implementation only supports EXCEPT (TABLE t1,t2,t3);
>
> Specifically, it doesn't have the same flexibility you get from a
> table inclusion list.
>
> e.g. IMO cmd should also work for:
> EXCEPT (TABLE t1, TABLE t2,t3);
> etc.
>
> ======
> src/bin/pg_dump/pg_dump.c
>
> 6.
> Code comment still refers to "EXCEPT TABLE clause", but it doesn't
> look like that anymore.
>
> ======
> src/test/regress/sql/publication.sql
>
> 7.
> Comments still refer to "EXCEPT TABLE" and "EXCEPT TABLE clause", but
> it doesn't look like that anymore.
>
> ~~~
>
> 8.
> There needs to be additional tests to confirm that the syntax has the
> same flexibility as TABLE inclusion lists do
>
> e.g. all these variations below are valid and equivalent:
> ... EXCEPT (TABLE t1, t2, t3, t4);
> ... EXCEPT (TABLE t1, t2, TABLE t3, t4);
> ... EXCEPT (TABLE t1, t2, TABLE t3, t4);
> ... EXCEPT (TABLE t1, TABLE t2, TABLE t3, TABLE t4);
>
I have not tested all the scenarios as per Amit's suggestion in [1]. I
have just modified an existing test.

> ======
> src/test/subscription/t/037_except.pl
>
> 9.
> Comments still refer to "EXCEPT TABLE", but it doesn't look like that anymore.
>
> ======
> (more files)
>
> 10.
> There are still more source files (not currently part of this patch)
> that are still calling this the "EXCEPT TABLE" clause, even though the
> syntax is different now.
>
> e.g. "src/backend/commands/publicationcmds.c" and others.
>
> Need to search the master source to find every affected comment.
>
Hi Peter,

I have addressed the comments. Attached the updated patch.

[1]: https://www.postgresql.org/message-id/[email protected]...

Thanks,
Shlok Kyal


Attachments:

  [application/octet-stream] v2-0001-Change-syntax-of-EXCEPT-TABLE-clause-in-publicati.patch (46.6K, 2-v2-0001-Change-syntax-of-EXCEPT-TABLE-clause-in-publicati.patch)
  download | inline diff:
From 96ad85e6991c0eee3d2f2599750f6a8539c9b591 Mon Sep 17 00:00:00 2001
From: Vignesh C <[email protected]>
Date: Thu, 26 Mar 2026 20:30:14 +0530
Subject: [PATCH v2] Change syntax of EXCEPT TABLE clause in publication
 commands

Adjust the syntax of the EXCEPT clause in CREATE/ALTER PUBLICATION
to move the TABLE keyword inside the relation list.

Old syntax:
CREATE PUBLICATION ... FOR ALL TABLES EXCEPT TABLE (t1, ...);
ALTER PUBLICATION  ... SET ALL TABLES EXCEPT TABLE (t1, ...);

New syntax:
CREATE PUBLICATION ... FOR ALL TABLES EXCEPT (TABLE t1, ...);
ALTER PUBLICATION  ... SET ALL TABLES EXCEPT (TABLE t1, ...);

This change is purely syntactic and does not alter behavior.
---
 doc/src/sgml/catalogs.sgml                |  2 +-
 doc/src/sgml/logical-replication.sgml     |  2 +-
 doc/src/sgml/ref/alter_publication.sgml   | 24 +++++-----
 doc/src/sgml/ref/create_publication.sgml  | 16 +++----
 src/backend/catalog/pg_publication.c      |  2 +-
 src/backend/parser/gram.y                 | 56 +++++++++++++++++++++--
 src/bin/pg_dump/pg_dump.c                 | 14 +++---
 src/bin/pg_dump/t/002_pg_dump.pl          | 12 ++---
 src/bin/psql/describe.c                   |  2 +-
 src/bin/psql/tab-complete.in.c            | 24 +++++-----
 src/test/regress/expected/publication.out | 52 +++++++++++----------
 src/test/regress/sql/publication.sql      | 51 +++++++++++----------
 src/test/subscription/t/037_except.pl     | 53 +++++++++++----------
 13 files changed, 182 insertions(+), 128 deletions(-)

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 61ffe804ee8..92d179b5ee0 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -7096,7 +7096,7 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
       </para>
       <para>
        True if the table is excluded from the publication. See
-       <link linkend="sql-createpublication-params-for-except-table"><literal>EXCEPT TABLE</literal></link>.
+       <link linkend="sql-createpublication-params-for-except-table"><literal>EXCEPT</literal></link>.
       </para></entry>
      </row>
 
diff --git a/doc/src/sgml/logical-replication.sgml b/doc/src/sgml/logical-replication.sgml
index 72c8d3d59bd..23b268273b9 100644
--- a/doc/src/sgml/logical-replication.sgml
+++ b/doc/src/sgml/logical-replication.sgml
@@ -119,7 +119,7 @@
    <xref linkend="logical-replication-sequences"/>. When a publication is
    created with <literal>FOR ALL TABLES</literal>, a table or set of tables can
    be explicitly excluded from publication using the
-   <link linkend="sql-createpublication-params-for-except-table"><literal>EXCEPT TABLE</literal></link>
+   <link linkend="sql-createpublication-params-for-except-table"><literal>EXCEPT</literal></link>
    clause.
   </para>
 
diff --git a/doc/src/sgml/ref/alter_publication.sgml b/doc/src/sgml/ref/alter_publication.sgml
index 7f0e46380cc..eba1620abdc 100644
--- a/doc/src/sgml/ref/alter_publication.sgml
+++ b/doc/src/sgml/ref/alter_publication.sgml
@@ -35,7 +35,7 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
 
 <phrase>and <replaceable class="parameter">publication_all_object</replaceable> is one of:</phrase>
 
-    ALL TABLES [ EXCEPT TABLE ( <replaceable class="parameter">except_table_object</replaceable> [, ... ] ) ]
+    ALL TABLES [ EXCEPT ( <replaceable class="parameter">except_table_object</replaceable> [, ... ] ) ]
     ALL SEQUENCES
 
 <phrase>and <replaceable class="parameter">publication_drop_object</replaceable> is one of:</phrase>
@@ -49,7 +49,7 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
 
 <phrase>and <replaceable class="parameter">except_table_object</replaceable> is:</phrase>
 
-    [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ]
+    TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ]
 </synopsis>
  </refsynopsisdiv>
 
@@ -71,7 +71,7 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
    The third variant either modifies the included tables/schemas
    or marks the publication as <literal>FOR ALL SEQUENCES</literal> or
    <literal>FOR ALL TABLES</literal>, optionally using
-   <literal>EXCEPT TABLE</literal> to exclude specific tables. The
+   <literal>EXCEPT</literal> to exclude specific tables. The
    <literal>SET ALL TABLES</literal> clause can transform an empty publication,
    or one defined for <literal>ALL SEQUENCES</literal> (or both
    <literal>ALL TABLES</literal> and <literal>ALL SEQUENCES</literal>), into
@@ -80,13 +80,13 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
    one defined for <literal>ALL TABLES</literal> (or both
    <literal>ALL TABLES</literal> and <literal>ALL SEQUENCES</literal>), into a
    publication defined for <literal>ALL SEQUENCES</literal>. In addition,
-   <literal>SET ALL TABLES</literal> can be used to update the
-   <literal>EXCEPT TABLE</literal> list of a <literal>FOR ALL TABLES</literal>
-   publication. If <literal>EXCEPT TABLE</literal> is specified with a list of
-   tables, the existing exclusion list is replaced with the specified tables.
-   If <literal>EXCEPT TABLE</literal> is omitted, the existing exclusion list
-   is cleared. The <literal>SET</literal> clause, when used with a publication
-   defined with <literal>FOR TABLE</literal> or
+   <literal>SET ALL TABLES</literal> can be used to update the tables specified
+   in the <literal>EXCEPT</literal> clause of a
+   <literal>FOR ALL TABLES</literal> publication. If <literal>EXCEPT</literal>
+   is specified with a list of tables, the existing exclusion list is replaced
+   with the specified tables. If <literal>EXCEPT</literal> is omitted, the
+   existing exclusion list is cleared. The <literal>SET</literal> clause, when
+   used with a publication defined with <literal>FOR TABLE</literal> or
    <literal>FOR TABLES IN SCHEMA</literal>, replaces the list of tables/schemas
    in the publication with the specified list; the existing tables or schemas
    that were present in the publication will be removed.
@@ -263,9 +263,9 @@ ALTER PUBLICATION mypublication SET TABLE users (user_id, firstname, lastname),
 </programlisting></para>
 
   <para>
-   Replace the publication's EXCEPT TABLE list:
+   Replace the table list in the publication's EXCEPT clause:
 <programlisting>
-ALTER PUBLICATION mypublication SET ALL TABLES EXCEPT TABLE (users, departments);
+ALTER PUBLICATION mypublication SET ALL TABLES EXCEPT (TABLE users, departments);
 </programlisting></para>
 
   <para>
diff --git a/doc/src/sgml/ref/create_publication.sgml b/doc/src/sgml/ref/create_publication.sgml
index 77066ef680b..6004e1421d4 100644
--- a/doc/src/sgml/ref/create_publication.sgml
+++ b/doc/src/sgml/ref/create_publication.sgml
@@ -32,7 +32,7 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
 
 <phrase>and <replaceable class="parameter">publication_all_object</replaceable> is one of:</phrase>
 
-    ALL TABLES [ EXCEPT TABLE ( <replaceable class="parameter">except_table_object</replaceable> [, ... ] ) ]
+    ALL TABLES [ EXCEPT ( <replaceable class="parameter">except_table_object</replaceable> [, ... ] ) ]
     ALL SEQUENCES
 
 <phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
@@ -41,7 +41,7 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
 
 <phrase>and <replaceable class="parameter">except_table_object</replaceable> is:</phrase>
 
-    [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ]
+    TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ]
 </synopsis>
  </refsynopsisdiv>
 
@@ -169,7 +169,7 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
      <para>
       Marks the publication as one that replicates changes for all tables in
       the database, including tables created in the future. Tables listed in
-      EXCEPT TABLE are excluded from the publication.
+      <literal>EXCEPT</literal> clause are excluded from the publication.
      </para>
     </listitem>
    </varlistentry>
@@ -190,7 +190,7 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
    </varlistentry>
 
    <varlistentry id="sql-createpublication-params-for-except-table">
-    <term><literal>EXCEPT TABLE</literal></term>
+    <term><literal>EXCEPT</literal></term>
     <listitem>
      <para>
       This clause specifies a list of tables to be excluded from the
@@ -206,7 +206,7 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
      </para>
      <para>
       For partitioned tables, only the root partitioned table may be specified
-      in <literal>EXCEPT TABLE</literal>. Doing so excludes the root table and
+      in <literal>EXCEPT</literal>. Doing so excludes the root table and
       all of its partitions from replication. The optional
       <literal>ONLY</literal> and <literal>*</literal> has no effect for
       partitioned tables.
@@ -214,7 +214,7 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
      <para>
       There can be a case where a subscription includes multiple publications.
       In such a case, a table or partition that is included in one publication
-      and listed in the <literal>EXCEPT TABLE</literal> clause of another is
+      and listed in the <literal>EXCEPT</literal> clause of another is
       considered included for replication.
      </para>
     </listitem>
@@ -532,7 +532,7 @@ CREATE PUBLICATION all_tables_sequences FOR ALL TABLES, ALL SEQUENCES;
    Create a publication that publishes all changes in all tables except
    <structname>users</structname> and <structname>departments</structname>:
 <programlisting>
-CREATE PUBLICATION all_tables_except FOR ALL TABLES EXCEPT TABLE (users, departments);
+CREATE PUBLICATION all_tables_except FOR ALL TABLES EXCEPT (TABLE users, departments);
 </programlisting>
   </para>
 
@@ -541,7 +541,7 @@ CREATE PUBLICATION all_tables_except FOR ALL TABLES EXCEPT TABLE (users, departm
    all changes in all tables except <structname>users</structname> and
    <structname>departments</structname>:
 <programlisting>
-CREATE PUBLICATION all_sequences_tables_except FOR ALL SEQUENCES, ALL TABLES EXCEPT TABLE (users, departments);
+CREATE PUBLICATION all_sequences_tables_except FOR ALL SEQUENCES, ALL TABLES EXCEPT (TABLE users, departments);
 </programlisting>
   </para>
  </refsect1>
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index c92ff3f51c3..8d62c5bb251 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -1001,7 +1001,7 @@ GetAllTablesPublications(void)
  * publication.
  *
  * For a FOR ALL TABLES publication, the returned list excludes tables mentioned
- * in EXCEPT TABLE clause.
+ * in EXCEPT clause.
  */
 List *
 GetAllPublicationRelations(Oid pubid, char relkind, bool pubviaroot)
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 0fea726cdd5..edda2a1e1c9 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -209,6 +209,8 @@ static void preprocess_pub_all_objtype_list(List *all_objects_list,
 											core_yyscan_t yyscanner);
 static void preprocess_pubobj_list(List *pubobjspec_list,
 								   core_yyscan_t yyscanner);
+static void preprocess_except_pubobj_list(List *pubexceptobjspec_list,
+										 core_yyscan_t yyscanner);
 static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
 %}
@@ -11256,7 +11258,7 @@ AlterOwnerStmt: ALTER AGGREGATE aggregate_with_argtypes OWNER TO RoleSpec
  *
  * pub_all_obj_type is one of:
  *
- *		TABLES [EXCEPT TABLE ( table [, ...] )]
+ *		TABLES [EXCEPT (TABLE table [, ...] )]
  *		SEQUENCES
  *
  * CREATE PUBLICATION FOR pub_obj [, ...] [WITH options]
@@ -11399,7 +11401,7 @@ pub_obj_list:	PublicationObjSpec
 	;
 
 opt_pub_except_clause:
-			EXCEPT TABLE '(' pub_except_obj_list ')'	{ $$ = $4; }
+			EXCEPT '(' pub_except_obj_list ')'			{ $$ = $3; }
 			| /*EMPTY*/									{ $$ = NIL; }
 		;
 
@@ -11408,6 +11410,7 @@ PublicationAllObjSpec:
 					{
 						$$ = makeNode(PublicationAllObjSpec);
 						$$->pubobjtype = PUBLICATION_ALL_TABLES;
+						preprocess_except_pubobj_list($3, yyscanner);
 						$$->except_tables = $3;
 						$$->location = @1;
 					}
@@ -11426,12 +11429,21 @@ pub_all_obj_type_list:	PublicationAllObjSpec
 	;
 
 PublicationExceptObjSpec:
-			 relation_expr
+			 TABLE relation_expr
 				{
 					$$ = makeNode(PublicationObjSpec);
 					$$->pubobjtype = PUBLICATIONOBJ_EXCEPT_TABLE;
 					$$->pubtable = makeNode(PublicationTable);
 					$$->pubtable->except = true;
+					$$->pubtable->relation = $2;
+					$$->location = @2;
+				}
+			 | relation_expr
+				{
+					$$ = makeNode(PublicationObjSpec);
+					$$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
+					$$->pubtable = makeNode(PublicationTable);
+					$$->pubtable->except = true;
 					$$->pubtable->relation = $1;
 					$$->location = @1;
 				}
@@ -11462,7 +11474,7 @@ pub_except_obj_list: PublicationExceptObjSpec
  *
  * pub_all_obj_type is one of:
  *
- *		ALL TABLES [ EXCEPT TABLE ( table_name [, ...] ) ]
+ *		ALL TABLES [ EXCEPT ( TABLE table_name [, ...] ) ]
  *		ALL SEQUENCES
  *
  *****************************************************************************/
@@ -20764,6 +20776,42 @@ preprocess_pubobj_list(List *pubobjspec_list, core_yyscan_t yyscanner)
 	}
 }
 
+/*
+ * Similar with preprocess_pubobj_list(), but used for the EXCEPT clause.
+ */
+static void
+preprocess_except_pubobj_list(List *pubexceptobjspec_list,
+							  core_yyscan_t yyscanner)
+{
+	ListCell   *cell;
+	PublicationObjSpec *pubobj;
+	PublicationObjSpecType prevobjtype = PUBLICATIONOBJ_CONTINUATION;
+
+	if (!pubexceptobjspec_list)
+		return;
+
+	pubobj = (PublicationObjSpec *) linitial(pubexceptobjspec_list);
+
+	Assert(pubobj->pubobjtype == PUBLICATIONOBJ_EXCEPT_TABLE);
+
+	if (pubobj->pubobjtype == PUBLICATIONOBJ_CONTINUATION)
+		ereport(ERROR,
+				errcode(ERRCODE_SYNTAX_ERROR),
+				errmsg("invalid publication object list"),
+				errdetail("TABLE must be specified before a standalone table."),
+				parser_errposition(pubobj->location));
+
+	foreach(cell, pubexceptobjspec_list)
+	{
+		pubobj = (PublicationObjSpec *) lfirst(cell);
+
+		if (pubobj->pubobjtype == PUBLICATIONOBJ_CONTINUATION)
+			pubobj->pubobjtype = prevobjtype;
+
+		prevobjtype = pubobj->pubobjtype;
+	}
+}
+
 /*----------
  * Recursive view transformation
  *
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 5d1f7682f11..c0eafab816a 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -4625,14 +4625,14 @@ getPublications(Archive *fout)
 		 * Get the list of tables for publications specified in the EXCEPT
 		 * TABLE clause.
 		 *
-		 * Although individual EXCEPT TABLE entries could be stored in
+		 * Although individual table entries in EXCEPT list could be stored in
 		 * PublicationRelInfo, dumpPublicationTable cannot be used to emit
 		 * them, because there is no ALTER PUBLICATION ... ADD command to add
-		 * individual table entries to the EXCEPT TABLE list.
+		 * individual table entries to the EXCEPT list.
 		 *
-		 * Therefore, the approach is to dump the complete EXCEPT TABLE list
-		 * in a single CREATE PUBLICATION statement. PublicationInfo is used
-		 * to collect this information, which is then emitted by
+		 * Therefore, the approach is to dump the complete EXCEPT list in a
+		 * single CREATE PUBLICATION statement. PublicationInfo is used to
+		 * collect this information, which is then emitted by
 		 * dumpPublication().
 		 */
 		if (fout->remoteVersion >= 190000)
@@ -4708,13 +4708,13 @@ dumpPublication(Archive *fout, const PublicationInfo *pubinfo)
 
 		appendPQExpBufferStr(query, " FOR ALL TABLES");
 
-		/* Include EXCEPT TABLE clause if there are except_tables. */
+		/* Include EXCEPT (TABLE) clause if there are except_tables. */
 		for (SimplePtrListCell *cell = pubinfo->except_tables.head; cell; cell = cell->next)
 		{
 			TableInfo  *tbinfo = (TableInfo *) cell->ptr;
 
 			if (++n_except == 1)
-				appendPQExpBufferStr(query, " EXCEPT TABLE (");
+				appendPQExpBufferStr(query, " EXCEPT (TABLE ");
 			else
 				appendPQExpBufferStr(query, ", ");
 			appendPQExpBuffer(query, "ONLY %s", fmtQualifiedDumpable(tbinfo));
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index 051a3d8ea3d..b4b8efed513 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -3215,9 +3215,9 @@ my %tests = (
 	'CREATE PUBLICATION pub8' => {
 		create_order => 50,
 		create_sql =>
-		  'CREATE PUBLICATION pub8 FOR ALL TABLES EXCEPT TABLE (dump_test.test_table);',
+		  'CREATE PUBLICATION pub8 FOR ALL TABLES EXCEPT (TABLE dump_test.test_table);',
 		regexp => qr/^
-			\QCREATE PUBLICATION pub8 FOR ALL TABLES EXCEPT TABLE (ONLY dump_test.test_table) WITH (publish = 'insert, update, delete, truncate');\E
+			\QCREATE PUBLICATION pub8 FOR ALL TABLES EXCEPT (TABLE ONLY dump_test.test_table) WITH (publish = 'insert, update, delete, truncate');\E
 			/xm,
 		like => { %full_runs, section_post_data => 1, },
 	},
@@ -3225,9 +3225,9 @@ my %tests = (
 	'CREATE PUBLICATION pub9' => {
 		create_order => 50,
 		create_sql =>
-		  'CREATE PUBLICATION pub9 FOR ALL TABLES EXCEPT TABLE (dump_test.test_table, dump_test.test_second_table);',
+		  'CREATE PUBLICATION pub9 FOR ALL TABLES EXCEPT (TABLE dump_test.test_table, dump_test.test_second_table);',
 		regexp => qr/^
-			\QCREATE PUBLICATION pub9 FOR ALL TABLES EXCEPT TABLE (ONLY dump_test.test_table, ONLY dump_test.test_second_table) WITH (publish = 'insert, update, delete, truncate');\E
+			\QCREATE PUBLICATION pub9 FOR ALL TABLES EXCEPT (TABLE ONLY dump_test.test_table, ONLY dump_test.test_second_table) WITH (publish = 'insert, update, delete, truncate');\E
 			/xm,
 		like => { %full_runs, section_post_data => 1, },
 	},
@@ -3235,9 +3235,9 @@ my %tests = (
 	'CREATE PUBLICATION pub10' => {
 		create_order => 92,
 		create_sql =>
-		  'CREATE PUBLICATION pub10 FOR ALL TABLES EXCEPT TABLE (dump_test.test_inheritance_parent);',
+		  'CREATE PUBLICATION pub10 FOR ALL TABLES EXCEPT (TABLE dump_test.test_inheritance_parent);',
 		regexp => qr/^
-			\QCREATE PUBLICATION pub10 FOR ALL TABLES EXCEPT TABLE (ONLY dump_test.test_inheritance_parent, ONLY dump_test.test_inheritance_child) WITH (publish = 'insert, update, delete, truncate');\E
+			\QCREATE PUBLICATION pub10 FOR ALL TABLES EXCEPT (TABLE ONLY dump_test.test_inheritance_parent, ONLY dump_test.test_inheritance_child) WITH (publish = 'insert, update, delete, truncate');\E
 			/xm,
 		like => { %full_runs, section_post_data => 1, },
 	},
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 7d1231c0558..94f25ef5d07 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -3206,7 +3206,7 @@ describeOneTableDetails(const char *schemaname,
 				{
 					/*
 					 * Skip entries where this relation appears in the
-					 * publication's EXCEPT TABLE list.
+					 * publication's EXCEPT list.
 					 */
 					appendPQExpBuffer(&buf,
 									  " AND NOT pr.prexcept\n"
diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c
index 523d3f39fc5..cbdd02cd01b 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -2333,13 +2333,13 @@ match_previous_words(int pattern_id,
 	else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL"))
 		COMPLETE_WITH("SEQUENCES", "TABLES");
 	else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "TABLES"))
-		COMPLETE_WITH("EXCEPT TABLE (");
+		COMPLETE_WITH("EXCEPT ( TABLE");
 	else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "TABLES", "EXCEPT"))
-		COMPLETE_WITH("TABLE (");
-	else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "TABLES", "EXCEPT", "TABLE"))
-		COMPLETE_WITH("(");
+		COMPLETE_WITH("( TABLE");
+	else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "TABLES", "EXCEPT", "("))
+		COMPLETE_WITH("TABLE");
 	/* Complete "ALTER PUBLICATION <name> FOR TABLE" with "<table>, ..." */
-	else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "TABLES", "EXCEPT", "TABLE", "("))
+	else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "TABLES", "EXCEPT", "(", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
 	else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD|DROP|SET", "TABLES", "IN", "SCHEMA"))
 		COMPLETE_WITH_QUERY_PLUS(Query_for_list_of_schemas
@@ -3738,16 +3738,16 @@ match_previous_words(int pattern_id,
 	else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL"))
 		COMPLETE_WITH("TABLES", "SEQUENCES");
 	else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES"))
-		COMPLETE_WITH("EXCEPT TABLE (", "WITH (");
+		COMPLETE_WITH("EXCEPT ( TABLE", "WITH (");
 	else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES", "EXCEPT"))
-		COMPLETE_WITH("TABLE (");
-	else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES", "EXCEPT", "TABLE"))
-		COMPLETE_WITH("(");
-	else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES", "EXCEPT", "TABLE", "("))
+		COMPLETE_WITH("( TABLE");
+	else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES", "EXCEPT", "("))
+		COMPLETE_WITH("TABLE");
+	else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES", "EXCEPT", "(", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
-	else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES", "EXCEPT", "TABLE", "(", MatchAnyN) && ends_with(prev_wd, ','))
+	else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES", "EXCEPT", "(", "TABLE", MatchAnyN) && ends_with(prev_wd, ','))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
-	else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES", "EXCEPT", "TABLE", "(", MatchAnyN) && !ends_with(prev_wd, ','))
+	else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES", "EXCEPT", "(", "TABLE", MatchAnyN) && !ends_with(prev_wd, ','))
 		COMPLETE_WITH(")");
 	else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLES"))
 		COMPLETE_WITH("IN SCHEMA");
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index a220f48b285..d18ad08b5fa 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -214,11 +214,12 @@ Not-null constraints:
 (1 row)
 
 ---------------------------------------------
--- EXCEPT TABLE tests for normal tables
+-- EXCEPT clause tests for normal tables
 ---------------------------------------------
 SET client_min_messages = 'ERROR';
--- Specify table list in the EXCEPT TABLE clause of a FOR ALL TABLES publication
-CREATE PUBLICATION testpub_foralltables_excepttable FOR ALL TABLES EXCEPT TABLE (testpub_tbl1, testpub_tbl2);
+CREATE TABLE testpub_tbl3 (id serial primary key, data text);
+-- Specify table list in the EXCEPT clause of a FOR ALL TABLES publication
+CREATE PUBLICATION testpub_foralltables_excepttable FOR ALL TABLES EXCEPT (TABLE testpub_tbl1, testpub_tbl2, TABLE testpub_tbl3);
 \dRp+ testpub_foralltables_excepttable
                                                  Publication testpub_foralltables_excepttable
           Owner           | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Generated columns | Via root | Description 
@@ -227,9 +228,10 @@ CREATE PUBLICATION testpub_foralltables_excepttable FOR ALL TABLES EXCEPT TABLE
 Except tables:
     "public.testpub_tbl1"
     "public.testpub_tbl2"
+    "public.testpub_tbl3"
 
--- Specify table in the EXCEPT TABLE clause of a FOR ALL TABLES publication
-CREATE PUBLICATION testpub_foralltables_excepttable1 FOR ALL TABLES EXCEPT TABLE (testpub_tbl1);
+-- Specify table in the EXCEPT clause of a FOR ALL TABLES publication
+CREATE PUBLICATION testpub_foralltables_excepttable1 FOR ALL TABLES EXCEPT (TABLE testpub_tbl1);
 \dRp+ testpub_foralltables_excepttable1
                                                 Publication testpub_foralltables_excepttable1
           Owner           | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Generated columns | Via root | Description 
@@ -239,7 +241,7 @@ Except tables:
     "public.testpub_tbl1"
 
 -- Check that the table description shows the publications where it is listed
--- in the EXCEPT TABLE clause
+-- in the EXCEPT clause
 \d testpub_tbl1
                             Table "public.testpub_tbl1"
  Column |  Type   | Collation | Nullable |                 Default                  
@@ -254,12 +256,13 @@ Except Publications:
     "testpub_foralltables_excepttable"
     "testpub_foralltables_excepttable1"
 
+DROP TABLE testpub_tbl3;
 ---------------------------------------------
 -- SET ALL TABLES/SEQUENCES
 ---------------------------------------------
--- Replace the existing EXCEPT TABLE list (testpub_tbl1) with a new
--- EXCEPT TABLE list containing only (testpub_tbl2).
-ALTER PUBLICATION testpub_foralltables_excepttable SET ALL TABLES EXCEPT TABLE (testpub_tbl2);
+-- Replace the existing table list in EXCEPT clause (testpub_tbl1) with a new
+-- table list containing only (testpub_tbl2).
+ALTER PUBLICATION testpub_foralltables_excepttable SET ALL TABLES EXCEPT (TABLE testpub_tbl2);
 \dRp+ testpub_foralltables_excepttable
                                                  Publication testpub_foralltables_excepttable
           Owner           | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Generated columns | Via root | Description 
@@ -268,7 +271,8 @@ ALTER PUBLICATION testpub_foralltables_excepttable SET ALL TABLES EXCEPT TABLE (
 Except tables:
     "public.testpub_tbl2"
 
--- Clear the EXCEPT TABLE list, making the publication include all tables.
+-- Clear the table list in EXCEPT clause, making the publication include all
+-- tables.
 ALTER PUBLICATION testpub_foralltables_excepttable SET ALL TABLES;
 \dRp+ testpub_foralltables_excepttable
                                                  Publication testpub_foralltables_excepttable
@@ -309,7 +313,7 @@ ALTER PUBLICATION testpub_forall_tbls_seqs SET ALL SEQUENCES;
 (1 row)
 
 -- fail - SET ALL TABLES/SEQUENCES is not allowed for a 'FOR TABLE' publication
-ALTER PUBLICATION testpub_fortable SET ALL TABLES EXCEPT TABLE (testpub_tbl1);
+ALTER PUBLICATION testpub_fortable SET ALL TABLES EXCEPT (TABLE testpub_tbl1);
 ERROR:  publication "testpub_fortable" does not support ALL TABLES operations
 DETAIL:  This operation requires the publication to be defined as FOR ALL TABLES/SEQUENCES or to be empty.
 ALTER PUBLICATION testpub_fortable SET ALL TABLES;
@@ -319,7 +323,7 @@ ALTER PUBLICATION testpub_fortable SET ALL SEQUENCES;
 ERROR:  publication "testpub_fortable" does not support ALL SEQUENCES operations
 DETAIL:  This operation requires the publication to be defined as FOR ALL TABLES/SEQUENCES or to be empty.
 -- fail - SET ALL TABLES/SEQUENCES is not allowed for a schema publication
-ALTER PUBLICATION testpub_forschema SET ALL TABLES EXCEPT TABLE (pub_test.testpub_nopk);
+ALTER PUBLICATION testpub_forschema SET ALL TABLES EXCEPT (TABLE pub_test.testpub_nopk);
 ERROR:  publication "testpub_forschema" does not support ALL TABLES operations
 DETAIL:  This operation requires the publication to be defined as FOR ALL TABLES/SEQUENCES or to be empty.
 ALTER PUBLICATION testpub_forschema SET ALL TABLES;
@@ -334,7 +338,7 @@ DROP PUBLICATION testpub_foralltables, testpub_fortable, testpub_forschema, test
 DROP PUBLICATION testpub_forall_tbls_seqs, testpub_foralltables_excepttable, testpub_foralltables_excepttable1;
 ---------------------------------------------
 -- Tests for inherited tables, and
--- EXCEPT TABLE tests for inherited tables
+-- EXCEPT clause tests for inherited tables
 ---------------------------------------------
 SET client_min_messages = 'ERROR';
 CREATE TABLE testpub_tbl_parent (a int);
@@ -358,8 +362,8 @@ CREATE PUBLICATION testpub4 FOR TABLE ONLY testpub_tbl_parent;
 Tables:
     "public.testpub_tbl_parent"
 
--- List the parent table in the EXCEPT TABLE clause (without ONLY or '*')
-CREATE PUBLICATION testpub5 FOR ALL TABLES EXCEPT TABLE (testpub_tbl_parent);
+-- List the parent table in the EXCEPT clause (without ONLY or '*')
+CREATE PUBLICATION testpub5 FOR ALL TABLES EXCEPT (TABLE testpub_tbl_parent);
 \dRp+ testpub5
                                                              Publication testpub5
           Owner           | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Generated columns | Via root | Description 
@@ -369,8 +373,8 @@ Except tables:
     "public.testpub_tbl_child"
     "public.testpub_tbl_parent"
 
--- EXCEPT with '*': list the table and all its descendants in the EXCEPT TABLE clause
-CREATE PUBLICATION testpub6 FOR ALL TABLES EXCEPT TABLE (testpub_tbl_parent *);
+-- EXCEPT with '*': list the table and all its descendants in the EXCEPT clause
+CREATE PUBLICATION testpub6 FOR ALL TABLES EXCEPT (TABLE testpub_tbl_parent *);
 \dRp+ testpub6
                                                              Publication testpub6
           Owner           | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Generated columns | Via root | Description 
@@ -380,8 +384,8 @@ Except tables:
     "public.testpub_tbl_child"
     "public.testpub_tbl_parent"
 
--- EXCEPT with ONLY: list the table in the EXCEPT TABLE clause, but not its descendants
-CREATE PUBLICATION testpub7 FOR ALL TABLES EXCEPT TABLE (ONLY testpub_tbl_parent);
+-- EXCEPT with ONLY: list the table in the EXCEPT clause, but not its descendants
+CREATE PUBLICATION testpub7 FOR ALL TABLES EXCEPT (TABLE ONLY testpub_tbl_parent);
 \dRp+ testpub7
                                                              Publication testpub7
           Owner           | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Generated columns | Via root | Description 
@@ -394,12 +398,12 @@ RESET client_min_messages;
 DROP TABLE testpub_tbl_parent, testpub_tbl_child;
 DROP PUBLICATION testpub3, testpub4, testpub5, testpub6, testpub7;
 ---------------------------------------------
--- EXCEPT TABLE tests for partitioned tables
+-- EXCEPT clause tests for partitioned tables
 ---------------------------------------------
 SET client_min_messages = 'ERROR';
 CREATE TABLE testpub_root(a int) PARTITION BY RANGE(a);
 CREATE TABLE testpub_part1 PARTITION OF testpub_root FOR VALUES FROM (0) TO (100);
-CREATE PUBLICATION testpub8 FOR ALL TABLES EXCEPT TABLE (testpub_root);
+CREATE PUBLICATION testpub8 FOR ALL TABLES EXCEPT (TABLE testpub_root);
 \dRp+ testpub8;
                                                              Publication testpub8
           Owner           | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Generated columns | Via root | Description 
@@ -427,12 +431,12 @@ Except Publications:
     "testpub8"
 Number of partitions: 1 (Use \d+ to list them.)
 
-CREATE PUBLICATION testpub9 FOR ALL TABLES EXCEPT TABLE (testpub_part1);
+CREATE PUBLICATION testpub9 FOR ALL TABLES EXCEPT (TABLE testpub_part1);
 ERROR:  cannot use publication EXCEPT clause for relation "testpub_part1"
 DETAIL:  This operation is not supported for individual partitions.
 CREATE TABLE tab_main (a int) PARTITION BY RANGE(a);
 -- Attaching a partition is not allowed if the partitioned table appears in a
--- publication's EXCEPT TABLE clause.
+-- publication's EXCEPT clause.
 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.
@@ -1623,7 +1627,7 @@ RESET client_min_messages;
 ALTER PUBLICATION testpub5 OWNER TO regress_publication_user3;
 SET ROLE regress_publication_user3;
 -- fail - SET ALL TABLES/SEQUENCES on a publication requires superuser privileges
-ALTER PUBLICATION testpub5 SET ALL TABLES EXCEPT TABLE (testpub_tbl1); -- fail
+ALTER PUBLICATION testpub5 SET ALL TABLES EXCEPT (TABLE testpub_tbl1); -- fail
 ERROR:  must be superuser to set ALL TABLES
 ALTER PUBLICATION testpub5 SET ALL TABLES; -- fail
 ERROR:  must be superuser to set ALL TABLES
diff --git a/src/test/regress/sql/publication.sql b/src/test/regress/sql/publication.sql
index 22e0a30b5c7..5cc3aa63b90 100644
--- a/src/test/regress/sql/publication.sql
+++ b/src/test/regress/sql/publication.sql
@@ -106,28 +106,31 @@ SELECT pubname, puballtables FROM pg_publication WHERE pubname = 'testpub_forall
 \dRp+ testpub_foralltables
 
 ---------------------------------------------
--- EXCEPT TABLE tests for normal tables
+-- EXCEPT clause tests for normal tables
 ---------------------------------------------
 SET client_min_messages = 'ERROR';
--- Specify table list in the EXCEPT TABLE clause of a FOR ALL TABLES publication
-CREATE PUBLICATION testpub_foralltables_excepttable FOR ALL TABLES EXCEPT TABLE (testpub_tbl1, testpub_tbl2);
+CREATE TABLE testpub_tbl3 (id serial primary key, data text);
+-- Specify table list in the EXCEPT clause of a FOR ALL TABLES publication
+CREATE PUBLICATION testpub_foralltables_excepttable FOR ALL TABLES EXCEPT (TABLE testpub_tbl1, testpub_tbl2, TABLE testpub_tbl3);
 \dRp+ testpub_foralltables_excepttable
--- Specify table in the EXCEPT TABLE clause of a FOR ALL TABLES publication
-CREATE PUBLICATION testpub_foralltables_excepttable1 FOR ALL TABLES EXCEPT TABLE (testpub_tbl1);
+-- Specify table in the EXCEPT clause of a FOR ALL TABLES publication
+CREATE PUBLICATION testpub_foralltables_excepttable1 FOR ALL TABLES EXCEPT (TABLE testpub_tbl1);
 \dRp+ testpub_foralltables_excepttable1
 -- Check that the table description shows the publications where it is listed
--- in the EXCEPT TABLE clause
+-- in the EXCEPT clause
 \d testpub_tbl1
+DROP TABLE testpub_tbl3;
 
 ---------------------------------------------
 -- SET ALL TABLES/SEQUENCES
 ---------------------------------------------
--- Replace the existing EXCEPT TABLE list (testpub_tbl1) with a new
--- EXCEPT TABLE list containing only (testpub_tbl2).
-ALTER PUBLICATION testpub_foralltables_excepttable SET ALL TABLES EXCEPT TABLE (testpub_tbl2);
+-- Replace the existing table list in EXCEPT clause (testpub_tbl1) with a new
+-- table list containing only (testpub_tbl2).
+ALTER PUBLICATION testpub_foralltables_excepttable SET ALL TABLES EXCEPT (TABLE testpub_tbl2);
 \dRp+ testpub_foralltables_excepttable
 
--- Clear the EXCEPT TABLE list, making the publication include all tables.
+-- Clear the table list in EXCEPT clause, making the publication include all
+-- tables.
 ALTER PUBLICATION testpub_foralltables_excepttable SET ALL TABLES;
 \dRp+ testpub_foralltables_excepttable
 
@@ -149,12 +152,12 @@ ALTER PUBLICATION testpub_forall_tbls_seqs SET ALL SEQUENCES;
 \dRp+ testpub_forall_tbls_seqs
 
 -- fail - SET ALL TABLES/SEQUENCES is not allowed for a 'FOR TABLE' publication
-ALTER PUBLICATION testpub_fortable SET ALL TABLES EXCEPT TABLE (testpub_tbl1);
+ALTER PUBLICATION testpub_fortable SET ALL TABLES EXCEPT (TABLE testpub_tbl1);
 ALTER PUBLICATION testpub_fortable SET ALL TABLES;
 ALTER PUBLICATION testpub_fortable SET ALL SEQUENCES;
 
 -- fail - SET ALL TABLES/SEQUENCES is not allowed for a schema publication
-ALTER PUBLICATION testpub_forschema SET ALL TABLES EXCEPT TABLE (pub_test.testpub_nopk);
+ALTER PUBLICATION testpub_forschema SET ALL TABLES EXCEPT (TABLE pub_test.testpub_nopk);
 ALTER PUBLICATION testpub_forschema SET ALL TABLES;
 ALTER PUBLICATION testpub_forschema SET ALL SEQUENCES;
 
@@ -165,7 +168,7 @@ DROP PUBLICATION testpub_forall_tbls_seqs, testpub_foralltables_excepttable, tes
 
 ---------------------------------------------
 -- Tests for inherited tables, and
--- EXCEPT TABLE tests for inherited tables
+-- EXCEPT clause tests for inherited tables
 ---------------------------------------------
 SET client_min_messages = 'ERROR';
 CREATE TABLE testpub_tbl_parent (a int);
@@ -174,14 +177,14 @@ CREATE PUBLICATION testpub3 FOR TABLE testpub_tbl_parent;
 \dRp+ testpub3
 CREATE PUBLICATION testpub4 FOR TABLE ONLY testpub_tbl_parent;
 \dRp+ testpub4
--- List the parent table in the EXCEPT TABLE clause (without ONLY or '*')
-CREATE PUBLICATION testpub5 FOR ALL TABLES EXCEPT TABLE (testpub_tbl_parent);
+-- List the parent table in the EXCEPT clause (without ONLY or '*')
+CREATE PUBLICATION testpub5 FOR ALL TABLES EXCEPT (TABLE testpub_tbl_parent);
 \dRp+ testpub5
--- EXCEPT with '*': list the table and all its descendants in the EXCEPT TABLE clause
-CREATE PUBLICATION testpub6 FOR ALL TABLES EXCEPT TABLE (testpub_tbl_parent *);
+-- EXCEPT with '*': list the table and all its descendants in the EXCEPT clause
+CREATE PUBLICATION testpub6 FOR ALL TABLES EXCEPT (TABLE testpub_tbl_parent *);
 \dRp+ testpub6
--- EXCEPT with ONLY: list the table in the EXCEPT TABLE clause, but not its descendants
-CREATE PUBLICATION testpub7 FOR ALL TABLES EXCEPT TABLE (ONLY testpub_tbl_parent);
+-- EXCEPT with ONLY: list the table in the EXCEPT clause, but not its descendants
+CREATE PUBLICATION testpub7 FOR ALL TABLES EXCEPT (TABLE ONLY testpub_tbl_parent);
 \dRp+ testpub7
 
 RESET client_min_messages;
@@ -189,20 +192,20 @@ DROP TABLE testpub_tbl_parent, testpub_tbl_child;
 DROP PUBLICATION testpub3, testpub4, testpub5, testpub6, testpub7;
 
 ---------------------------------------------
--- EXCEPT TABLE tests for partitioned tables
+-- EXCEPT clause tests for partitioned tables
 ---------------------------------------------
 SET client_min_messages = 'ERROR';
 CREATE TABLE testpub_root(a int) PARTITION BY RANGE(a);
 CREATE TABLE testpub_part1 PARTITION OF testpub_root FOR VALUES FROM (0) TO (100);
-CREATE PUBLICATION testpub8 FOR ALL TABLES EXCEPT TABLE (testpub_root);
+CREATE PUBLICATION testpub8 FOR ALL TABLES EXCEPT (TABLE testpub_root);
 \dRp+ testpub8;
 \d testpub_part1
 \d testpub_root
-CREATE PUBLICATION testpub9 FOR ALL TABLES EXCEPT TABLE (testpub_part1);
+CREATE PUBLICATION testpub9 FOR ALL TABLES EXCEPT (TABLE testpub_part1);
 
 CREATE TABLE tab_main (a int) PARTITION BY RANGE(a);
 -- Attaching a partition is not allowed if the partitioned table appears in a
--- publication's EXCEPT TABLE clause.
+-- publication's EXCEPT clause.
 ALTER TABLE tab_main ATTACH PARTITION testpub_root FOR VALUES FROM (0) TO (200);
 
 RESET client_min_messages;
@@ -1037,7 +1040,7 @@ RESET client_min_messages;
 ALTER PUBLICATION testpub5 OWNER TO regress_publication_user3;
 SET ROLE regress_publication_user3;
 -- fail - SET ALL TABLES/SEQUENCES on a publication requires superuser privileges
-ALTER PUBLICATION testpub5 SET ALL TABLES EXCEPT TABLE (testpub_tbl1); -- fail
+ALTER PUBLICATION testpub5 SET ALL TABLES EXCEPT (TABLE testpub_tbl1); -- fail
 ALTER PUBLICATION testpub5 SET ALL TABLES; -- fail
 ALTER PUBLICATION testpub5 SET ALL SEQUENCES; -- fail
 
diff --git a/src/test/subscription/t/037_except.pl b/src/test/subscription/t/037_except.pl
index 13b99eda258..9e2533b43ee 100644
--- a/src/test/subscription/t/037_except.pl
+++ b/src/test/subscription/t/037_except.pl
@@ -1,7 +1,7 @@
 
 # Copyright (c) 2026, PostgreSQL Global Development Group
 
-# Logical replication tests for EXCEPT TABLE publications
+# Logical replication tests for publications with EXCEPT clause
 use strict;
 use warnings;
 use PostgreSQL::Test::Cluster;
@@ -26,12 +26,12 @@ sub test_except_root_partition
 {
 	my ($pubviaroot) = @_;
 
-	# If the root partitioned table is in the EXCEPT TABLE clause, all its
+	# If the root partitioned table is in the EXCEPT clause, all its
 	# partitions are excluded from publication, regardless of the
 	# publish_via_partition_root setting.
 	$node_publisher->safe_psql(
 		'postgres', qq(
-		CREATE PUBLICATION tap_pub_part FOR ALL TABLES EXCEPT TABLE (root1) WITH (publish_via_partition_root = $pubviaroot);
+		CREATE PUBLICATION tap_pub_part FOR ALL TABLES EXCEPT (TABLE root1) WITH (publish_via_partition_root = $pubviaroot);
 		INSERT INTO root1 VALUES (1), (101);
 	));
 	$node_subscriber->safe_psql('postgres',
@@ -48,7 +48,7 @@ sub test_except_root_partition
 		"INSERT INTO root1 VALUES (2), (102)");
 
 	# Verify that data inserted into the partitioned table is not published when
-	# it is in the EXCEPT TABLE clause.
+	# it is in the EXCEPT clause.
 	$result = $node_publisher->safe_psql('postgres',
 		"SELECT count(*) = 0 FROM pg_logical_slot_get_binary_changes('test_slot', NULL, NULL, 'proto_version', '1', 'publication_names', 'tap_pub_part')"
 	);
@@ -67,7 +67,7 @@ sub test_except_root_partition
 }
 
 # ============================================
-# EXCEPT TABLE test cases for non-partitioned tables and inherited tables.
+# EXCEPT clause test cases for non-partitioned tables and inherited tables.
 # ============================================
 
 # Create schemas and tables on publisher
@@ -92,9 +92,9 @@ $node_subscriber->safe_psql(
 
 # Exclude tab1 (non-inheritance case), and also exclude parent and ONLY parent1
 # to verify exclusion behavior for inherited tables, including the effect of
-# ONLY in the EXCEPT TABLE clause.
+# ONLY in the EXCEPT clause.
 $node_publisher->safe_psql('postgres',
-	"CREATE PUBLICATION tab_pub FOR ALL TABLES EXCEPT TABLE (tab1, parent, only parent1)"
+	"CREATE PUBLICATION tab_pub FOR ALL TABLES EXCEPT (TABLE tab1, parent, only parent1)"
 );
 
 # Create a logical replication slot to help with later tests.
@@ -108,38 +108,37 @@ $node_subscriber->safe_psql('postgres',
 # Wait for initial table sync to finish
 $node_subscriber->wait_for_subscription_sync($node_publisher, 'tab_sub');
 
-# Check the table data does not sync for the tables specified in EXCEPT TABLE
-# clause.
+# Check the table data does not sync for the tables specified in EXCEPT clause.
 $result =
   $node_subscriber->safe_psql('postgres', "SELECT count(*) FROM tab1");
 is($result, qq(0),
-	'check there is no initial data copied for the tables specified in the EXCEPT TABLE clause'
+	'check there is no initial data copied for the tables specified in the EXCEPT clause'
 );
 
-# Insert some data into the table listed in the EXCEPT TABLE clause
+# Insert some data into the table listed in the EXCEPT clause
 $node_publisher->safe_psql(
 	'postgres', qq(
 	INSERT INTO tab1 VALUES(generate_series(11,20));
 	INSERT INTO child VALUES(generate_series(11,20), generate_series(11,20));
 ));
 
-# Verify that data inserted into a table listed in the EXCEPT TABLE clause is
+# Verify that data inserted into a table listed in the EXCEPT clause is
 # not published.
 $result = $node_publisher->safe_psql('postgres',
 	"SELECT count(*) = 0 FROM pg_logical_slot_get_binary_changes('test_slot', NULL, NULL, 'proto_version', '1', 'publication_names', 'tab_pub')"
 );
 is($result, qq(t),
-	'verify no changes for table listed in the EXCEPT TABLE clause are present in the replication slot'
+	'verify no changes for table listed in the EXCEPT clause are present in the replication slot'
 );
 
 # This should be published because ONLY parent1 was specified in the
-# EXCEPT TABLE clause, so the exclusion applies only to the parent table and not
+# EXCEPT clause, so the exclusion applies only to the parent table and not
 # to its child.
 $node_publisher->safe_psql('postgres',
 	"INSERT INTO child1 VALUES(generate_series(11,20), generate_series(11,20))"
 );
 
-# Verify that data inserted into a table listed in the EXCEPT TABLE clause is
+# Verify that data inserted into a table listed in the EXCEPT clause is
 # not replicated.
 $node_publisher->wait_for_catchup('tab_sub');
 $result =
@@ -156,9 +155,9 @@ $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.
+# Replace the table list in EXCEPT clause so that only tab2 is excluded.
 $node_publisher->safe_psql('postgres',
-	"ALTER PUBLICATION tab_pub SET ALL TABLES EXCEPT TABLE (tab2)");
+	"ALTER PUBLICATION tab_pub SET ALL TABLES EXCEPT (TABLE tab2)");
 
 # Refresh the subscription so the subscriber picks up the updated
 # publication definition and initiates table synchronization.
@@ -169,19 +168,19 @@ $node_subscriber->safe_psql('postgres',
 $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.
+# listed in the EXCEPT 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'
+	'check there is no initial data copied for the tables specified in the EXCEPT clause'
 );
 
 # Verify that table synchronization now happens for tab1. Table tab1 is
-# included now since the EXCEPT TABLE list is only (tab2).
+# included now since the table list of EXCEPT clause is only (tab2).
 $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'
+	'check that the data is copied as the tab1 is removed from EXCEPT clause'
 );
 
 # cleanup
@@ -199,7 +198,7 @@ $node_publisher->safe_psql(
 ));
 
 # ============================================
-# EXCEPT TABLE test cases for partitioned tables
+# EXCEPT clause test cases for partitioned tables
 # ============================================
 # Setup partitioned table and partitions on the publisher that map to normal
 # tables on the subscriber.
@@ -227,11 +226,11 @@ test_except_root_partition('true');
 # Test when a subscription is subscribing to multiple publications
 # ============================================
 
-# OK when a table is excluded by pub1 EXCEPT TABLE, but it is included by pub2
+# OK when a table is excluded by pub1 EXCEPT clause, but it is included by pub2
 # FOR TABLE.
 $node_publisher->safe_psql(
 	'postgres', qq(
-	CREATE PUBLICATION tap_pub1 FOR ALL TABLES EXCEPT TABLE (tab1);
+	CREATE PUBLICATION tap_pub1 FOR ALL TABLES EXCEPT (TABLE tab1);
 	CREATE PUBLICATION tap_pub2 FOR TABLE tab1;
 	INSERT INTO tab1 VALUES(1);
 ));
@@ -247,7 +246,7 @@ $result =
   $node_publisher->safe_psql('postgres', "SELECT * FROM tab1 ORDER BY a");
 is( $result, qq(1
 2),
-	"check replication of a table in the EXCEPT TABLE clause of one publication but included by another"
+	"check replication of a table in the EXCEPT clause of one publication but included by another"
 );
 $node_publisher->safe_psql(
 	'postgres', qq(
@@ -256,7 +255,7 @@ $node_publisher->safe_psql(
 ));
 $node_subscriber->safe_psql('postgres', qq(TRUNCATE tab1));
 
-# OK when a table is excluded by pub1 EXCEPT TABLE, but it is included by pub2
+# OK when a table is excluded by pub1 EXCEPT clause, but it is included by pub2
 # FOR ALL TABLES.
 $node_publisher->safe_psql(
 	'postgres', qq(
@@ -275,7 +274,7 @@ $result =
   $node_publisher->safe_psql('postgres', "SELECT * FROM tab1 ORDER BY a");
 is( $result, qq(1
 2),
-	"check replication of a table in the EXCEPT TABLE clause of one publication but included by another"
+	"check replication of a table in the EXCEPT clause of one publication but included by another"
 );
 
 $node_subscriber->safe_psql('postgres', 'DROP SUBSCRIPTION tap_sub');
-- 
2.34.1



view thread (377+ messages)  latest in thread

reply

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Reply to all the recipients using the --to and --cc options:
  reply via email

  To: [email protected]
  Cc: [email protected], [email protected], [email protected], [email protected], [email protected], [email protected], [email protected], [email protected], [email protected], [email protected], [email protected], [email protected], [email protected], [email protected], [email protected], [email protected]
  Subject: Re: Skipping schema changes in publication
  In-Reply-To: <CANhcyEVLp5kbaVR4=nh1jR4YWqv7YpVx_SnYoshbnOrnY79_fg@mail.gmail.com>

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox