From 6b1bb0b7faa1b68d6507d1efcf158a753f79eed7 Mon Sep 17 00:00:00 2001 From: Nisha Moond Date: Tue, 2 Jun 2026 11:39:36 +0530 Subject: [PATCH v9 3/3] Add EXCEPT support to ALTER PUBLICATION SET TABLES IN SCHEMA Extend AlterPublicationExceptTables() with the AP_SetObjects case, which redefines the publication and replaces the entire EXCEPT list. Syntax: ALTER PUBLICATION pub SET TABLES IN SCHEMA s EXCEPT (TABLE t1); This patch also cleans up EXCEPT entries when a schema is dropped from the publication. --- doc/src/sgml/ref/alter_publication.sgml | 27 +++- src/backend/commands/publicationcmds.c | 135 +++++++++++++++++--- src/backend/replication/pgoutput/pgoutput.c | 10 +- src/bin/psql/tab-complete.in.c | 15 +++ src/test/regress/expected/publication.out | 86 +++++++++++++ src/test/regress/sql/publication.sql | 38 ++++++ src/test/subscription/t/037_except.pl | 85 ++++++++++++ 7 files changed, 369 insertions(+), 27 deletions(-) diff --git a/doc/src/sgml/ref/alter_publication.sgml b/doc/src/sgml/ref/alter_publication.sgml index 73f6375a66f..80b038e4b2e 100644 --- a/doc/src/sgml/ref/alter_publication.sgml +++ b/doc/src/sgml/ref/alter_publication.sgml @@ -97,7 +97,11 @@ ALTER PUBLICATION name RENAME TO FOR TABLE or FOR TABLES IN SCHEMA, 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. + that were present in the publication will be removed. When + SET TABLES IN SCHEMA is used with an + EXCEPT clause, the excluded tables for each schema are + replaced with the specified list; if EXCEPT is omitted + for a schema, any existing exclusions for that schema are cleared. @@ -116,10 +120,8 @@ ALTER PUBLICATION name RENAME TO The EXCEPT clause can be used with - ADD TABLES IN SCHEMA to exclude specific tables from the - publication. Using DROP TABLES IN SCHEMA on a publication - will automatically also remove any associated EXCEPT - entries. + ADD TABLES IN SCHEMA to exclude specific tables from a + schema-level publication. @@ -214,7 +216,8 @@ ALTER PUBLICATION name RENAME TO EXCEPT ( except_table_object [, ... ] ) - When used with ADD TABLES IN SCHEMA, specifies + When used with ADD TABLES IN SCHEMA + or SET TABLES IN SCHEMA, specifies tables to be excluded from the publication. Each named table must belong to the schema specified in the same TABLES IN SCHEMA clause. Table names may be @@ -326,6 +329,18 @@ ALTER PUBLICATION sales_publication ADD TABLES IN SCHEMA sales EXCEPT (TABLE int + + Replace the schema list of sales_publication with + sales, excluding only + sales.drafts. Other than + sales.drafts, any previously excluded tables for schema + sales are no longer excluded. Any schemas previously in + sales_publication are removed: + +ALTER PUBLICATION sales_publication SET TABLES IN SCHEMA sales EXCEPT (TABLE drafts); + + + Add tables users, departments and schema diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c index 09663579058..8bf71edcdaa 100644 --- a/src/backend/commands/publicationcmds.c +++ b/src/backend/commands/publicationcmds.c @@ -1556,8 +1556,8 @@ AlterPublicationSchemas(AlterPublicationStmt *stmt, /* * Increment the command counter so that is_schema_publication() in - * GetExcludedPublicationTables() can see the just-inserted schema - * rows when AlterPublicationSchemaExceptTables runs next. + * GetExcludedPublicationTables() can see the just-inserted schema rows + * when AlterPublicationSchemaExceptTables runs next. */ if (stmt->action == AP_AddObjects || stmt->action == AP_SetObjects) CommandCounterIncrement(); @@ -1575,16 +1575,18 @@ AlterPublicationSchemas(AlterPublicationStmt *stmt, */ static void AlterPublicationSchemaExceptTables(AlterPublicationStmt *stmt, - HeapTuple tup, List *except_pubtables, - List *schemaidlist) + HeapTuple tup, List *except_pubtables, + List *schemaidlist) { Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup); Oid pubid = pubform->oid; /* - * Nothing to do if no EXCEPT entries. + * Nothing to do if there are no EXCEPT entries, unless handling the SET + * command, because if the user has removed all exceptions we need to drop + * any existing ones. */ - if (!except_pubtables) + if (!except_pubtables && stmt->action != AP_SetObjects) return; /* @@ -1605,16 +1607,6 @@ AlterPublicationSchemaExceptTables(AlterPublicationStmt *stmt, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("EXCEPT clause is not supported with DROP in ALTER PUBLICATION"))); - /* - * XXX EXCEPT with SET is not currently implemented. Workaround: DROP and - * re-ADD the schema with the desired EXCEPT list. - */ - if (stmt->action == AP_SetObjects) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("EXCEPT clause is not supported with SET in ALTER PUBLICATION"), - errhint("Drop and re-add the schema with the desired EXCEPT list."))); - if (stmt->action == AP_AddObjects) { List *rels; @@ -1642,6 +1634,84 @@ AlterPublicationSchemaExceptTables(AlterPublicationStmt *stmt, PublicationAddTables(pubid, rels, false, stmt); + CloseTableList(rels); + } + else /* AP_SetObjects */ + { + List *oldexceptrelids = NIL; + List *newexceptrelids = NIL; + List *delrelids = NIL; + List *rels; + List *explicitrelids; + + rels = OpenTableList(except_pubtables); + + /* Collect OIDs of the desired new EXCEPT list. */ + foreach_ptr(PublicationRelInfo, pri, rels) + newexceptrelids = lappend_oid(newexceptrelids, + RelationGetRelid(pri->relation)); + + explicitrelids = GetIncludedPublicationRelations(pubid, + PUBLICATION_PART_ROOT); + + /* + * Validate that each excluded table is not also in the explicit table + * list (which would be contradictory). + */ + foreach_ptr(PublicationRelInfo, pri, rels) + { + Oid relid = RelationGetRelid(pri->relation); + + if (list_member_oid(explicitrelids, relid)) + ereport(ERROR, + errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("table \"%s\" cannot appear in both the table list and the EXCEPT clause", + RelationGetQualifiedRelationName(pri->relation))); + } + + /* + * Get the current set of EXCEPT entries. Only FOR ALL TABLES and + * schema-level publications can have EXCEPT entries; for any other + * publication type oldexceptrelids stays NIL. + * + * Note: we check is_schema_publication() against the current catalog + * state (before AlterPublicationSchemas has run), so if the caller is + * doing SET TABLE t1 to convert a schema publication into a plain + * table publication, is_schema_publication() still returns true here. + * That is intentional: it lets us discover and clean up any stale + * EXCEPT entries that belong to the old schema definition. + */ + if (GetPublication(pubid)->alltables || is_schema_publication(pubid)) + oldexceptrelids = GetExcludedPublicationTables(pubid, + PUBLICATION_PART_ROOT); + + /* Build a list of old EXCEPT entries not present in the new list. */ + foreach_oid(oldrelid, oldexceptrelids) + { + if (!list_member_oid(newexceptrelids, oldrelid)) + delrelids = lappend_oid(delrelids, oldrelid); + } + + /* Drop old EXCEPT entries not present in the new list. */ + foreach_oid(relid, delrelids) + { + Oid proid; + ObjectAddress obj; + + proid = GetSysCacheOid2(PUBLICATIONRELMAP, + Anum_pg_publication_rel_oid, + ObjectIdGetDatum(relid), + ObjectIdGetDatum(pubid)); + if (OidIsValid(proid)) + { + ObjectAddressSet(obj, PublicationRelRelationId, proid); + performDeletion(&obj, DROP_CASCADE, 0); + } + } + + /* Add new EXCEPT entries, skipping any already present. */ + PublicationAddTables(pubid, rels, true, stmt); + CloseTableList(rels); } } @@ -2291,6 +2361,7 @@ PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok) foreach(lc, schemas) { Oid schemaid = lfirst_oid(lc); + List *except_relids; psid = GetSysCacheOid2(PUBLICATIONNAMESPACEMAP, Anum_pg_publication_namespace_oid, @@ -2307,8 +2378,40 @@ PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok) get_namespace_name(schemaid)))); } + /* + * Collect EXCEPT entries for tables belonging to this schema before + * removing the schema entry. + */ + except_relids = GetExcludedPublicationTables(pubid, PUBLICATION_PART_ROOT); + ObjectAddressSet(obj, PublicationNamespaceRelationId, psid); performDeletion(&obj, DROP_CASCADE, 0); + + /* + * Drop any prexcept rows for tables belonging to this schema. These + * rows have no pg_depend entry pointing at the + * pg_publication_namespace row, so they are not cascaded by the + * performDeletion() call above and must be cleaned up explicitly. + */ + foreach_oid(relid, except_relids) + { + Oid proid; + + if (get_rel_namespace(relid) != schemaid) + continue; + + proid = GetSysCacheOid2(PUBLICATIONRELMAP, + Anum_pg_publication_rel_oid, + ObjectIdGetDatum(relid), + ObjectIdGetDatum(pubid)); + if (OidIsValid(proid)) + { + ObjectAddressSet(obj, PublicationRelRelationId, proid); + performDeletion(&obj, DROP_CASCADE, 0); + } + } + + list_free(except_relids); } } diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c index 7ee84ec1c83..9831be54b47 100644 --- a/src/backend/replication/pgoutput/pgoutput.c +++ b/src/backend/replication/pgoutput/pgoutput.c @@ -2229,7 +2229,7 @@ get_rel_sync_entry(PGOutputData *data, Relation relation) */ if (pub->alltables) { - List *exceptpubids = NIL; + List *except_pubids = NIL; if (am_partition) { @@ -2252,7 +2252,7 @@ get_rel_sync_entry(PGOutputData *data, Relation relation) * clause. Therefore, for a partition, exclusion must be * evaluated at the top-most ancestor. */ - exceptpubids = GetRelationExcludedPublications(last_ancestor_relid); + except_pubids = GetRelationExcludedPublications(last_ancestor_relid); } else { @@ -2260,13 +2260,13 @@ get_rel_sync_entry(PGOutputData *data, Relation relation) * For a regular table or a root partitioned table, check * exclusion on table itself. */ - exceptpubids = GetRelationExcludedPublications(pub_relid); + except_pubids = GetRelationExcludedPublications(pub_relid); } - if (!list_member_oid(exceptpubids, pub->oid)) + if (!list_member_oid(except_pubids, pub->oid)) publish = true; - list_free(exceptpubids); + list_free(except_pubids); if (!publish) continue; diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c index 8db3e129928..11c87b1b006 100644 --- a/src/bin/psql/tab-complete.in.c +++ b/src/bin/psql/tab-complete.in.c @@ -2379,6 +2379,21 @@ match_previous_words(int pattern_id, } else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD", "TABLES", "IN", "SCHEMA", MatchAny, "EXCEPT", "(", "TABLE", MatchAnyN) && !ends_with(prev_wd, ',')) COMPLETE_WITH(")"); + /* After a single schema name in SET context, offer EXCEPT ( TABLE */ + else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "TABLES", "IN", "SCHEMA", MatchAny) && + !ends_with(prev_wd, ',')) + COMPLETE_WITH("EXCEPT ( TABLE"); + else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "TABLES", "IN", "SCHEMA", MatchAny, "EXCEPT")) + COMPLETE_WITH("( TABLE"); + else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "TABLES", "IN", "SCHEMA", MatchAny, "EXCEPT", "(")) + COMPLETE_WITH("TABLE"); + else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "TABLES", "IN", "SCHEMA", MatchAny, "EXCEPT", "(", "TABLE")) + { + set_completion_reference(prev4_wd); + COMPLETE_WITH_QUERY_VERBATIM(Query_for_list_of_tables_in_schema); + } + else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "TABLES", "IN", "SCHEMA", MatchAny, "EXCEPT", "(", "TABLE", MatchAnyN) && !ends_with(prev_wd, ',')) + COMPLETE_WITH(")"); /* ALTER PUBLICATION SET ( */ else if (Matches("ALTER", "PUBLICATION", MatchAny, MatchAnyN, "SET", "(")) COMPLETE_WITH("publish", "publish_generated_columns", "publish_via_partition_root"); diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out index f56b0524ae9..70715ce7e10 100644 --- a/src/test/regress/expected/publication.out +++ b/src/test/regress/expected/publication.out @@ -594,6 +594,92 @@ Except tables: "pub_test.testpub_tbl_s1" "pub_test.testpub_tbl_s2" +-- SET: replace the except list (keep same schema, different except table) +ALTER PUBLICATION testpub_alter_except SET TABLES IN SCHEMA pub_test EXCEPT (TABLE pub_test.testpub_tbl_s2); +\dRp+ testpub_alter_except + Publication testpub_alter_except + Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Generated columns | Via root | Description +--------------------------+------------+---------------+---------+---------+---------+-----------+-------------------+----------+------------- + regress_publication_user | f | f | t | t | t | t | none | f | +Tables from schemas: + "pub_test" +Except tables: + "pub_test.testpub_tbl_s2" + +-- fail: table in EXCEPT clause also appears in the explicit table list +ALTER PUBLICATION testpub_alter_except SET TABLE pub_test.testpub_tbl_s1, TABLES IN SCHEMA pub_test EXCEPT (TABLE pub_test.testpub_tbl_s1); +ERROR: table "pub_test.testpub_tbl_s1" cannot appear in both the table list and the EXCEPT clause +-- error: except table's schema (public) not in the publication's schema list (pub_test) +ALTER PUBLICATION testpub_alter_except SET TABLES IN SCHEMA pub_test EXCEPT (TABLE public.testpub_tbl1); +ERROR: table "public.testpub_tbl1" in EXCEPT clause does not belong to schema "pub_test" +LINE 1: ...xcept SET TABLES IN SCHEMA pub_test EXCEPT (TABLE public.tes... + ^ +-- SET: unqualified name in EXCEPT is implicitly qualified with the schema +ALTER PUBLICATION testpub_alter_except SET TABLES IN SCHEMA pub_test EXCEPT (TABLE testpub_tbl_s1); +\dRp+ testpub_alter_except + Publication testpub_alter_except + Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Generated columns | Via root | Description +--------------------------+------------+---------------+---------+---------+---------+-----------+-------------------+----------+------------- + regress_publication_user | f | f | t | t | t | t | none | f | +Tables from schemas: + "pub_test" +Except tables: + "pub_test.testpub_tbl_s1" + +-- SET without EXCEPT clears the existing except list +ALTER PUBLICATION testpub_alter_except SET TABLES IN SCHEMA pub_test; +\dRp+ testpub_alter_except + Publication testpub_alter_except + Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Generated columns | Via root | Description +--------------------------+------------+---------------+---------+---------+---------+-----------+-------------------+----------+------------- + regress_publication_user | f | f | t | t | t | t | none | f | +Tables from schemas: + "pub_test" + +-- SET to a different schema removes old schema's EXCEPT entries +ALTER PUBLICATION testpub_alter_except SET TABLES IN SCHEMA pub_test EXCEPT (TABLE testpub_tbl_s1); +ALTER PUBLICATION testpub_alter_except SET TABLES IN SCHEMA public; +\dRp+ testpub_alter_except + Publication testpub_alter_except + Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Generated columns | Via root | Description +--------------------------+------------+---------------+---------+---------+---------+-----------+-------------------+----------+------------- + regress_publication_user | f | f | t | t | t | t | none | f | +Tables from schemas: + "public" + +-- fail: nonexistent table in EXCEPT clause (SET path) +ALTER PUBLICATION testpub_alter_except SET TABLES IN SCHEMA pub_test EXCEPT (TABLE pub_test.nonexistent_table); +ERROR: relation "pub_test.nonexistent_table" does not exist +-- SET: multiple schemas each with their own EXCEPT clause +ALTER PUBLICATION testpub_alter_except SET TABLES IN SCHEMA pub_test EXCEPT (TABLE pub_test.testpub_tbl_s1), + public EXCEPT (TABLE testpub_tbl1); +\dRp+ testpub_alter_except + Publication testpub_alter_except + Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Generated columns | Via root | Description +--------------------------+------------+---------------+---------+---------+---------+-----------+-------------------+----------+------------- + regress_publication_user | f | f | t | t | t | t | none | f | +Tables from schemas: + "pub_test" + "public" +Except tables: + "pub_test.testpub_tbl_s1" + "public.testpub_tbl1" + +-- error: EXCEPT is not allowed with DROP +ALTER PUBLICATION testpub_alter_except DROP TABLES IN SCHEMA pub_test EXCEPT (TABLE pub_test.testpub_tbl_s2); +ERROR: EXCEPT clause is not supported with DROP in ALTER PUBLICATION +-- DROP TABLES IN SCHEMA removes associated EXCEPT entries +ALTER PUBLICATION testpub_alter_except DROP TABLES IN SCHEMA pub_test; +\dRp+ testpub_alter_except + Publication testpub_alter_except + Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Generated columns | Via root | Description +--------------------------+------------+---------------+---------+---------+---------+-----------+-------------------+----------+------------- + regress_publication_user | f | f | t | t | t | t | none | f | +Tables from schemas: + "public" +Except tables: + "public.testpub_tbl1" + -- Cleanup RESET client_min_messages; DROP TABLE pub_test.testpub_tbl_s1, pub_test.testpub_tbl_s2; diff --git a/src/test/regress/sql/publication.sql b/src/test/regress/sql/publication.sql index 072d50050cd..72bb2f7a028 100644 --- a/src/test/regress/sql/publication.sql +++ b/src/test/regress/sql/publication.sql @@ -296,6 +296,44 @@ ALTER PUBLICATION testpub_alter_except ADD TABLES IN SCHEMA pub_test EXCEPT (tes ALTER PUBLICATION testpub_alter_except ADD TABLES IN SCHEMA pub_test EXCEPT (TABLE pub_test.testpub_tbl_s1, testpub_tbl_s2); \dRp+ testpub_alter_except +-- SET: replace the except list (keep same schema, different except table) +ALTER PUBLICATION testpub_alter_except SET TABLES IN SCHEMA pub_test EXCEPT (TABLE pub_test.testpub_tbl_s2); +\dRp+ testpub_alter_except + +-- fail: table in EXCEPT clause also appears in the explicit table list +ALTER PUBLICATION testpub_alter_except SET TABLE pub_test.testpub_tbl_s1, TABLES IN SCHEMA pub_test EXCEPT (TABLE pub_test.testpub_tbl_s1); + +-- error: except table's schema (public) not in the publication's schema list (pub_test) +ALTER PUBLICATION testpub_alter_except SET TABLES IN SCHEMA pub_test EXCEPT (TABLE public.testpub_tbl1); + +-- SET: unqualified name in EXCEPT is implicitly qualified with the schema +ALTER PUBLICATION testpub_alter_except SET TABLES IN SCHEMA pub_test EXCEPT (TABLE testpub_tbl_s1); +\dRp+ testpub_alter_except + +-- SET without EXCEPT clears the existing except list +ALTER PUBLICATION testpub_alter_except SET TABLES IN SCHEMA pub_test; +\dRp+ testpub_alter_except + +-- SET to a different schema removes old schema's EXCEPT entries +ALTER PUBLICATION testpub_alter_except SET TABLES IN SCHEMA pub_test EXCEPT (TABLE testpub_tbl_s1); +ALTER PUBLICATION testpub_alter_except SET TABLES IN SCHEMA public; +\dRp+ testpub_alter_except + +-- fail: nonexistent table in EXCEPT clause (SET path) +ALTER PUBLICATION testpub_alter_except SET TABLES IN SCHEMA pub_test EXCEPT (TABLE pub_test.nonexistent_table); + +-- SET: multiple schemas each with their own EXCEPT clause +ALTER PUBLICATION testpub_alter_except SET TABLES IN SCHEMA pub_test EXCEPT (TABLE pub_test.testpub_tbl_s1), + public EXCEPT (TABLE testpub_tbl1); +\dRp+ testpub_alter_except + +-- error: EXCEPT is not allowed with DROP +ALTER PUBLICATION testpub_alter_except DROP TABLES IN SCHEMA pub_test EXCEPT (TABLE pub_test.testpub_tbl_s2); + +-- DROP TABLES IN SCHEMA removes associated EXCEPT entries +ALTER PUBLICATION testpub_alter_except DROP TABLES IN SCHEMA pub_test; +\dRp+ testpub_alter_except + -- Cleanup RESET client_min_messages; DROP TABLE pub_test.testpub_tbl_s1, pub_test.testpub_tbl_s2; diff --git a/src/test/subscription/t/037_except.pl b/src/test/subscription/t/037_except.pl index 0ba6d6f8bb2..01eafb5b7c8 100644 --- a/src/test/subscription/t/037_except.pl +++ b/src/test/subscription/t/037_except.pl @@ -376,6 +376,61 @@ $result = is($result, qq(0), 'ALTER ... ADD TABLES IN SCHEMA EXCEPT: excluded table not synced'); +# SET: replace the except list; tab_excluded is now included and tab_published is excluded. +$node_publisher->safe_psql('postgres', + "ALTER PUBLICATION sch_pub SET TABLES IN SCHEMA sch1 EXCEPT (TABLE sch1.tab_published)" +); +$node_subscriber->safe_psql('postgres', + "ALTER SUBSCRIPTION sch_sub REFRESH PUBLICATION"); +$node_subscriber->wait_for_subscription_sync($node_publisher, 'sch_sub'); + +$node_publisher->safe_psql( + 'postgres', qq( + INSERT INTO sch1.tab_published VALUES (7); + INSERT INTO sch1.tab_excluded VALUES (7); +)); +$node_publisher->wait_for_catchup('sch_sub'); + +$result = + $node_subscriber->safe_psql('postgres', + "SELECT count(*) FROM sch1.tab_excluded WHERE a = 7"); +is($result, qq(1), + 'ALTER ... SET TABLES IN SCHEMA EXCEPT: newly included table is replicated' +); +$result = + $node_subscriber->safe_psql('postgres', + "SELECT count(*) FROM sch1.tab_published WHERE a = 7"); +is($result, qq(0), + 'ALTER ... SET TABLES IN SCHEMA EXCEPT: now-excluded table is not replicated' +); + +# SET without EXCEPT: clears the except list; both tables are now published. +$node_publisher->safe_psql('postgres', + "ALTER PUBLICATION sch_pub SET TABLES IN SCHEMA sch1"); +$node_subscriber->safe_psql('postgres', + "ALTER SUBSCRIPTION sch_sub REFRESH PUBLICATION"); +$node_subscriber->wait_for_subscription_sync($node_publisher, 'sch_sub'); + +$node_publisher->safe_psql( + 'postgres', qq( + INSERT INTO sch1.tab_published VALUES (8); + INSERT INTO sch1.tab_excluded VALUES (8); +)); +$node_publisher->wait_for_catchup('sch_sub'); + +$result = + $node_subscriber->safe_psql('postgres', + "SELECT count(*) FROM sch1.tab_published WHERE a = 8"); +is($result, qq(1), + 'ALTER ... SET TABLES IN SCHEMA (no EXCEPT): tab_published replicated after except list cleared' +); +$result = + $node_subscriber->safe_psql('postgres', + "SELECT count(*) FROM sch1.tab_excluded WHERE a = 8"); +is($result, qq(1), + 'ALTER ... SET TABLES IN SCHEMA (no EXCEPT): tab_excluded replicated after except list cleared' +); + $node_subscriber->safe_psql('postgres', 'DROP SUBSCRIPTION sch_sub'); $node_publisher->safe_psql('postgres', 'DROP PUBLICATION sch_pub'); @@ -443,6 +498,36 @@ $node_subscriber->safe_psql('postgres', 'DROP SUBSCRIPTION tap_sub'); $node_publisher->safe_psql('postgres', 'DROP PUBLICATION tap_pub1'); $node_publisher->safe_psql('postgres', 'DROP PUBLICATION tap_pub2'); +# OK when a table is excluded by a TABLES IN SCHEMA EXCEPT publication, +# but is included by another publication. +$node_publisher->safe_psql('postgres', 'TRUNCATE tab1'); +$node_subscriber->safe_psql('postgres', 'TRUNCATE tab1'); + +$node_publisher->safe_psql( + 'postgres', qq( + CREATE PUBLICATION tap_pub1 FOR TABLES IN SCHEMA public EXCEPT (TABLE public.tab1); + CREATE PUBLICATION tap_pub2 FOR TABLE tab1; + INSERT INTO tab1 VALUES(1); +)); +$node_subscriber->psql('postgres', + "CREATE SUBSCRIPTION tap_sub CONNECTION '$publisher_connstr' PUBLICATION tap_pub1, tap_pub2" +); +$node_subscriber->wait_for_subscription_sync($node_publisher, 'tap_sub'); + +$node_publisher->safe_psql('postgres', qq(INSERT INTO tab1 VALUES(2))); +$node_publisher->wait_for_catchup('tap_sub'); + +$result = + $node_subscriber->safe_psql('postgres', "SELECT * FROM tab1 ORDER BY a"); +is( $result, qq(1 +2), + "TABLES IN SCHEMA EXCEPT: table excluded in schema pub but included by another pub is replicated" +); + +$node_subscriber->safe_psql('postgres', 'DROP SUBSCRIPTION tap_sub'); +$node_publisher->safe_psql('postgres', 'DROP PUBLICATION tap_pub1'); +$node_publisher->safe_psql('postgres', 'DROP PUBLICATION tap_pub2'); + $node_publisher->stop('fast'); done_testing(); -- 2.50.1 (Apple Git-155)