From 96a71db9e1ef0b2fdfbde71624e70ea92261899a Mon Sep 17 00:00:00 2001 From: Shlok Kyal Date: Tue, 31 Mar 2026 15:25:21 +0530 Subject: [PATCH v1 1/2] Support EXCEPT for ALL SEQUENCES in CREATE PUBLICATION Extend CREATE PUBLICATION ... FOR ALL SEQUENCES to support the EXCEPT syntax. This allows one or more sequences to be excluded. The publisher will not send the data of excluded sequences to the subscriber. Example: CREATE PUBLICATION pub1 FOR ALL SEQUENCES EXCEPT (SEQUENCE s1, s2); --- doc/src/sgml/catalogs.sgml | 4 +- doc/src/sgml/logical-replication.sgml | 7 +- doc/src/sgml/ref/create_publication.sgml | 24 +++-- src/backend/catalog/pg_publication.c | 41 +++++--- src/backend/commands/publicationcmds.c | 104 +++++++++++-------- src/backend/parser/gram.y | 115 ++++++++++++++-------- src/bin/pg_dump/pg_dump.c | 44 +++++++-- src/bin/pg_dump/pg_dump.h | 1 + src/bin/pg_dump/t/002_pg_dump.pl | 14 ++- src/bin/psql/describe.c | 65 +++++++++++- src/bin/psql/tab-complete.in.c | 10 ++ src/include/catalog/pg_publication.h | 7 +- src/include/nodes/parsenodes.h | 10 +- src/test/regress/expected/publication.out | 42 +++++++- src/test/regress/sql/publication.sql | 21 +++- src/test/subscription/t/037_except.pl | 66 +++++++++++++ src/tools/pgindent/typedefs.list | 2 +- 17 files changed, 447 insertions(+), 130 deletions(-) diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml index 4b474c13917..2790dae2900 100644 --- a/doc/src/sgml/catalogs.sgml +++ b/doc/src/sgml/catalogs.sgml @@ -7108,8 +7108,8 @@ SCRAM-SHA-256$<iteration count>:&l prexcept bool - True if the table is excluded from the publication. See - EXCEPT. + True if the table or the sequence is excluded from the publication. See + EXCEPT. diff --git a/doc/src/sgml/logical-replication.sgml b/doc/src/sgml/logical-replication.sgml index 23b268273b9..785a036989a 100644 --- a/doc/src/sgml/logical-replication.sgml +++ b/doc/src/sgml/logical-replication.sgml @@ -119,8 +119,11 @@ . When a publication is created with FOR ALL TABLES, a table or set of tables can be explicitly excluded from publication using the - EXCEPT - clause. + EXCEPT + clause. When a publication is created with FOR ALL SEQUENCES, + a sequence or set of sequences can be explicitly excluded from publication + using the + EXCEPT diff --git a/doc/src/sgml/ref/create_publication.sgml b/doc/src/sgml/ref/create_publication.sgml index 0ac576d3f10..02136ecc0d5 100644 --- a/doc/src/sgml/ref/create_publication.sgml +++ b/doc/src/sgml/ref/create_publication.sgml @@ -33,7 +33,7 @@ CREATE PUBLICATION name and publication_all_object is one of: ALL TABLES [ EXCEPT ( except_table_object [, ... ] ) ] - ALL SEQUENCES + ALL SEQUENCES [ EXCEPT ( except_sequence_object [, ... ] ) ] and table_and_columns is: @@ -46,6 +46,10 @@ CREATE PUBLICATION name and table_object is: [ ONLY ] table_name [ * ] + +and except_sequence_object is: + + SEQUENCE sequence_name [, ... ] @@ -183,7 +187,9 @@ CREATE PUBLICATION name Marks the publication as one that synchronizes changes for all sequences - in the database, including sequences created in the future. + in the database, including sequences created in the future. Sequences + listed in EXCEPT clause are excluded from the + publication. @@ -193,12 +199,13 @@ CREATE PUBLICATION name - + EXCEPT - This clause specifies a list of tables to be excluded from the - publication. + This clause specifies a list of tables for ALL TABLES + publication or a list of sequences for ALL SEQUENCES + to be excluded from the publication. For inherited tables, if ONLY is specified before the @@ -217,9 +224,10 @@ CREATE PUBLICATION name 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 - but excluded (explicitly or implicitly) by the EXCEPT - clause of another is considered included for replication. + In such a case, a table or partition or sequence that is included in one + publication but excluded (explicitly or implicitly) by the + EXCEPT clause of another is considered included for + replication. diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c index a43d385c605..12e08144d20 100644 --- a/src/backend/catalog/pg_publication.c +++ b/src/backend/catalog/pg_publication.c @@ -53,7 +53,7 @@ typedef struct * error if not. */ static void -check_publication_add_relation(PublicationRelInfo *pri) +check_publication_add_relation(PublicationRelInfo *pri, char pubrelkind) { Relation targetrel = pri->relation; const char *errormsg; @@ -70,14 +70,26 @@ check_publication_add_relation(PublicationRelInfo *pri) errmsg(errormsg, RelationGetRelationName(targetrel)), errdetail("This operation is not supported for individual partitions."))); - /* Must be a regular or partitioned table */ - if (RelationGetForm(targetrel)->relkind != RELKIND_RELATION && + /* + * Must be a regular or partitioned table when specified in FOR TABLE or + * EXCEPT table list + */ + if (pubrelkind == RELKIND_RELATION && + RelationGetForm(targetrel)->relkind != RELKIND_RELATION && RelationGetForm(targetrel)->relkind != RELKIND_PARTITIONED_TABLE) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg(errormsg, RelationGetRelationName(targetrel)), errdetail_relkind_not_supported(RelationGetForm(targetrel)->relkind))); + /* Must be a sequence if specified in EXCEPT sequence list */ + if (pubrelkind == RELKIND_SEQUENCE && + RelationGetForm(targetrel)->relkind != RELKIND_SEQUENCE) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg(errormsg, RelationGetRelationName(targetrel)), + errdetail_relkind_not_supported(RelationGetForm(targetrel)->relkind))); + /* Can't be system table */ if (IsCatalogRelation(targetrel)) ereport(ERROR, @@ -514,7 +526,8 @@ attnumstoint2vector(Bitmapset *attrs) */ ObjectAddress publication_add_relation(Oid pubid, PublicationRelInfo *pri, - bool if_not_exists, AlterPublicationStmt *alter_stmt) + bool if_not_exists, AlterPublicationStmt *alter_stmt, + char pubrelkind) { Relation rel; HeapTuple tup; @@ -552,7 +565,7 @@ publication_add_relation(Oid pubid, PublicationRelInfo *pri, RelationGetRelationName(targetrel), pub->name))); } - check_publication_add_relation(pri); + check_publication_add_relation(pri, pubrelkind); /* Validate and translate column names into a Bitmapset of attnums. */ attnums = pub_collist_validate(pri->relation, pri->columns); @@ -972,15 +985,16 @@ GetIncludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt) } /* - * Gets list of table oids that were specified in the EXCEPT clause for a + * Gets list of relation oids that were specified in the EXCEPT clause for a * publication. * - * This should only be used FOR ALL TABLES publications. + * This should only be used FOR ALL TABLES/ FOR ALL SEQUENCES publications. */ List * -GetExcludedPublicationTables(Oid pubid, PublicationPartOpt pub_partopt) +GetExcludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt) { - Assert(GetPublication(pubid)->alltables); + Assert(GetPublication(pubid)->alltables || + GetPublication(pubid)->allsequences); return get_publication_relations(pubid, pub_partopt, true); } @@ -1046,11 +1060,10 @@ GetAllPublicationRelations(Oid pubid, char relkind, bool pubviaroot) Assert(!(relkind == RELKIND_SEQUENCE && pubviaroot)); - /* EXCEPT filtering applies only to relations, not sequences */ - if (relkind == RELKIND_RELATION) - exceptlist = GetExcludedPublicationTables(pubid, pubviaroot ? - PUBLICATION_PART_ROOT : - PUBLICATION_PART_LEAF); + /* EXCEPT filtering applies to tables and sequences */ + exceptlist = GetExcludedPublicationRelations(pubid, pubviaroot ? + PUBLICATION_PART_ROOT : + PUBLICATION_PART_LEAF); classRel = table_open(RelationRelationId, AccessShareLock); diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c index 440adb356ad..9ca5708f31c 100644 --- a/src/backend/commands/publicationcmds.c +++ b/src/backend/commands/publicationcmds.c @@ -61,11 +61,11 @@ typedef struct rf_context Oid parentid; /* relid of the parent relation */ } rf_context; -static List *OpenTableList(List *tables); -static void CloseTableList(List *rels); +static List *OpenRelationList(List *tables); +static void CloseRelationList(List *rels); static void LockSchemaList(List *schemalist); -static void PublicationAddTables(Oid pubid, List *rels, bool if_not_exists, - AlterPublicationStmt *stmt); +static void PublicationAddRelations(Oid pubid, List *rels, bool if_not_exists, + AlterPublicationStmt *stmt, char relkind); static void PublicationDropTables(Oid pubid, List *rels, bool missing_ok); static void PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists, AlterPublicationStmt *stmt); @@ -177,11 +177,12 @@ parse_publication_options(ParseState *pstate, /* * Convert the PublicationObjSpecType list into schema oid list and - * PublicationTable list. + * PublicationRelation list. */ static void ObjectsInPublicationToOids(List *pubobjspec_list, ParseState *pstate, - List **rels, List **exceptrels, List **schemas) + List **rels, List **excepttbls, List **exceptseqs, + List **schemas) { ListCell *cell; PublicationObjSpec *pubobj; @@ -199,12 +200,16 @@ ObjectsInPublicationToOids(List *pubobjspec_list, ParseState *pstate, switch (pubobj->pubobjtype) { case PUBLICATIONOBJ_EXCEPT_TABLE: - pubobj->pubtable->except = true; - *exceptrels = lappend(*exceptrels, pubobj->pubtable); + pubobj->pubrelation->except = true; + *excepttbls = lappend(*excepttbls, pubobj->pubrelation); + break; + case PUBLICATIONOBJ_EXCEPT_SEQUENCE: + pubobj->pubrelation->except = true; + *exceptseqs = lappend(*exceptseqs, pubobj->pubrelation); break; case PUBLICATIONOBJ_TABLE: - pubobj->pubtable->except = false; - *rels = lappend(*rels, pubobj->pubtable); + pubobj->pubrelation->except = false; + *rels = lappend(*rels, pubobj->pubrelation); break; case PUBLICATIONOBJ_TABLES_IN_SCHEMA: schemaid = get_namespace_oid(pubobj->name, false); @@ -849,7 +854,8 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt) char publish_generated_columns; AclResult aclresult; List *relations = NIL; - List *exceptrelations = NIL; + List *excepttbls = NIL; + List *exceptseqs = NIL; List *schemaidlist = NIL; /* must have CREATE privilege on database */ @@ -936,18 +942,27 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt) /* Associate objects with the publication. */ ObjectsInPublicationToOids(stmt->pubobjects, pstate, &relations, - &exceptrelations, &schemaidlist); + &excepttbls, &exceptseqs, &schemaidlist); + + if (stmt->for_all_sequences && exceptseqs != NIL) + { + List *rels; + /* Process EXCEPT sequence list */ + rels = OpenRelationList(exceptseqs); + PublicationAddRelations(puboid, rels, true, NULL, RELKIND_SEQUENCE); + CloseRelationList(rels); + } if (stmt->for_all_tables) { /* Process EXCEPT table list */ - if (exceptrelations != NIL) + if (excepttbls != NIL) { List *rels; - rels = OpenTableList(exceptrelations); - PublicationAddTables(puboid, rels, true, NULL); - CloseTableList(rels); + rels = OpenRelationList(excepttbls); + PublicationAddRelations(puboid, rels, true, NULL, RELKIND_RELATION); + CloseRelationList(rels); } /* @@ -969,7 +984,7 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt) { List *rels; - rels = OpenTableList(relations); + rels = OpenRelationList(relations); TransformPubWhereClauses(rels, pstate->p_sourcetext, publish_via_partition_root); @@ -977,8 +992,8 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt) schemaidlist != NIL, publish_via_partition_root); - PublicationAddTables(puboid, rels, true, NULL); - CloseTableList(rels); + PublicationAddRelations(puboid, rels, true, NULL, RELKIND_RELATION); + CloseRelationList(rels); } if (schemaidlist != NIL) @@ -1255,7 +1270,7 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup, if (!tables && stmt->action != AP_SetObjects) return; - rels = OpenTableList(tables); + rels = OpenRelationList(tables); if (stmt->action == AP_AddObjects) { @@ -1266,7 +1281,7 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup, CheckPubRelationColumnList(stmt->pubname, rels, publish_schema, pubform->pubviaroot); - PublicationAddTables(pubid, rels, false, stmt); + PublicationAddRelations(pubid, rels, false, stmt, RELKIND_RELATION); } else if (stmt->action == AP_DropObjects) PublicationDropTables(pubid, rels, false); @@ -1289,8 +1304,8 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup, */ if (pubform->puballtables) { - oldrelids = GetExcludedPublicationTables(pubid, - PUBLICATION_PART_ROOT); + oldrelids = GetExcludedPublicationRelations(pubid, + PUBLICATION_PART_ROOT); } } else @@ -1363,8 +1378,8 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup, /* * Validate the column list. If the column list or WHERE * clause changes, then the validation done here will be - * duplicated inside PublicationAddTables(). The validation - * is cheap enough that that seems harmless. + * duplicated inside PublicationAddRelations(). The + * validation is cheap enough that that seems harmless. */ newcolumns = pub_collist_validate(newpubrel->relation, newpubrel->columns); @@ -1410,12 +1425,12 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup, * Don't bother calculating the difference for adding, we'll catch and * skip existing ones when doing catalog update. */ - PublicationAddTables(pubid, rels, true, stmt); + PublicationAddRelations(pubid, rels, true, stmt, RELKIND_RELATION); - CloseTableList(delrels); + CloseRelationList(delrels); } - CloseTableList(rels); + CloseRelationList(rels); } /* @@ -1683,12 +1698,16 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt) else { List *relations = NIL; - List *exceptrelations = NIL; + List *excepttbls = NIL; + List *exceptseqs = NIL; List *schemaidlist = NIL; Oid pubid = pubform->oid; ObjectsInPublicationToOids(stmt->pubobjects, pstate, &relations, - &exceptrelations, &schemaidlist); + &excepttbls, &exceptseqs, &schemaidlist); + + /* EXCEPT clause is not supported with ALTER PUBLICATION */ + Assert(exceptseqs == NIL); CheckAlterPublication(stmt, tup, relations, schemaidlist); @@ -1711,7 +1730,7 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt) errmsg("publication \"%s\" does not exist", stmt->pubname)); - relations = list_concat(relations, exceptrelations); + relations = list_concat(relations, excepttbls); AlterPublicationTables(stmt, tup, relations, pstate->p_sourcetext, schemaidlist != NIL); AlterPublicationSchemas(stmt, tup, schemaidlist); @@ -1830,12 +1849,12 @@ RemovePublicationSchemaById(Oid psoid) } /* - * Open relations specified by a PublicationTable list. - * The returned tables are locked in ShareUpdateExclusiveLock mode in order to - * add them to a publication. + * Open relations specified by a PublicationRelation list. + * The returned relations are locked in ShareUpdateExclusiveLock mode in order + * to add them to a publication. */ static List * -OpenTableList(List *tables) +OpenRelationList(List *tables) { List *relids = NIL; List *rels = NIL; @@ -1848,7 +1867,7 @@ OpenTableList(List *tables) */ foreach(lc, tables) { - PublicationTable *t = lfirst_node(PublicationTable, lc); + PublicationRelation *t = lfirst_node(PublicationRelation, lc); bool recurse = t->relation->inh; Relation rel; Oid myrelid; @@ -1987,7 +2006,7 @@ OpenTableList(List *tables) * Close all relations in the list. */ static void -CloseTableList(List *rels) +CloseRelationList(List *rels) { ListCell *lc; @@ -2032,11 +2051,11 @@ LockSchemaList(List *schemalist) } /* - * Add listed tables to the publication. + * Add listed relations to the publication. */ static void -PublicationAddTables(Oid pubid, List *rels, bool if_not_exists, - AlterPublicationStmt *stmt) +PublicationAddRelations(Oid pubid, List *rels, bool if_not_exists, + AlterPublicationStmt *stmt, char pubrelkind) { ListCell *lc; @@ -2046,12 +2065,13 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists, Relation rel = pub_rel->relation; ObjectAddress obj; - /* Must be owner of the table or superuser. */ + /* Must be owner of the relation or superuser. */ if (!object_ownercheck(RelationRelationId, RelationGetRelid(rel), GetUserId())) aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(rel->rd_rel->relkind), RelationGetRelationName(rel)); - obj = publication_add_relation(pubid, pub_rel, if_not_exists, stmt); + obj = publication_add_relation(pubid, pub_rel, if_not_exists, stmt, pubrelkind); + if (stmt) { EventTriggerCollectSimpleCommand(obj, InvalidObjectAddress, diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 2d1b19d1f53..e344a24d15d 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -457,7 +457,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); TriggerTransitions TriggerReferencing vacuum_relation_list opt_vacuum_relation_list drop_option_list pub_obj_list pub_all_obj_type_list - pub_except_obj_list opt_pub_except_clause + pub_except_tbl_list opt_pub_except_tbl_clause + opt_pub_except_seq_clause pub_except_seq_list %type returning_clause %type returning_option @@ -597,7 +598,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); %type var_value zone_value %type auth_ident RoleSpec opt_granted_by %type PublicationObjSpec -%type PublicationExceptObjSpec +%type PublicationExceptTblSpec +%type PublicationExceptSeqSpec %type PublicationAllObjSpec %type unreserved_keyword type_func_name_keyword @@ -11327,10 +11329,10 @@ PublicationObjSpec: { $$ = makeNode(PublicationObjSpec); $$->pubobjtype = PUBLICATIONOBJ_TABLE; - $$->pubtable = makeNode(PublicationTable); - $$->pubtable->relation = $2; - $$->pubtable->columns = $3; - $$->pubtable->whereClause = $4; + $$->pubrelation = makeNode(PublicationRelation); + $$->pubrelation->relation = $2; + $$->pubrelation->columns = $3; + $$->pubrelation->whereClause = $4; } | TABLES IN_P SCHEMA ColId { @@ -11351,7 +11353,7 @@ PublicationObjSpec: $$->pubobjtype = PUBLICATIONOBJ_CONTINUATION; /* * If either a row filter or column list is specified, create - * a PublicationTable object. + * a PublicationRelation object. */ if ($2 || $3) { @@ -11361,10 +11363,10 @@ PublicationObjSpec: * error will be thrown later via * preprocess_pubobj_list(). */ - $$->pubtable = makeNode(PublicationTable); - $$->pubtable->relation = makeRangeVar(NULL, $1, @1); - $$->pubtable->columns = $2; - $$->pubtable->whereClause = $3; + $$->pubrelation = makeNode(PublicationRelation); + $$->pubrelation->relation = makeRangeVar(NULL, $1, @1); + $$->pubrelation->columns = $2; + $$->pubrelation->whereClause = $3; } else { @@ -11376,10 +11378,10 @@ PublicationObjSpec: { $$ = makeNode(PublicationObjSpec); $$->pubobjtype = PUBLICATIONOBJ_CONTINUATION; - $$->pubtable = makeNode(PublicationTable); - $$->pubtable->relation = makeRangeVarFromQualifiedName($1, $2, @1, yyscanner); - $$->pubtable->columns = $3; - $$->pubtable->whereClause = $4; + $$->pubrelation = makeNode(PublicationRelation); + $$->pubrelation->relation = makeRangeVarFromQualifiedName($1, $2, @1, yyscanner); + $$->pubrelation->columns = $3; + $$->pubrelation->whereClause = $4; $$->location = @1; } /* grammar like tablename * , ONLY tablename, ONLY ( tablename ) */ @@ -11387,10 +11389,10 @@ PublicationObjSpec: { $$ = makeNode(PublicationObjSpec); $$->pubobjtype = PUBLICATIONOBJ_CONTINUATION; - $$->pubtable = makeNode(PublicationTable); - $$->pubtable->relation = $1; - $$->pubtable->columns = $2; - $$->pubtable->whereClause = $3; + $$->pubrelation = makeNode(PublicationRelation); + $$->pubrelation->relation = $1; + $$->pubrelation->columns = $2; + $$->pubrelation->whereClause = $3; } | CURRENT_SCHEMA { @@ -11406,23 +11408,29 @@ pub_obj_list: PublicationObjSpec { $$ = lappend($1, $3); } ; -opt_pub_except_clause: - EXCEPT '(' TABLE pub_except_obj_list ')' { $$ = $4; } +opt_pub_except_tbl_clause: + EXCEPT '(' TABLE pub_except_tbl_list ')' { $$ = $4; } + | /*EMPTY*/ { $$ = NIL; } + ; + +opt_pub_except_seq_clause: + EXCEPT '(' SEQUENCE pub_except_seq_list ')' { $$ = $4; } | /*EMPTY*/ { $$ = NIL; } ; PublicationAllObjSpec: - ALL TABLES opt_pub_except_clause + ALL TABLES opt_pub_except_tbl_clause { $$ = makeNode(PublicationAllObjSpec); $$->pubobjtype = PUBLICATION_ALL_TABLES; - $$->except_tables = $3; + $$->except_relations = $3; $$->location = @1; } - | ALL SEQUENCES + | ALL SEQUENCES opt_pub_except_seq_clause { $$ = makeNode(PublicationAllObjSpec); $$->pubobjtype = PUBLICATION_ALL_SEQUENCES; + $$->except_relations = $3; $$->location = @1; } ; @@ -11433,21 +11441,41 @@ pub_all_obj_type_list: PublicationAllObjSpec { $$ = lappend($1, $3); } ; -PublicationExceptObjSpec: +PublicationExceptTblSpec: relation_expr { $$ = makeNode(PublicationObjSpec); $$->pubobjtype = PUBLICATIONOBJ_EXCEPT_TABLE; - $$->pubtable = makeNode(PublicationTable); - $$->pubtable->except = true; - $$->pubtable->relation = $1; + $$->pubrelation = makeNode(PublicationRelation); + $$->pubrelation->except = true; + $$->pubrelation->relation = $1; + $$->location = @1; + } + ; + +PublicationExceptSeqSpec: + relation_expr + { + $$ = makeNode(PublicationObjSpec); + $$->pubobjtype = PUBLICATIONOBJ_EXCEPT_SEQUENCE; + $$->pubrelation = makeNode(PublicationRelation); + $$->pubrelation->except = true; + $$->pubrelation->relation = $1; $$->location = @1; } ; -pub_except_obj_list: PublicationExceptObjSpec +pub_except_tbl_list: PublicationExceptTblSpec { $$ = list_make1($1); } - | pub_except_obj_list ',' opt_table PublicationExceptObjSpec + | pub_except_tbl_list ',' opt_table PublicationExceptTblSpec + { $$ = lappend($1, $4); } + ; + +pub_except_seq_list: PublicationExceptSeqSpec + { $$ = list_make1($1); } + | pub_except_seq_list ',' PublicationExceptSeqSpec + { $$ = lappend($1, $3); } + | pub_except_seq_list ',' SEQUENCE PublicationExceptSeqSpec { $$ = lappend($1, $4); } ; @@ -14007,6 +14035,10 @@ opt_table: TABLE | /*EMPTY*/ ; +opt_sequence: SEQUENCE + | /*EMPTY*/ + ; + set_quantifier: ALL { $$ = SET_QUANTIFIER_ALL; } | DISTINCT { $$ = SET_QUANTIFIER_DISTINCT; } @@ -20765,7 +20797,7 @@ preprocess_pub_all_objtype_list(List *all_objects_list, List **pubobjects, parser_errposition(obj->location)); *all_tables = true; - *pubobjects = list_concat(*pubobjects, obj->except_tables); + *pubobjects = list_concat(*pubobjects, obj->except_relations); } else if (obj->pubobjtype == PUBLICATION_ALL_SEQUENCES) { @@ -20777,6 +20809,7 @@ preprocess_pub_all_objtype_list(List *all_objects_list, List **pubobjects, parser_errposition(obj->location)); *all_sequences = true; + *pubobjects = list_concat(*pubobjects, obj->except_relations); } } } @@ -20812,8 +20845,8 @@ preprocess_pubobj_list(List *pubobjspec_list, core_yyscan_t yyscanner) if (pubobj->pubobjtype == PUBLICATIONOBJ_TABLE) { - /* relation name or pubtable must be set for this type of object */ - if (!pubobj->name && !pubobj->pubtable) + /* relation name or pubrelation must be set for this type of object */ + if (!pubobj->name && !pubobj->pubrelation) ereport(ERROR, errcode(ERRCODE_SYNTAX_ERROR), errmsg("invalid table name"), @@ -20821,12 +20854,12 @@ preprocess_pubobj_list(List *pubobjspec_list, core_yyscan_t yyscanner) if (pubobj->name) { - /* convert it to PublicationTable */ - PublicationTable *pubtable = makeNode(PublicationTable); + /* convert it to PublicationRelation */ + PublicationRelation *pubrelation = makeNode(PublicationRelation); - pubtable->relation = + pubrelation->relation = makeRangeVar(NULL, pubobj->name, pubobj->location); - pubobj->pubtable = pubtable; + pubobj->pubrelation = pubrelation; pubobj->name = NULL; } } @@ -20834,14 +20867,14 @@ preprocess_pubobj_list(List *pubobjspec_list, core_yyscan_t yyscanner) pubobj->pubobjtype == PUBLICATIONOBJ_TABLES_IN_CUR_SCHEMA) { /* WHERE clause is not allowed on a schema object */ - if (pubobj->pubtable && pubobj->pubtable->whereClause) + if (pubobj->pubrelation && pubobj->pubrelation->whereClause) ereport(ERROR, errcode(ERRCODE_SYNTAX_ERROR), errmsg("WHERE clause not allowed for schema"), parser_errposition(pubobj->location)); /* Column list is not allowed on a schema object */ - if (pubobj->pubtable && pubobj->pubtable->columns) + if (pubobj->pubrelation && pubobj->pubrelation->columns) ereport(ERROR, errcode(ERRCODE_SYNTAX_ERROR), errmsg("column specification not allowed for schema"), @@ -20849,11 +20882,11 @@ preprocess_pubobj_list(List *pubobjspec_list, core_yyscan_t yyscanner) /* * We can distinguish between the different type of schema objects - * based on whether name and pubtable is set. + * based on whether name and pubrelation is set. */ if (pubobj->name) pubobj->pubobjtype = PUBLICATIONOBJ_TABLES_IN_SCHEMA; - else if (!pubobj->name && !pubobj->pubtable) + else if (!pubobj->name && !pubobj->pubrelation) pubobj->pubobjtype = PUBLICATIONOBJ_TABLES_IN_CUR_SCHEMA; else ereport(ERROR, diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index d34240073bb..e1d0214bb4b 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -4617,6 +4617,10 @@ getPublications(Archive *fout) { NULL, NULL }; + pubinfo[i].except_sequences = (SimplePtrList) + { + NULL, NULL + }; /* Decide whether we want to dump it */ selectDumpableObject(&(pubinfo[i].dobj), fout); @@ -4642,9 +4646,10 @@ getPublications(Archive *fout) resetPQExpBuffer(query); appendPQExpBuffer(query, - "SELECT prrelid\n" - "FROM pg_catalog.pg_publication_rel\n" - "WHERE prpubid = %u AND prexcept", + "SELECT pr.prrelid, pc.relkind\n" + "FROM pg_catalog.pg_publication_rel pr\n" + "JOIN pg_catalog.pg_class pc ON pr.prrelid = pc.oid\n" + "WHERE pr.prpubid = %u AND pr.prexcept", pubinfo[i].dobj.catId.oid); res_tbls = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK); @@ -4654,14 +4659,21 @@ getPublications(Archive *fout) for (int j = 0; j < ntbls; j++) { Oid prrelid; + char relkind; TableInfo *tbinfo; prrelid = atooid(PQgetvalue(res_tbls, j, 0)); + relkind = *PQgetvalue(res_tbls, j, 1); tbinfo = findTableByOid(prrelid); if (tbinfo != NULL) - simple_ptr_list_append(&pubinfo[i].except_tables, tbinfo); + { + if (relkind == RELKIND_SEQUENCE) + simple_ptr_list_append(&pubinfo[i].except_sequences, tbinfo); + else + simple_ptr_list_append(&pubinfo[i].except_tables, tbinfo); + } } PQclear(res_tbls); @@ -4721,12 +4733,30 @@ dumpPublication(Archive *fout, const PublicationInfo *pubinfo) } if (n_except > 0) appendPQExpBufferStr(query, ")"); + } + if (pubinfo->puballsequences) + { + int n_except = 0; - if (pubinfo->puballsequences) + if (pubinfo->puballtables) appendPQExpBufferStr(query, ", ALL SEQUENCES"); + else + appendPQExpBufferStr(query, " FOR ALL SEQUENCES"); + + /* Include EXCEPT (SEQUENCE) clause if there are except_sequences. */ + for (SimplePtrListCell *cell = pubinfo->except_sequences.head; cell; cell = cell->next) + { + TableInfo *tbinfo = (TableInfo *) cell->ptr; + + if (++n_except == 1) + appendPQExpBufferStr(query, " EXCEPT ("); + else + appendPQExpBufferStr(query, ", "); + appendPQExpBuffer(query, "SEQUENCE %s", fmtQualifiedDumpable(tbinfo)); + } + if (n_except > 0) + appendPQExpBufferStr(query, ")"); } - else if (pubinfo->puballsequences) - appendPQExpBufferStr(query, " FOR ALL SEQUENCES"); appendPQExpBufferStr(query, " WITH (publish = '"); if (pubinfo->pubinsert) diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h index 5a6726d8b12..8e869fe791a 100644 --- a/src/bin/pg_dump/pg_dump.h +++ b/src/bin/pg_dump/pg_dump.h @@ -679,6 +679,7 @@ typedef struct _PublicationInfo bool pubviaroot; PublishGencolsType pubgencols_type; SimplePtrList except_tables; + SimplePtrList except_sequences; } PublicationInfo; /* diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl index 3bc8e51561d..fcd100dcd55 100644 --- a/src/bin/pg_dump/t/002_pg_dump.pl +++ b/src/bin/pg_dump/t/002_pg_dump.pl @@ -3242,6 +3242,16 @@ my %tests = ( like => { %full_runs, section_post_data => 1, }, }, + 'CREATE PUBLICATION pub11' => { + create_order => 92, + create_sql => + 'CREATE PUBLICATION pub11 FOR ALL SEQUENCES EXCEPT (SEQUENCE public.test_table_col1_seq);', + regexp => qr/^ + \QCREATE PUBLICATION pub11 FOR ALL SEQUENCES EXCEPT (SEQUENCE public.test_table_col1_seq) WITH (publish = 'insert, update, delete, truncate');\E + /xm, + like => { %full_runs, section_post_data => 1, }, + }, + 'CREATE SUBSCRIPTION sub1' => { create_order => 50, create_sql => 'CREATE SUBSCRIPTION sub1 @@ -4042,6 +4052,8 @@ my %tests = ( }, 'CREATE SEQUENCE test_table_col1_seq' => { + create_order => 90, + create_sql => 'CREATE SEQUENCE test_table_col1_seq', regexp => qr/^ \QCREATE SEQUENCE dump_test.test_table_col1_seq\E \n\s+\QAS integer\E @@ -5279,7 +5291,7 @@ foreach my $run (sort keys %pgdump_runs) # # Either "all_runs" should be set or there should be a "like" list, # even if it is empty. (This makes the test more self-documenting.) - if (!defined($tests{$test}->{all_runs}) + if ( !defined($tests{$test}->{all_runs}) && !defined($tests{$test}->{like})) { die "missing \"like\" in test \"$test\""; diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c index 4e8ff00394a..43849559e99 100644 --- a/src/bin/psql/describe.c +++ b/src/bin/psql/describe.c @@ -1778,7 +1778,7 @@ describeOneTableDetails(const char *schemaname, { PGresult *result = NULL; printQueryOpt myopt = pset.popt; - char *footers[3] = {NULL, NULL, NULL}; + char *footers[4] = {NULL, NULL, NULL, NULL}; printfPQExpBuffer(&buf, "/* %s */\n", _("Get sequence information")); if (pset.sversion >= 100000) @@ -1885,8 +1885,13 @@ describeOneTableDetails(const char *schemaname, appendPQExpBuffer(&buf, "SELECT pubname FROM pg_catalog.pg_publication p" "\nWHERE p.puballsequences" "\n AND pg_catalog.pg_relation_is_publishable('%s')" + "\n AND NOT EXISTS (\n" + " SELECT 1\n" + " FROM pg_catalog.pg_publication_rel pr\n" + " WHERE pr.prpubid = p.oid AND\n" + " pr.prrelid = '%s')" "\nORDER BY 1", - oid); + oid, oid); result = PSQLexec(buf.data); if (result) @@ -1912,6 +1917,42 @@ describeOneTableDetails(const char *schemaname, } } + /* Print publications where the sequence is in the EXCEPT clause */ + if (pset.sversion >= 190000) + { + printfPQExpBuffer(&buf, + "SELECT pubname\n" + "FROM pg_catalog.pg_publication p\n" + "JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n" + "WHERE pr.prrelid = '%s' AND pr.prexcept\n" + "ORDER BY 1;", oid); + + result = PSQLexec(buf.data); + if (result) + { + int tuples = PQntuples(result); + + if (tuples > 0) + { + printfPQExpBuffer(&tmpbuf, _("Except Publications:")); + + /* Might be an empty set - that's ok */ + for (i = 0; i < tuples; i++) + appendPQExpBuffer(&tmpbuf, "\n \"%s\"", PQgetvalue(result, i, 0)); + + if (footers[0] == NULL) + footers[0] = pg_strdup(tmpbuf.data); + else if (footers[1] == NULL) + footers[1] = pg_strdup(tmpbuf.data); + else + footers[2] = pg_strdup(tmpbuf.data); + resetPQExpBuffer(&tmpbuf); + } + + PQclear(result); + } + } + if (tableinfo.relpersistence == RELPERSISTENCE_UNLOGGED) printfPQExpBuffer(&title, _("Unlogged sequence \"%s.%s\""), schemaname, relationname); @@ -6950,6 +6991,7 @@ describePublications(const char *pattern) char *pubid = PQgetvalue(res, i, 0); char *pubname = PQgetvalue(res, i, 1); bool puballtables = strcmp(PQgetvalue(res, i, 3), "t") == 0; + bool puballsequences = strcmp(PQgetvalue(res, i, 4), "t") == 0; printTableOpt myopt = pset.popt.topt; initPQExpBuffer(&title); @@ -7052,13 +7094,30 @@ describePublications(const char *pattern) "FROM pg_catalog.pg_class c\n" " JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n" " JOIN pg_catalog.pg_publication_rel pr ON c.oid = pr.prrelid\n" - "WHERE pr.prpubid = '%s' AND pr.prexcept\n" + "WHERE pr.prpubid = '%s' AND pr.prexcept AND c.relkind IN ('r','p')\n" "ORDER BY 1", pubid); if (!addFooterToPublicationDesc(&buf, _("Except tables:"), true, &cont)) goto error_return; } } + if (puballsequences) + { + if (pset.sversion >= 190000) + { + /* Get sequences in the EXCEPT clause for this publication */ + printfPQExpBuffer(&buf, + "SELECT n.nspname || '.' || c.relname\n" + "FROM pg_catalog.pg_class c\n" + " JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n" + " JOIN pg_catalog.pg_publication_rel pr ON c.oid = pr.prrelid\n" + "WHERE pr.prpubid = '%s' AND pr.prexcept AND c.relkind = 'S'\n" + "ORDER BY 1", pubid); + if (!addFooterToPublicationDesc(&buf, _("Except Sequences:"), + true, &cont)) + goto error_return; + } + } printTable(&cont, pset.queryFout, false, pset.logfile); printTableCleanup(&cont); diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c index 9990f818942..baa954a02a7 100644 --- a/src/bin/psql/tab-complete.in.c +++ b/src/bin/psql/tab-complete.in.c @@ -3758,6 +3758,16 @@ match_previous_words(int pattern_id, 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, ',')) COMPLETE_WITH(")"); + else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "SEQUENCES")) + COMPLETE_WITH("EXCEPT ( SEQUENCE", "WITH ("); + else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "SEQUENCES", "EXCEPT")) + COMPLETE_WITH("( SEQUENCE"); + else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "SEQUENCES", "EXCEPT", "(", "SEQUENCE")) + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences); + else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "SEQUENCES", "EXCEPT", "(", "SEQUENCE", MatchAnyN) && ends_with(prev_wd, ',')) + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences); + else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "SEQUENCES", "EXCEPT", "(", "SEQUENCE", MatchAnyN) && !ends_with(prev_wd, ',')) + COMPLETE_WITH(")"); else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLES")) COMPLETE_WITH("IN SCHEMA"); else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE", MatchAny) && !ends_with(prev_wd, ',')) diff --git a/src/include/catalog/pg_publication.h b/src/include/catalog/pg_publication.h index 89b4bb14f62..49c29a87630 100644 --- a/src/include/catalog/pg_publication.h +++ b/src/include/catalog/pg_publication.h @@ -177,8 +177,8 @@ typedef enum PublicationPartOpt extern List *GetIncludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt); -extern List *GetExcludedPublicationTables(Oid pubid, - PublicationPartOpt pub_partopt); +extern List *GetExcludedPublicationRelations(Oid pubid, + PublicationPartOpt pub_partopt); extern List *GetAllTablesPublications(void); extern List *GetAllPublicationRelations(Oid pubid, char relkind, bool pubviaroot); extern List *GetPublicationSchemas(Oid pubid); @@ -200,7 +200,8 @@ extern bool check_and_fetch_column_list(Publication *pub, Oid relid, MemoryContext mcxt, Bitmapset **cols); extern ObjectAddress publication_add_relation(Oid pubid, PublicationRelInfo *pri, bool if_not_exists, - AlterPublicationStmt *alter_stmt); + AlterPublicationStmt *alter_stmt, + char pubrelkind); extern Bitmapset *pub_collist_validate(Relation targetrel, List *columns); extern ObjectAddress publication_add_schema(Oid pubid, Oid schemaid, bool if_not_exists); diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 91377a6cde3..02a1bc15262 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -4465,14 +4465,14 @@ typedef struct AlterTSConfigurationStmt bool missing_ok; /* for DROP - skip error if missing? */ } AlterTSConfigurationStmt; -typedef struct PublicationTable +typedef struct PublicationRelation { NodeTag type; RangeVar *relation; /* publication relation */ Node *whereClause; /* qualifications */ List *columns; /* List of columns in a publication table */ bool except; /* True if listed in the EXCEPT clause */ -} PublicationTable; +} PublicationRelation; /* * Publication object type @@ -4481,6 +4481,7 @@ typedef enum PublicationObjSpecType { PUBLICATIONOBJ_TABLE, /* A table */ PUBLICATIONOBJ_EXCEPT_TABLE, /* A table in the EXCEPT clause */ + PUBLICATIONOBJ_EXCEPT_SEQUENCE, /* A sequence in the EXCEPT clause */ PUBLICATIONOBJ_TABLES_IN_SCHEMA, /* All tables in schema */ PUBLICATIONOBJ_TABLES_IN_CUR_SCHEMA, /* All tables in first element of * search_path */ @@ -4492,7 +4493,7 @@ typedef struct PublicationObjSpec NodeTag type; PublicationObjSpecType pubobjtype; /* type of this publication object */ char *name; - PublicationTable *pubtable; + PublicationRelation *pubrelation; ParseLoc location; /* token location, or -1 if unknown */ } PublicationObjSpec; @@ -4509,7 +4510,8 @@ typedef struct PublicationAllObjSpec { NodeTag type; PublicationAllObjType pubobjtype; /* type of this publication object */ - List *except_tables; /* tables specified in the EXCEPT clause */ + List *except_relations; /* relations specified in the EXCEPT + * clause */ ParseLoc location; /* token location, or -1 if unknown */ } PublicationAllObjSpec; diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out index a9059a39138..a9b2e492659 100644 --- a/src/test/regress/expected/publication.out +++ b/src/test/regress/expected/publication.out @@ -464,6 +464,7 @@ DROP PUBLICATION testpub8; --- Tests for publications with SEQUENCES CREATE SEQUENCE regress_pub_seq0; CREATE SEQUENCE pub_test.regress_pub_seq1; +CREATE SEQUENCE regress_pub_seq2; -- FOR ALL SEQUENCES SET client_min_messages = 'ERROR'; CREATE PUBLICATION regress_pub_forallsequences1 FOR ALL SEQUENCES; @@ -525,10 +526,49 @@ SELECT pubname, puballtables, puballsequences FROM pg_publication WHERE pubname regress_publication_user | t | t | t | f | f | f | stored | f | (1 row) -DROP SEQUENCE regress_pub_seq0, pub_test.regress_pub_seq1; +-- Test ALL SEQUENCES with EXCEPT clause +SET client_min_messages = 'ERROR'; +CREATE PUBLICATION regress_pub_forallsequences3 FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_pub_seq0, pub_test.regress_pub_seq1, SEQUENCE regress_pub_seq2); +\dRp+ regress_pub_forallsequences3 + Publication regress_pub_forallsequences3 + 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: + "pub_test.regress_pub_seq1" + "public.regress_pub_seq0" + "public.regress_pub_seq2" + +-- Check that the sequence description shows the publications where it is listed +-- in the EXCEPT clause +\d+ regress_pub_seq0 + Sequence "public.regress_pub_seq0" + Type | Start | Minimum | Maximum | Increment | Cycles? | Cache +--------+-------+---------+---------------------+-----------+---------+------- + bigint | 1 | 1 | 9223372036854775807 | 1 | no | 1 +Publications: + "regress_pub_for_allsequences_alltables" + "regress_pub_forallsequences1" + "regress_pub_forallsequences2" +Except Publications: + "regress_pub_forallsequences3" + +RESET client_min_messages; +-- Throw error as sequence is specified in EXCEPT table list +CREATE PUBLICATION regress_pub_forallsequences4 FOR ALL TABLES EXCEPT (TABLE regress_pub_seq0); +ERROR: cannot specify relation "regress_pub_seq0" in the publication EXCEPT clause +DETAIL: This operation is not supported for sequences. +-- Throw error as table is specified in EXCEPT sequence list +CREATE TABLE tab_seq(a int); +CREATE PUBLICATION regress_pub_forallsequences4 FOR ALL SEQUENCES EXCEPT (SEQUENCE tab_seq); +ERROR: cannot specify relation "tab_seq" in the publication EXCEPT clause +DETAIL: This operation is not supported for tables. +DROP SEQUENCE regress_pub_seq0, pub_test.regress_pub_seq1, regress_pub_seq2; DROP PUBLICATION regress_pub_forallsequences1; DROP PUBLICATION regress_pub_forallsequences2; +DROP PUBLICATION regress_pub_forallsequences3; DROP PUBLICATION regress_pub_for_allsequences_alltables; +DROP TABLE tab_seq; -- fail - Specifying ALL TABLES more than once CREATE PUBLICATION regress_pub_for_allsequences_alltables FOR ALL SEQUENCES, ALL TABLES, ALL TABLES; ERROR: invalid publication object list diff --git a/src/test/regress/sql/publication.sql b/src/test/regress/sql/publication.sql index 642e32fa098..90ed959830a 100644 --- a/src/test/regress/sql/publication.sql +++ b/src/test/regress/sql/publication.sql @@ -221,6 +221,7 @@ DROP PUBLICATION testpub8; --- Tests for publications with SEQUENCES CREATE SEQUENCE regress_pub_seq0; CREATE SEQUENCE pub_test.regress_pub_seq1; +CREATE SEQUENCE regress_pub_seq2; -- FOR ALL SEQUENCES SET client_min_messages = 'ERROR'; @@ -251,10 +252,28 @@ RESET client_min_messages; SELECT pubname, puballtables, puballsequences FROM pg_publication WHERE pubname = 'regress_pub_for_allsequences_alltables'; \dRp+ regress_pub_for_allsequences_alltables -DROP SEQUENCE regress_pub_seq0, pub_test.regress_pub_seq1; +-- Test ALL SEQUENCES with EXCEPT clause +SET client_min_messages = 'ERROR'; +CREATE PUBLICATION regress_pub_forallsequences3 FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_pub_seq0, pub_test.regress_pub_seq1, SEQUENCE regress_pub_seq2); +\dRp+ regress_pub_forallsequences3 +-- Check that the sequence description shows the publications where it is listed +-- in the EXCEPT clause +\d+ regress_pub_seq0 +RESET client_min_messages; + +-- Throw error as sequence is specified in EXCEPT table list +CREATE PUBLICATION regress_pub_forallsequences4 FOR ALL TABLES EXCEPT (TABLE regress_pub_seq0); + +-- Throw error as table is specified in EXCEPT sequence list +CREATE TABLE tab_seq(a int); +CREATE PUBLICATION regress_pub_forallsequences4 FOR ALL SEQUENCES EXCEPT (SEQUENCE tab_seq); + +DROP SEQUENCE regress_pub_seq0, pub_test.regress_pub_seq1, regress_pub_seq2; DROP PUBLICATION regress_pub_forallsequences1; DROP PUBLICATION regress_pub_forallsequences2; +DROP PUBLICATION regress_pub_forallsequences3; DROP PUBLICATION regress_pub_for_allsequences_alltables; +DROP TABLE tab_seq; -- fail - Specifying ALL TABLES more than once CREATE PUBLICATION regress_pub_for_allsequences_alltables FOR ALL SEQUENCES, ALL TABLES, ALL TABLES; diff --git a/src/test/subscription/t/037_except.pl b/src/test/subscription/t/037_except.pl index 8c58d282eee..ef7c757ecb3 100644 --- a/src/test/subscription/t/037_except.pl +++ b/src/test/subscription/t/037_except.pl @@ -282,6 +282,72 @@ $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'); +# ============================================ +# EXCEPT clause test cases for sequences +# ============================================ +$node_publisher->safe_psql( + 'postgres', qq ( + CREATE TABLE seq_test (v BIGINT); + CREATE SEQUENCE seq1; + CREATE SEQUENCE seq2; + INSERT INTO seq_test SELECT nextval('seq1') FROM generate_series(1,100); + INSERT INTO seq_test SELECT nextval('seq2') FROM generate_series(1,100); + CREATE PUBLICATION tap_pub1 FOR ALL SEQUENCES EXCEPT (SEQUENCE seq1); +)); +$node_subscriber->safe_psql( + 'postgres', qq( + CREATE SEQUENCE seq1; + CREATE SEQUENCE seq2; + CREATE SUBSCRIPTION tap_sub CONNECTION '$publisher_connstr' PUBLICATION tap_pub1; +)); + +# Wait for initial sync to finish +my $synced_query = + "SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('r');"; +$node_subscriber->poll_query_until('postgres', $synced_query) + or die "Timed out while waiting for subscriber to synchronize data"; + +# Check the initial data on subscriber +$result = $node_subscriber->safe_psql('postgres', + "SELECT last_value, is_called FROM seq1"); +is($result, '1|f', 'sequences in EXCEPT list is excluded'); + +$result = $node_subscriber->safe_psql('postgres', + "SELECT last_value, is_called FROM seq2"); +is($result, '100|t', 'initial test data replicated for seq2'); + +# ============================================ +# Test when a subscription is subscribing to multiple publications +# ============================================ +$node_publisher->safe_psql( + 'postgres', qq( + INSERT INTO seq_test SELECT nextval('seq1') FROM generate_series(1,100); + INSERT INTO seq_test SELECT nextval('seq2') FROM generate_series(1,100); + CREATE PUBLICATION tap_pub2 FOR ALL SEQUENCES EXCEPT (SEQUENCE seq2); +)); +$node_subscriber->safe_psql( + 'postgres', qq( + ALTER SUBSCRIPTION tap_sub SET PUBLICATION tap_pub1, tap_pub2; + ALTER SUBSCRIPTION tap_sub REFRESH SEQUENCES; +)); +$synced_query = + "SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('r');"; +$node_subscriber->poll_query_until('postgres', $synced_query) + or die "Timed out while waiting for subscriber to synchronize data"; + +# Check the initial data on subscriber +$result = $node_subscriber->safe_psql('postgres', + "SELECT last_value, is_called FROM seq1"); +is($result, '200|t', 'sequences in EXCEPT list is excluded'); + +$result = $node_subscriber->safe_psql('postgres', + "SELECT last_value, is_called FROM seq2"); +is($result, '200|t', 'initial test data replicated for seq2'); + +$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(); diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index ea95e7984bc..ff20501223c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -2461,7 +2461,7 @@ PublicationPartOpt PublicationRelInfo PublicationRelKind PublicationSchemaInfo -PublicationTable +PublicationRelation PublishGencolsType PullFilter PullFilterOps -- 2.34.1