From 36772f3135aee6e5512224c6eba7ac5bb07fcf81 Mon Sep 17 00:00:00 2001 From: Shlok Kyal Date: Fri, 24 Jul 2026 15:44:59 +0530 Subject: [PATCH v21 2/2] Support EXCEPT for ALL SEQUENCES in ALTER PUBLICATION Extend ALTER PUBLICATION to support an EXCEPT clause when using ALL SEQUENCES, allowing specific sequences to be excluded from the publication. If the EXCEPT clause is specified, the existing exclusion list for the publication is replaced with the provided sequences. If the EXCEPT clause is omitted, any existing exclusions for sequences are cleared. Example: ALTER PUBLICATION pub1 SET ALL SEQUENCES; This clears any existing sequence exclusions for the publication. ALTER PUBLICATION pub1 SET ALL SEQUENCES EXCEPT (SEQUENCE s1, s2); This sets the exclusion list to the specified sequences. --- doc/src/sgml/ref/alter_publication.sgml | 51 ++++- src/backend/catalog/pg_publication.c | 31 ++- src/backend/commands/publicationcmds.c | 266 ++++++++++++---------- src/backend/parser/gram.y | 2 +- src/bin/psql/tab-complete.in.c | 14 ++ src/include/catalog/pg_publication.h | 6 +- src/test/regress/expected/publication.out | 35 +++ src/test/regress/sql/publication.sql | 13 ++ src/test/subscription/t/037_except.pl | 20 ++ 9 files changed, 291 insertions(+), 147 deletions(-) diff --git a/doc/src/sgml/ref/alter_publication.sgml b/doc/src/sgml/ref/alter_publication.sgml index d2898d2633e..d07db2971eb 100644 --- a/doc/src/sgml/ref/alter_publication.sgml +++ b/doc/src/sgml/ref/alter_publication.sgml @@ -36,7 +36,7 @@ ALTER PUBLICATION name RENAME TO and publication_all_object is one of: ALL TABLES [ EXCEPT ( except_table_object [, ... ] ) ] - ALL SEQUENCES + ALL SEQUENCES [ EXCEPT ( except_sequence_object [, ... ] ) ] and publication_drop_object is one of: @@ -54,6 +54,10 @@ ALTER PUBLICATION name RENAME TO and table_object is: [ ONLY ] table_name [ * ] + +and except_sequence_object is: + + SEQUENCE sequence_name [, ... ] @@ -73,9 +77,9 @@ ALTER PUBLICATION name RENAME TO The third variant either modifies the included tables/schemas - or marks the publication as FOR ALL SEQUENCES or - FOR ALL TABLES, optionally using - EXCEPT to exclude specific tables. The + or marks the publication as FOR ALL TABLES or + FOR ALL SEQUENCES, optionally using + EXCEPT to exclude specific tables or sequences. The SET ALL TABLES clause can transform an empty publication, or one defined for ALL SEQUENCES (or both ALL TABLES and ALL SEQUENCES), into @@ -86,11 +90,15 @@ ALTER PUBLICATION name RENAME TO ALL SEQUENCES. In addition, SET ALL TABLES can be used to update the tables specified in the EXCEPT clause of a - FOR ALL TABLES publication. If EXCEPT - is specified with a list of tables, the existing exclusion list is replaced - with the specified tables. If EXCEPT is omitted, the - existing exclusion list is cleared. The SET clause, when - used with a publication defined with FOR TABLE or + FOR ALL TABLES publication and + SET ALL SEQUENCES can be used to update the sequences + specified in the EXCEPT clause of a + FOR ALL SEQUENCES publication. If + EXCEPT is specified with a list of tables or sequences, + the existing exclusion list is replaced with the specified tables or + sequences. If EXCEPT is omitted, the existing exclusion + list is cleared. The SET clause, when used with a + publication defined with 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. @@ -285,10 +293,31 @@ ALTER PUBLICATION mypublication SET ALL TABLES EXCEPT (TABLE users, departments) - Reset the publication to be a FOR ALL TABLES publication - with no excluded tables: + Reset the publication to be FOR ALL TABLES with no + exclusions: ALTER PUBLICATION mypublication SET ALL TABLES; + + + + Reset the publication to be ALL SEQUENCES with no + exclusions: + +ALTER PUBLICATION mypublication SET ALL SEQUENCES; + + + + Replace the sequence list in the publication's EXCEPT + clause: + +ALTER PUBLICATION mypublication SET ALL SEQUENCES EXCEPT (SEQUENCE seq1, seq2); + + + + Replace the table and sequence list in the publication's + EXCEPT clauses: + +ALTER PUBLICATION mypublication SET ALL TABLES EXCEPT (TABLE users, departments), ALL SEQUENCES EXCEPT (SEQUENCE seq1, seq2); diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c index fd4d29a148b..68c547b3ab2 100644 --- a/src/backend/catalog/pg_publication.c +++ b/src/backend/catalog/pg_publication.c @@ -956,7 +956,7 @@ GetRelationExcludedPublications(Oid relid) */ static List * get_publication_relations(Oid pubid, PublicationPartOpt pub_partopt, - bool except_flag) + bool except_flag, char pubrelkind) { List *result; Relation pubrelsrel; @@ -964,6 +964,8 @@ get_publication_relations(Oid pubid, PublicationPartOpt pub_partopt, SysScanDesc scan; HeapTuple tup; + Assert(pubrelkind == RELKIND_RELATION || pubrelkind == RELKIND_SEQUENCE); + /* Find all relations associated with the publication. */ pubrelsrel = table_open(PublicationRelRelationId, AccessShareLock); @@ -983,8 +985,15 @@ get_publication_relations(Oid pubid, PublicationPartOpt pub_partopt, pubrel = (Form_pg_publication_rel) GETSTRUCT(tup); if (except_flag == pubrel->prexcept) - result = GetPubPartitionOptionRelations(result, pub_partopt, - pubrel->prrelid); + { + char relkind = get_rel_relkind(pubrel->prrelid); + + if ((pubrelkind == RELKIND_RELATION && + (relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_TABLE)) || + (pubrelkind == RELKIND_SEQUENCE && relkind == RELKIND_SEQUENCE)) + result = GetPubPartitionOptionRelations(result, pub_partopt, + pubrel->prrelid); + } } systable_endscan(scan); @@ -1009,7 +1018,7 @@ GetIncludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt) { Assert(!GetPublication(pubid)->alltables); - return get_publication_relations(pubid, pub_partopt, false); + return get_publication_relations(pubid, pub_partopt, false, RELKIND_RELATION); } /* @@ -1017,7 +1026,8 @@ GetIncludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt) * 'FOR ALL TABLES' or a 'FOR ALL SEQUENCES' publication. */ List * -GetExcludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt) +GetExcludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt, + char pubrelkind) { #ifdef USE_ASSERT_CHECKING Publication *pub = GetPublication(pubid); @@ -1025,7 +1035,7 @@ GetExcludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt) Assert(pub->alltables || pub->allsequences); #endif - return get_publication_relations(pubid, pub_partopt, true); + return get_publication_relations(pubid, pub_partopt, true, pubrelkind); } /* @@ -1080,7 +1090,7 @@ GetAllTablesPublications(void) * clause. */ List * -GetAllPublicationRelations(Oid pubid, char relkind, bool pubviaroot) +GetAllPublicationRelations(Oid pubid, char pubrelkind, bool pubviaroot) { Relation classRel; ScanKeyData key[1]; @@ -1089,18 +1099,19 @@ GetAllPublicationRelations(Oid pubid, char relkind, bool pubviaroot) List *result = NIL; List *exceptlist = NIL; - Assert(!(relkind == RELKIND_SEQUENCE && pubviaroot)); + Assert(!(pubrelkind == RELKIND_SEQUENCE && pubviaroot)); exceptlist = GetExcludedPublicationRelations(pubid, pubviaroot ? PUBLICATION_PART_ROOT : - PUBLICATION_PART_LEAF); + PUBLICATION_PART_LEAF, + pubrelkind); classRel = table_open(RelationRelationId, AccessShareLock); ScanKeyInit(&key[0], Anum_pg_class_relkind, BTEqualStrategyNumber, F_CHAREQ, - CharGetDatum(relkind)); + CharGetDatum(pubrelkind)); scan = table_beginscan_catalog(classRel, 1, key); diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c index 62d290b5142..e61a9a45111 100644 --- a/src/backend/commands/publicationcmds.c +++ b/src/backend/commands/publicationcmds.c @@ -66,7 +66,7 @@ static void CloseRelationList(List *rels); static void LockSchemaList(List *schemalist); static void PublicationAddRelations(Oid pubid, List *rels, bool if_not_exists, AlterPublicationStmt *stmt, char pubrelkind); -static void PublicationDropTables(Oid pubid, List *rels, bool missing_ok); +static void PublicationDropRelations(Oid pubid, List *rels, bool missing_ok); static void PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists, AlterPublicationStmt *stmt); static void PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok); @@ -1253,26 +1253,131 @@ InvalidatePublicationRels(List *relids) } /* - * Add or remove table to/from publication. + * Return the list of tables/sequences to be removed from a publication during + * ALTER PUBLICATION ... SET. + * + * 'rels' contains the relations specified by the SET command, and 'oldrelids' + * contains the existing relations in the publication. The function returns the + * existing relations that are not present in 'rels' and therefore need to be + * removed. + */ +static List * +get_delete_rels(Oid pubid, List *rels, List *oldrelids) +{ + List *delrels = NIL; + + foreach_oid(oldrelid, oldrelids) + { + ListCell *newlc; + PublicationRelInfo *oldrel; + bool found = false; + HeapTuple rftuple; + Node *oldrelwhereclause = NULL; + Bitmapset *oldcolumns = NULL; + + /* Look up the cache for the old relmap */ + rftuple = SearchSysCache2(PUBLICATIONRELMAP, + ObjectIdGetDatum(oldrelid), + ObjectIdGetDatum(pubid)); + + /* + * See if the existing relation currently has a WHERE clause or a + * column list. We need to compare those too. + */ + if (HeapTupleIsValid(rftuple)) + { + bool isnull = true; + Datum whereClauseDatum; + Datum columnListDatum; + + /* Load the WHERE clause for this table. */ + whereClauseDatum = SysCacheGetAttr(PUBLICATIONRELMAP, rftuple, + Anum_pg_publication_rel_prqual, + &isnull); + if (!isnull) + oldrelwhereclause = stringToNode(TextDatumGetCString(whereClauseDatum)); + + /* Transform the int2vector column list to a bitmap. */ + columnListDatum = SysCacheGetAttr(PUBLICATIONRELMAP, rftuple, + Anum_pg_publication_rel_prattrs, + &isnull); + + if (!isnull) + oldcolumns = pub_collist_to_bitmapset(NULL, columnListDatum, NULL); + + ReleaseSysCache(rftuple); + } + + /* + * Check if any of the new set of relations matches with the existing + * relations in the publication. Additionally, if the relation has an + * associated WHERE clause, check the WHERE expressions also match. + * Same for the column list. Drop the rest. + */ + foreach(newlc, rels) + { + PublicationRelInfo *newpubrel; + Oid newrelid; + Bitmapset *newcolumns = NULL; + + newpubrel = (PublicationRelInfo *) lfirst(newlc); + newrelid = RelationGetRelid(newpubrel->relation); + + /* + * Validate the column list. If the column list or WHERE clause + * changes, then the validation done here will be duplicated + * inside PublicationAddRelations(). The validation is cheap + * enough that that seems harmless. + */ + newcolumns = pub_collist_validate(newpubrel->relation, + newpubrel->columns); + + found = (newrelid == oldrelid) && + equal(oldrelwhereclause, newpubrel->whereClause) && + bms_equal(oldcolumns, newcolumns); + + if (found) + break; + } + + /* + * Add non-matching relations to the drop list. The relation will be + * dropped irrespective of the column list and WHERE clause. + */ + if (!found) + { + oldrel = palloc0_object(PublicationRelInfo); + oldrel->relation = table_open(oldrelid, + ShareUpdateExclusiveLock); + delrels = lappend(delrels, oldrel); + } + } + + return delrels; +} + +/* + * Add or remove table or sequence to/from publication. */ static void -AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup, - List *tables, const char *queryString, - bool publish_schema) +AlterPublicationRelations(AlterPublicationStmt *stmt, HeapTuple tup, + List *tables, List *sequences, const char *queryString, + bool publish_schema) { List *rels = NIL; + List *seqs = NIL; Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup); Oid pubid = pubform->oid; /* - * Nothing to do if no objects, except in SET: for that it is quite - * possible that user has not specified any tables in which case we need - * to remove all the existing tables. + * Nothing to do if no objects were specified, unless this is a SET + * command, which may need to remove all existing tables and sequences. */ - if (!tables && stmt->action != AP_SetObjects) + if (!tables && !sequences && stmt->action != AP_SetObjects) return; rels = OpenRelationList(tables); + seqs = OpenRelationList(sequences); if (stmt->action == AP_AddObjects) { @@ -1286,32 +1391,38 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup, PublicationAddRelations(pubid, rels, false, stmt, RELKIND_RELATION); } else if (stmt->action == AP_DropObjects) - PublicationDropTables(pubid, rels, false); + PublicationDropRelations(pubid, rels, false); else /* AP_SetObjects */ { List *oldrelids = NIL; + List *oldseqids = NIL; List *delrels = NIL; - ListCell *oldlc; if (stmt->for_all_tables || stmt->for_all_sequences) { /* - * In FOR ALL TABLES mode, relations are tracked as exclusions - * (EXCEPT clause). Fetch the current excluded relations so they - * can be reconciled with the specified EXCEPT list. + * In FOR ALL TABLES or FOR ALL SEQUENCES mode, relations are + * tracked as exclusions (EXCEPT clause). Fetch the current + * excluded relations so they can be reconciled with the specified + * EXCEPT list. * * This applies only if the existing publication is already - * defined as FOR ALL TABLES; otherwise, there are no exclusion - * entries to process. + * defined as FOR ALL TABLES or FOR ALL SEQUENCES; otherwise, + * there are no exclusion entries to process. */ if (pubform->puballtables) - { oldrelids = GetExcludedPublicationRelations(pubid, - PUBLICATION_PART_ROOT); - } + PUBLICATION_PART_ROOT, + RELKIND_RELATION); + if (pubform->puballsequences) + oldseqids = GetExcludedPublicationRelations(pubid, + PUBLICATION_PART_ROOT, + RELKIND_SEQUENCE); } else { + Assert(!sequences); + oldrelids = GetIncludedPublicationRelations(pubid, PUBLICATION_PART_ROOT); @@ -1321,118 +1432,25 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup, pubform->pubviaroot); } - /* - * To recreate the relation list for the publication, look for - * existing relations that do not need to be dropped. - */ - foreach(oldlc, oldrelids) - { - Oid oldrelid = lfirst_oid(oldlc); - ListCell *newlc; - PublicationRelInfo *oldrel; - bool found = false; - HeapTuple rftuple; - Node *oldrelwhereclause = NULL; - Bitmapset *oldcolumns = NULL; - - /* look up the cache for the old relmap */ - rftuple = SearchSysCache2(PUBLICATIONRELMAP, - ObjectIdGetDatum(oldrelid), - ObjectIdGetDatum(pubid)); - - /* - * See if the existing relation currently has a WHERE clause or a - * column list. We need to compare those too. - */ - if (HeapTupleIsValid(rftuple)) - { - bool isnull = true; - Datum whereClauseDatum; - Datum columnListDatum; - - /* Load the WHERE clause for this table. */ - whereClauseDatum = SysCacheGetAttr(PUBLICATIONRELMAP, rftuple, - Anum_pg_publication_rel_prqual, - &isnull); - if (!isnull) - oldrelwhereclause = stringToNode(TextDatumGetCString(whereClauseDatum)); - - /* Transform the int2vector column list to a bitmap. */ - columnListDatum = SysCacheGetAttr(PUBLICATIONRELMAP, rftuple, - Anum_pg_publication_rel_prattrs, - &isnull); - - if (!isnull) - oldcolumns = pub_collist_to_bitmapset(NULL, columnListDatum, NULL); - - ReleaseSysCache(rftuple); - } - - foreach(newlc, rels) - { - PublicationRelInfo *newpubrel; - Oid newrelid; - Bitmapset *newcolumns = NULL; - - newpubrel = (PublicationRelInfo *) lfirst(newlc); - newrelid = RelationGetRelid(newpubrel->relation); - - /* - * Validate the column list. If the column list or WHERE - * clause changes, then the validation done here will be - * duplicated inside PublicationAddRelations(). The - * validation is cheap enough that that seems harmless. - */ - newcolumns = pub_collist_validate(newpubrel->relation, - newpubrel->columns); - - /* - * Check if any of the new set of relations matches with the - * existing relations in the publication. Additionally, if the - * relation has an associated WHERE clause, check the WHERE - * expressions also match. Same for the column list. Drop the - * rest. - */ - if (newrelid == oldrelid) - { - if (equal(oldrelwhereclause, newpubrel->whereClause) && - bms_equal(oldcolumns, newcolumns)) - { - found = true; - break; - } - } - } - - /* - * Add the non-matched relations to a list so that they can be - * dropped. - */ - if (!found) - { - oldrel = palloc_object(PublicationRelInfo); - oldrel->whereClause = NULL; - oldrel->columns = NIL; - oldrel->except = false; - oldrel->relation = table_open(oldrelid, - ShareUpdateExclusiveLock); - delrels = lappend(delrels, oldrel); - } - } + /* Get tables and sequences to be dropped */ + delrels = get_delete_rels(pubid, rels, oldrelids); + delrels = list_concat(delrels, get_delete_rels(pubid, seqs, oldseqids)); /* And drop them. */ - PublicationDropTables(pubid, delrels, true); + PublicationDropRelations(pubid, delrels, true); /* * Don't bother calculating the difference for adding, we'll catch and * skip existing ones when doing catalog update. */ PublicationAddRelations(pubid, rels, true, stmt, RELKIND_RELATION); + PublicationAddRelations(pubid, seqs, true, stmt, RELKIND_SEQUENCE); CloseRelationList(delrels); } CloseRelationList(rels); + CloseRelationList(seqs); } /* @@ -1708,7 +1726,9 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt) ObjectsInPublicationToOids(stmt->pubobjects, pstate, &relations, &excepttbls, &exceptseqs, &schemaidlist); - Assert(exceptseqs == NIL); + /* EXCEPT clause is only supported for ALTER PUBLICATION ... SET */ + Assert((excepttbls == NIL && exceptseqs == NIL) || + stmt->action == AP_SetObjects); CheckAlterPublication(stmt, tup, relations, schemaidlist); @@ -1732,8 +1752,8 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt) stmt->pubname)); relations = list_concat(relations, excepttbls); - AlterPublicationTables(stmt, tup, relations, pstate->p_sourcetext, - schemaidlist != NIL); + AlterPublicationRelations(stmt, tup, relations, exceptseqs, + pstate->p_sourcetext, schemaidlist != NIL); AlterPublicationSchemas(stmt, tup, schemaidlist); AlterPublicationAllFlags(stmt, rel, tup); } @@ -2088,10 +2108,10 @@ PublicationAddRelations(Oid pubid, List *rels, bool if_not_exists, } /* - * Remove listed tables from the publication. + * Remove listed relations from the publication. */ static void -PublicationDropTables(Oid pubid, List *rels, bool missing_ok) +PublicationDropRelations(Oid pubid, List *rels, bool missing_ok) { ObjectAddress obj; ListCell *lc; diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 4f6b33cb39d..0218788de45 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -11509,7 +11509,7 @@ pub_except_seq_list: PublicationExceptSeqSpec * pub_all_obj_type is one of: * * ALL TABLES [ EXCEPT ( TABLE table_name [, ...] ) ] - * ALL SEQUENCES + * ALL SEQUENCES [ EXCEPT ( SEQUENCE sequence_name [, ...] ) ] * *****************************************************************************/ diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c index d75b67f9ea4..3a3e77e1125 100644 --- a/src/bin/psql/tab-complete.in.c +++ b/src/bin/psql/tab-complete.in.c @@ -2337,6 +2337,20 @@ match_previous_words(int pattern_id, COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables); else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "TABLES", "EXCEPT", "(", "TABLE", MatchAnyN) && !ends_with(prev_wd, ',')) COMPLETE_WITH(")"); + else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "SEQUENCES", "EXCEPT", "(")) + COMPLETE_WITH("SEQUENCE"); + else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "SEQUENCES")) + COMPLETE_WITH("EXCEPT ( SEQUENCE"); + else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "SEQUENCES", "EXCEPT")) + COMPLETE_WITH("( SEQUENCE"); + else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "SEQUENCES", "EXCEPT", "(")) + COMPLETE_WITH("SEQUENCE"); + else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "SEQUENCES", "EXCEPT", "(", "SEQUENCE")) + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences); + else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "SEQUENCES", "EXCEPT", "(", "SEQUENCE", MatchAnyN) && ends_with(prev_wd, ',')) + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences); + else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "SEQUENCES", "EXCEPT", "(", "SEQUENCE", MatchAnyN) && !ends_with(prev_wd, ',')) + COMPLETE_WITH(")"); else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD|DROP|SET", "TABLES", "IN", "SCHEMA")) COMPLETE_WITH_QUERY_PLUS(Query_for_list_of_schemas " AND nspname NOT LIKE E'pg\\\\_%%'", diff --git a/src/include/catalog/pg_publication.h b/src/include/catalog/pg_publication.h index 49c29a87630..21a1527bcc7 100644 --- a/src/include/catalog/pg_publication.h +++ b/src/include/catalog/pg_publication.h @@ -178,9 +178,11 @@ typedef enum PublicationPartOpt extern List *GetIncludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt); extern List *GetExcludedPublicationRelations(Oid pubid, - PublicationPartOpt pub_partopt); + PublicationPartOpt pub_partopt, + char pubrelkind); extern List *GetAllTablesPublications(void); -extern List *GetAllPublicationRelations(Oid pubid, char relkind, bool pubviaroot); +extern List *GetAllPublicationRelations(Oid pubid, char pubrelkind, + bool pubviaroot); extern List *GetPublicationSchemas(Oid pubid); extern List *GetSchemaPublications(Oid schemaid); extern List *GetSchemaPublicationRelations(Oid schemaid, diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out index 58fdf9e9b9a..c4436977905 100644 --- a/src/test/regress/expected/publication.out +++ b/src/test/regress/expected/publication.out @@ -586,6 +586,25 @@ Except sequences: "pub_test.regress_seq2" "public.regress_seq0" +-- Modify the sequence list in the EXCEPT clause +ALTER PUBLICATION regress_pub_forallsequences_except SET ALL SEQUENCES EXCEPT (SEQUENCE regress_seq0); +\dRp+ regress_pub_forallsequences_except + Publication regress_pub_forallsequences_except + Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Generated columns | Via root | Description +--------------------------+------------+---------------+---------+---------+---------+-----------+-------------------+----------+------------- + regress_publication_user | f | t | t | t | t | t | none | f | +Except sequences: + "public.regress_seq0" + +-- Clear the sequence list in the EXCEPT clause +ALTER PUBLICATION regress_pub_forallsequences_except SET ALL SEQUENCES; +\dRp+ regress_pub_forallsequences_except + Publication regress_pub_forallsequences_except + Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Generated columns | Via root | Description +--------------------------+------------+---------------+---------+---------+---------+-----------+-------------------+----------+------------- + regress_publication_user | f | t | t | t | t | t | none | f | +(1 row) + -- Test combination of ALL SEQUENCES and ALL TABLES each with an EXCEPT clause CREATE TABLE regress_tab1(a int); CREATE PUBLICATION regress_pub_for_allsequences_alltables_except FOR ALL TABLES EXCEPT (TABLE regress_tab1), ALL SEQUENCES EXCEPT (SEQUENCE regress_seq0); @@ -605,24 +624,40 @@ CREATE PUBLICATION regress_pub_should_fail FOR ALL TABLES EXCEPT (regress_seq0, ERROR: syntax error at or near "regress_seq0" LINE 1: ...ON regress_pub_should_fail FOR ALL TABLES EXCEPT (regress_se... ^ +ALTER PUBLICATION regress_pub_forallsequences_except SET ALL SEQUENCES EXCEPT (regress_seq0, pub_test.regress_seq1); +ERROR: syntax error at or near "regress_seq0" +LINE 1: ..._forallsequences_except SET ALL SEQUENCES EXCEPT (regress_se... + ^ -- fail - unlogged sequence is specified in EXCEPT sequence list CREATE UNLOGGED SEQUENCE regress_seq_unlogged; CREATE PUBLICATION regress_pub_should_fail FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_seq_unlogged); ERROR: cannot specify "public.regress_seq_unlogged" in the publication EXCEPT (SEQUENCE) clause DETAIL: This operation is not supported for unlogged sequences. +ALTER PUBLICATION regress_pub_forallsequences_except SET ALL SEQUENCES EXCEPT (SEQUENCE regress_seq_unlogged); +ERROR: cannot specify "public.regress_seq_unlogged" in the publication EXCEPT (SEQUENCE) clause +DETAIL: This operation is not supported for unlogged sequences. -- fail - temporary sequence is specified in EXCEPT sequence list CREATE TEMPORARY SEQUENCE regress_seq_temp; CREATE PUBLICATION regress_pub_should_fail FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_seq_temp); ERROR: cannot specify "pg_temp.regress_seq_temp" in the publication EXCEPT (SEQUENCE) clause DETAIL: This operation is not supported for temporary sequences. +ALTER PUBLICATION regress_pub_forallsequences_except SET ALL SEQUENCES EXCEPT (SEQUENCE regress_seq_temp); +ERROR: cannot specify "pg_temp.regress_seq_temp" in the publication EXCEPT (SEQUENCE) clause +DETAIL: This operation is not supported for temporary sequences. -- fail - sequence object is specified in EXCEPT table list CREATE PUBLICATION regress_pub_should_fail FOR ALL TABLES EXCEPT (TABLE regress_seq0); ERROR: cannot specify "public.regress_seq0" in the publication EXCEPT (TABLE) clause DETAIL: This operation is not supported for sequences. +ALTER PUBLICATION regress_pub_forallsequences_except SET ALL TABLES EXCEPT (TABLE regress_seq0); +ERROR: cannot specify "public.regress_seq0" in the publication EXCEPT (TABLE) clause +DETAIL: This operation is not supported for sequences. -- fail - table object is specified in EXCEPT sequence list CREATE PUBLICATION regress_pub_should_fail FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_tab1); ERROR: cannot specify "public.regress_tab1" in the publication EXCEPT (SEQUENCE) clause DETAIL: This operation is not supported for tables. +ALTER PUBLICATION regress_pub_forallsequences_except SET ALL SEQUENCES EXCEPT (SEQUENCE regress_tab1); +ERROR: cannot specify "public.regress_tab1" in the publication EXCEPT (SEQUENCE) clause +DETAIL: This operation is not supported for tables. DROP SEQUENCE regress_seq0, pub_test.regress_seq1, pub_test.regress_seq2, regress_seq_unlogged, regress_seq_temp; DROP PUBLICATION regress_pub_forallsequences1; DROP PUBLICATION regress_pub_forallsequences2; diff --git a/src/test/regress/sql/publication.sql b/src/test/regress/sql/publication.sql index 9e37343e8c6..4148988ae94 100644 --- a/src/test/regress/sql/publication.sql +++ b/src/test/regress/sql/publication.sql @@ -271,6 +271,14 @@ CREATE PUBLICATION regress_pub_forallsequences_except FOR ALL SEQUENCES EXCEPT ( ALTER SEQUENCE regress_seq2 SET SCHEMA pub_test; \dRp+ regress_pub_forallsequences_except +-- Modify the sequence list in the EXCEPT clause +ALTER PUBLICATION regress_pub_forallsequences_except SET ALL SEQUENCES EXCEPT (SEQUENCE regress_seq0); +\dRp+ regress_pub_forallsequences_except + +-- Clear the sequence list in the EXCEPT clause +ALTER PUBLICATION regress_pub_forallsequences_except SET ALL SEQUENCES; +\dRp+ regress_pub_forallsequences_except + -- Test combination of ALL SEQUENCES and ALL TABLES each with an EXCEPT clause CREATE TABLE regress_tab1(a int); CREATE PUBLICATION regress_pub_for_allsequences_alltables_except FOR ALL TABLES EXCEPT (TABLE regress_tab1), ALL SEQUENCES EXCEPT (SEQUENCE regress_seq0); @@ -279,20 +287,25 @@ RESET client_min_messages; -- fail - first sequence in the EXCEPT list should use SEQUENCE keyword CREATE PUBLICATION regress_pub_should_fail FOR ALL TABLES EXCEPT (regress_seq0, pub_test.regress_seq1); +ALTER PUBLICATION regress_pub_forallsequences_except SET ALL SEQUENCES EXCEPT (regress_seq0, pub_test.regress_seq1); -- fail - unlogged sequence is specified in EXCEPT sequence list CREATE UNLOGGED SEQUENCE regress_seq_unlogged; CREATE PUBLICATION regress_pub_should_fail FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_seq_unlogged); +ALTER PUBLICATION regress_pub_forallsequences_except SET ALL SEQUENCES EXCEPT (SEQUENCE regress_seq_unlogged); -- fail - temporary sequence is specified in EXCEPT sequence list CREATE TEMPORARY SEQUENCE regress_seq_temp; CREATE PUBLICATION regress_pub_should_fail FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_seq_temp); +ALTER PUBLICATION regress_pub_forallsequences_except SET ALL SEQUENCES EXCEPT (SEQUENCE regress_seq_temp); -- fail - sequence object is specified in EXCEPT table list CREATE PUBLICATION regress_pub_should_fail FOR ALL TABLES EXCEPT (TABLE regress_seq0); +ALTER PUBLICATION regress_pub_forallsequences_except SET ALL TABLES EXCEPT (TABLE regress_seq0); -- fail - table object is specified in EXCEPT sequence list CREATE PUBLICATION regress_pub_should_fail FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_tab1); +ALTER PUBLICATION regress_pub_forallsequences_except SET ALL SEQUENCES EXCEPT (SEQUENCE regress_tab1); DROP SEQUENCE regress_seq0, pub_test.regress_seq1, pub_test.regress_seq2, regress_seq_unlogged, regress_seq_temp; DROP PUBLICATION regress_pub_forallsequences1; diff --git a/src/test/subscription/t/037_except.pl b/src/test/subscription/t/037_except.pl index 989d5800826..c220cf3905e 100644 --- a/src/test/subscription/t/037_except.pl +++ b/src/test/subscription/t/037_except.pl @@ -289,6 +289,7 @@ $node_publisher->safe_psql( 'postgres', qq ( CREATE TABLE seq_test (v BIGINT); CREATE SEQUENCE seq_excluded_in_pub1; + CREATE SEQUENCE seq_excluded_in_pub1_2; CREATE SEQUENCE seq_excluded_in_pub2; INSERT INTO seq_test SELECT nextval('seq_excluded_in_pub1') FROM generate_series(1,100); INSERT INTO seq_test SELECT nextval('seq_excluded_in_pub2') FROM generate_series(1,100); @@ -297,6 +298,7 @@ $node_publisher->safe_psql( $node_subscriber->safe_psql( 'postgres', qq( CREATE SEQUENCE seq_excluded_in_pub1; + CREATE SEQUENCE seq_excluded_in_pub1_2; CREATE SEQUENCE seq_excluded_in_pub2; CREATE SUBSCRIPTION tap_sub_all_seq_except CONNECTION '$publisher_connstr' PUBLICATION tap_pub_all_seq_except1; )); @@ -316,6 +318,24 @@ $result = $node_subscriber->safe_psql('postgres', "SELECT last_value, is_called FROM seq_excluded_in_pub2"); is($result, '100|t', 'initial test data replicated for seq_excluded_in_pub2'); +# Check ALTER PUBLICATION ... ALL SEQUENCES EXCEPT (SEQUENCE ...) +$node_publisher->safe_psql( + 'postgres', qq( + ALTER PUBLICATION tap_pub_all_seq_except1 SET ALL SEQUENCES EXCEPT (SEQUENCE seq_excluded_in_pub1, seq_excluded_in_pub1_2); + INSERT INTO seq_test SELECT nextval('seq_excluded_in_pub1_2') FROM generate_series(1,100); +)); +$node_subscriber->safe_psql( + 'postgres', qq( + ALTER SUBSCRIPTION tap_sub_all_seq_except REFRESH PUBLICATION; + ALTER SUBSCRIPTION tap_sub_all_seq_except REFRESH SEQUENCES; +)); +$node_subscriber->poll_query_until('postgres', $synced_query) + or die "Timed out while waiting for subscriber to synchronize data"; + +$result = $node_subscriber->safe_psql('postgres', + "SELECT last_value, is_called FROM seq_excluded_in_pub1_2"); +is($result, '1|f', 'sequences in the EXCEPT list are excluded'); + # ============================================ # Test when a subscription is subscribing to multiple publications # ============================================ -- 2.34.1