From fb1599b790933ec9e3836f14a189fc5148db5cfe Mon Sep 17 00:00:00 2001 From: Nisha Moond Date: Thu, 18 Jun 2026 11:32:32 +0530 Subject: [PATCH v17 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. --- src/backend/commands/publicationcmds.c | 170 ++++++++++++++++++---- src/bin/psql/tab-complete.in.c | 17 +++ src/test/regress/expected/publication.out | 103 +++++++++++++ src/test/regress/sql/publication.sql | 44 ++++++ src/test/subscription/t/037_except.pl | 85 +++++++++++ 5 files changed, 394 insertions(+), 25 deletions(-) diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c index 542579dae86..00f8d003779 100644 --- a/src/backend/commands/publicationcmds.c +++ b/src/backend/commands/publicationcmds.c @@ -66,7 +66,8 @@ static void CloseTableList(List *rels); static void LockSchemaList(List *schemalist); static void PublicationAddTables(Oid pubid, List *rels, bool if_not_exists, AlterPublicationStmt *stmt); -static void PublicationDropTables(Oid pubid, List *rels, bool missing_ok); +static void PublicationDropTables(Oid pubid, List *rels, bool missing_ok, + bool delete_excluded); static void PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists, AlterPublicationStmt *stmt); static void PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok); @@ -1360,7 +1361,7 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup, PublicationAddTables(pubid, rels, false, stmt); } else if (stmt->action == AP_DropObjects) - PublicationDropTables(pubid, rels, false); + PublicationDropTables(pubid, rels, false, false); else /* AP_SetObjects */ { List *oldrelids = NIL; @@ -1495,7 +1496,7 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup, } /* And drop them. */ - PublicationDropTables(pubid, delrels, true); + PublicationDropTables(pubid, delrels, true, true); /* * Don't bother calculating the difference for adding, we'll catch and @@ -1597,8 +1598,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(); @@ -1616,16 +1617,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; /* @@ -1646,16 +1649,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; @@ -1674,6 +1667,75 @@ 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). + */ + CheckExceptNotInTableList(rels, explicitrelids); + + /* + * 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); } } @@ -2297,29 +2359,54 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists, /* * Remove listed tables from the publication. + * + * delete_excluded controls how EXCEPT (prexcept=true) rows are handled: + * true - delete them silently. Used by the SET path to clear stale + * EXCEPT rows that are being replaced by a new EXCEPT list. + * false - treat them as "not a member". Used by ALTER ... DROP TABLE: an + * EXCEPT entry is not something DROP TABLE is expected to remove. */ static void -PublicationDropTables(Oid pubid, List *rels, bool missing_ok) +PublicationDropTables(Oid pubid, List *rels, bool missing_ok, + bool delete_excluded) { ObjectAddress obj; ListCell *lc; - Oid prid; foreach(lc, rels) { PublicationRelInfo *pubrel = (PublicationRelInfo *) lfirst(lc); Relation rel = pubrel->relation; Oid relid = RelationGetRelid(rel); + HeapTuple tup; + Form_pg_publication_rel pubrelform; + Oid prid = InvalidOid; + bool is_except = false; if (pubrel->columns) ereport(ERROR, errcode(ERRCODE_SYNTAX_ERROR), errmsg("column list must not be specified in ALTER PUBLICATION ... DROP")); - prid = GetSysCacheOid2(PUBLICATIONRELMAP, Anum_pg_publication_rel_oid, - ObjectIdGetDatum(relid), - ObjectIdGetDatum(pubid)); - if (!OidIsValid(prid)) + tup = SearchSysCache2(PUBLICATIONRELMAP, + ObjectIdGetDatum(relid), + ObjectIdGetDatum(pubid)); + if (HeapTupleIsValid(tup)) + { + pubrelform = (Form_pg_publication_rel) GETSTRUCT(tup); + is_except = pubrelform->prexcept; + prid = pubrelform->oid; + ReleaseSysCache(tup); + } + + /* + * DROP TABLE operates only on regular member rows. A missing row + * or an EXCEPT row both mean "not a member" from the user's + * perspective, so we treat them the same way. The SET-cleanup + * caller (delete_excluded = true) intends to remove EXCEPT rows + * and so bypasses the EXCEPT half of this check. + */ + if (!OidIsValid(prid) || (is_except && !delete_excluded)) { if (missing_ok) continue; @@ -2379,6 +2466,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, @@ -2395,8 +2483,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/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c index 277ae97ad41..b7841503573 100644 --- a/src/bin/psql/tab-complete.in.c +++ b/src/bin/psql/tab-complete.in.c @@ -2390,6 +2390,23 @@ 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", "CURRENT_SCHEMA", "EXCEPT", "(", "TABLE")) + COMPLETE_WITH_QUERY_VERBATIM(Query_for_list_of_tables_in_current_schema); + 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 c40a8884a8f..6be5b163d99 100644 --- a/src/test/regress/expected/publication.out +++ b/src/test/regress/expected/publication.out @@ -621,6 +621,109 @@ 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: ALTER PUBLICATION ... DROP TABLE on an excluded table is rejected +-- with "not part of the publication" — an EXCEPT entry is not a member. +ALTER PUBLICATION testpub_alter_except DROP TABLE pub_test.testpub_tbl_s1; +ERROR: relation "testpub_tbl_s1" is not part of the publication +-- the EXCEPT entry is still present after the rejected DROP TABLE +\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 cd5ed06dcad..f684e75d6f4 100644 --- a/src/test/regress/sql/publication.sql +++ b/src/test/regress/sql/publication.sql @@ -308,6 +308,50 @@ 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: ALTER PUBLICATION ... DROP TABLE on an excluded table is rejected +-- with "not part of the publication" — an EXCEPT entry is not a member. +ALTER PUBLICATION testpub_alter_except DROP TABLE pub_test.testpub_tbl_s1; +-- the EXCEPT entry is still present after the rejected DROP TABLE +\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)