From a8c9c92498af646a3b0f75d20c6e09b4c3cc1137 Mon Sep 17 00:00:00 2001 From: Tomas Vondra Date: Sun, 26 Nov 2023 15:40:15 +0100 Subject: [PATCH v20231127 3/4] Add decoding of sequences to built-in replication This commit adds support for decoding of sequences to the built-in replication (the infrastructure was added by commit 0da92dc530). The syntax and behavior mostly mimics handling of tables, i.e. a publication may be defined as FOR ALL SEQUENCES (replicating all sequences in a database), FOR ALL SEQUENCES IN SCHEMA (replicating all sequences in a particular schema) or individual sequences. To publish sequence modifications, the publication has to include 'sequence' action. The protocol is extended with a new message, describing sequence increments. A new system view pg_publication_sequences lists all the sequences added to a publication, both directly and indirectly. Various psql commands (\d and \dRp) are improved to also display publications including a given sequence, or sequences included in a publication. Author: Tomas Vondra, Cary Huang, Masahiko Sawada Reviewed-by: Ashutosh Bapat, Amit Kapila, Peter Eisentraut, Amit Kapila, Hannu Krosing, Andres Freund, Petr Jelinek, Zhijie Hou Discussion: https://postgr.es/m/d045f3c2-6cfb-06d3-5540-e63c320df8bc@enterprisedb.com Discussion: https://postgr.es/m/1710ed7e13b.cd7177461430746.3372264562543607781@highgo.ca Discussion: https://postgr.es/m/CAD21AoDtKpdJcHOLjfPQ7TmpFqNB5__%3DQ_g1e8OBRrwT5LP-%3Dg%40mail.gmail.com --- doc/src/sgml/catalogs.sgml | 10 + doc/src/sgml/protocol.sgml | 80 ++ doc/src/sgml/ref/alter_publication.sgml | 25 +- doc/src/sgml/ref/alter_subscription.sgml | 8 +- doc/src/sgml/ref/create_publication.sgml | 51 +- doc/src/sgml/system-views.sgml | 71 ++ src/backend/catalog/objectaddress.c | 45 +- src/backend/catalog/pg_publication.c | 336 +++++- src/backend/catalog/system_views.sql | 10 + src/backend/commands/publicationcmds.c | 422 +++++-- src/backend/commands/sequence.c | 248 +++- src/backend/commands/subscriptioncmds.c | 101 +- src/backend/executor/execReplication.c | 4 +- src/backend/parser/gram.y | 73 +- src/backend/replication/logical/proto.c | 55 + src/backend/replication/logical/tablesync.c | 135 ++- src/backend/replication/logical/worker.c | 97 ++ src/backend/replication/pgoutput/pgoutput.c | 108 +- src/backend/utils/cache/relcache.c | 28 +- src/backend/utils/cache/syscache.c | 5 +- src/bin/pg_dump/pg_dump.c | 64 +- src/bin/pg_dump/pg_dump.h | 3 + src/bin/pg_dump/t/002_pg_dump.pl | 47 +- src/bin/psql/describe.c | 297 +++-- src/bin/psql/tab-complete.c | 39 +- src/include/catalog/pg_proc.dat | 13 + src/include/catalog/pg_publication.h | 26 +- .../catalog/pg_publication_namespace.h | 10 +- src/include/commands/sequence.h | 1 + src/include/nodes/parsenodes.h | 10 +- src/include/replication/logicalproto.h | 19 +- src/test/regress/expected/object_address.out | 20 +- src/test/regress/expected/psql.out | 6 +- src/test/regress/expected/publication.out | 1055 +++++++++++++---- src/test/regress/expected/rules.out | 8 + src/test/regress/sql/object_address.sql | 5 +- src/test/regress/sql/publication.sql | 231 +++- src/test/subscription/t/034_sequences.pl | 207 ++++ src/tools/pgindent/typedefs.list | 1 + 39 files changed, 3452 insertions(+), 522 deletions(-) create mode 100644 src/test/subscription/t/034_sequences.pl diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml index 3ec7391ec5e..d61fa014316 100644 --- a/doc/src/sgml/catalogs.sgml +++ b/doc/src/sgml/catalogs.sgml @@ -6425,6 +6425,16 @@ SCRAM-SHA-256$<iteration count>:&l Reference to schema + + + + pntype char + + + Determines which object type is included from this schema. + t for tables, s for sequences + + diff --git a/doc/src/sgml/protocol.sgml b/doc/src/sgml/protocol.sgml index af3f016f746..bfff15e0407 100644 --- a/doc/src/sgml/protocol.sgml +++ b/doc/src/sgml/protocol.sgml @@ -6376,6 +6376,86 @@ psql "dbname=postgres replication=database" -c "IDENTIFY_SYSTEM;" + + Sequence + + + + Byte1('X') + + + Identifies the message as a sequence message. + + + + + + Int32 (TransactionId) + + + Xid of the transaction (only present for streamed transactions). + This field is available since protocol version 2. + + + + + + Int8 (0) + + + Flags; currently unused. + + + + + + Int64 (XLogRecPtr) + + + The LSN of the sequence increment. + + + + + + String + + + Namespace (empty string for pg_catalog). + + + + + + String + + + Sequence name. + + + + + + Int8 + + + 1 if the sequence update is transactional, 0 otherwise. + + + + + + Int64 + + + value value of the sequence. + + + + + + + Type diff --git a/doc/src/sgml/ref/alter_publication.sgml b/doc/src/sgml/ref/alter_publication.sgml index 44ae7e0e871..8caf2135fc7 100644 --- a/doc/src/sgml/ref/alter_publication.sgml +++ b/doc/src/sgml/ref/alter_publication.sgml @@ -31,7 +31,9 @@ ALTER PUBLICATION name RENAME TO where publication_object is one of: TABLE [ ONLY ] table_name [ * ] [ ( column_name [, ... ] ) ] [ WHERE ( expression ) ] [, ... ] + SEQUENCE sequence_name [, ... ] TABLES IN SCHEMA { schema_name | CURRENT_SCHEMA } [, ... ] + SEQUENCES IN SCHEMA { schema_name | CURRENT_SCHEMA } [, ... ] @@ -44,13 +46,13 @@ ALTER PUBLICATION name RENAME TO - The first three variants change which tables/schemas are part of the - publication. The SET clause will replace the list of - tables/schemas in the publication with the specified list; the existing - tables/schemas that were present in the publication will be removed. The - ADD and DROP clauses will add and - remove one or more tables/schemas from the publication. Note that adding - tables/schemas to a publication that is already subscribed to will require an + The first three variants change which objects (tables, sequences or schemas) + are part of the publication. The SET clause will replace + the list of objects in the publication with the specified list; the existing + objects that were present in the publication will be removed. + The ADD and DROP clauses will add and + remove one or more objects from the publication. Note that adding objects + to a publication that is already subscribed to will require an ALTER SUBSCRIPTION ... REFRESH PUBLICATION action on the subscribing side in order to become effective. Note also that @@ -138,6 +140,15 @@ ALTER PUBLICATION name RENAME TO + + sequence_name + + + Name of an existing sequence. + + + + schema_name diff --git a/doc/src/sgml/ref/alter_subscription.sgml b/doc/src/sgml/ref/alter_subscription.sgml index 6d36ff0dc90..479ec495896 100644 --- a/doc/src/sgml/ref/alter_subscription.sgml +++ b/doc/src/sgml/ref/alter_subscription.sgml @@ -152,8 +152,8 @@ ALTER SUBSCRIPTION name RENAME TO < Fetch missing table information from publisher. This will start - replication of tables that were added to the subscribed-to publications - since + replication of tables and sequences that were added to the subscribed-to + publications since CREATE SUBSCRIPTION or the last invocation of REFRESH PUBLICATION. @@ -172,8 +172,8 @@ ALTER SUBSCRIPTION name RENAME TO < The default is true. - Previously subscribed tables are not copied, even if a table's row - filter WHERE clause has since been modified. + Previously subscribed tables and sequences are not copied, even if a + table's row filter WHERE clause has since been modified. See for details of diff --git a/doc/src/sgml/ref/create_publication.sgml b/doc/src/sgml/ref/create_publication.sgml index fd9c5deac95..ba578eb9aa4 100644 --- a/doc/src/sgml/ref/create_publication.sgml +++ b/doc/src/sgml/ref/create_publication.sgml @@ -22,14 +22,21 @@ PostgreSQL documentation CREATE PUBLICATION name - [ FOR ALL TABLES + [ FOR ALL object_type [, ...] | FOR publication_object [, ... ] ] [ WITH ( publication_parameter [= value] [, ... ] ) ] +where object type is one of: + + TABLES + SEQUENCES + where publication_object is one of: TABLE [ ONLY ] table_name [ * ] [ ( column_name [, ... ] ) ] [ WHERE ( expression ) ] [, ... ] + SEQUENCE sequence_name [ * ] [, ... ] TABLES IN SCHEMA { schema_name | CURRENT_SCHEMA } [, ... ] + SEQUENCES IN SCHEMA { schema_name | CURRENT_SCHEMA } [, ... ] @@ -117,22 +124,39 @@ CREATE PUBLICATION name + + FOR SEQUENCE + + + Specifies a list of sequences to add to the publication. + + + + Specifying a sequence that is part of a schema specified by FOR + ALL SEQUENCES IN SCHEMA is not supported. + + + + FOR ALL TABLES + FOR ALL SEQUENCES - Marks the publication as one that replicates changes for all tables in - the database, including tables created in the future. + Marks the publication as one that replicates changes for all tables or + sequences in the database, including tables created in the future. FOR TABLES IN SCHEMA + FOR SEQUENCES IN SCHEMA - Marks the publication as one that replicates changes for all tables in - the specified list of schemas, including tables created in the future. + Marks the publication as one that replicates changes for all tables or + sequences in the specified list of schemas, including tables or sequences + created in the future. @@ -240,10 +264,12 @@ CREATE PUBLICATION name Notes - If FOR TABLE, FOR ALL TABLES or - FOR TABLES IN SCHEMA are not specified, then the - publication starts out with an empty set of tables. That is useful if - tables or schemas are to be added later. + If FOR TABLE, FOR SEQUENCE, + FOR ALL TABLES, FOR ALL SEQUENCES, + FOR TABLES IN SCHEMA or FOR SEQUENCES IN SCHEMA + are not specified, then the publication starts out with an empty set + of tables/sequences. That is useful if tables, sequences or schemas + are to be added later. @@ -258,9 +284,10 @@ CREATE PUBLICATION name - To add a table to a publication, the invoking user must have ownership - rights on the table. The FOR ALL TABLES and - FOR TABLES IN SCHEMA clauses require the invoking + To add a table or a sequence to a publication, the invoking user must + have ownership rights on the object. The FOR ALL TABLES, + FOR ALL SEQUENCES, FOR TABLES IN SCHEMA + and FOR SEQUENCES IN SCHEMA clauses require the invoking user to be a superuser. diff --git a/doc/src/sgml/system-views.sgml b/doc/src/sgml/system-views.sgml index 7078491c4c0..e629be7ce9b 100644 --- a/doc/src/sgml/system-views.sgml +++ b/doc/src/sgml/system-views.sgml @@ -126,6 +126,11 @@ prepared transactions + + pg_publication_sequences + publications and information of their associated sequences + + pg_publication_tables publications and information of their associated tables @@ -2138,6 +2143,72 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx + + <structname>pg_publication_sequences</structname> + + + pg_publication_sequences + + + + The view pg_publication_sequences provides + information about the mapping between publications and information of + sequences they contain. Unlike the underlying catalog + pg_publication_rel, + this view expands publications defined as FOR ALL SEQUENCES + and FOR SEQUENCES IN SCHEMA, so for such publications + there will be a row for each eligible sequence. + + + + <structname>pg_publication_sequences</structname> Columns + + + + + Column Type + + + Description + + + + + + + + pubname name + (references pg_publication.pubname) + + + Name of publication + + + + + + schemaname name + (references pg_namespace.nspname) + + + Name of schema containing sequence + + + + + + sequencename name + (references pg_class.relname) + + + Name of sequence + + + + +
+
+ <structname>pg_publication_tables</structname> diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c index bb4efcad202..c155cd57a70 100644 --- a/src/backend/catalog/objectaddress.c +++ b/src/backend/catalog/objectaddress.c @@ -1964,12 +1964,14 @@ get_object_address_publication_schema(List *object, bool missing_ok) char *pubname; char *schemaname; Oid schemaid; + char *objtype; ObjectAddressSet(address, PublicationNamespaceRelationId, InvalidOid); /* Fetch schema name and publication name from input list */ schemaname = strVal(linitial(object)); pubname = strVal(lsecond(object)); + objtype = strVal(lthird(object)); schemaid = get_namespace_oid(schemaname, missing_ok); if (!OidIsValid(schemaid)) @@ -1982,10 +1984,12 @@ get_object_address_publication_schema(List *object, bool missing_ok) /* Find the publication schema mapping in syscache */ address.objectId = - GetSysCacheOid2(PUBLICATIONNAMESPACEMAP, + GetSysCacheOid3(PUBLICATIONNAMESPACEMAP, Anum_pg_publication_namespace_oid, ObjectIdGetDatum(schemaid), - ObjectIdGetDatum(pub->oid)); + ObjectIdGetDatum(pub->oid), + CharGetDatum(objtype[0])); + if (!OidIsValid(address.objectId) && !missing_ok) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), @@ -2260,7 +2264,6 @@ pg_get_object_address(PG_FUNCTION_ARGS) */ switch (type) { - case OBJECT_PUBLICATION_NAMESPACE: case OBJECT_USER_MAPPING: if (list_length(name) != 1) ereport(ERROR, @@ -2293,6 +2296,7 @@ pg_get_object_address(PG_FUNCTION_ARGS) errmsg("name list length must be at least %d", 3))); /* fall through to check args length */ /* FALLTHROUGH */ + case OBJECT_PUBLICATION_NAMESPACE: case OBJECT_OPERATOR: if (list_length(args) != 2) ereport(ERROR, @@ -2365,6 +2369,8 @@ pg_get_object_address(PG_FUNCTION_ARGS) objnode = (Node *) list_make2(name, linitial(args)); break; case OBJECT_PUBLICATION_NAMESPACE: + objnode = (Node *) list_make3(linitial(name), linitial(args), lsecond(args)); + break; case OBJECT_USER_MAPPING: objnode = (Node *) list_make2(linitial(name), linitial(args)); break; @@ -2868,11 +2874,12 @@ get_catalog_object_by_oid(Relation catalog, AttrNumber oidcol, Oid objectId) * * Get publication name and schema name from the object address into pubname and * nspname. Both pubname and nspname are palloc'd strings which will be freed by - * the caller. + * the caller. The last parameter specifies which object type is included from + * the schema. */ static bool getPublicationSchemaInfo(const ObjectAddress *object, bool missing_ok, - char **pubname, char **nspname) + char **pubname, char **nspname, char **objtype) { HeapTuple tup; Form_pg_publication_namespace pnform; @@ -2908,6 +2915,14 @@ getPublicationSchemaInfo(const ObjectAddress *object, bool missing_ok, return false; } + /* + * The type is always a single character, but we need to pass it as a + * string, so allocate two charaters and set the first one. The second one + * is \0. + */ + *objtype = palloc0(2); + *objtype[0] = pnform->pntype; + ReleaseSysCache(tup); return true; } @@ -3978,15 +3993,17 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok) { char *pubname; char *nspname; + char *objtype; if (!getPublicationSchemaInfo(object, missing_ok, - &pubname, &nspname)) + &pubname, &nspname, &objtype)) break; - appendStringInfo(&buffer, _("publication of schema %s in publication %s"), - nspname, pubname); + appendStringInfo(&buffer, _("publication of schema %s in publication %s type %s"), + nspname, pubname, objtype); pfree(pubname); pfree(nspname); + pfree(objtype); break; } @@ -5832,18 +5849,24 @@ getObjectIdentityParts(const ObjectAddress *object, { char *pubname; char *nspname; + char *objtype; if (!getPublicationSchemaInfo(object, missing_ok, &pubname, - &nspname)) + &nspname, &objtype)) break; - appendStringInfo(&buffer, "%s in publication %s", - nspname, pubname); + appendStringInfo(&buffer, "%s in publication %s type %s", + nspname, pubname, objtype); if (objargs) *objargs = list_make1(pubname); else pfree(pubname); + if (objargs) + *objargs = lappend(*objargs, objtype); + else + pfree(objtype); + if (objname) *objname = list_make1(nspname); else diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c index c488b6370b6..bbcbce61718 100644 --- a/src/backend/catalog/pg_publication.c +++ b/src/backend/catalog/pg_publication.c @@ -63,9 +63,10 @@ static void publication_translate_columns(Relation targetrel, List *columns, static void check_publication_add_relation(Relation targetrel) { - /* Must be a regular or partitioned table */ + /* Must be a regular or partitioned table, or a sequence */ if (RelationGetForm(targetrel)->relkind != RELKIND_RELATION && - RelationGetForm(targetrel)->relkind != RELKIND_PARTITIONED_TABLE) + RelationGetForm(targetrel)->relkind != RELKIND_PARTITIONED_TABLE && + RelationGetForm(targetrel)->relkind != RELKIND_SEQUENCE) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("cannot add relation \"%s\" to publication", @@ -142,7 +143,8 @@ static bool is_publishable_class(Oid relid, Form_pg_class reltuple) { return (reltuple->relkind == RELKIND_RELATION || - reltuple->relkind == RELKIND_PARTITIONED_TABLE) && + reltuple->relkind == RELKIND_PARTITIONED_TABLE || + reltuple->relkind == RELKIND_SEQUENCE) && !IsCatalogRelationOid(relid) && reltuple->relpersistence == RELPERSISTENCE_PERMANENT && relid >= FirstNormalObjectId; @@ -233,6 +235,52 @@ filter_partitions(List *table_infos) } } +/* + * Check the character is a valid object type for schema publication. + * + * This recognizes either 't' for tables or 's' for sequences. Places that + * need to handle 'u' for unsupported relkinds need to do that explicitlyl + */ +static void +AssertObjectTypeValid(char objectType) +{ +#ifdef USE_ASSERT_CHECKING + Assert(objectType == PUB_OBJTYPE_SEQUENCE || objectType == PUB_OBJTYPE_TABLE); +#endif +} + +/* + * Determine object type matching a given a relkind value. + */ +char +pub_get_object_type_for_relkind(char relkind) +{ + /* sequence maps directly to sequence relkind */ + if (relkind == RELKIND_SEQUENCE) + return PUB_OBJTYPE_SEQUENCE; + + /* for table, we match either regular or partitioned table */ + if (relkind == RELKIND_RELATION || + relkind == RELKIND_PARTITIONED_TABLE) + return PUB_OBJTYPE_TABLE; + + return PUB_OBJTYPE_UNSUPPORTED; +} + +/* + * Determine if publication object type matches the relkind. + * + * Returns true if the relation matches object type replicated by this schema, + * false otherwise. + */ +static bool +pub_object_type_matches_relkind(char objectType, char relkind) +{ + AssertObjectTypeValid(objectType); + + return (pub_get_object_type_for_relkind(relkind) == objectType); +} + /* * Returns true if any schema is associated with the publication, false if no * schema is associated with the publication. @@ -253,7 +301,7 @@ is_schema_publication(Oid pubid) ObjectIdGetDatum(pubid)); scan = systable_beginscan(pubschsrel, - PublicationNamespacePnnspidPnpubidIndexId, + PublicationNamespacePnnspidPnpubidPntypeIndexId, true, NULL, 1, &scankey); tup = systable_getnext(scan); result = HeapTupleIsValid(tup); @@ -339,7 +387,9 @@ GetTopMostAncestorInPublication(Oid puboid, List *ancestors, int *ancestor_level } else { - aschemaPubids = GetSchemaPublications(get_rel_namespace(ancestor)); + /* we only search for ancestors of tables, so PUB_OBJTYPE_TABLE */ + aschemaPubids = GetSchemaPublications(get_rel_namespace(ancestor), + PUB_OBJTYPE_TABLE); if (list_member_oid(aschemaPubids, puboid)) { topmost_relid = ancestor; @@ -608,7 +658,7 @@ pub_collist_to_bitmapset(Bitmapset *columns, Datum pubcols, MemoryContext mcxt) * Insert new publication / schema mapping. */ ObjectAddress -publication_add_schema(Oid pubid, Oid schemaid, bool if_not_exists) +publication_add_schema(Oid pubid, Oid schemaid, char objectType, bool if_not_exists) { Relation rel; HeapTuple tup; @@ -620,6 +670,8 @@ publication_add_schema(Oid pubid, Oid schemaid, bool if_not_exists) ObjectAddress myself, referenced; + AssertObjectTypeValid(objectType); + rel = table_open(PublicationNamespaceRelationId, RowExclusiveLock); /* @@ -627,9 +679,10 @@ publication_add_schema(Oid pubid, Oid schemaid, bool if_not_exists) * duplicates, it's here just to provide nicer error message in common * case. The real protection is the unique key on the catalog. */ - if (SearchSysCacheExists2(PUBLICATIONNAMESPACEMAP, + if (SearchSysCacheExists3(PUBLICATIONNAMESPACEMAP, ObjectIdGetDatum(schemaid), - ObjectIdGetDatum(pubid))) + ObjectIdGetDatum(pubid), + CharGetDatum(objectType))) { table_close(rel, RowExclusiveLock); @@ -655,6 +708,8 @@ publication_add_schema(Oid pubid, Oid schemaid, bool if_not_exists) ObjectIdGetDatum(pubid); values[Anum_pg_publication_namespace_pnnspid - 1] = ObjectIdGetDatum(schemaid); + values[Anum_pg_publication_namespace_pntype - 1] = + CharGetDatum(objectType); tup = heap_form_tuple(RelationGetDescr(rel), values, nulls); @@ -680,7 +735,7 @@ publication_add_schema(Oid pubid, Oid schemaid, bool if_not_exists) * publication_add_relation for why we need to consider all the * partitions. */ - schemaRels = GetSchemaPublicationRelations(schemaid, + schemaRels = GetSchemaPublicationRelations(schemaid, objectType, PUBLICATION_PART_ALL); InvalidatePublicationRels(schemaRels); @@ -714,11 +769,14 @@ GetRelationPublications(Oid relid) /* * Gets list of relation oids for a publication. * - * This should only be used FOR TABLE publications, the FOR ALL TABLES - * should use GetAllTablesPublicationRelations(). + * This should only be used FOR TABLE / FOR SEQUENCE publications, the FOR + * ALL TABLES / SEQUENCES should use GetAllTablesPublicationRelations() + * and GetAllSequencesPublicationRelations(). + * + * XXX pub_partopt only matters for tables, not sequences. */ List * -GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt) +GetPublicationRelations(Oid pubid, char objectType, PublicationPartOpt pub_partopt) { List *result; Relation pubrelsrel; @@ -726,6 +784,8 @@ GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt) SysScanDesc scan; HeapTuple tup; + AssertObjectTypeValid(objectType); + /* Find all publications associated with the relation. */ pubrelsrel = table_open(PublicationRelRelationId, AccessShareLock); @@ -740,11 +800,29 @@ GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt) result = NIL; while (HeapTupleIsValid(tup = systable_getnext(scan))) { + char relkind; Form_pg_publication_rel pubrel; pubrel = (Form_pg_publication_rel) GETSTRUCT(tup); - result = GetPubPartitionOptionRelations(result, pub_partopt, - pubrel->prrelid); + relkind = get_rel_relkind(pubrel->prrelid); + + /* + * If the relkind does not match the requested object type, ignore the + * relation. For example we might be interested only in sequences, so + * we ignore tables. + */ + if (!pub_object_type_matches_relkind(objectType, relkind)) + continue; + + /* + * We don't have partitioned sequences, so just add them to the list. + * Otherwise consider adding all child relations, if requested. + */ + if (relkind == RELKIND_SEQUENCE) + result = lappend_oid(result, pubrel->prrelid); + else + result = GetPubPartitionOptionRelations(result, pub_partopt, + pubrel->prrelid); } systable_endscan(scan); @@ -794,6 +872,43 @@ GetAllTablesPublications(void) return result; } +/* + * Gets list of publication oids for publications marked as FOR ALL SEQUENCES. + */ +List * +GetAllSequencesPublications(void) +{ + List *result; + Relation rel; + ScanKeyData scankey; + SysScanDesc scan; + HeapTuple tup; + + /* Find all publications that are marked as for all sequences. */ + rel = table_open(PublicationRelationId, AccessShareLock); + + ScanKeyInit(&scankey, + Anum_pg_publication_puballsequences, + BTEqualStrategyNumber, F_BOOLEQ, + BoolGetDatum(true)); + + scan = systable_beginscan(rel, InvalidOid, false, + NULL, 1, &scankey); + + result = NIL; + while (HeapTupleIsValid(tup = systable_getnext(scan))) + { + Oid oid = ((Form_pg_publication) GETSTRUCT(tup))->oid; + + result = lappend_oid(result, oid); + } + + systable_endscan(scan); + table_close(rel, AccessShareLock); + + return result; +} + /* * Gets list of all relation published by FOR ALL TABLES publication(s). * @@ -860,28 +975,38 @@ GetAllTablesPublicationRelations(bool pubviaroot) /* * Gets the list of schema oids for a publication. * - * This should only be used FOR TABLES IN SCHEMA publications. + * This should only be used FOR TABLES IN SCHEMA and FOR SEQUENCES IN SCHEMA + * publications. + * + * 'objectType' determines whether to get FOR TABLE or FOR SEQUENCES schemas */ List * -GetPublicationSchemas(Oid pubid) +GetPublicationSchemas(Oid pubid, char objectType) { List *result = NIL; Relation pubschsrel; - ScanKeyData scankey; + ScanKeyData scankey[2]; SysScanDesc scan; HeapTuple tup; + AssertObjectTypeValid(objectType); + /* Find all schemas associated with the publication */ pubschsrel = table_open(PublicationNamespaceRelationId, AccessShareLock); - ScanKeyInit(&scankey, + ScanKeyInit(&scankey[0], Anum_pg_publication_namespace_pnpubid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(pubid)); + ScanKeyInit(&scankey[1], + Anum_pg_publication_namespace_pntype, + BTEqualStrategyNumber, F_CHAREQ, + CharGetDatum(objectType)); + scan = systable_beginscan(pubschsrel, - PublicationNamespacePnnspidPnpubidIndexId, - true, NULL, 1, &scankey); + PublicationNamespacePnnspidPnpubidPntypeIndexId, + true, NULL, 2, scankey); while (HeapTupleIsValid(tup = systable_getnext(scan))) { Form_pg_publication_namespace pubsch; @@ -899,14 +1024,26 @@ GetPublicationSchemas(Oid pubid) /* * Gets the list of publication oids associated with a specified schema. + * + * objectType specifies whether we're looking for schemas including tables or + * sequences. + * + * Note: relcache calls this for all object types, not just tables and sequences. + * Which is why we handle the PUB_OBJTYPE_UNSUPPORTED object type too. */ List * -GetSchemaPublications(Oid schemaid) +GetSchemaPublications(Oid schemaid, char objectType) { List *result = NIL; CatCList *pubschlist; int i; + /* unsupported object type */ + if (objectType == PUB_OBJTYPE_UNSUPPORTED) + return result; + + AssertObjectTypeValid(objectType); + /* Find all publications associated with the schema */ pubschlist = SearchSysCacheList1(PUBLICATIONNAMESPACEMAP, ObjectIdGetDatum(schemaid)); @@ -914,6 +1051,11 @@ GetSchemaPublications(Oid schemaid) { HeapTuple tup = &pubschlist->members[i]->tuple; Oid pubid = ((Form_pg_publication_namespace) GETSTRUCT(tup))->pnpubid; + char pntype = ((Form_pg_publication_namespace) GETSTRUCT(tup))->pntype; + + /* Skip schemas publishing a different object type. */ + if (pntype != objectType) + continue; result = lappend_oid(result, pubid); } @@ -925,9 +1067,13 @@ GetSchemaPublications(Oid schemaid) /* * Get the list of publishable relation oids for a specified schema. + * + * objectType specifies whether this is FOR ALL TABLES IN SCHEMA or FOR ALL + * SEQUENCES IN SCHEMA */ List * -GetSchemaPublicationRelations(Oid schemaid, PublicationPartOpt pub_partopt) +GetSchemaPublicationRelations(Oid schemaid, char objectType, + PublicationPartOpt pub_partopt) { Relation classRel; ScanKeyData key[1]; @@ -936,6 +1082,7 @@ GetSchemaPublicationRelations(Oid schemaid, PublicationPartOpt pub_partopt) List *result = NIL; Assert(OidIsValid(schemaid)); + AssertObjectTypeValid(objectType); classRel = table_open(RelationRelationId, AccessShareLock); @@ -956,9 +1103,17 @@ GetSchemaPublicationRelations(Oid schemaid, PublicationPartOpt pub_partopt) continue; relkind = get_rel_relkind(relid); - if (relkind == RELKIND_RELATION) - result = lappend_oid(result, relid); - else if (relkind == RELKIND_PARTITIONED_TABLE) + + /* Skip if the relkind does not match FOR ALL TABLES / SEQUENCES. */ + if (!pub_object_type_matches_relkind(objectType, relkind)) + continue; + + /* + * If the object is a partitioned table, lookup all the child + * relations (if requested). Otherwise just add the object to the + * list. + */ + if (relkind == RELKIND_PARTITIONED_TABLE) { List *partitionrels = NIL; @@ -971,7 +1126,11 @@ GetSchemaPublicationRelations(Oid schemaid, PublicationPartOpt pub_partopt) pub_partopt, relForm->oid); result = list_concat_unique_oid(result, partitionrels); + continue; } + + /* non-partitioned tables and sequences */ + result = lappend_oid(result, relid); } table_endscan(scan); @@ -980,28 +1139,68 @@ GetSchemaPublicationRelations(Oid schemaid, PublicationPartOpt pub_partopt) } /* - * Gets the list of all relations published by FOR TABLES IN SCHEMA - * publication. + * Gets the list of all relations published by FOR TABLES IN SCHEMA or + * FOR SEQUENCES IN SCHEMA publication. */ List * -GetAllSchemaPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt) +GetAllSchemaPublicationRelations(Oid pubid, char objectType, + PublicationPartOpt pub_partopt) { List *result = NIL; - List *pubschemalist = GetPublicationSchemas(pubid); + List *pubschemalist = GetPublicationSchemas(pubid, objectType); ListCell *cell; + AssertObjectTypeValid(objectType); + foreach(cell, pubschemalist) { Oid schemaid = lfirst_oid(cell); List *schemaRels = NIL; - schemaRels = GetSchemaPublicationRelations(schemaid, pub_partopt); + schemaRels = GetSchemaPublicationRelations(schemaid, objectType, + pub_partopt); result = list_concat(result, schemaRels); } return result; } +/* + * Gets list of all relation published by FOR ALL SEQUENCES publication(s). + */ +List * +GetAllSequencesPublicationRelations(void) +{ + Relation classRel; + ScanKeyData key[1]; + TableScanDesc scan; + HeapTuple tuple; + List *result = NIL; + + classRel = table_open(RelationRelationId, AccessShareLock); + + ScanKeyInit(&key[0], + Anum_pg_class_relkind, + BTEqualStrategyNumber, F_CHAREQ, + CharGetDatum(RELKIND_SEQUENCE)); + + scan = table_beginscan_catalog(classRel, 1, key); + + while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL) + { + Form_pg_class relForm = (Form_pg_class) GETSTRUCT(tuple); + Oid relid = relForm->oid; + + if (is_publishable_class(relid, relForm)) + result = lappend_oid(result, relid); + } + + table_endscan(scan); + + table_close(classRel, AccessShareLock); + return result; +} + /* * Get publication using oid * @@ -1024,10 +1223,12 @@ GetPublication(Oid pubid) pub->oid = pubid; pub->name = pstrdup(NameStr(pubform->pubname)); pub->alltables = pubform->puballtables; + pub->allsequences = pubform->puballsequences; pub->pubactions.pubinsert = pubform->pubinsert; pub->pubactions.pubupdate = pubform->pubupdate; pub->pubactions.pubdelete = pubform->pubdelete; pub->pubactions.pubtruncate = pubform->pubtruncate; + pub->pubactions.pubsequence = pubform->pubsequence; pub->pubviaroot = pubform->pubviaroot; ReleaseSysCache(tup); @@ -1108,10 +1309,12 @@ pg_get_publication_tables(PG_FUNCTION_ARGS) *schemarelids; relids = GetPublicationRelations(pub_elem->oid, + PUB_OBJTYPE_TABLE, pub_elem->pubviaroot ? PUBLICATION_PART_ROOT : PUBLICATION_PART_LEAF); schemarelids = GetAllSchemaPublicationRelations(pub_elem->oid, + PUB_OBJTYPE_TABLE, pub_elem->pubviaroot ? PUBLICATION_PART_ROOT : PUBLICATION_PART_LEAF); @@ -1197,9 +1400,10 @@ pg_get_publication_tables(PG_FUNCTION_ARGS) * FOR TABLES IN SCHEMA publications. */ if (!pub->alltables && - !SearchSysCacheExists2(PUBLICATIONNAMESPACEMAP, + !SearchSysCacheExists3(PUBLICATIONNAMESPACEMAP, ObjectIdGetDatum(schemaid), - ObjectIdGetDatum(pub->oid))) + ObjectIdGetDatum(pub->oid), + PUB_OBJTYPE_TABLE)) pubtuple = SearchSysCacheCopy2(PUBLICATIONRELMAP, ObjectIdGetDatum(relid), ObjectIdGetDatum(pub->oid)); @@ -1259,3 +1463,71 @@ pg_get_publication_tables(PG_FUNCTION_ARGS) SRF_RETURN_DONE(funcctx); } + +/* + * Returns Oids of sequences in a publication. + */ +Datum +pg_get_publication_sequences(PG_FUNCTION_ARGS) +{ + FuncCallContext *funcctx; + char *pubname = text_to_cstring(PG_GETARG_TEXT_PP(0)); + Publication *publication; + List *sequences; + + /* stuff done only on the first call of the function */ + if (SRF_IS_FIRSTCALL()) + { + MemoryContext oldcontext; + + /* create a function context for cross-call persistence */ + funcctx = SRF_FIRSTCALL_INIT(); + + /* switch to memory context appropriate for multiple function calls */ + oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); + + publication = GetPublicationByName(pubname, false); + + /* + * Publications support partitioned tables, although all changes are + * replicated using leaf partition identity and schema, so we only + * need those. + */ + if (publication->allsequences) + sequences = GetAllSequencesPublicationRelations(); + else + { + List *relids, + *schemarelids; + + relids = GetPublicationRelations(publication->oid, + PUB_OBJTYPE_SEQUENCE, + publication->pubviaroot ? + PUBLICATION_PART_ROOT : + PUBLICATION_PART_LEAF); + schemarelids = GetAllSchemaPublicationRelations(publication->oid, + PUB_OBJTYPE_SEQUENCE, + publication->pubviaroot ? + PUBLICATION_PART_ROOT : + PUBLICATION_PART_LEAF); + sequences = list_concat_unique_oid(relids, schemarelids); + } + + funcctx->user_fctx = (void *) sequences; + + MemoryContextSwitchTo(oldcontext); + } + + /* stuff done on every call of the function */ + funcctx = SRF_PERCALL_SETUP(); + sequences = (List *) funcctx->user_fctx; + + if (funcctx->call_cntr < list_length(sequences)) + { + Oid relid = list_nth_oid(sequences, funcctx->call_cntr); + + SRF_RETURN_NEXT(funcctx, ObjectIdGetDatum(relid)); + } + + SRF_RETURN_DONE(funcctx); +} diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index b65f6b52490..a42af769fee 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -380,6 +380,16 @@ CREATE VIEW pg_publication_tables AS pg_class C JOIN pg_namespace N ON (N.oid = C.relnamespace) WHERE C.oid = GPT.relid; +CREATE VIEW pg_publication_sequences AS + SELECT + P.pubname AS pubname, + N.nspname AS schemaname, + C.relname AS sequencename + FROM pg_publication P, + LATERAL pg_get_publication_sequences(P.pubname) GPS, + pg_class C JOIN pg_namespace N ON (N.oid = C.relnamespace) + WHERE C.oid = GPS.relid; + CREATE VIEW pg_locks AS SELECT * FROM pg_lock_status() AS L; diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c index f4ba572697a..e7b2667f64a 100644 --- a/src/backend/commands/publicationcmds.c +++ b/src/backend/commands/publicationcmds.c @@ -16,6 +16,7 @@ #include "access/genam.h" #include "access/htup_details.h" +#include "access/relation.h" #include "access/table.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -68,15 +69,17 @@ 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 *rels, char objectType); +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 PublicationDropTables(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); +static void PublicationAddRelations(Oid pubid, List *rels, bool if_not_exists, + AlterPublicationStmt *stmt); +static void PublicationDropRelations(Oid pubid, List *rels, bool missing_ok); +static void PublicationAddSchemas(Oid pubid, List *schemas, char objectType, + bool if_not_exists, AlterPublicationStmt *stmt); +static void PublicationDropSchemas(Oid pubid, List *schemas, char objectType, + bool missing_ok); + static void @@ -97,6 +100,7 @@ parse_publication_options(ParseState *pstate, pubactions->pubupdate = true; pubactions->pubdelete = true; pubactions->pubtruncate = true; + pubactions->pubsequence = true; *publish_via_partition_root = false; /* Parse options */ @@ -121,6 +125,7 @@ parse_publication_options(ParseState *pstate, pubactions->pubupdate = false; pubactions->pubdelete = false; pubactions->pubtruncate = false; + pubactions->pubsequence = false; *publish_given = true; publish = defGetString(defel); @@ -144,6 +149,8 @@ parse_publication_options(ParseState *pstate, pubactions->pubdelete = true; else if (strcmp(publish_opt, "truncate") == 0) pubactions->pubtruncate = true; + else if (strcmp(publish_opt, "sequence") == 0) + pubactions->pubsequence = true; else ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), @@ -171,7 +178,8 @@ parse_publication_options(ParseState *pstate, */ static void ObjectsInPublicationToOids(List *pubobjspec_list, ParseState *pstate, - List **rels, List **schemas) + List **tables, List **sequences, + List **tables_schemas, List **sequences_schemas) { ListCell *cell; PublicationObjSpec *pubobj; @@ -189,13 +197,22 @@ ObjectsInPublicationToOids(List *pubobjspec_list, ParseState *pstate, switch (pubobj->pubobjtype) { case PUBLICATIONOBJ_TABLE: - *rels = lappend(*rels, pubobj->pubtable); + *tables = lappend(*tables, pubobj->pubtable); + break; + case PUBLICATIONOBJ_SEQUENCE: + *sequences = lappend(*sequences, pubobj->pubtable); break; case PUBLICATIONOBJ_TABLES_IN_SCHEMA: schemaid = get_namespace_oid(pubobj->name, false); /* Filter out duplicates if user specifies "sch1, sch1" */ - *schemas = list_append_unique_oid(*schemas, schemaid); + *tables_schemas = list_append_unique_oid(*tables_schemas, schemaid); + break; + case PUBLICATIONOBJ_SEQUENCES_IN_SCHEMA: + schemaid = get_namespace_oid(pubobj->name, false); + + /* Filter out duplicates if user specifies "sch1, sch1" */ + *sequences_schemas = list_append_unique_oid(*sequences_schemas, schemaid); break; case PUBLICATIONOBJ_TABLES_IN_CUR_SCHEMA: search_path = fetch_search_path(false); @@ -208,7 +225,20 @@ ObjectsInPublicationToOids(List *pubobjspec_list, ParseState *pstate, list_free(search_path); /* Filter out duplicates if user specifies "sch1, sch1" */ - *schemas = list_append_unique_oid(*schemas, schemaid); + *tables_schemas = list_append_unique_oid(*tables_schemas, schemaid); + break; + case PUBLICATIONOBJ_SEQUENCES_IN_CUR_SCHEMA: + search_path = fetch_search_path(false); + if (search_path == NIL) /* nothing valid in search_path? */ + ereport(ERROR, + errcode(ERRCODE_UNDEFINED_SCHEMA), + errmsg("no schema has been selected for CURRENT_SCHEMA")); + + schemaid = linitial_oid(search_path); + list_free(search_path); + + /* Filter out duplicates if user specifies "sch1, sch1" */ + *sequences_schemas = list_append_unique_oid(*sequences_schemas, schemaid); break; default: /* shouldn't happen */ @@ -734,6 +764,7 @@ CheckPubRelationColumnList(char *pubname, List *tables, ObjectAddress CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt) { + ListCell *lc; Relation rel; ObjectAddress myself; Oid puboid; @@ -745,8 +776,27 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt) bool publish_via_partition_root_given; bool publish_via_partition_root; AclResult aclresult; - List *relations = NIL; - List *schemaidlist = NIL; + List *tables = NIL; + List *sequences = NIL; + List *tables_schemaidlist = NIL; + List *sequences_schemaidlist = NIL; + + bool for_all_tables = false; + bool for_all_sequences = false; + + /* + * Translate the list of object types (represented by strings) to bool + * flags. + */ + foreach(lc, stmt->for_all_objects) + { + char *val = strVal(lfirst(lc)); + + if (strcmp(val, "tables") == 0) + for_all_tables = true; + else if (strcmp(val, "sequences") == 0) + for_all_sequences = true; + } /* must have CREATE privilege on database */ aclresult = object_aclcheck(DatabaseRelationId, MyDatabaseId, GetUserId(), ACL_CREATE); @@ -755,11 +805,17 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt) get_database_name(MyDatabaseId)); /* FOR ALL TABLES requires superuser */ - if (stmt->for_all_tables && !superuser()) + if (for_all_tables && !superuser()) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("must be superuser to create FOR ALL TABLES publication"))); + /* FOR ALL SEQUENCES requires superuser */ + if (for_all_sequences && !superuser()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be superuser to create FOR ALL SEQUENCES publication"))); + rel = table_open(PublicationRelationId, RowExclusiveLock); /* Check if name is used */ @@ -789,7 +845,9 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt) Anum_pg_publication_oid); values[Anum_pg_publication_oid - 1] = ObjectIdGetDatum(puboid); values[Anum_pg_publication_puballtables - 1] = - BoolGetDatum(stmt->for_all_tables); + BoolGetDatum(for_all_tables); + values[Anum_pg_publication_puballsequences - 1] = + BoolGetDatum(for_all_sequences); values[Anum_pg_publication_pubinsert - 1] = BoolGetDatum(pubactions.pubinsert); values[Anum_pg_publication_pubupdate - 1] = @@ -798,6 +856,8 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt) BoolGetDatum(pubactions.pubdelete); values[Anum_pg_publication_pubtruncate - 1] = BoolGetDatum(pubactions.pubtruncate); + values[Anum_pg_publication_pubsequence - 1] = + BoolGetDatum(pubactions.pubsequence); values[Anum_pg_publication_pubviaroot - 1] = BoolGetDatum(publish_via_partition_root); @@ -815,46 +875,88 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt) CommandCounterIncrement(); /* Associate objects with the publication. */ - if (stmt->for_all_tables) + if (for_all_tables || for_all_sequences) { /* Invalidate relcache so that publication info is rebuilt. */ CacheInvalidateRelcacheAll(); } - else + + /* + * If the publication might have either tables or sequences (directly or + * through a schema), process that. + */ + if (!for_all_tables || !for_all_sequences) { - ObjectsInPublicationToOids(stmt->pubobjects, pstate, &relations, - &schemaidlist); + ObjectsInPublicationToOids(stmt->pubobjects, pstate, + &tables, &sequences, + &tables_schemaidlist, + &sequences_schemaidlist); /* FOR TABLES IN SCHEMA requires superuser */ - if (schemaidlist != NIL && !superuser()) + if (tables_schemaidlist != NIL && !superuser()) ereport(ERROR, errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("must be superuser to create FOR TABLES IN SCHEMA publication")); - if (relations != NIL) + /* FOR SEQUENCES IN SCHEMA requires superuser */ + if (sequences_schemaidlist != NIL && !superuser()) + ereport(ERROR, + errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be superuser to create FOR ALL SEQUENCES IN SCHEMA publication")); + + /* tables added directly */ + if (tables != NIL) { List *rels; - rels = OpenTableList(relations); + rels = OpenRelationList(tables, PUB_OBJTYPE_TABLE); + TransformPubWhereClauses(rels, pstate->p_sourcetext, publish_via_partition_root); CheckPubRelationColumnList(stmt->pubname, rels, - schemaidlist != NIL, + tables_schemaidlist != NIL, publish_via_partition_root); - PublicationAddTables(puboid, rels, true, NULL); - CloseTableList(rels); + PublicationAddRelations(puboid, rels, true, NULL); + CloseRelationList(rels); + } + + /* sequences added directly */ + if (sequences != NIL) + { + List *rels; + + rels = OpenRelationList(sequences, PUB_OBJTYPE_SEQUENCE); + + PublicationAddRelations(puboid, rels, true, NULL); + CloseRelationList(rels); } - if (schemaidlist != NIL) + /* tables added through a schema */ + if (tables_schemaidlist != NIL) { /* * Schema lock is held until the publication is created to prevent * concurrent schema deletion. */ - LockSchemaList(schemaidlist); - PublicationAddSchemas(puboid, schemaidlist, true, NULL); + LockSchemaList(tables_schemaidlist); + PublicationAddSchemas(puboid, + tables_schemaidlist, PUB_OBJTYPE_TABLE, + true, NULL); + } + + /* sequences added through a schema */ + if (sequences_schemaidlist != NIL) + { + /* + * Schema lock is held until the publication is created to prevent + * concurrent schema deletion. + */ + LockSchemaList(sequences_schemaidlist); + PublicationAddSchemas(puboid, + sequences_schemaidlist, PUB_OBJTYPE_SEQUENCE, + true, NULL); } } @@ -917,6 +1019,7 @@ AlterPublicationOptions(ParseState *pstate, AlterPublicationStmt *stmt, AccessShareLock); root_relids = GetPublicationRelations(pubform->oid, + PUB_OBJTYPE_TABLE, PUBLICATION_PART_ROOT); foreach(lc, root_relids) @@ -996,6 +1099,9 @@ AlterPublicationOptions(ParseState *pstate, AlterPublicationStmt *stmt, values[Anum_pg_publication_pubtruncate - 1] = BoolGetDatum(pubactions.pubtruncate); replaces[Anum_pg_publication_pubtruncate - 1] = true; + + values[Anum_pg_publication_pubsequence - 1] = BoolGetDatum(pubactions.pubsequence); + replaces[Anum_pg_publication_pubsequence - 1] = true; } if (publish_via_partition_root_given) @@ -1015,7 +1121,7 @@ AlterPublicationOptions(ParseState *pstate, AlterPublicationStmt *stmt, pubform = (Form_pg_publication) GETSTRUCT(tup); /* Invalidate the relcache. */ - if (pubform->puballtables) + if (pubform->puballtables || pubform->puballsequences) { CacheInvalidateRelcacheAll(); } @@ -1031,6 +1137,7 @@ AlterPublicationOptions(ParseState *pstate, AlterPublicationStmt *stmt, */ if (root_relids == NIL) relids = GetPublicationRelations(pubform->oid, + PUB_OBJTYPE_TABLE, PUBLICATION_PART_ALL); else { @@ -1044,7 +1151,20 @@ AlterPublicationOptions(ParseState *pstate, AlterPublicationStmt *stmt, lfirst_oid(lc)); } + /* tables */ schemarelids = GetAllSchemaPublicationRelations(pubform->oid, + PUB_OBJTYPE_TABLE, + PUBLICATION_PART_ALL); + relids = list_concat_unique_oid(relids, schemarelids); + + /* sequences */ + relids = list_concat_unique_oid(relids, + GetPublicationRelations(pubform->oid, + PUB_OBJTYPE_SEQUENCE, + PUBLICATION_PART_ALL)); + + schemarelids = GetAllSchemaPublicationRelations(pubform->oid, + PUB_OBJTYPE_SEQUENCE, PUBLICATION_PART_ALL); relids = list_concat_unique_oid(relids, schemarelids); @@ -1099,7 +1219,7 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup, if (!tables && stmt->action != AP_SetObjects) return; - rels = OpenTableList(tables); + rels = OpenRelationList(tables, PUB_OBJTYPE_TABLE); if (stmt->action == AP_AddObjects) { @@ -1110,13 +1230,14 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup, CheckPubRelationColumnList(stmt->pubname, rels, publish_schema, pubform->pubviaroot); - PublicationAddTables(pubid, rels, false, stmt); + PublicationAddRelations(pubid, rels, false, stmt); } else if (stmt->action == AP_DropObjects) - PublicationDropTables(pubid, rels, false); + PublicationDropRelations(pubid, rels, false); else /* AP_SetObjects */ { List *oldrelids = GetPublicationRelations(pubid, + PUB_OBJTYPE_TABLE, PUBLICATION_PART_ROOT); List *delrels = NIL; ListCell *oldlc; @@ -1233,18 +1354,18 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup, } /* 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. */ - PublicationAddTables(pubid, rels, true, stmt); + PublicationAddRelations(pubid, rels, true, stmt); - CloseTableList(delrels); + CloseRelationList(delrels); } - CloseTableList(rels); + CloseRelationList(rels); } /* @@ -1254,7 +1375,8 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup, */ static void AlterPublicationSchemas(AlterPublicationStmt *stmt, - HeapTuple tup, List *schemaidlist) + HeapTuple tup, List *schemaidlist, + char objectType) { Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup); @@ -1276,7 +1398,7 @@ AlterPublicationSchemas(AlterPublicationStmt *stmt, ListCell *lc; List *reloids; - reloids = GetPublicationRelations(pubform->oid, PUBLICATION_PART_ROOT); + reloids = GetPublicationRelations(pubform->oid, objectType, PUBLICATION_PART_ROOT); foreach(lc, reloids) { @@ -1303,13 +1425,13 @@ AlterPublicationSchemas(AlterPublicationStmt *stmt, ReleaseSysCache(coltuple); } - PublicationAddSchemas(pubform->oid, schemaidlist, false, stmt); + PublicationAddSchemas(pubform->oid, schemaidlist, objectType, false, stmt); } else if (stmt->action == AP_DropObjects) - PublicationDropSchemas(pubform->oid, schemaidlist, false); + PublicationDropSchemas(pubform->oid, schemaidlist, objectType, false); else /* AP_SetObjects */ { - List *oldschemaids = GetPublicationSchemas(pubform->oid); + List *oldschemaids = GetPublicationSchemas(pubform->oid, objectType); List *delschemas = NIL; /* Identify which schemas should be dropped */ @@ -1322,13 +1444,13 @@ AlterPublicationSchemas(AlterPublicationStmt *stmt, LockSchemaList(delschemas); /* And drop them */ - PublicationDropSchemas(pubform->oid, delschemas, true); + PublicationDropSchemas(pubform->oid, delschemas, objectType, true); /* * Don't bother calculating the difference for adding, we'll catch and * skip existing ones when doing catalog update. */ - PublicationAddSchemas(pubform->oid, schemaidlist, true, stmt); + PublicationAddSchemas(pubform->oid, schemaidlist, objectType, true, stmt); } } @@ -1338,12 +1460,13 @@ AlterPublicationSchemas(AlterPublicationStmt *stmt, */ static void CheckAlterPublication(AlterPublicationStmt *stmt, HeapTuple tup, - List *tables, List *schemaidlist) + List *tables, List *tables_schemaidlist, + List *sequences, List *sequences_schemaidlist) { Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup); if ((stmt->action == AP_AddObjects || stmt->action == AP_SetObjects) && - schemaidlist && !superuser()) + (tables_schemaidlist || sequences_schemaidlist) && !superuser()) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("must be superuser to add or set schemas"))); @@ -1352,13 +1475,24 @@ CheckAlterPublication(AlterPublicationStmt *stmt, HeapTuple tup, * Check that user is allowed to manipulate the publication tables in * schema */ - if (schemaidlist && pubform->puballtables) + if (tables_schemaidlist && pubform->puballtables) ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("publication \"%s\" is defined as FOR ALL TABLES", NameStr(pubform->pubname)), errdetail("Schemas cannot be added to or dropped from FOR ALL TABLES publications."))); + /* + * Check that user is allowed to manipulate the publication sequences in + * schema + */ + if (sequences_schemaidlist && pubform->puballsequences) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("publication \"%s\" is defined as FOR ALL SEQUENCES", + NameStr(pubform->pubname)), + errdetail("Sequences from schema cannot be added to, dropped from, or set on FOR ALL SEQUENCES publications."))); + /* Check that user is allowed to manipulate the publication tables. */ if (tables && pubform->puballtables) ereport(ERROR, @@ -1366,6 +1500,107 @@ CheckAlterPublication(AlterPublicationStmt *stmt, HeapTuple tup, errmsg("publication \"%s\" is defined as FOR ALL TABLES", NameStr(pubform->pubname)), errdetail("Tables cannot be added to or dropped from FOR ALL TABLES publications."))); + + /* Check that user is allowed to manipulate the publication sequences. */ + if (sequences && pubform->puballsequences) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("publication \"%s\" is defined as FOR ALL SEQUENCES", + NameStr(pubform->pubname)), + errdetail("Sequences cannot be added to or dropped from FOR ALL SEQUENCES publications."))); +} + +/* + * Add or remove table to/from publication. + */ +static void +AlterPublicationSequences(AlterPublicationStmt *stmt, HeapTuple tup, + List *sequences) +{ + List *rels = 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. + */ + if (!sequences && stmt->action != AP_SetObjects) + return; + + rels = OpenRelationList(sequences, PUB_OBJTYPE_SEQUENCE); + + if (stmt->action == AP_AddObjects) + { + PublicationAddRelations(pubid, rels, false, stmt); + } + else if (stmt->action == AP_DropObjects) + PublicationDropRelations(pubid, rels, false); + else /* AP_SetObjects */ + { + List *oldrelids = GetPublicationRelations(pubid, + PUB_OBJTYPE_SEQUENCE, + PUBLICATION_PART_ROOT); + List *delrels = NIL; + ListCell *oldlc; + + /* + * 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; + + foreach(newlc, rels) + { + PublicationRelInfo *newpubrel; + + newpubrel = (PublicationRelInfo *) lfirst(newlc); + + /* + * Check if any of the new set of relations matches with the + * existing relations in the publication. + */ + if (RelationGetRelid(newpubrel->relation) == oldrelid) + { + found = true; + break; + } + } + + /* + * Add the non-matched relations to a list so that they can be + * dropped. + */ + if (!found) + { + oldrel = palloc(sizeof(PublicationRelInfo)); + oldrel->whereClause = NULL; + oldrel->columns = NIL; + oldrel->relation = table_open(oldrelid, + ShareUpdateExclusiveLock); + delrels = lappend(delrels, oldrel); + } + } + + /* And drop them. */ + 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); + + CloseRelationList(delrels); + } + + CloseRelationList(rels); } /* @@ -1403,14 +1638,20 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt) AlterPublicationOptions(pstate, stmt, rel, tup); else { - List *relations = NIL; - List *schemaidlist = NIL; + List *tables = NIL; + List *sequences = NIL; + List *tables_schemaidlist = NIL; + List *sequences_schemaidlist = NIL; Oid pubid = pubform->oid; - ObjectsInPublicationToOids(stmt->pubobjects, pstate, &relations, - &schemaidlist); + ObjectsInPublicationToOids(stmt->pubobjects, pstate, + &tables, &sequences, + &tables_schemaidlist, + &sequences_schemaidlist); - CheckAlterPublication(stmt, tup, relations, schemaidlist); + CheckAlterPublication(stmt, tup, + tables, tables_schemaidlist, + sequences, sequences_schemaidlist); heap_freetuple(tup); @@ -1431,9 +1672,16 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt) errmsg("publication \"%s\" does not exist", stmt->pubname)); - AlterPublicationTables(stmt, tup, relations, pstate->p_sourcetext, - schemaidlist != NIL); - AlterPublicationSchemas(stmt, tup, schemaidlist); + AlterPublicationTables(stmt, tup, tables, pstate->p_sourcetext, + tables_schemaidlist != NIL); + + AlterPublicationSchemas(stmt, tup, tables_schemaidlist, + PUB_OBJTYPE_TABLE); + + AlterPublicationSequences(stmt, tup, sequences); + + AlterPublicationSchemas(stmt, tup, sequences_schemaidlist, + PUB_OBJTYPE_SEQUENCE); } /* Cleanup. */ @@ -1501,7 +1749,7 @@ RemovePublicationById(Oid pubid) pubform = (Form_pg_publication) GETSTRUCT(tup); /* Invalidate relcache so that publication info is rebuilt. */ - if (pubform->puballtables) + if (pubform->puballtables || pubform->puballsequences) CacheInvalidateRelcacheAll(); CatalogTupleDelete(rel, &tup->t_self); @@ -1537,6 +1785,7 @@ RemovePublicationSchemaById(Oid psoid) * partitions. */ schemaRels = GetSchemaPublicationRelations(pubsch->pnnspid, + pubsch->pntype, PUBLICATION_PART_ALL); InvalidatePublicationRels(schemaRels); @@ -1553,10 +1802,10 @@ RemovePublicationSchemaById(Oid psoid) * add them to a publication. */ static List * -OpenTableList(List *tables) +OpenRelationList(List *rels, char objectType) { List *relids = NIL; - List *rels = NIL; + List *result = NIL; ListCell *lc; List *relids_with_rf = NIL; List *relids_with_collist = NIL; @@ -1564,19 +1813,35 @@ OpenTableList(List *tables) /* * Open, share-lock, and check all the explicitly-specified relations */ - foreach(lc, tables) + foreach(lc, rels) { PublicationTable *t = lfirst_node(PublicationTable, lc); bool recurse = t->relation->inh; Relation rel; Oid myrelid; PublicationRelInfo *pub_rel; + char myrelkind; /* Allow query cancel in case this takes a long time */ CHECK_FOR_INTERRUPTS(); rel = table_openrv(t->relation, ShareUpdateExclusiveLock); myrelid = RelationGetRelid(rel); + myrelkind = get_rel_relkind(myrelid); + + /* + * Make sure the relkind matches the expected object type. This may + * happen e.g. when adding a sequence using ADD TABLE or a table using + * ADD SEQUENCE). + * + * XXX We let through unsupported object types (views etc.). Those + * will be caught later in check_publication_add_relation. + */ + if (pub_get_object_type_for_relkind(myrelkind) != PUB_OBJTYPE_UNSUPPORTED && + pub_get_object_type_for_relkind(myrelkind) != objectType) + ereport(ERROR, + errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("object type does not match type expected by command")); /* * Filter out duplicates if user specifies "foo, foo". @@ -1609,7 +1874,7 @@ OpenTableList(List *tables) pub_rel->relation = rel; pub_rel->whereClause = t->whereClause; pub_rel->columns = t->columns; - rels = lappend(rels, pub_rel); + result = lappend(result, pub_rel); relids = lappend_oid(relids, myrelid); if (t->whereClause) @@ -1678,10 +1943,9 @@ OpenTableList(List *tables) pub_rel->relation = rel; /* child inherits WHERE clause from parent */ pub_rel->whereClause = t->whereClause; - /* child inherits column list from parent */ pub_rel->columns = t->columns; - rels = lappend(rels, pub_rel); + result = lappend(result, pub_rel); relids = lappend_oid(relids, childrelid); if (t->whereClause) @@ -1696,14 +1960,14 @@ OpenTableList(List *tables) list_free(relids); list_free(relids_with_rf); - return rels; + return result; } /* * Close all relations in the list. */ static void -CloseTableList(List *rels) +CloseRelationList(List *rels) { ListCell *lc; @@ -1751,12 +2015,12 @@ LockSchemaList(List *schemalist) * Add listed tables 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) { ListCell *lc; - Assert(!stmt || !stmt->for_all_tables); + Assert(!stmt || !stmt->for_all_objects); foreach(lc, rels) { @@ -1785,7 +2049,7 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists, * Remove listed tables 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; @@ -1830,19 +2094,19 @@ PublicationDropTables(Oid pubid, List *rels, bool missing_ok) * Add listed schemas to the publication. */ static void -PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists, - AlterPublicationStmt *stmt) +PublicationAddSchemas(Oid pubid, List *schemas, char objectType, + bool if_not_exists, AlterPublicationStmt *stmt) { ListCell *lc; - Assert(!stmt || !stmt->for_all_tables); + Assert(!stmt || !stmt->for_all_objects); foreach(lc, schemas) { Oid schemaid = lfirst_oid(lc); ObjectAddress obj; - obj = publication_add_schema(pubid, schemaid, if_not_exists); + obj = publication_add_schema(pubid, schemaid, objectType, if_not_exists); if (stmt) { EventTriggerCollectSimpleCommand(obj, InvalidObjectAddress, @@ -1858,7 +2122,7 @@ PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists, * Remove listed schemas from the publication. */ static void -PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok) +PublicationDropSchemas(Oid pubid, List *schemas, char objectType, bool missing_ok) { ObjectAddress obj; ListCell *lc; @@ -1868,10 +2132,11 @@ PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok) { Oid schemaid = lfirst_oid(lc); - psid = GetSysCacheOid2(PUBLICATIONNAMESPACEMAP, + psid = GetSysCacheOid3(PUBLICATIONNAMESPACEMAP, Anum_pg_publication_namespace_oid, ObjectIdGetDatum(schemaid), - ObjectIdGetDatum(pubid)); + ObjectIdGetDatum(pubid), + CharGetDatum(objectType)); if (!OidIsValid(psid)) { if (missing_ok) @@ -1926,6 +2191,13 @@ AlterPublicationOwner_internal(Relation rel, HeapTuple tup, Oid newOwnerId) NameStr(form->pubname)), errhint("The owner of a FOR ALL TABLES publication must be a superuser."))); + if (form->puballsequences && !superuser_arg(newOwnerId)) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("permission denied to change owner of publication \"%s\"", + NameStr(form->pubname)), + errhint("The owner of a FOR ALL SEQUENCES publication must be a superuser."))); + if (!superuser_arg(newOwnerId) && is_schema_publication(form->oid)) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c index 616b8685e39..d8404a6f75e 100644 --- a/src/backend/commands/sequence.c +++ b/src/backend/commands/sequence.c @@ -44,6 +44,7 @@ #include "utils/acl.h" #include "utils/builtins.h" #include "utils/lsyscache.h" +#include "utils/pg_lsn.h" #include "utils/resowner.h" #include "utils/syscache.h" #include "utils/varlena.h" @@ -101,7 +102,8 @@ static Relation lock_and_open_sequence(SeqTable seq); static void create_seq_hashtable(void); static void init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel); static Form_pg_sequence_data read_seq_tuple(Relation rel, - Buffer *buf, HeapTuple seqdatatuple); + Buffer *buf, HeapTuple seqdatatuple, + XLogRecPtr *lsn); static void init_params(ParseState *pstate, List *options, bool for_identity, bool isInit, Form_pg_sequence seqform, @@ -284,7 +286,7 @@ ResetSequence(Oid seq_relid) * indeed a sequence. */ init_sequence(seq_relid, &elm, &seq_rel); - (void) read_seq_tuple(seq_rel, &buf, &seqdatatuple); + (void) read_seq_tuple(seq_rel, &buf, &seqdatatuple, NULL); pgstuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seq_relid)); if (!HeapTupleIsValid(pgstuple)) @@ -342,6 +344,166 @@ ResetSequence(Oid seq_relid) relation_close(seq_rel, NoLock); } +/* + * Update the sequence state by modifying the existing sequence data row. + * + * This keeps the same relfilenode, so the behavior is non-transactional. + */ +static void +SetSequence_non_transactional(Oid seqrelid, int64 value) +{ + SeqTable elm; + Relation seqrel; + Buffer buf; + HeapTupleData seqdatatuple; + Form_pg_sequence_data seq; + + /* open and lock sequence */ + init_sequence(seqrelid, &elm, &seqrel); + + /* lock page' buffer and read tuple */ + seq = read_seq_tuple(seqrel, &buf, &seqdatatuple, NULL); + + /* check the comment above nextval_internal()'s equivalent call. */ + if (RelationNeedsWAL(seqrel)) + { + GetTopTransactionId(); + + if (XLogLogicalInfoActive()) + GetCurrentTransactionId(); + } + + /* ready to change the on-disk (or really, in-buffer) tuple */ + START_CRIT_SECTION(); + + seq->last_value = value; + seq->is_called = true; + seq->log_cnt = 0; + + MarkBufferDirty(buf); + + /* XLOG stuff */ + if (RelationNeedsWAL(seqrel)) + { + xl_seq_rec xlrec; + XLogRecPtr recptr; + Page page = BufferGetPage(buf); + + XLogBeginInsert(); + XLogRegisterBuffer(0, buf, REGBUF_WILL_INIT); + + xlrec.locator = seqrel->rd_locator; + + XLogRegisterData((char *) &xlrec, sizeof(xl_seq_rec)); + XLogRegisterData((char *) seqdatatuple.t_data, seqdatatuple.t_len); + + /* allow filtering by origin on a sequence update */ + XLogSetRecordFlags(XLOG_INCLUDE_ORIGIN); + + recptr = XLogInsert(RM_SEQ_ID, XLOG_SEQ_LOG); + + PageSetLSN(page, recptr); + } + + END_CRIT_SECTION(); + + UnlockReleaseBuffer(buf); + + /* Clear local cache so that we don't think we have cached numbers */ + /* Note that we do not change the currval() state */ + elm->cached = elm->last; + + relation_close(seqrel, NoLock); +} + +/* + * Update the sequence state by creating a new relfilenode. + * + * This creates a new relfilenode, to allow transactional behavior. + */ +static void +SetSequence_transactional(Oid seq_relid, int64 value) +{ + SeqTable elm; + Relation seqrel; + Buffer buf; + HeapTupleData seqdatatuple; + Form_pg_sequence_data seq; + HeapTuple tuple; + + /* open and lock sequence */ + init_sequence(seq_relid, &elm, &seqrel); + + /* lock page' buffer and read tuple */ + seq = read_seq_tuple(seqrel, &buf, &seqdatatuple, NULL); + + /* Copy the existing sequence tuple. */ + tuple = heap_copytuple(&seqdatatuple); + + /* Now we're done with the old page */ + UnlockReleaseBuffer(buf); + + /* + * Modify the copied tuple to update the sequence state (similar to what + * ResetSequence does). + */ + seq = (Form_pg_sequence_data) GETSTRUCT(tuple); + seq->last_value = value; + seq->is_called = true; + seq->log_cnt = 0; + + /* make sure the relfilenode creation is associated with the XID */ + if (XLogLogicalInfoActive()) + GetCurrentTransactionId(); + + /* + * Create a new storage file for the sequence - this is needed for the + * transactional behavior. + */ + RelationSetNewRelfilenumber(seqrel, seqrel->rd_rel->relpersistence); + + /* + * Ensure sequence's relfrozenxid is at 0, since it won't contain any + * unfrozen XIDs. Same with relminmxid, since a sequence will never + * contain multixacts. + */ + Assert(seqrel->rd_rel->relfrozenxid == InvalidTransactionId); + Assert(seqrel->rd_rel->relminmxid == InvalidMultiXactId); + + /* + * Insert the modified tuple into the new storage file. This does all the + * necessary WAL-logging etc. + */ + fill_seq_with_data(seqrel, tuple); + + /* Clear local cache so that we don't think we have cached numbers */ + /* Note that we do not change the currval() state */ + elm->cached = elm->last; + + relation_close(seqrel, NoLock); +} + +/* + * Set a sequence to a specified internal state. + * + * The change is made transactionally, so that on failure of the current + * transaction, the sequence will be restored to its previous state. + * We do that by creating a whole new relfilenode for the sequence; so this + * works much like the rewriting forms of ALTER TABLE. + * + * Caller is assumed to have acquired AccessExclusiveLock on the sequence, + * which must not be released until end of transaction. Caller is also + * responsible for permissions checking. + */ +void +SetSequence(Oid seq_relid, bool transactional, int64 value) +{ + if (transactional) + SetSequence_transactional(seq_relid, value); + else + SetSequence_non_transactional(seq_relid, value); +} + /* * Initialize a sequence's relation with the specified tuple as content * @@ -504,7 +666,7 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt) seqform = (Form_pg_sequence) GETSTRUCT(seqtuple); /* lock page buffer and read tuple into new sequence structure */ - (void) read_seq_tuple(seqrel, &buf, &datatuple); + (void) read_seq_tuple(seqrel, &buf, &datatuple, NULL); /* copy the existing sequence data tuple, so it can be modified locally */ newdatatuple = heap_copytuple(&datatuple); @@ -598,7 +760,7 @@ SequenceChangePersistence(Oid relid, char newrelpersistence) GetCurrentTransactionId(); } - (void) read_seq_tuple(seqrel, &buf, &seqdatatuple); + (void) read_seq_tuple(seqrel, &buf, &seqdatatuple, NULL); RelationSetNewRelfilenumber(seqrel, newrelpersistence); fill_seq_with_data(seqrel, &seqdatatuple); UnlockReleaseBuffer(buf); @@ -727,7 +889,7 @@ nextval_internal(Oid relid, bool check_permissions) ReleaseSysCache(pgstuple); /* lock page buffer and read tuple */ - seq = read_seq_tuple(seqrel, &buf, &seqdatatuple); + seq = read_seq_tuple(seqrel, &buf, &seqdatatuple, NULL); page = BufferGetPage(buf); elm->increment = incby; @@ -1050,7 +1212,7 @@ do_setval(Oid relid, int64 next, bool iscalled) PreventCommandIfParallelMode("setval()"); /* lock page buffer and read tuple */ - seq = read_seq_tuple(seqrel, &buf, &seqdatatuple); + seq = read_seq_tuple(seqrel, &buf, &seqdatatuple, NULL); if ((next < minv) || (next > maxv)) ereport(ERROR, @@ -1270,11 +1432,13 @@ init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel) * *buf receives the reference to the pinned-and-ex-locked buffer * *seqdatatuple receives the reference to the sequence tuple proper * (this arg should point to a local variable of type HeapTupleData) + * *lsn receives LSN of the last sequence change (page LSN), optional * * Function's return value points to the data payload of the tuple */ static Form_pg_sequence_data -read_seq_tuple(Relation rel, Buffer *buf, HeapTuple seqdatatuple) +read_seq_tuple(Relation rel, Buffer *buf, HeapTuple seqdatatuple, + XLogRecPtr *lsn) { Page page; ItemId lp; @@ -1291,6 +1455,13 @@ read_seq_tuple(Relation rel, Buffer *buf, HeapTuple seqdatatuple) elog(ERROR, "bad magic number in sequence \"%s\": %08X", RelationGetRelationName(rel), sm->magic); + /* + * If the caller requested it, set the page LSN. This allows deciding + * which sequence changes are before/after the returned sequence state. + */ + if (lsn) + *lsn = PageGetLSN(page); + lp = PageGetItemId(page, FirstOffsetNumber); Assert(ItemIdIsNormal(lp)); @@ -1886,7 +2057,7 @@ pg_sequence_last_value(PG_FUNCTION_ARGS) errmsg("permission denied for sequence %s", RelationGetRelationName(seqrel)))); - seq = read_seq_tuple(seqrel, &buf, &seqtuple); + seq = read_seq_tuple(seqrel, &buf, &seqtuple, NULL); is_called = seq->is_called; result = seq->last_value; @@ -1900,6 +2071,67 @@ pg_sequence_last_value(PG_FUNCTION_ARGS) PG_RETURN_NULL(); } +/* + * Return the current on-disk state of the sequence. + * + * Note: This is roughly equivalent to selecting the data from the sequence, + * except that it also returns the page LSN. + */ +Datum +pg_sequence_state(PG_FUNCTION_ARGS) +{ + Oid relid = PG_GETARG_OID(0); + SeqTable elm; + Relation seqrel; + Buffer buf; + HeapTupleData seqtuple; + Form_pg_sequence_data seq; + Datum result; + + int64 last_value; + int64 log_cnt; + bool is_called; + XLogRecPtr lsn; + + TupleDesc tupdesc; + HeapTuple tuple; + Datum values[4]; + bool nulls[4]; + + if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) + elog(ERROR, "return type must be a row type"); + + /* open and lock sequence */ + init_sequence(relid, &elm, &seqrel); + + if (pg_class_aclcheck(elm->relid, GetUserId(), + ACL_SELECT | ACL_USAGE) != ACLCHECK_OK) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("permission denied for sequence %s", + RelationGetRelationName(seqrel)))); + + seq = read_seq_tuple(seqrel, &buf, &seqtuple, &lsn); + + is_called = seq->is_called; + last_value = seq->last_value; + log_cnt = seq->log_cnt; + + UnlockReleaseBuffer(buf); + relation_close(seqrel, NoLock); + + values[0] = LSNGetDatum(lsn); + values[1] = Int64GetDatum(last_value); + values[2] = Int64GetDatum(log_cnt); + values[3] = BoolGetDatum(is_called); + + memset(nulls, 0, sizeof(nulls)); + + tuple = heap_form_tuple(tupdesc, values, nulls); + result = HeapTupleGetDatum(tuple); + + PG_RETURN_DATUM(result); +} void seq_redo(XLogReaderState *record) diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c index edc82c11beb..0e06b36611f 100644 --- a/src/backend/commands/subscriptioncmds.c +++ b/src/backend/commands/subscriptioncmds.c @@ -100,6 +100,7 @@ typedef struct SubOpts } SubOpts; static List *fetch_table_list(WalReceiverConn *wrconn, List *publications); +static List *fetch_sequence_list(WalReceiverConn *wrconn, List *publications); static void check_publications_origin(WalReceiverConn *wrconn, List *publications, bool copydata, char *origin, Oid *subrel_local_oids, @@ -730,9 +731,9 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt, { char *err; WalReceiverConn *wrconn; - List *tables; + List *relations; ListCell *lc; - char table_state; + char sync_state; bool must_use_password; /* Try to connect to the publisher. */ @@ -754,14 +755,17 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt, * Set sync state based on if we were asked to do data copy or * not. */ - table_state = opts.copy_data ? SUBREL_STATE_INIT : SUBREL_STATE_READY; + sync_state = opts.copy_data ? SUBREL_STATE_INIT : SUBREL_STATE_READY; /* - * Get the table list from publisher and build local table status - * info. + * Get the table and sequence list from publisher and build local + * relation sync status info. */ - tables = fetch_table_list(wrconn, publications); - foreach(lc, tables) + relations = fetch_table_list(wrconn, publications); + relations = list_concat(relations, + fetch_sequence_list(wrconn, publications)); + + foreach(lc, relations) { RangeVar *rv = (RangeVar *) lfirst(lc); Oid relid; @@ -772,7 +776,7 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt, CheckSubscriptionRelkind(get_rel_relkind(relid), rv->schemaname, rv->relname); - AddSubscriptionRelState(subid, relid, table_state, + AddSubscriptionRelState(subid, relid, sync_state, InvalidXLogRecPtr); } @@ -798,12 +802,12 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt, * * Note that if tables were specified but copy_data is false * then it is safe to enable two_phase up-front because those - * tables are already initially in READY state. When the - * subscription has no tables, we leave the twophase state as - * PENDING, to allow ALTER SUBSCRIPTION ... REFRESH + * relations are already initially in READY state. When the + * subscription has no relations, we leave the twophase state + * as PENDING, to allow ALTER SUBSCRIPTION ... REFRESH * PUBLICATION to work. */ - if (opts.twophase && !opts.copy_data && tables != NIL) + if (opts.twophase && !opts.copy_data && relations != NIL) twophase_enabled = true; walrcv_create_slot(wrconn, opts.slot_name, false, twophase_enabled, @@ -882,8 +886,10 @@ AlterSubscription_refresh(Subscription *sub, bool copy_data, if (validate_publications) check_publications(wrconn, validate_publications); - /* Get the table list from publisher. */ + /* Get the list of relations from publisher. */ pubrel_names = fetch_table_list(wrconn, sub->publications); + pubrel_names = list_concat(pubrel_names, + fetch_sequence_list(wrconn, sub->publications)); /* Get local table list. */ subrel_states = GetSubscriptionRelations(sub->oid, false); @@ -2153,6 +2159,75 @@ fetch_table_list(WalReceiverConn *wrconn, List *publications) return tablelist; } +/* + * Get the list of sequences which belong to specified publications on the + * publisher connection. + */ +static List * +fetch_sequence_list(WalReceiverConn *wrconn, List *publications) +{ + WalRcvExecResult *res; + StringInfoData cmd; + TupleTableSlot *slot; + Oid tableRow[2] = {TEXTOID, TEXTOID}; + ListCell *lc; + bool first; + List *tablelist = NIL; + + Assert(list_length(publications) > 0); + + initStringInfo(&cmd); + appendStringInfoString(&cmd, "SELECT DISTINCT s.schemaname, s.sequencename\n" + " FROM pg_catalog.pg_publication_sequences s\n" + " WHERE s.pubname IN ("); + first = true; + foreach(lc, publications) + { + char *pubname = strVal(lfirst(lc)); + + if (first) + first = false; + else + appendStringInfoString(&cmd, ", "); + + appendStringInfoString(&cmd, quote_literal_cstr(pubname)); + } + appendStringInfoChar(&cmd, ')'); + + res = walrcv_exec(wrconn, cmd.data, 2, tableRow); + pfree(cmd.data); + + if (res->status != WALRCV_OK_TUPLES) + ereport(ERROR, + (errmsg("could not receive list of replicated sequences from the publisher: %s", + res->err))); + + /* Process sequences. */ + slot = MakeSingleTupleTableSlot(res->tupledesc, &TTSOpsMinimalTuple); + while (tuplestore_gettupleslot(res->tuplestore, true, false, slot)) + { + char *nspname; + char *relname; + bool isnull; + RangeVar *rv; + + nspname = TextDatumGetCString(slot_getattr(slot, 1, &isnull)); + Assert(!isnull); + relname = TextDatumGetCString(slot_getattr(slot, 2, &isnull)); + Assert(!isnull); + + rv = makeRangeVar(nspname, relname, -1); + tablelist = lappend(tablelist, rv); + + ExecClearTuple(slot); + } + ExecDropSingleTupleTableSlot(slot); + + walrcv_clear_result(res); + + return tablelist; +} + /* * This is to report the connection failure while dropping replication slots. * Here, we report the WARNING for all tablesync slots so that user can drop diff --git a/src/backend/executor/execReplication.c b/src/backend/executor/execReplication.c index 81f27042bc4..1d5639b47fc 100644 --- a/src/backend/executor/execReplication.c +++ b/src/backend/executor/execReplication.c @@ -745,7 +745,9 @@ void CheckSubscriptionRelkind(char relkind, const char *nspname, const char *relname) { - if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE) + if (relkind != RELKIND_RELATION && + relkind != RELKIND_PARTITIONED_TABLE && + relkind != RELKIND_SEQUENCE) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("cannot use relation \"%s.%s\" as logical replication target", diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index c224df4eccc..71a4abcb839 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -452,7 +452,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); transform_element_list transform_type_list TriggerTransitions TriggerReferencing vacuum_relation_list opt_vacuum_relation_list - drop_option_list pub_obj_list + drop_option_list pub_obj_list pub_obj_type_list %type opt_routine_body %type group_clause @@ -586,6 +586,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); %type var_value zone_value %type auth_ident RoleSpec opt_granted_by %type PublicationObjSpec +%type pub_obj_type %type unreserved_keyword type_func_name_keyword %type col_name_keyword reserved_keyword @@ -10414,12 +10415,16 @@ AlterOwnerStmt: ALTER AGGREGATE aggregate_with_argtypes OWNER TO RoleSpec * * CREATE PUBLICATION FOR ALL TABLES [WITH options] * + * CREATE PUBLICATION FOR ALL SEQUENCES [WITH options] + * * CREATE PUBLICATION FOR pub_obj [, ...] [WITH options] * * pub_obj is one of: * * TABLE table [, ...] + * SEQUENCE table [, ...] * TABLES IN SCHEMA schema [, ...] + * SEQUENCES IN SCHEMA schema [, ...] * *****************************************************************************/ @@ -10432,13 +10437,13 @@ CreatePublicationStmt: n->options = $4; $$ = (Node *) n; } - | CREATE PUBLICATION name FOR ALL TABLES opt_definition + | CREATE PUBLICATION name FOR ALL pub_obj_type_list opt_definition { CreatePublicationStmt *n = makeNode(CreatePublicationStmt); n->pubname = $3; n->options = $7; - n->for_all_tables = true; + n->for_all_objects = $6; $$ = (Node *) n; } | CREATE PUBLICATION name FOR pub_obj_list opt_definition @@ -10489,6 +10494,26 @@ PublicationObjSpec: $$->pubobjtype = PUBLICATIONOBJ_TABLES_IN_CUR_SCHEMA; $$->location = @4; } + | SEQUENCE relation_expr + { + $$ = makeNode(PublicationObjSpec); + $$->pubobjtype = PUBLICATIONOBJ_SEQUENCE; + $$->pubtable = makeNode(PublicationTable); + $$->pubtable->relation = $2; + } + | SEQUENCES IN_P SCHEMA ColId + { + $$ = makeNode(PublicationObjSpec); + $$->pubobjtype = PUBLICATIONOBJ_SEQUENCES_IN_SCHEMA; + $$->name = $4; + $$->location = @4; + } + | SEQUENCES IN_P SCHEMA CURRENT_SCHEMA + { + $$ = makeNode(PublicationObjSpec); + $$->pubobjtype = PUBLICATIONOBJ_SEQUENCES_IN_CUR_SCHEMA; + $$->location = @4; + } | ColId opt_column_list OptWhereClause { $$ = makeNode(PublicationObjSpec); @@ -10550,6 +10575,19 @@ pub_obj_list: PublicationObjSpec { $$ = lappend($1, $3); } ; +pub_obj_type: TABLES + { $$ = (Node *) makeString("tables"); } + | SEQUENCES + { $$ = (Node *) makeString("sequences"); } + ; + +pub_obj_type_list: pub_obj_type + { $$ = list_make1($1); } + | pub_obj_type_list ',' pub_obj_type + { $$ = lappend($1, $3); } + ; + + /***************************************************************************** * * ALTER PUBLICATION name SET ( options ) @@ -10563,7 +10601,9 @@ pub_obj_list: PublicationObjSpec * pub_obj is one of: * * TABLE table_name [, ...] + * SEQUENCE table_name [, ...] * TABLES IN SCHEMA schema_name [, ...] + * SEQUENCES IN SCHEMA schema_name [, ...] * *****************************************************************************/ @@ -18909,7 +18949,8 @@ preprocess_pubobj_list(List *pubobjspec_list, core_yyscan_t yyscanner) if (pubobj->pubobjtype == PUBLICATIONOBJ_CONTINUATION) pubobj->pubobjtype = prevobjtype; - if (pubobj->pubobjtype == PUBLICATIONOBJ_TABLE) + if (pubobj->pubobjtype == PUBLICATIONOBJ_TABLE || + pubobj->pubobjtype == PUBLICATIONOBJ_SEQUENCE) { /* relation name or pubtable must be set for this type of object */ if (!pubobj->name && !pubobj->pubtable) @@ -18960,6 +19001,30 @@ preprocess_pubobj_list(List *pubobjspec_list, core_yyscan_t yyscanner) errmsg("invalid schema name"), parser_errposition(pubobj->location)); } + else if (pubobj->pubobjtype == PUBLICATIONOBJ_SEQUENCES_IN_SCHEMA || + pubobj->pubobjtype == PUBLICATIONOBJ_SEQUENCES_IN_CUR_SCHEMA) + { + /* WHERE clause is not allowed on a schema object */ + if (pubobj->pubtable && pubobj->pubtable->whereClause) + ereport(ERROR, + errcode(ERRCODE_SYNTAX_ERROR), + errmsg("WHERE clause not allowed for schema"), + parser_errposition(pubobj->location)); + + /* + * We can distinguish between the different type of schema + * objects based on whether name and pubtable is set. + */ + if (pubobj->name) + pubobj->pubobjtype = PUBLICATIONOBJ_SEQUENCES_IN_SCHEMA; + else if (!pubobj->name && !pubobj->pubtable) + pubobj->pubobjtype = PUBLICATIONOBJ_SEQUENCES_IN_CUR_SCHEMA; + else + ereport(ERROR, + errcode(ERRCODE_SYNTAX_ERROR), + errmsg("invalid schema name at or near"), + parser_errposition(pubobj->location)); + } prevobjtype = pubobj->pubobjtype; } diff --git a/src/backend/replication/logical/proto.c b/src/backend/replication/logical/proto.c index aa471dccdf3..9d4fbe92ce0 100644 --- a/src/backend/replication/logical/proto.c +++ b/src/backend/replication/logical/proto.c @@ -663,6 +663,59 @@ logicalrep_write_message(StringInfo out, TransactionId xid, XLogRecPtr lsn, pq_sendbytes(out, message, sz); } +/* + * Write SEQUENCE to stream + */ +void +logicalrep_write_sequence(StringInfo out, Relation rel, TransactionId xid, + XLogRecPtr lsn, bool transactional, + int64 value) +{ + uint8 flags = 0; + + pq_sendbyte(out, LOGICAL_REP_MSG_SEQUENCE); + + /* transaction ID (if not valid, we're not streaming) */ + if (TransactionIdIsValid(xid)) + pq_sendint32(out, xid); + + pq_sendint8(out, flags); + + /* use Oid as relation identifier */ + pq_sendint32(out, RelationGetRelid(rel)); + + /* write the LSN of the sequence change */ + pq_sendint64(out, lsn); + + /* write sequence info */ + pq_sendint8(out, transactional); + pq_sendint64(out, value); +} + +/* + * Read SEQUENCE from the stream. + */ +LogicalRepRelId +logicalrep_read_sequence(StringInfo in, LogicalRepSequence *seqdata) +{ + LogicalRepRelId relid; + + /* XXX skipping flags */ + pq_getmsgint(in, 1); + + /* read the relation id */ + relid = pq_getmsgint(in, 4); + + /* read the LSN of the sequence change */ + seqdata->lsn = pq_getmsgint64(in); + + /* info about the sequence change */ + seqdata->transactional = pq_getmsgint(in, 1); + seqdata->value = pq_getmsgint64(in); + + return relid; +} + /* * Write relation description to the output stream. */ @@ -1258,6 +1311,8 @@ logicalrep_message_type(LogicalRepMsgType action) return "STREAM ABORT"; case LOGICAL_REP_MSG_STREAM_PREPARE: return "STREAM PREPARE"; + case LOGICAL_REP_MSG_SEQUENCE: + return "SEQUENCE"; } /* diff --git a/src/backend/replication/logical/tablesync.c b/src/backend/replication/logical/tablesync.c index df3c42eb5de..f1312b2f69b 100644 --- a/src/backend/replication/logical/tablesync.c +++ b/src/backend/replication/logical/tablesync.c @@ -100,6 +100,7 @@ #include "catalog/pg_subscription_rel.h" #include "catalog/pg_type.h" #include "commands/copy.h" +#include "commands/sequence.h" #include "miscadmin.h" #include "nodes/makefuncs.h" #include "parser/parse_relation.h" @@ -118,6 +119,7 @@ #include "utils/builtins.h" #include "utils/lsyscache.h" #include "utils/memutils.h" +#include "utils/pg_lsn.h" #include "utils/rls.h" #include "utils/snapmgr.h" #include "utils/syscache.h" @@ -1229,6 +1231,106 @@ copy_table(Relation rel) logicalrep_rel_close(relmapentry, NoLock); } +/* + * Fetch sequence data (current state) from the remote node, including the + * page LSN. + */ +static int64 +fetch_sequence_data(Oid remoteid, XLogRecPtr *lsn) +{ + WalRcvExecResult *res; + StringInfoData cmd; + TupleTableSlot *slot; + Oid tableRow[2] = {INT8OID, LSNOID}; + int64 value = (Datum) 0; + + initStringInfo(&cmd); + appendStringInfo(&cmd, "SELECT (last_value + log_cnt), page_lsn " + "FROM pg_sequence_state(%d)", remoteid); + + res = walrcv_exec(LogRepWorkerWalRcvConn, cmd.data, 2, tableRow); + pfree(cmd.data); + + if (res->status != WALRCV_OK_TUPLES) + ereport(ERROR, + (errmsg("could not receive list of replicated tables from the publisher: %s", + res->err))); + + /* Process the sequence. */ + slot = MakeSingleTupleTableSlot(res->tupledesc, &TTSOpsMinimalTuple); + while (tuplestore_gettupleslot(res->tuplestore, true, false, slot)) + { + bool isnull; + + value = DatumGetInt64(slot_getattr(slot, 1, &isnull)); + Assert(!isnull); + + *lsn = DatumGetInt64(slot_getattr(slot, 2, &isnull)); + Assert(!isnull); + } + + ExecDropSingleTupleTableSlot(slot); + + walrcv_clear_result(res); + + return value; +} + +/* + * Copy existing data of a sequence from publisher. + * + * Caller is responsible for locking the local relation. + */ +static XLogRecPtr +copy_sequence(Relation rel) +{ + LogicalRepRelMapEntry *relmapentry; + LogicalRepRelation lrel; + List *qual = NIL; + StringInfoData cmd; + int64 sequence_value; + XLogRecPtr lsn = InvalidXLogRecPtr; + + /* Get the publisher relation info. */ + fetch_remote_table_info(get_namespace_name(RelationGetNamespace(rel)), + RelationGetRelationName(rel), &lrel, &qual); + + /* sequences don't have row filters */ + Assert(!qual); + + /* Put the relation into relmap. */ + logicalrep_relmap_update(&lrel); + + /* Map the publisher relation to local one. */ + relmapentry = logicalrep_rel_open(lrel.remoteid, NoLock); + Assert(rel == relmapentry->localrel); + + /* Start copy on the publisher. */ + initStringInfo(&cmd); + + Assert(lrel.relkind == RELKIND_SEQUENCE); + + /* + * Logical replication of sequences is based on decoding WAL records, + * describing the "next" state of the sequence the current state in the + * relfilenode is yet to reach. But during the initial sync we read the + * current state, so we need to reconstruct the WAL record logged when we + * started the current batch of sequence values. + * + * Otherwise we might get duplicate values (on subscriber) if we failed + * over right after the sync. + */ + sequence_value = fetch_sequence_data(lrel.remoteid, &lsn); + + /* tablesync sets the sequences in non-transactional way */ + SetSequence(RelationGetRelid(rel), false, sequence_value); + + logicalrep_rel_close(relmapentry, NoLock); + + /* return the LSN when the sequence state was set */ + return lsn; +} + /* * Determine the tablesync slot name. * @@ -1277,6 +1379,7 @@ LogicalRepSyncTableStart(XLogRecPtr *origin_startpos) UserContext ucxt; bool must_use_password; bool run_as_owner; + XLogRecPtr sequence_lsn = InvalidXLogRecPtr; /* Check the state of the table synchronization. */ StartTransactionCommand(); @@ -1488,10 +1591,34 @@ LogicalRepSyncTableStart(XLogRecPtr *origin_startpos) GetUserNameFromId(GetUserId(), true), RelationGetRelationName(rel)))); - /* Now do the initial data copy */ - PushActiveSnapshot(GetTransactionSnapshot()); - copy_table(rel); - PopActiveSnapshot(); + /* Do the right action depending on the relation kind. */ + if (get_rel_relkind(RelationGetRelid(rel)) == RELKIND_SEQUENCE) + { + /* Now do the initial sequence copy */ + PushActiveSnapshot(GetTransactionSnapshot()); + sequence_lsn = copy_sequence(rel); + PopActiveSnapshot(); + + /* + * Sequences are not consistent (in the MVCC sense) with respect to + * the replication slot, so the copy might have read a more recent + * state than origin_startpos. The sequence_lsn comes from page LSN + * (which is LSN of the last sequence change), so that's the right + * position where to start with the catchup apply. + * + * It might be before the slot, though (if the sequence was not used + * since between the slot creation and copy), so make sure the + * position does not move backwards. + */ + *origin_startpos = Max(*origin_startpos, sequence_lsn); + } + else + { + /* Now do the initial data copy */ + PushActiveSnapshot(GetTransactionSnapshot()); + copy_table(rel); + PopActiveSnapshot(); + } res = walrcv_exec(LogRepWorkerWalRcvConn, "COMMIT", 0, NULL); if (res->status != WALRCV_OK_COMMAND) diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c index 21abf34ef7a..a5b52f60a5a 100644 --- a/src/backend/replication/logical/worker.c +++ b/src/backend/replication/logical/worker.c @@ -154,6 +154,7 @@ #include "catalog/pg_subscription.h" #include "catalog/pg_subscription_rel.h" #include "catalog/pg_tablespace.h" +#include "commands/sequence.h" #include "commands/tablecmds.h" #include "commands/tablespace.h" #include "commands/trigger.h" @@ -1437,6 +1438,97 @@ apply_handle_origin(StringInfo s) errmsg_internal("ORIGIN message sent out of order"))); } +/* + * Handle SEQUENCE message. + */ +static void +apply_handle_sequence(StringInfo s) +{ + LogicalRepSequence seq; + LogicalRepRelMapEntry *rel; + Oid relid; + bool already_in_transaction PG_USED_FOR_ASSERTS_ONLY; + + if (is_skipping_changes() || + handle_streamed_transaction(LOGICAL_REP_MSG_SEQUENCE, s)) + return; + + /* + * Remember if we're already in transaction (begin_replication step starts + * a transaction, so we can't use IsTransactionState() after that point. + */ + already_in_transaction = IsTransactionState(); + + /* + * Make sure we're in a transaction (needed by SetSequence). For + * non-transactional updates we're guaranteed to start a new one, and + * we'll commit it at the end. + */ + begin_replication_step(); + + relid = logicalrep_read_sequence(s, &seq); + + /* + * Non-transactional sequence updates should not be part of a remote + * transaction. There should not be any running transaction. + */ + Assert((!seq.transactional) || in_remote_transaction); + Assert(!(!seq.transactional && in_remote_transaction)); + Assert(!(!seq.transactional && already_in_transaction)); + + /* lock the sequence in AccessExclusiveLock, as expected by SetSequence */ + rel = logicalrep_rel_open(relid, AccessExclusiveLock); + if (!should_apply_changes_for_rel(rel)) + { + /* + * The relation can't become interesting in the middle of the + * transaction so it's safe to unlock it. + */ + logicalrep_rel_close(rel, AccessExclusiveLock); + end_replication_step(); + + /* + * Commit the per-stream transaction (we only do this when not in + * remote transaction, i.e. for non-transactional sequence updates.) + */ + if (!in_remote_transaction) + CommitTransactionCommand(); + + return; + } + + /* + * apply the sequence change + * + * XXX We don't need to reconstruct the value from last_value, log_cnt + * etc. because that happens on the publisher. + */ + SetSequence(rel->localreloid, seq.transactional, seq.value); + + logicalrep_rel_close(rel, NoLock); + + end_replication_step(); + + /* + * Commit the per-stream transaction (we only do this when not in remote + * transaction, i.e. for non-transactional sequence updates.) + */ + if (!in_remote_transaction) + { + CommitTransactionCommand(); + + /* + * Update origin state so we don't try applying this sequence change + * in case of crash. We only do this for non-trasactional changes, + * that are replayed directly. + * + * XXX We don't have replorigin_session_origin_timestamp, but we can + * just leave that set to 0. + */ + replorigin_session_origin_lsn = seq.lsn; + } +} + /* * Initialize fileset (if not already done). * @@ -3347,6 +3439,10 @@ apply_dispatch(StringInfo s) */ break; + case LOGICAL_REP_MSG_SEQUENCE: + apply_handle_sequence(s); + return; + case LOGICAL_REP_MSG_STREAM_START: apply_handle_stream_start(s); break; @@ -4365,6 +4461,7 @@ set_stream_options(WalRcvStreamOptions *options, server_version = walrcv_server_version(LogRepWorkerWalRcvConn); options->proto.logical.proto_version = + server_version >= 170000 ? LOGICALREP_PROTO_SEQUENCES_VERSION_NUM : server_version >= 160000 ? LOGICALREP_PROTO_STREAM_PARALLEL_VERSION_NUM : server_version >= 150000 ? LOGICALREP_PROTO_TWOPHASE_VERSION_NUM : server_version >= 140000 ? LOGICALREP_PROTO_STREAM_VERSION_NUM : diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c index e8add5ee5d9..b64f7849ac6 100644 --- a/src/backend/replication/pgoutput/pgoutput.c +++ b/src/backend/replication/pgoutput/pgoutput.c @@ -15,6 +15,7 @@ #include "access/tupconvert.h" #include "catalog/partition.h" #include "catalog/pg_publication.h" +#include "catalog/pg_publication_namespace.h" #include "catalog/pg_publication_rel.h" #include "catalog/pg_subscription.h" #include "commands/defrem.h" @@ -55,6 +56,10 @@ static void pgoutput_message(LogicalDecodingContext *ctx, ReorderBufferTXN *txn, XLogRecPtr message_lsn, bool transactional, const char *prefix, Size sz, const char *message); +static void pgoutput_sequence(LogicalDecodingContext *ctx, + ReorderBufferTXN *txn, XLogRecPtr sequence_lsn, + Relation relation, bool transactional, + int64 value); static bool pgoutput_origin_filter(LogicalDecodingContext *ctx, RepOriginId origin_id); static void pgoutput_begin_prepare_txn(LogicalDecodingContext *ctx, @@ -252,6 +257,7 @@ _PG_output_plugin_init(OutputPluginCallbacks *cb) cb->change_cb = pgoutput_change; cb->truncate_cb = pgoutput_truncate; cb->message_cb = pgoutput_message; + cb->sequence_cb = pgoutput_sequence; cb->commit_cb = pgoutput_commit_txn; cb->begin_prepare_cb = pgoutput_begin_prepare_txn; @@ -268,6 +274,7 @@ _PG_output_plugin_init(OutputPluginCallbacks *cb) cb->stream_commit_cb = pgoutput_stream_commit; cb->stream_change_cb = pgoutput_change; cb->stream_message_cb = pgoutput_message; + cb->stream_sequence_cb = pgoutput_sequence; cb->stream_truncate_cb = pgoutput_truncate; /* transaction streaming - two-phase commit */ cb->stream_prepare_cb = pgoutput_stream_prepare_txn; @@ -673,7 +680,7 @@ pgoutput_rollback_prepared_txn(LogicalDecodingContext *ctx, */ static void maybe_send_schema(LogicalDecodingContext *ctx, - ReorderBufferChange *change, + ReorderBufferTXN *txn, Relation relation, RelationSyncEntry *relentry) { PGOutputData *data = (PGOutputData *) ctx->output_plugin_private; @@ -690,10 +697,10 @@ maybe_send_schema(LogicalDecodingContext *ctx, * the write methods will not include it. */ if (data->in_streaming) - xid = change->txn->xid; + xid = txn->xid; - if (rbtxn_is_subtxn(change->txn)) - topxid = rbtxn_get_toptxn(change->txn)->xid; + if (rbtxn_is_subtxn(txn)) + topxid = rbtxn_get_toptxn(txn)->xid; else topxid = xid; @@ -902,9 +909,10 @@ pgoutput_row_filter_init(PGOutputData *data, List *publications, * (even if other publications have a row filter). */ if (!pub->alltables && - !SearchSysCacheExists2(PUBLICATIONNAMESPACEMAP, + !SearchSysCacheExists3(PUBLICATIONNAMESPACEMAP, ObjectIdGetDatum(schemaid), - ObjectIdGetDatum(pub->oid))) + ObjectIdGetDatum(pub->oid), + PUB_OBJTYPE_TABLE)) { /* * Check for the presence of a row filter in this publication. @@ -1518,7 +1526,7 @@ pgoutput_change(LogicalDecodingContext *ctx, ReorderBufferTXN *txn, * Schema should be sent using the original relation because it also sends * the ancestor's relation. */ - maybe_send_schema(ctx, change, relation, relentry); + maybe_send_schema(ctx, txn, relation, relentry); OutputPluginPrepareWrite(ctx, true); @@ -1603,7 +1611,7 @@ pgoutput_truncate(LogicalDecodingContext *ctx, ReorderBufferTXN *txn, if (txndata && !txndata->sent_begin_txn) pgoutput_send_begin(ctx, txn); - maybe_send_schema(ctx, change, relation, relentry); + maybe_send_schema(ctx, change->txn, relation, relentry); } if (nrelids > 0) @@ -1663,6 +1671,70 @@ pgoutput_message(LogicalDecodingContext *ctx, ReorderBufferTXN *txn, OutputPluginWrite(ctx, true); } +static void +pgoutput_sequence(LogicalDecodingContext *ctx, + ReorderBufferTXN *txn, XLogRecPtr sequence_lsn, + Relation relation, bool transactional, + int64 value) +{ + PGOutputData *data = (PGOutputData *) ctx->output_plugin_private; + TransactionId xid = InvalidTransactionId; + RelationSyncEntry *relentry; + + if (!is_publishable_relation(relation)) + return; + + /* + * If the negotiated protocol version does not support sequences, yet we + * found a sequence in the publication, error out. + */ + if (data->protocol_version < LOGICALREP_PROTO_SEQUENCES_VERSION_NUM) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("protocol version does not support sequence replication"))); + + /* + * Remember the xid for the message in streaming mode. See + * pgoutput_change. + */ + if (data->in_streaming) + xid = txn->xid; + + relentry = get_rel_sync_entry(data, relation); + + /* + * First check the sequence filter. + * + * We handle just REORDER_BUFFER_CHANGE_SEQUENCE here. + */ + if (!relentry->pubactions.pubsequence) + return; + + /* + * Output BEGIN if we haven't yet. Avoid for non-transactional sequence + * changes. + */ + if (transactional) + { + PGOutputTxnData *txndata = (PGOutputTxnData *) txn->output_plugin_private; + + /* Send BEGIN if we haven't yet */ + if (txndata && !txndata->sent_begin_txn) + pgoutput_send_begin(ctx, txn); + } + + maybe_send_schema(ctx, txn, relation, relentry); + + OutputPluginPrepareWrite(ctx, true); + logicalrep_write_sequence(ctx->out, + relation, + xid, + sequence_lsn, + transactional, + value); + OutputPluginWrite(ctx, true); +} + /* * Return true if the data is associated with an origin and the user has * requested the changes that don't have an origin, false otherwise. @@ -1978,7 +2050,8 @@ get_rel_sync_entry(PGOutputData *data, Relation relation) entry->schema_sent = false; entry->streamed_txns = NIL; entry->pubactions.pubinsert = entry->pubactions.pubupdate = - entry->pubactions.pubdelete = entry->pubactions.pubtruncate = false; + entry->pubactions.pubdelete = entry->pubactions.pubtruncate = + entry->pubactions.pubsequence = false; entry->new_slot = NULL; entry->old_slot = NULL; memset(entry->exprstate, 0, sizeof(entry->exprstate)); @@ -1993,18 +2066,19 @@ get_rel_sync_entry(PGOutputData *data, Relation relation) { Oid schemaId = get_rel_namespace(relid); List *pubids = GetRelationPublications(relid); + char relkind = get_rel_relkind(relid); + char objectType = pub_get_object_type_for_relkind(relkind); /* * We don't acquire a lock on the namespace system table as we build * the cache entry using a historic snapshot and all the later changes * are absorbed while decoding WAL. */ - List *schemaPubids = GetSchemaPublications(schemaId); + List *schemaPubids = GetSchemaPublications(schemaId, objectType); ListCell *lc; Oid publish_as_relid = relid; int publish_ancestor_level = 0; bool am_partition = get_rel_relispartition(relid); - char relkind = get_rel_relkind(relid); List *rel_publications = NIL; /* Reload publications if needed before use. */ @@ -2036,6 +2110,7 @@ get_rel_sync_entry(PGOutputData *data, Relation relation) entry->pubactions.pubupdate = false; entry->pubactions.pubdelete = false; entry->pubactions.pubtruncate = false; + entry->pubactions.pubsequence = false; /* * Tuple slots cleanups. (Will be rebuilt later if needed). @@ -2083,9 +2158,11 @@ get_rel_sync_entry(PGOutputData *data, Relation relation) /* * If this is a FOR ALL TABLES publication, pick the partition - * root and set the ancestor level accordingly. + * root and set the ancestor level accordingly. If this is a FOR + * ALL SEQUENCES publication, we publish it too but we don't need + * to pick the partition root etc. */ - if (pub->alltables) + if (pub->alltables && (objectType == PUB_OBJTYPE_TABLE)) { publish = true; if (pub->pubviaroot && am_partition) @@ -2096,6 +2173,10 @@ get_rel_sync_entry(PGOutputData *data, Relation relation) ancestor_level = list_length(ancestors); } } + else if (pub->allsequences && (objectType == PUB_OBJTYPE_SEQUENCE)) + { + publish = true; + } if (!publish) { @@ -2149,6 +2230,7 @@ get_rel_sync_entry(PGOutputData *data, Relation relation) entry->pubactions.pubupdate |= pub->pubactions.pubupdate; entry->pubactions.pubdelete |= pub->pubactions.pubdelete; entry->pubactions.pubtruncate |= pub->pubactions.pubtruncate; + entry->pubactions.pubsequence |= pub->pubactions.pubsequence; /* * We want to publish the changes as the top-most ancestor diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c index b3faccbefe5..a797756b262 100644 --- a/src/backend/utils/cache/relcache.c +++ b/src/backend/utils/cache/relcache.c @@ -57,6 +57,7 @@ #include "catalog/pg_opclass.h" #include "catalog/pg_proc.h" #include "catalog/pg_publication.h" +#include "catalog/pg_publication_namespace.h" #include "catalog/pg_rewrite.h" #include "catalog/pg_shseclabel.h" #include "catalog/pg_statistic_ext.h" @@ -5715,6 +5716,8 @@ RelationBuildPublicationDesc(Relation relation, PublicationDesc *pubdesc) Oid schemaid; List *ancestors = NIL; Oid relid = RelationGetRelid(relation); + char relkind = relation->rd_rel->relkind; + char objType; /* * If not publishable, it publishes no actions. (pgoutput_change() will @@ -5745,8 +5748,15 @@ RelationBuildPublicationDesc(Relation relation, PublicationDesc *pubdesc) /* Fetch the publication membership info. */ puboids = GetRelationPublications(relid); schemaid = RelationGetNamespace(relation); - puboids = list_concat_unique_oid(puboids, GetSchemaPublications(schemaid)); + objType = pub_get_object_type_for_relkind(relkind); + puboids = list_concat_unique_oid(puboids, + GetSchemaPublications(schemaid, objType)); + + /* + * If this is a partion (and thus a table), lookup all ancestors and track + * all publications them too. + */ if (relation->rd_rel->relispartition) { /* Add publications that the ancestors are in too. */ @@ -5758,12 +5768,23 @@ RelationBuildPublicationDesc(Relation relation, PublicationDesc *pubdesc) puboids = list_concat_unique_oid(puboids, GetRelationPublications(ancestor)); + + /* include all publications publishing schema of all ancestors */ schemaid = get_rel_namespace(ancestor); puboids = list_concat_unique_oid(puboids, - GetSchemaPublications(schemaid)); + GetSchemaPublications(schemaid, + PUB_OBJTYPE_TABLE)); } } - puboids = list_concat_unique_oid(puboids, GetAllTablesPublications()); + + /* + * Consider also FOR ALL TABLES and FOR ALL SEQUENCES publications, + * depending on the relkind of the relation. + */ + if (relation->rd_rel->relkind == RELKIND_SEQUENCE) + puboids = list_concat_unique_oid(puboids, GetAllSequencesPublications()); + else + puboids = list_concat_unique_oid(puboids, GetAllTablesPublications()); foreach(lc, puboids) { @@ -5782,6 +5803,7 @@ RelationBuildPublicationDesc(Relation relation, PublicationDesc *pubdesc) pubdesc->pubactions.pubupdate |= pubform->pubupdate; pubdesc->pubactions.pubdelete |= pubform->pubdelete; pubdesc->pubactions.pubtruncate |= pubform->pubtruncate; + pubdesc->pubactions.pubsequence |= pubform->pubsequence; /* * Check if all columns referenced in the filter expression are part diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c index 8dbda0024f9..a080d588db2 100644 --- a/src/backend/utils/cache/syscache.c +++ b/src/backend/utils/cache/syscache.c @@ -450,9 +450,10 @@ static const struct cachedesc cacheinfo[] = { }, [PUBLICATIONNAMESPACEMAP] = { PublicationNamespaceRelationId, - PublicationNamespacePnnspidPnpubidIndexId, + PublicationNamespacePnnspidPnpubidPntypeIndexId, KEY(Anum_pg_publication_namespace_pnnspid, - Anum_pg_publication_namespace_pnpubid), + Anum_pg_publication_namespace_pnpubid, + Anum_pg_publication_namespace_pntype), 64 }, [PUBLICATIONOID] = { diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index 34fd0a86e9c..bbe15c74746 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -4058,10 +4058,12 @@ getPublications(Archive *fout, int *numPublications) int i_pubname; int i_pubowner; int i_puballtables; + int i_puballsequences; int i_pubinsert; int i_pubupdate; int i_pubdelete; int i_pubtruncate; + int i_pubsequence; int i_pubviaroot; int i, ntups; @@ -4077,23 +4079,29 @@ getPublications(Archive *fout, int *numPublications) resetPQExpBuffer(query); /* Get the publications. */ - if (fout->remoteVersion >= 130000) + if (fout->remoteVersion >= 170000) + appendPQExpBufferStr(query, + "SELECT p.tableoid, p.oid, p.pubname, " + "p.pubowner, " + "p.puballtables, p.puballsequences, p.pubinsert, p.pubupdate, p.pubdelete, p.pubtruncate, p.pubsequence, p.pubviaroot " + "FROM pg_publication p"); + else if (fout->remoteVersion >= 130000) appendPQExpBufferStr(query, "SELECT p.tableoid, p.oid, p.pubname, " "p.pubowner, " - "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, p.pubtruncate, p.pubviaroot " + "p.puballtables, false as p.puballsequences, p.pubinsert, p.pubupdate, p.pubdelete, p.pubtruncate, false as p.pubsequence, p.pubviaroot " "FROM pg_publication p"); else if (fout->remoteVersion >= 110000) appendPQExpBufferStr(query, "SELECT p.tableoid, p.oid, p.pubname, " "p.pubowner, " - "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, p.pubtruncate, false AS pubviaroot " + "p.puballtables, false as p.puballsequences, p.pubinsert, p.pubupdate, p.pubdelete, p.pubtruncate, false AS pubsequence, false AS pubviaroot " "FROM pg_publication p"); else appendPQExpBufferStr(query, "SELECT p.tableoid, p.oid, p.pubname, " "p.pubowner, " - "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, false AS pubtruncate, false AS pubviaroot " + "p.puballtables, false as p.puballsequences, p.pubinsert, p.pubupdate, p.pubdelete, false AS pubtruncate, false AS pubsequence, false AS pubviaroot " "FROM pg_publication p"); res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK); @@ -4105,10 +4113,12 @@ getPublications(Archive *fout, int *numPublications) i_pubname = PQfnumber(res, "pubname"); i_pubowner = PQfnumber(res, "pubowner"); i_puballtables = PQfnumber(res, "puballtables"); + i_puballsequences = PQfnumber(res, "puballsequences"); i_pubinsert = PQfnumber(res, "pubinsert"); i_pubupdate = PQfnumber(res, "pubupdate"); i_pubdelete = PQfnumber(res, "pubdelete"); i_pubtruncate = PQfnumber(res, "pubtruncate"); + i_pubsequence = PQfnumber(res, "pubsequence"); i_pubviaroot = PQfnumber(res, "pubviaroot"); pubinfo = pg_malloc(ntups * sizeof(PublicationInfo)); @@ -4124,6 +4134,8 @@ getPublications(Archive *fout, int *numPublications) pubinfo[i].rolname = getRoleName(PQgetvalue(res, i, i_pubowner)); pubinfo[i].puballtables = (strcmp(PQgetvalue(res, i, i_puballtables), "t") == 0); + pubinfo[i].puballsequences = + (strcmp(PQgetvalue(res, i, i_puballsequences), "t") == 0); pubinfo[i].pubinsert = (strcmp(PQgetvalue(res, i, i_pubinsert), "t") == 0); pubinfo[i].pubupdate = @@ -4132,6 +4144,8 @@ getPublications(Archive *fout, int *numPublications) (strcmp(PQgetvalue(res, i, i_pubdelete), "t") == 0); pubinfo[i].pubtruncate = (strcmp(PQgetvalue(res, i, i_pubtruncate), "t") == 0); + pubinfo[i].pubsequence = + (strcmp(PQgetvalue(res, i, i_pubsequence), "t") == 0); pubinfo[i].pubviaroot = (strcmp(PQgetvalue(res, i, i_pubviaroot), "t") == 0); @@ -4177,6 +4191,9 @@ dumpPublication(Archive *fout, const PublicationInfo *pubinfo) if (pubinfo->puballtables) appendPQExpBufferStr(query, " FOR ALL TABLES"); + if (pubinfo->puballsequences) + appendPQExpBufferStr(query, " FOR ALL SEQUENCES"); + appendPQExpBufferStr(query, " WITH (publish = '"); if (pubinfo->pubinsert) { @@ -4211,6 +4228,15 @@ dumpPublication(Archive *fout, const PublicationInfo *pubinfo) first = false; } + if (pubinfo->pubsequence) + { + if (!first) + appendPQExpBufferStr(query, ", "); + + appendPQExpBufferStr(query, "sequence"); + first = false; + } + appendPQExpBufferChar(query, '\''); if (pubinfo->pubviaroot) @@ -4257,6 +4283,7 @@ getPublicationNamespaces(Archive *fout) int i_oid; int i_pnpubid; int i_pnnspid; + int i_pntype; int i, j, ntups; @@ -4268,7 +4295,7 @@ getPublicationNamespaces(Archive *fout) /* Collect all publication membership info. */ appendPQExpBufferStr(query, - "SELECT tableoid, oid, pnpubid, pnnspid " + "SELECT tableoid, oid, pnpubid, pnnspid, pntype " "FROM pg_catalog.pg_publication_namespace"); res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK); @@ -4278,6 +4305,7 @@ getPublicationNamespaces(Archive *fout) i_oid = PQfnumber(res, "oid"); i_pnpubid = PQfnumber(res, "pnpubid"); i_pnnspid = PQfnumber(res, "pnnspid"); + i_pntype = PQfnumber(res, "pntype"); /* this allocation may be more than we need */ pubsinfo = pg_malloc(ntups * sizeof(PublicationSchemaInfo)); @@ -4287,6 +4315,7 @@ getPublicationNamespaces(Archive *fout) { Oid pnpubid = atooid(PQgetvalue(res, i, i_pnpubid)); Oid pnnspid = atooid(PQgetvalue(res, i, i_pnnspid)); + char pntype = PQgetvalue(res, i, i_pntype)[0]; PublicationInfo *pubinfo; NamespaceInfo *nspinfo; @@ -4318,6 +4347,7 @@ getPublicationNamespaces(Archive *fout) pubsinfo[j].dobj.name = nspinfo->dobj.name; pubsinfo[j].publication = pubinfo; pubsinfo[j].pubschema = nspinfo; + pubsinfo[j].pubtype = pntype; /* Decide whether we want to dump it */ selectDumpablePublicationObject(&(pubsinfo[j].dobj), fout); @@ -4483,7 +4513,11 @@ dumpPublicationNamespace(Archive *fout, const PublicationSchemaInfo *pubsinfo) query = createPQExpBuffer(); appendPQExpBuffer(query, "ALTER PUBLICATION %s ", fmtId(pubinfo->dobj.name)); - appendPQExpBuffer(query, "ADD TABLES IN SCHEMA %s;\n", fmtId(schemainfo->dobj.name)); + + if (pubsinfo->pubtype == 't') + appendPQExpBuffer(query, "ADD TABLES IN SCHEMA %s;\n", fmtId(schemainfo->dobj.name)); + else + appendPQExpBuffer(query, "ADD SEQUENCES IN SCHEMA %s;\n", fmtId(schemainfo->dobj.name)); /* * There is no point in creating drop query as the drop is done by schema @@ -4516,6 +4550,7 @@ dumpPublicationTable(Archive *fout, const PublicationRelInfo *pubrinfo) TableInfo *tbinfo = pubrinfo->pubtable; PQExpBuffer query; char *tag; + char *description; /* Do nothing in data-only dump */ if (dopt->dataOnly) @@ -4525,8 +4560,19 @@ dumpPublicationTable(Archive *fout, const PublicationRelInfo *pubrinfo) query = createPQExpBuffer(); - appendPQExpBuffer(query, "ALTER PUBLICATION %s ADD TABLE ONLY", - fmtId(pubinfo->dobj.name)); + if (tbinfo->relkind == RELKIND_SEQUENCE) + { + appendPQExpBuffer(query, "ALTER PUBLICATION %s ADD SEQUENCE", + fmtId(pubinfo->dobj.name)); + description = "PUBLICATION SEQUENCE"; + } + else + { + appendPQExpBuffer(query, "ALTER PUBLICATION %s ADD TABLE ONLY", + fmtId(pubinfo->dobj.name)); + description = "PUBLICATION TABLE"; + } + appendPQExpBuffer(query, " %s", fmtQualifiedDumpable(tbinfo)); @@ -4556,7 +4602,7 @@ dumpPublicationTable(Archive *fout, const PublicationRelInfo *pubrinfo) ARCHIVE_OPTS(.tag = tag, .namespace = tbinfo->dobj.namespace->dobj.name, .owner = pubinfo->rolname, - .description = "PUBLICATION TABLE", + .description = description, .section = SECTION_POST_DATA, .createStmt = query->data)); diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h index 2fe3cbed9ac..f6593b0c499 100644 --- a/src/bin/pg_dump/pg_dump.h +++ b/src/bin/pg_dump/pg_dump.h @@ -622,10 +622,12 @@ typedef struct _PublicationInfo DumpableObject dobj; const char *rolname; bool puballtables; + bool puballsequences; bool pubinsert; bool pubupdate; bool pubdelete; bool pubtruncate; + bool pubsequence; bool pubviaroot; } PublicationInfo; @@ -651,6 +653,7 @@ typedef struct _PublicationSchemaInfo DumpableObject dobj; PublicationInfo *publication; NamespaceInfo *pubschema; + char pubtype; } PublicationSchemaInfo; /* diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl index eb3ec534b4d..1757dec8c65 100644 --- a/src/bin/pg_dump/t/002_pg_dump.pl +++ b/src/bin/pg_dump/t/002_pg_dump.pl @@ -2927,7 +2927,7 @@ my %tests = ( create_order => 50, create_sql => 'CREATE PUBLICATION pub1;', regexp => qr/^ - \QCREATE PUBLICATION pub1 WITH (publish = 'insert, update, delete, truncate');\E + \QCREATE PUBLICATION pub1 WITH (publish = 'insert, update, delete, truncate, sequence');\E /xm, like => { %full_runs, section_post_data => 1, }, }, @@ -2947,7 +2947,7 @@ my %tests = ( create_order => 50, create_sql => 'CREATE PUBLICATION pub3;', regexp => qr/^ - \QCREATE PUBLICATION pub3 WITH (publish = 'insert, update, delete, truncate');\E + \QCREATE PUBLICATION pub3 WITH (publish = 'insert, update, delete, truncate, sequence');\E /xm, like => { %full_runs, section_post_data => 1, }, }, @@ -2956,7 +2956,27 @@ my %tests = ( create_order => 50, create_sql => 'CREATE PUBLICATION pub4;', regexp => qr/^ - \QCREATE PUBLICATION pub4 WITH (publish = 'insert, update, delete, truncate');\E + \QCREATE PUBLICATION pub4 WITH (publish = 'insert, update, delete, truncate, sequence');\E + /xm, + like => { %full_runs, section_post_data => 1, }, + }, + + 'CREATE PUBLICATION pub5' => { + create_order => 50, + create_sql => 'CREATE PUBLICATION pub5 + FOR ALL SEQUENCES + WITH (publish = \'\');', + regexp => qr/^ + \QCREATE PUBLICATION pub5 FOR ALL SEQUENCES WITH (publish = '');\E + /xm, + like => { %full_runs, section_post_data => 1, }, + }, + + 'CREATE PUBLICATION pub6' => { + create_order => 50, + create_sql => 'CREATE PUBLICATION pub6;', + regexp => qr/^ + \QCREATE PUBLICATION pub6 WITH (publish = 'insert, update, delete, truncate, sequence');\E /xm, like => { %full_runs, section_post_data => 1, }, }, @@ -3102,6 +3122,27 @@ my %tests = ( unlike => { exclude_dump_test_schema => 1, }, }, + 'ALTER PUBLICATION pub3 ADD SEQUENCES IN SCHEMA dump_test' => { + create_order => 51, + create_sql => + 'ALTER PUBLICATION pub3 ADD SEQUENCES IN SCHEMA dump_test;', + regexp => qr/^ + \QALTER PUBLICATION pub3 ADD SEQUENCES IN SCHEMA dump_test;\E + /xm, + like => { %full_runs, section_post_data => 1, }, + unlike => { exclude_dump_test_schema => 1, }, + }, + + 'ALTER PUBLICATION pub3 ADD SEQUENCES IN SCHEMA public' => { + create_order => 52, + create_sql => + 'ALTER PUBLICATION pub3 ADD SEQUENCES IN SCHEMA public;', + regexp => qr/^ + \QALTER PUBLICATION pub3 ADD SEQUENCES IN SCHEMA public;\E + /xm, + like => { %full_runs, section_post_data => 1, }, + }, + 'CREATE SCHEMA public' => { regexp => qr/^CREATE SCHEMA public;/m, diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c index 5077e7b3581..e0dc4dcfccd 100644 --- a/src/bin/psql/describe.c +++ b/src/bin/psql/describe.c @@ -1711,28 +1711,19 @@ describeOneTableDetails(const char *schemaname, if (tableinfo.relkind == RELKIND_SEQUENCE) { PGresult *result = NULL; - printQueryOpt myopt = pset.popt; - char *footers[2] = {NULL, NULL}; if (pset.sversion >= 100000) { printfPQExpBuffer(&buf, - "SELECT pg_catalog.format_type(seqtypid, NULL) AS \"%s\",\n" - " seqstart AS \"%s\",\n" - " seqmin AS \"%s\",\n" - " seqmax AS \"%s\",\n" - " seqincrement AS \"%s\",\n" - " CASE WHEN seqcycle THEN '%s' ELSE '%s' END AS \"%s\",\n" - " seqcache AS \"%s\"\n", - gettext_noop("Type"), - gettext_noop("Start"), - gettext_noop("Minimum"), - gettext_noop("Maximum"), - gettext_noop("Increment"), + "SELECT pg_catalog.format_type(seqtypid, NULL),\n" + " seqstart,\n" + " seqmin,\n" + " seqmax,\n" + " seqincrement,\n" + " CASE WHEN seqcycle THEN '%s' ELSE '%s' END,\n" + " seqcache\n", gettext_noop("yes"), - gettext_noop("no"), - gettext_noop("Cycles?"), - gettext_noop("Cache")); + gettext_noop("no")); appendPQExpBuffer(&buf, "FROM pg_catalog.pg_sequence\n" "WHERE seqrelid = '%s';", @@ -1741,22 +1732,15 @@ describeOneTableDetails(const char *schemaname, else { printfPQExpBuffer(&buf, - "SELECT 'bigint' AS \"%s\",\n" - " start_value AS \"%s\",\n" - " min_value AS \"%s\",\n" - " max_value AS \"%s\",\n" - " increment_by AS \"%s\",\n" - " CASE WHEN is_cycled THEN '%s' ELSE '%s' END AS \"%s\",\n" - " cache_value AS \"%s\"\n", - gettext_noop("Type"), - gettext_noop("Start"), - gettext_noop("Minimum"), - gettext_noop("Maximum"), - gettext_noop("Increment"), + "SELECT 'bigint',\n" + " start_value,\n" + " min_value,\n" + " max_value,\n" + " increment_by,\n" + " CASE WHEN is_cycled THEN '%s' ELSE '%s' END,\n" + " cache_value\n", gettext_noop("yes"), - gettext_noop("no"), - gettext_noop("Cycles?"), - gettext_noop("Cache")); + gettext_noop("no")); appendPQExpBuffer(&buf, "FROM %s", fmtId(schemaname)); /* must be separate because fmtId isn't reentrant */ appendPQExpBuffer(&buf, ".%s;", fmtId(relationname)); @@ -1766,6 +1750,59 @@ describeOneTableDetails(const char *schemaname, if (!res) goto error_return; + numrows = PQntuples(res); + + /* + * XXX reset to use expanded output for sequences (maybe we should + * keep this disabled, just like for tables?) + */ + myopt.expanded = pset.popt.topt.expanded; + + printTableInit(&cont, &myopt, title.data, 7, numrows); + printTableInitialized = true; + + if (tableinfo.relpersistence == 'u') + printfPQExpBuffer(&title, _("Unlogged sequence \"%s.%s\""), + schemaname, relationname); + else + printfPQExpBuffer(&title, _("Sequence \"%s.%s\""), + schemaname, relationname); + + printTableAddHeader(&cont, gettext_noop("Type"), true, 'l'); + printTableAddHeader(&cont, gettext_noop("Start"), true, 'r'); + printTableAddHeader(&cont, gettext_noop("Minimum"), true, 'r'); + printTableAddHeader(&cont, gettext_noop("Maximum"), true, 'r'); + printTableAddHeader(&cont, gettext_noop("Increment"), true, 'r'); + printTableAddHeader(&cont, gettext_noop("Cycles?"), true, 'l'); + printTableAddHeader(&cont, gettext_noop("Cache"), true, 'r'); + + /* Generate table cells to be printed */ + for (i = 0; i < numrows; i++) + { + /* Type */ + printTableAddCell(&cont, PQgetvalue(res, i, 0), false, false); + + /* Start */ + printTableAddCell(&cont, PQgetvalue(res, i, 1), false, false); + + /* Minimum */ + printTableAddCell(&cont, PQgetvalue(res, i, 2), false, false); + + /* Maximum */ + printTableAddCell(&cont, PQgetvalue(res, i, 3), false, false); + + /* Increment */ + printTableAddCell(&cont, PQgetvalue(res, i, 4), false, false); + + /* Cycles? */ + printTableAddCell(&cont, PQgetvalue(res, i, 5), false, false); + + /* Cache */ + printTableAddCell(&cont, PQgetvalue(res, i, 6), false, false); + } + + /* Footer information about a sequence */ + /* Get the column that owns this sequence */ printfPQExpBuffer(&buf, "SELECT pg_catalog.quote_ident(nspname) || '.' ||" "\n pg_catalog.quote_ident(relname) || '.' ||" @@ -1797,32 +1834,63 @@ describeOneTableDetails(const char *schemaname, switch (PQgetvalue(result, 0, 1)[0]) { case 'a': - footers[0] = psprintf(_("Owned by: %s"), - PQgetvalue(result, 0, 0)); + printTableAddFooter(&cont, + psprintf(_("Owned by: %s"), + PQgetvalue(result, 0, 0))); break; case 'i': - footers[0] = psprintf(_("Sequence for identity column: %s"), - PQgetvalue(result, 0, 0)); + printTableAddFooter(&cont, + psprintf(_("Sequence for identity column: %s"), + PQgetvalue(result, 0, 0))); break; } } PQclear(result); - if (tableinfo.relpersistence == 'u') - printfPQExpBuffer(&title, _("Unlogged sequence \"%s.%s\""), - schemaname, relationname); - else - printfPQExpBuffer(&title, _("Sequence \"%s.%s\""), - schemaname, relationname); + /* print any publications */ + if (pset.sversion >= 170000) + { + int tuples = 0; - myopt.footers = footers; - myopt.topt.default_footer = false; - myopt.title = title.data; - myopt.translate_header = true; + printfPQExpBuffer(&buf, + "SELECT pubname\n" + "FROM pg_catalog.pg_publication p\n" + " JOIN pg_catalog.pg_publication_namespace pn ON p.oid = pn.pnpubid\n" + " JOIN pg_catalog.pg_class pc ON pc.relnamespace = pn.pnnspid\n" + "WHERE pc.oid ='%s' and pn.pntype = 's' and pg_catalog.pg_relation_is_publishable('%s')\n" + "UNION\n" + "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'\n" + "UNION\n" + "SELECT pubname\n" + "FROM pg_catalog.pg_publication p\n" + "WHERE p.puballsequences AND pg_catalog.pg_relation_is_publishable('%s')\n" + "ORDER BY 1;", + oid, oid, oid, oid); - printQuery(res, &myopt, pset.queryFout, false, pset.logfile); + result = PSQLexec(buf.data); + if (!result) + goto error_return; + else + tuples = PQntuples(result); + + if (tuples > 0) + printTableAddFooter(&cont, _("Publications:")); + + /* Might be an empty set - that's ok */ + for (i = 0; i < tuples; i++) + { + printfPQExpBuffer(&buf, " \"%s\"", + PQgetvalue(result, i, 0)); + + printTableAddFooter(&cont, buf.data); + } + PQclear(result); + } - free(footers[0]); + printTable(&cont, pset.queryFout, false, pset.logfile); retval = true; goto error_return; /* not an error, just return early */ @@ -2049,6 +2117,11 @@ describeOneTableDetails(const char *schemaname, for (i = 0; i < cols; i++) printTableAddHeader(&cont, headers[i], true, 'l'); + res = PSQLexec(buf.data); + if (!res) + goto error_return; + numrows = PQntuples(res); + /* Generate table cells to be printed */ for (i = 0; i < numrows; i++) { @@ -2974,7 +3047,7 @@ describeOneTableDetails(const char *schemaname, "FROM pg_catalog.pg_publication p\n" " JOIN pg_catalog.pg_publication_namespace pn ON p.oid = pn.pnpubid\n" " JOIN pg_catalog.pg_class pc ON pc.relnamespace = pn.pnnspid\n" - "WHERE pc.oid ='%s' and pg_catalog.pg_relation_is_publishable('%s')\n" + "WHERE pc.oid ='%s' and pn.pntype = 't' and pg_catalog.pg_relation_is_publishable('%s')\n" "UNION\n" "SELECT pubname\n" " , pg_get_expr(pr.prqual, c.oid)\n" @@ -5111,7 +5184,7 @@ listSchemas(const char *pattern, bool verbose, bool showSystem) int i; printfPQExpBuffer(&buf, - "SELECT pubname \n" + "SELECT pubname, (CASE WHEN pntype = 't' THEN 'tables' ELSE 'sequences' END) AS pubtype\n" "FROM pg_catalog.pg_publication p\n" " JOIN pg_catalog.pg_publication_namespace pn ON p.oid = pn.pnpubid\n" " JOIN pg_catalog.pg_namespace n ON n.oid = pn.pnnspid \n" @@ -5137,8 +5210,9 @@ listSchemas(const char *pattern, bool verbose, bool showSystem) /* Might be an empty set - that's ok */ for (i = 0; i < pub_schema_tuples; i++) { - printfPQExpBuffer(&buf, " \"%s\"", - PQgetvalue(result, i, 0)); + printfPQExpBuffer(&buf, " \"%s\" (%s)", + PQgetvalue(result, i, 0), + PQgetvalue(result, i, 1)); footers[i + 1] = pg_strdup(buf.data); } @@ -6254,7 +6328,7 @@ listPublications(const char *pattern) PQExpBufferData buf; PGresult *res; printQueryOpt myopt = pset.popt; - static const bool translate_columns[] = {false, false, false, false, false, false, false, false}; + static const bool translate_columns[] = {false, false, false, false, false, false, false, false, false, false}; if (pset.sversion < 100000) { @@ -6268,23 +6342,45 @@ listPublications(const char *pattern) initPQExpBuffer(&buf); - printfPQExpBuffer(&buf, - "SELECT pubname AS \"%s\",\n" - " pg_catalog.pg_get_userbyid(pubowner) AS \"%s\",\n" - " puballtables AS \"%s\",\n" - " pubinsert AS \"%s\",\n" - " pubupdate AS \"%s\",\n" - " pubdelete AS \"%s\"", - gettext_noop("Name"), - gettext_noop("Owner"), - gettext_noop("All tables"), - gettext_noop("Inserts"), - gettext_noop("Updates"), - gettext_noop("Deletes")); + if (pset.sversion >= 170000) + printfPQExpBuffer(&buf, + "SELECT pubname AS \"%s\",\n" + " pg_catalog.pg_get_userbyid(pubowner) AS \"%s\",\n" + " puballtables AS \"%s\",\n" + " puballsequences AS \"%s\",\n" + " pubinsert AS \"%s\",\n" + " pubupdate AS \"%s\",\n" + " pubdelete AS \"%s\"", + gettext_noop("Name"), + gettext_noop("Owner"), + gettext_noop("All tables"), + gettext_noop("All sequences"), + gettext_noop("Inserts"), + gettext_noop("Updates"), + gettext_noop("Deletes")); + else + printfPQExpBuffer(&buf, + "SELECT pubname AS \"%s\",\n" + " pg_catalog.pg_get_userbyid(pubowner) AS \"%s\",\n" + " puballtables AS \"%s\",\n" + " pubinsert AS \"%s\",\n" + " pubupdate AS \"%s\",\n" + " pubdelete AS \"%s\"", + gettext_noop("Name"), + gettext_noop("Owner"), + gettext_noop("All tables"), + gettext_noop("Inserts"), + gettext_noop("Updates"), + gettext_noop("Deletes")); + if (pset.sversion >= 110000) appendPQExpBuffer(&buf, ",\n pubtruncate AS \"%s\"", gettext_noop("Truncates")); + if (pset.sversion >= 170000) + appendPQExpBuffer(&buf, + ",\n pubsequence AS \"%s\"", + gettext_noop("Sequences")); if (pset.sversion >= 130000) appendPQExpBuffer(&buf, ",\n pubviaroot AS \"%s\"", @@ -6378,6 +6474,7 @@ describePublications(const char *pattern) PGresult *res; bool has_pubtruncate; bool has_pubviaroot; + bool has_pubsequence; PQExpBufferData title; printTableContent cont; @@ -6394,6 +6491,7 @@ describePublications(const char *pattern) has_pubtruncate = (pset.sversion >= 110000); has_pubviaroot = (pset.sversion >= 130000); + has_pubsequence = (pset.sversion >= 170000); initPQExpBuffer(&buf); @@ -6407,6 +6505,10 @@ describePublications(const char *pattern) if (has_pubviaroot) appendPQExpBufferStr(&buf, ", pubviaroot"); + if (has_pubsequence) + appendPQExpBufferStr(&buf, + ", puballsequences, pubsequence"); + appendPQExpBufferStr(&buf, "\nFROM pg_catalog.pg_publication\n"); @@ -6452,6 +6554,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, 9), "t") == 0; printTableOpt myopt = pset.popt.topt; if (has_pubtruncate) @@ -6459,29 +6562,43 @@ describePublications(const char *pattern) if (has_pubviaroot) ncols++; + /* sequences have two extra columns (puballsequences, pubsequences) */ + if (has_pubsequence) + ncols += 2; + initPQExpBuffer(&title); printfPQExpBuffer(&title, _("Publication %s"), pubname); printTableInit(&cont, &myopt, title.data, ncols, nrows); printTableAddHeader(&cont, gettext_noop("Owner"), true, align); printTableAddHeader(&cont, gettext_noop("All tables"), true, align); + if (has_pubsequence) + printTableAddHeader(&cont, gettext_noop("All sequences"), true, align); printTableAddHeader(&cont, gettext_noop("Inserts"), true, align); printTableAddHeader(&cont, gettext_noop("Updates"), true, align); printTableAddHeader(&cont, gettext_noop("Deletes"), true, align); if (has_pubtruncate) printTableAddHeader(&cont, gettext_noop("Truncates"), true, align); + if (has_pubsequence) + printTableAddHeader(&cont, gettext_noop("Sequences"), true, align); if (has_pubviaroot) printTableAddHeader(&cont, gettext_noop("Via root"), true, align); - printTableAddCell(&cont, PQgetvalue(res, i, 2), false, false); - printTableAddCell(&cont, PQgetvalue(res, i, 3), false, false); - printTableAddCell(&cont, PQgetvalue(res, i, 4), false, false); - printTableAddCell(&cont, PQgetvalue(res, i, 5), false, false); - printTableAddCell(&cont, PQgetvalue(res, i, 6), false, false); + printTableAddCell(&cont, PQgetvalue(res, i, 2), false, false); /* owner */ + printTableAddCell(&cont, PQgetvalue(res, i, 3), false, false); /* all tables */ + + if (has_pubsequence) + printTableAddCell(&cont, PQgetvalue(res, i, 9), false, false); /* all sequences */ + + printTableAddCell(&cont, PQgetvalue(res, i, 4), false, false); /* insert */ + printTableAddCell(&cont, PQgetvalue(res, i, 5), false, false); /* update */ + printTableAddCell(&cont, PQgetvalue(res, i, 6), false, false); /* delete */ if (has_pubtruncate) - printTableAddCell(&cont, PQgetvalue(res, i, 7), false, false); + printTableAddCell(&cont, PQgetvalue(res, i, 7), false, false); /* truncate */ + if (has_pubsequence) + printTableAddCell(&cont, PQgetvalue(res, i, 10), false, false); /* sequence */ if (has_pubviaroot) - printTableAddCell(&cont, PQgetvalue(res, i, 8), false, false); + printTableAddCell(&cont, PQgetvalue(res, i, 8), false, false); /* via root */ if (!puballtables) { @@ -6512,6 +6629,7 @@ describePublications(const char *pattern) "WHERE c.relnamespace = n.oid\n" " AND c.oid = pr.prrelid\n" " AND pr.prpubid = '%s'\n" + " AND c.relkind != 'S'\n" /* exclude sequences */ "ORDER BY 1,2", pubid); if (!addFooterToPublicationDesc(&buf, _("Tables:"), false, &cont)) goto error_return; @@ -6523,7 +6641,7 @@ describePublications(const char *pattern) "SELECT n.nspname\n" "FROM pg_catalog.pg_namespace n\n" " JOIN pg_catalog.pg_publication_namespace pn ON n.oid = pn.pnnspid\n" - "WHERE pn.pnpubid = '%s'\n" + "WHERE pn.pnpubid = '%s' AND pn.pntype = 't'\n" "ORDER BY 1", pubid); if (!addFooterToPublicationDesc(&buf, _("Tables from schemas:"), true, &cont)) @@ -6531,6 +6649,37 @@ describePublications(const char *pattern) } } + if (!puballsequences) + { + /* Get the sequences for the specified publication */ + printfPQExpBuffer(&buf, + "SELECT n.nspname, c.relname, NULL, NULL\n" + "FROM pg_catalog.pg_class c,\n" + " pg_catalog.pg_namespace n,\n" + " pg_catalog.pg_publication_rel pr\n" + "WHERE c.relnamespace = n.oid\n" + " AND c.oid = pr.prrelid\n" + " AND pr.prpubid = '%s'\n" + " AND c.relkind = 'S'\n" /* only sequences */ + "ORDER BY 1,2", pubid); + if (!addFooterToPublicationDesc(&buf, "Sequences:", false, &cont)) + goto error_return; + + if (pset.sversion >= 150000) + { + /* Get the schemas for the specified publication */ + printfPQExpBuffer(&buf, + "SELECT n.nspname\n" + "FROM pg_catalog.pg_namespace n\n" + " JOIN pg_catalog.pg_publication_namespace pn ON n.oid = pn.pnnspid\n" + "WHERE pn.pnpubid = '%s' AND pn.pntype = 's'\n" + "ORDER BY 1", pubid); + if (!addFooterToPublicationDesc(&buf, "Sequences from schemas:", + true, &cont)) + goto error_return; + } + } + printTable(&cont, pset.queryFout, false, pset.logfile); printTableCleanup(&cont); diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c index 006e10f5d2d..172d3b068d9 100644 --- a/src/bin/psql/tab-complete.c +++ b/src/bin/psql/tab-complete.c @@ -1883,11 +1883,15 @@ psql_completion(const char *text, int start, int end) COMPLETE_WITH("ADD", "DROP", "OWNER TO", "RENAME TO", "SET"); /* ALTER PUBLICATION ADD */ else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD")) - COMPLETE_WITH("TABLES IN SCHEMA", "TABLE"); + COMPLETE_WITH("TABLES IN SCHEMA", "TABLE", "SEQUENCES IN SCHEMA", "SEQUENCE"); else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD|SET", "TABLE") || (HeadMatches("ALTER", "PUBLICATION", MatchAny, "ADD|SET", "TABLE") && ends_with(prev_wd, ','))) COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables); + else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD|SET", "SEQUENCE") || + (HeadMatches("ALTER", "PUBLICATION", MatchAny, "ADD|SET", "SEQUENCE") && + ends_with(prev_wd, ','))) + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences); /* * "ALTER PUBLICATION SET TABLE WHERE (" - complete with @@ -1907,11 +1911,11 @@ psql_completion(const char *text, int start, int end) COMPLETE_WITH(","); /* ALTER PUBLICATION DROP */ else if (Matches("ALTER", "PUBLICATION", MatchAny, "DROP")) - COMPLETE_WITH("TABLES IN SCHEMA", "TABLE"); + COMPLETE_WITH("TABLES IN SCHEMA", "TABLE", "SEQUENCES IN SCHEMA", "SEQUENCE"); /* ALTER PUBLICATION SET */ else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET")) - COMPLETE_WITH("(", "TABLES IN SCHEMA", "TABLE"); - else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD|DROP|SET", "TABLES", "IN", "SCHEMA")) + COMPLETE_WITH("(", "TABLES IN SCHEMA", "TABLE", "SEQUENCES IN SCHEMA", "SEQUENCE"); + else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD|DROP|SET", "TABLES|SEQUENCES", "IN", "SCHEMA")) COMPLETE_WITH_QUERY_PLUS(Query_for_list_of_schemas " AND nspname NOT LIKE E'pg\\\\_%%'", "CURRENT_SCHEMA"); @@ -3102,24 +3106,31 @@ psql_completion(const char *text, int start, int end) /* CREATE PUBLICATION */ else if (Matches("CREATE", "PUBLICATION", MatchAny)) - COMPLETE_WITH("FOR TABLE", "FOR ALL TABLES", "FOR TABLES IN SCHEMA", "WITH ("); + COMPLETE_WITH("FOR TABLE", "FOR ALL TABLES", "FOR TABLES IN SCHEMA", "FOR SEQUENCE", "FOR ALL SEQUENCES", "FOR SEQUENCES IN SCHEMA", "WITH ("); else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR")) - COMPLETE_WITH("TABLE", "ALL TABLES", "TABLES IN SCHEMA"); + COMPLETE_WITH("TABLE", "ALL TABLES", "TABLES IN SCHEMA", "SEQUENCE", "ALL SEQUENCES", "SEQUENCES IN SCHEMA"); else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL")) - COMPLETE_WITH("TABLES"); - else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES")) + COMPLETE_WITH("TABLES", "SEQUENCES"); + else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES|SEQUENCES")) COMPLETE_WITH("WITH ("); - else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLES")) + else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLES|SEQUENCES")) COMPLETE_WITH("IN SCHEMA"); - else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE", MatchAny) && !ends_with(prev_wd, ',')) + else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE|SEQUENCE", MatchAny) && !ends_with(prev_wd, ',')) COMPLETE_WITH("WHERE (", "WITH ("); /* Complete "CREATE PUBLICATION FOR TABLE" with ", ..." */ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE")) COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables); /* - * "CREATE PUBLICATION FOR TABLE WHERE (" - complete with - * table attributes + * Complete "CREATE PUBLICATION FOR SEQUENCE" with ", + * ..." + */ + else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "SEQUENCE")) + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences); + + /* + * "CREATE PUBLICATION FOR TABLE|SEQUENCE WHERE (" - + * complete with table attributes */ else if (HeadMatches("CREATE", "PUBLICATION", MatchAny) && TailMatches("WHERE")) COMPLETE_WITH("("); @@ -3131,11 +3142,11 @@ psql_completion(const char *text, int start, int end) /* * Complete "CREATE PUBLICATION FOR TABLES IN SCHEMA , ..." */ - else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLES", "IN", "SCHEMA")) + else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLES|SEQUENCES", "IN", "SCHEMA")) COMPLETE_WITH_QUERY_PLUS(Query_for_list_of_schemas " AND nspname NOT LIKE E'pg\\\\_%%'", "CURRENT_SCHEMA"); - else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLES", "IN", "SCHEMA", MatchAny) && (!ends_with(prev_wd, ','))) + else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLES|SEQUENCES", "IN", "SCHEMA", MatchAny) && (!ends_with(prev_wd, ','))) COMPLETE_WITH("WITH ("); /* Complete "CREATE PUBLICATION [...] WITH" */ else if (HeadMatches("CREATE", "PUBLICATION") && TailMatches("WITH", "(")) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fb58dee3bcd..f4ae0bd4fb1 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -3329,6 +3329,14 @@ proname => 'pg_sequence_last_value', provolatile => 'v', proparallel => 'u', prorettype => 'int8', proargtypes => 'regclass', prosrc => 'pg_sequence_last_value' }, +{ oid => '6312', + descr => 'current on-disk sequence state', + proname => 'pg_sequence_state', provolatile => 'v', + prorettype => 'record', proargtypes => 'regclass', + proallargtypes => '{regclass,pg_lsn,int8,int8,bool}', + proargmodes => '{i,o,o,o,o}', + proargnames => '{seq_oid,page_lsn,last_value,log_cnt,is_called}', + prosrc => 'pg_sequence_state' }, { oid => '275', descr => 'return the next oid for a system table', proname => 'pg_nextoid', provolatile => 'v', proparallel => 'u', @@ -11873,6 +11881,11 @@ proargmodes => '{v,o,o,o,o}', proargnames => '{pubname,pubid,relid,attrs,qual}', prosrc => 'pg_get_publication_tables' }, +{ oid => '8000', descr => 'get OIDs of sequences in a publication', + proname => 'pg_get_publication_sequences', prorows => '1000', proretset => 't', + provolatile => 's', prorettype => 'oid', proargtypes => 'text', + proallargtypes => '{text,oid}', proargmodes => '{i,o}', + proargnames => '{pubname,relid}', prosrc => 'pg_get_publication_sequences' }, { oid => '6121', descr => 'returns whether a relation can be part of a publication', proname => 'pg_relation_is_publishable', provolatile => 's', diff --git a/src/include/catalog/pg_publication.h b/src/include/catalog/pg_publication.h index d929c9a1880..a26e4e51ca2 100644 --- a/src/include/catalog/pg_publication.h +++ b/src/include/catalog/pg_publication.h @@ -40,6 +40,12 @@ CATALOG(pg_publication,6104,PublicationRelationId) */ bool puballtables; + /* + * indicates that this is special publication which should encompass all + * sequences in the database (except for the unlogged and temp ones) + */ + bool puballsequences; + /* true if inserts are published */ bool pubinsert; @@ -52,6 +58,9 @@ CATALOG(pg_publication,6104,PublicationRelationId) /* true if truncates are published */ bool pubtruncate; + /* true if sequences are published */ + bool pubsequence; + /* true if partition changes are published using root schema */ bool pubviaroot; } FormData_pg_publication; @@ -72,6 +81,7 @@ typedef struct PublicationActions bool pubupdate; bool pubdelete; bool pubtruncate; + bool pubsequence; } PublicationActions; typedef struct PublicationDesc @@ -99,6 +109,7 @@ typedef struct Publication Oid oid; char *name; bool alltables; + bool allsequences; bool pubviaroot; PublicationActions pubactions; } Publication; @@ -130,14 +141,15 @@ typedef enum PublicationPartOpt PUBLICATION_PART_ALL, } PublicationPartOpt; -extern List *GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt); +extern List *GetPublicationRelations(Oid pubid, char objectType, + PublicationPartOpt pub_partopt); extern List *GetAllTablesPublications(void); extern List *GetAllTablesPublicationRelations(bool pubviaroot); -extern List *GetPublicationSchemas(Oid pubid); -extern List *GetSchemaPublications(Oid schemaid); -extern List *GetSchemaPublicationRelations(Oid schemaid, +extern List *GetPublicationSchemas(Oid pubid, char objectType); +extern List *GetSchemaPublications(Oid schemaid, char objectType); +extern List *GetSchemaPublicationRelations(Oid schemaid, char objectType, PublicationPartOpt pub_partopt); -extern List *GetAllSchemaPublicationRelations(Oid pubid, +extern List *GetAllSchemaPublicationRelations(Oid puboid, char objectType, PublicationPartOpt pub_partopt); extern List *GetPubPartitionOptionRelations(List *result, PublicationPartOpt pub_partopt, @@ -145,11 +157,15 @@ extern List *GetPubPartitionOptionRelations(List *result, extern Oid GetTopMostAncestorInPublication(Oid puboid, List *ancestors, int *ancestor_level); +extern List *GetAllSequencesPublications(void); +extern List *GetAllSequencesPublicationRelations(void); + extern bool is_publishable_relation(Relation rel); extern bool is_schema_publication(Oid pubid); extern ObjectAddress publication_add_relation(Oid pubid, PublicationRelInfo *pri, bool if_not_exists); extern ObjectAddress publication_add_schema(Oid pubid, Oid schemaid, + char objectType, bool if_not_exists); extern Bitmapset *pub_collist_to_bitmapset(Bitmapset *columns, Datum pubcols, diff --git a/src/include/catalog/pg_publication_namespace.h b/src/include/catalog/pg_publication_namespace.h index b361c8ab5e0..f58e670ba7b 100644 --- a/src/include/catalog/pg_publication_namespace.h +++ b/src/include/catalog/pg_publication_namespace.h @@ -32,6 +32,7 @@ CATALOG(pg_publication_namespace,6237,PublicationNamespaceRelationId) Oid oid; /* oid */ Oid pnpubid BKI_LOOKUP(pg_publication); /* Oid of the publication */ Oid pnnspid BKI_LOOKUP(pg_namespace); /* Oid of the schema */ + char pntype; /* object type to include */ } FormData_pg_publication_namespace; /* ---------------- @@ -42,6 +43,13 @@ CATALOG(pg_publication_namespace,6237,PublicationNamespaceRelationId) typedef FormData_pg_publication_namespace *Form_pg_publication_namespace; DECLARE_UNIQUE_INDEX_PKEY(pg_publication_namespace_oid_index, 6238, PublicationNamespaceObjectIndexId, pg_publication_namespace, btree(oid oid_ops)); -DECLARE_UNIQUE_INDEX(pg_publication_namespace_pnnspid_pnpubid_index, 6239, PublicationNamespacePnnspidPnpubidIndexId, pg_publication_namespace, btree(pnnspid oid_ops, pnpubid oid_ops)); +DECLARE_UNIQUE_INDEX(pg_publication_namespace_pnnspid_pnpubid_pntype_index, 8903, PublicationNamespacePnnspidPnpubidPntypeIndexId, pg_publication_namespace, btree(pnnspid oid_ops, pnpubid oid_ops, pntype char_ops)); + +/* object type to include from a schema, maps to relkind */ +#define PUB_OBJTYPE_TABLE 't' /* table (regular or partitioned) */ +#define PUB_OBJTYPE_SEQUENCE 's' /* sequence object */ +#define PUB_OBJTYPE_UNSUPPORTED 'u' /* used for non-replicated types */ + +extern char pub_get_object_type_for_relkind(char relkind); #endif /* PG_PUBLICATION_NAMESPACE_H */ diff --git a/src/include/commands/sequence.h b/src/include/commands/sequence.h index 7db7b3da7bc..fe3c813d993 100644 --- a/src/include/commands/sequence.h +++ b/src/include/commands/sequence.h @@ -60,6 +60,7 @@ extern ObjectAddress AlterSequence(ParseState *pstate, AlterSeqStmt *stmt); extern void SequenceChangePersistence(Oid relid, char newrelpersistence); extern void DeleteSequenceTuple(Oid relid); extern void ResetSequence(Oid seq_relid); +extern void SetSequence(Oid seq_relid, bool transactional, int64 value); extern void ResetSequenceCaches(void); extern void seq_redo(XLogReaderState *record); diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index e494309da8d..356f8724ec9 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -3990,6 +3990,10 @@ typedef enum PublicationObjSpecType PUBLICATIONOBJ_TABLES_IN_SCHEMA, /* All tables in schema */ PUBLICATIONOBJ_TABLES_IN_CUR_SCHEMA, /* All tables in first element of * search_path */ + PUBLICATIONOBJ_SEQUENCE, /* Sequence type */ + PUBLICATIONOBJ_SEQUENCES_IN_SCHEMA, /* Sequences in schema type */ + PUBLICATIONOBJ_SEQUENCES_IN_CUR_SCHEMA, /* Get the first element of + * search_path */ PUBLICATIONOBJ_CONTINUATION, /* Continuation of previous type */ } PublicationObjSpecType; @@ -4008,7 +4012,8 @@ typedef struct CreatePublicationStmt char *pubname; /* Name of the publication */ List *options; /* List of DefElem nodes */ List *pubobjects; /* Optional list of publication objects */ - bool for_all_tables; /* Special publication for all tables in db */ + List *for_all_objects; /* Special publication for all objects in + * db */ } CreatePublicationStmt; typedef enum AlterPublicationAction @@ -4031,7 +4036,8 @@ typedef struct AlterPublicationStmt * objects. */ List *pubobjects; /* Optional list of publication objects */ - bool for_all_tables; /* Special publication for all tables in db */ + List *for_all_objects; /* Special publication for all objects in + * db */ AlterPublicationAction action; /* What action to perform with the given * objects */ } AlterPublicationStmt; diff --git a/src/include/replication/logicalproto.h b/src/include/replication/logicalproto.h index 4bb04c4eac8..f6bbb74d780 100644 --- a/src/include/replication/logicalproto.h +++ b/src/include/replication/logicalproto.h @@ -42,7 +42,8 @@ #define LOGICALREP_PROTO_STREAM_VERSION_NUM 2 #define LOGICALREP_PROTO_TWOPHASE_VERSION_NUM 3 #define LOGICALREP_PROTO_STREAM_PARALLEL_VERSION_NUM 4 -#define LOGICALREP_PROTO_MAX_VERSION_NUM LOGICALREP_PROTO_STREAM_PARALLEL_VERSION_NUM +#define LOGICALREP_PROTO_SEQUENCES_VERSION_NUM 5 +#define LOGICALREP_PROTO_MAX_VERSION_NUM LOGICALREP_PROTO_SEQUENCES_VERSION_NUM /* * Logical message types @@ -66,6 +67,7 @@ typedef enum LogicalRepMsgType LOGICAL_REP_MSG_RELATION = 'R', LOGICAL_REP_MSG_TYPE = 'Y', LOGICAL_REP_MSG_MESSAGE = 'M', + LOGICAL_REP_MSG_SEQUENCE = 'Q', LOGICAL_REP_MSG_BEGIN_PREPARE = 'b', LOGICAL_REP_MSG_PREPARE = 'P', LOGICAL_REP_MSG_COMMIT_PREPARED = 'K', @@ -123,6 +125,17 @@ typedef struct LogicalRepTyp char *typname; /* name of the remote type */ } LogicalRepTyp; +/* Sequence info */ +typedef struct LogicalRepSequence +{ + XLogRecPtr lsn; /* LSN of the sequence change */ + Oid remoteid; /* unique id of the remote sequence */ + char *nspname; /* schema name of remote sequence */ + char *seqname; /* name of the remote sequence */ + bool transactional; + int64 value; +} LogicalRepSequence; + /* Transaction info */ typedef struct LogicalRepBeginData { @@ -246,6 +259,10 @@ extern List *logicalrep_read_truncate(StringInfo in, bool *cascade, bool *restart_seqs); extern void logicalrep_write_message(StringInfo out, TransactionId xid, XLogRecPtr lsn, bool transactional, const char *prefix, Size sz, const char *message); +extern void logicalrep_write_sequence(StringInfo out, Relation rel, + TransactionId xid, XLogRecPtr lsn, + bool transactional, int64 value); +extern LogicalRepRelId logicalrep_read_sequence(StringInfo in, LogicalRepSequence *seqdata); extern void logicalrep_write_rel(StringInfo out, TransactionId xid, Relation rel, Bitmapset *columns); extern LogicalRepRelation *logicalrep_read_rel(StringInfo in); diff --git a/src/test/regress/expected/object_address.out b/src/test/regress/expected/object_address.out index fc42d418bf1..9aec2612d01 100644 --- a/src/test/regress/expected/object_address.out +++ b/src/test/regress/expected/object_address.out @@ -47,6 +47,7 @@ CREATE TRANSFORM FOR int LANGUAGE SQL ( SET client_min_messages = 'ERROR'; CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable; CREATE PUBLICATION addr_pub_schema FOR TABLES IN SCHEMA addr_nsp; +CREATE PUBLICATION addr_pub_schema2 FOR SEQUENCES IN SCHEMA addr_nsp; RESET client_min_messages; CREATE SUBSCRIPTION regress_addr_sub CONNECTION '' PUBLICATION bar WITH (connect = false, slot_name = NONE); WARNING: subscription was created, but is not connected @@ -315,12 +316,12 @@ WARNING: error for function of access method,{addr_nsp,zwei},{}: name list leng WARNING: error for function of access method,{addr_nsp,zwei},{integer}: name list length must be at least 3 WARNING: error for function of access method,{eins,zwei,drei},{}: argument list length must be exactly 2 WARNING: error for function of access method,{eins,zwei,drei},{integer}: argument list length must be exactly 2 -WARNING: error for publication namespace,{eins},{}: argument list length must be exactly 1 -WARNING: error for publication namespace,{eins},{integer}: schema "eins" does not exist -WARNING: error for publication namespace,{addr_nsp,zwei},{}: name list length must be exactly 1 -WARNING: error for publication namespace,{addr_nsp,zwei},{integer}: name list length must be exactly 1 -WARNING: error for publication namespace,{eins,zwei,drei},{}: name list length must be exactly 1 -WARNING: error for publication namespace,{eins,zwei,drei},{integer}: name list length must be exactly 1 +WARNING: error for publication namespace,{eins},{}: argument list length must be exactly 2 +WARNING: error for publication namespace,{eins},{integer}: argument list length must be exactly 2 +WARNING: error for publication namespace,{addr_nsp,zwei},{}: argument list length must be exactly 2 +WARNING: error for publication namespace,{addr_nsp,zwei},{integer}: argument list length must be exactly 2 +WARNING: error for publication namespace,{eins,zwei,drei},{}: argument list length must be exactly 2 +WARNING: error for publication namespace,{eins,zwei,drei},{integer}: argument list length must be exactly 2 WARNING: error for publication relation,{eins},{}: argument list length must be exactly 1 WARNING: error for publication relation,{eins},{integer}: relation "eins" does not exist WARNING: error for publication relation,{addr_nsp,zwei},{}: argument list length must be exactly 1 @@ -441,7 +442,8 @@ WITH objects (type, name, args) AS (VALUES ('transform', '{int}', '{sql}'), ('access method', '{btree}', '{}'), ('publication', '{addr_pub}', '{}'), - ('publication namespace', '{addr_nsp}', '{addr_pub_schema}'), + ('publication namespace', '{addr_nsp}', '{addr_pub_schema, t}'), + ('publication namespace', '{addr_nsp}', '{addr_pub_schema2, s}'), ('publication relation', '{addr_nsp, gentable}', '{addr_pub}'), ('subscription', '{regress_addr_sub}', '{}'), ('statistics object', '{addr_nsp, gentable_stat}', '{}') @@ -504,7 +506,8 @@ text search template|addr_nsp|addr_ts_temp|addr_nsp.addr_ts_temp|t subscription|NULL|regress_addr_sub|regress_addr_sub|t publication|NULL|addr_pub|addr_pub|t publication relation|NULL|NULL|addr_nsp.gentable in publication addr_pub|t -publication namespace|NULL|NULL|addr_nsp in publication addr_pub_schema|t +publication namespace|NULL|NULL|addr_nsp in publication addr_pub_schema type t|t +publication namespace|NULL|NULL|addr_nsp in publication addr_pub_schema2 type s|t --- --- Cleanup resources --- @@ -516,6 +519,7 @@ drop cascades to server integer drop cascades to user mapping for regress_addr_user on server integer DROP PUBLICATION addr_pub; DROP PUBLICATION addr_pub_schema; +DROP PUBLICATION addr_pub_schema2; DROP SUBSCRIPTION regress_addr_sub; DROP SCHEMA addr_nsp CASCADE; NOTICE: drop cascades to 14 other objects diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index 13e4f6db7ba..bab5c27422c 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -6240,9 +6240,9 @@ List of schemas (0 rows) \dRp "no.such.publication" - List of publications - Name | Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root -------+-------+------------+---------+---------+---------+-----------+---------- + List of publications + Name | Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Sequences | Via root +------+-------+------------+---------------+---------+---------+---------+-----------+-----------+---------- (0 rows) \dRs "no.such.subscription" diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out index 16361a91f9f..2f5c72c9d2b 100644 --- a/src/test/regress/expected/publication.out +++ b/src/test/regress/expected/publication.out @@ -30,20 +30,20 @@ ERROR: conflicting or redundant options LINE 1: ...ub_xxx WITH (publish_via_partition_root = 'true', publish_vi... ^ \dRp - List of publications - Name | Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root ---------------------+--------------------------+------------+---------+---------+---------+-----------+---------- - testpib_ins_trunct | regress_publication_user | f | t | f | f | f | f - testpub_default | regress_publication_user | f | f | t | f | f | f + List of publications + Name | Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Sequences | Via root +--------------------+--------------------------+------------+---------------+---------+---------+---------+-----------+-----------+---------- + testpib_ins_trunct | regress_publication_user | f | f | t | f | f | f | f | f + testpub_default | regress_publication_user | f | f | f | t | f | f | f | f (2 rows) -ALTER PUBLICATION testpub_default SET (publish = 'insert, update, delete'); +ALTER PUBLICATION testpub_default SET (publish = 'insert, update, delete, sequence'); \dRp - List of publications - Name | Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root ---------------------+--------------------------+------------+---------+---------+---------+-----------+---------- - testpib_ins_trunct | regress_publication_user | f | t | f | f | f | f - testpub_default | regress_publication_user | f | t | t | t | f | f + List of publications + Name | Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Sequences | Via root +--------------------+--------------------------+------------+---------------+---------+---------+---------+-----------+-----------+---------- + testpib_ins_trunct | regress_publication_user | f | f | t | f | f | f | f | f + testpub_default | regress_publication_user | f | f | t | t | t | f | t | f (2 rows) --- adding tables @@ -61,6 +61,9 @@ CREATE TABLE testpub_tbl2 (id serial primary key, data text); ALTER PUBLICATION testpub_foralltables ADD TABLE testpub_tbl2; ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES DETAIL: Tables cannot be added to or dropped from FOR ALL TABLES publications. +-- fail - can't add a table using ADD SEQUENCE command +ALTER PUBLICATION testpub_foralltables ADD SEQUENCE testpub_tbl2; +ERROR: object type does not match type expected by command -- fail - can't drop from all tables publication ALTER PUBLICATION testpub_foralltables DROP TABLE testpub_tbl2; ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES @@ -87,10 +90,10 @@ RESET client_min_messages; -- should be able to add schema to 'FOR TABLE' publication ALTER PUBLICATION testpub_fortable ADD TABLES IN SCHEMA pub_test; \dRp+ testpub_fortable - Publication testpub_fortable - Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root ---------------------------+------------+---------+---------+---------+-----------+---------- - regress_publication_user | f | t | t | t | t | f + Publication testpub_fortable + Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Sequences | Via root +--------------------------+------------+---------------+---------+---------+---------+-----------+-----------+---------- + regress_publication_user | f | f | t | t | t | t | t | f Tables: "public.testpub_tbl1" Tables from schemas: @@ -99,20 +102,20 @@ Tables from schemas: -- should be able to drop schema from 'FOR TABLE' publication ALTER PUBLICATION testpub_fortable DROP TABLES IN SCHEMA pub_test; \dRp+ testpub_fortable - Publication testpub_fortable - Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root ---------------------------+------------+---------+---------+---------+-----------+---------- - regress_publication_user | f | t | t | t | t | f + Publication testpub_fortable + Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Sequences | Via root +--------------------------+------------+---------------+---------+---------+---------+-----------+-----------+---------- + regress_publication_user | f | f | t | t | t | t | t | f Tables: "public.testpub_tbl1" -- should be able to set schema to 'FOR TABLE' publication ALTER PUBLICATION testpub_fortable SET TABLES IN SCHEMA pub_test; \dRp+ testpub_fortable - Publication testpub_fortable - Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root ---------------------------+------------+---------+---------+---------+-----------+---------- - regress_publication_user | f | t | t | t | t | f + Publication testpub_fortable + Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Sequences | Via root +--------------------------+------------+---------------+---------+---------+---------+-----------+-----------+---------- + regress_publication_user | f | f | t | t | t | t | t | f Tables from schemas: "pub_test" @@ -123,10 +126,10 @@ CREATE PUBLICATION testpub_forschema FOR TABLES IN SCHEMA pub_test; CREATE PUBLICATION testpub_for_tbl_schema FOR TABLES IN SCHEMA pub_test, TABLE pub_test.testpub_nopk; RESET client_min_messages; \dRp+ testpub_for_tbl_schema - Publication testpub_for_tbl_schema - Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root ---------------------------+------------+---------+---------+---------+-----------+---------- - regress_publication_user | f | t | t | t | t | f + Publication testpub_for_tbl_schema + Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Sequences | Via root +--------------------------+------------+---------------+---------+---------+---------+-----------+-----------+---------- + regress_publication_user | f | f | t | t | t | t | t | f Tables: "pub_test.testpub_nopk" Tables from schemas: @@ -144,10 +147,10 @@ LINE 1: ...CATION testpub_parsertst FOR TABLES IN SCHEMA foo, test.foo; -- should be able to add a table of the same schema to the schema publication ALTER PUBLICATION testpub_forschema ADD TABLE pub_test.testpub_nopk; \dRp+ testpub_forschema - Publication testpub_forschema - Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root ---------------------------+------------+---------+---------+---------+-----------+---------- - regress_publication_user | f | t | t | t | t | f + Publication testpub_forschema + Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Sequences | Via root +--------------------------+------------+---------------+---------+---------+---------+-----------+-----------+---------- + regress_publication_user | f | f | t | t | t | t | t | f Tables: "pub_test.testpub_nopk" Tables from schemas: @@ -156,10 +159,10 @@ Tables from schemas: -- should be able to drop the table ALTER PUBLICATION testpub_forschema DROP TABLE pub_test.testpub_nopk; \dRp+ testpub_forschema - Publication testpub_forschema - Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root ---------------------------+------------+---------+---------+---------+-----------+---------- - regress_publication_user | f | t | t | t | t | f + Publication testpub_forschema + Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Sequences | Via root +--------------------------+------------+---------------+---------+---------+---------+-----------+-----------+---------- + regress_publication_user | f | f | t | t | t | t | t | f Tables from schemas: "pub_test" @@ -170,10 +173,10 @@ ERROR: relation "testpub_nopk" is not part of the publication -- should be able to set table to schema publication ALTER PUBLICATION testpub_forschema SET TABLE pub_test.testpub_nopk; \dRp+ testpub_forschema - Publication testpub_forschema - Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root ---------------------------+------------+---------+---------+---------+-----------+---------- - regress_publication_user | f | t | t | t | t | f + Publication testpub_forschema + Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Sequences | Via root +--------------------------+------------+---------------+---------+---------+---------+-----------+-----------+---------- + regress_publication_user | f | f | t | t | t | t | t | f Tables: "pub_test.testpub_nopk" @@ -197,10 +200,10 @@ Not-null constraints: "testpub_tbl2_id_not_null" NOT NULL "id" \dRp+ testpub_foralltables - Publication testpub_foralltables - Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root ---------------------------+------------+---------+---------+---------+-----------+---------- - regress_publication_user | t | t | t | f | f | f + Publication testpub_foralltables + Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Sequences | Via root +--------------------------+------------+---------------+---------+---------+---------+-----------+-----------+---------- + regress_publication_user | t | f | t | t | f | f | f | f (1 row) DROP TABLE testpub_tbl2; @@ -212,24 +215,525 @@ CREATE PUBLICATION testpub3 FOR TABLE testpub_tbl3; CREATE PUBLICATION testpub4 FOR TABLE ONLY testpub_tbl3; RESET client_min_messages; \dRp+ testpub3 - Publication testpub3 - Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root ---------------------------+------------+---------+---------+---------+-----------+---------- - regress_publication_user | f | t | t | t | t | f + Publication testpub3 + Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Sequences | Via root +--------------------------+------------+---------------+---------+---------+---------+-----------+-----------+---------- + regress_publication_user | f | f | t | t | t | t | t | f Tables: "public.testpub_tbl3" "public.testpub_tbl3a" \dRp+ testpub4 - Publication testpub4 - Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root ---------------------------+------------+---------+---------+---------+-----------+---------- - regress_publication_user | f | t | t | t | t | f + Publication testpub4 + Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Sequences | Via root +--------------------------+------------+---------------+---------+---------+---------+-----------+-----------+---------- + regress_publication_user | f | f | t | t | t | t | t | f Tables: "public.testpub_tbl3" DROP TABLE testpub_tbl3, testpub_tbl3a; DROP PUBLICATION testpub3, testpub4; +--- adding sequences +CREATE SEQUENCE testpub_seq0; +CREATE SEQUENCE pub_test.testpub_seq1; +SET client_min_messages = 'ERROR'; +CREATE PUBLICATION testpub_forallsequences FOR ALL SEQUENCES WITH (publish = 'sequence'); +RESET client_min_messages; +ALTER PUBLICATION testpub_forallsequences SET (publish = 'insert, sequence'); +CREATE SEQUENCE testpub_seq2; +-- fail - can't add to for all sequences publication +ALTER PUBLICATION testpub_forallsequences ADD SEQUENCE testpub_seq2; +ERROR: publication "testpub_forallsequences" is defined as FOR ALL SEQUENCES +DETAIL: Sequences cannot be added to or dropped from FOR ALL SEQUENCES publications. +-- fail - can't drop from all sequences publication +ALTER PUBLICATION testpub_forallsequences DROP SEQUENCE testpub_seq2; +ERROR: publication "testpub_forallsequences" is defined as FOR ALL SEQUENCES +DETAIL: Sequences cannot be added to or dropped from FOR ALL SEQUENCES publications. +-- fail - can't add to for all sequences publication +ALTER PUBLICATION testpub_forallsequences SET SEQUENCE pub_test.testpub_seq1; +ERROR: publication "testpub_forallsequences" is defined as FOR ALL SEQUENCES +DETAIL: Sequences cannot be added to or dropped from FOR ALL SEQUENCES publications. +-- fail - can't add schema to 'FOR ALL SEQUENCES' publication +ALTER PUBLICATION testpub_forallsequences ADD SEQUENCES IN SCHEMA pub_test; +ERROR: publication "testpub_forallsequences" is defined as FOR ALL SEQUENCES +DETAIL: Sequences from schema cannot be added to, dropped from, or set on FOR ALL SEQUENCES publications. +-- fail - can't drop schema from 'FOR ALL SEQUENCES' publication +ALTER PUBLICATION testpub_forallsequences DROP SEQUENCES IN SCHEMA pub_test; +ERROR: publication "testpub_forallsequences" is defined as FOR ALL SEQUENCES +DETAIL: Sequences from schema cannot be added to, dropped from, or set on FOR ALL SEQUENCES publications. +-- fail - can't set schema to 'FOR ALL SEQUENCES' publication +ALTER PUBLICATION testpub_forallsequences SET SEQUENCES IN SCHEMA pub_test; +ERROR: publication "testpub_forallsequences" is defined as FOR ALL SEQUENCES +DETAIL: Sequences from schema cannot be added to, dropped from, or set on FOR ALL SEQUENCES publications. +SET client_min_messages = 'ERROR'; +CREATE PUBLICATION testpub_forsequence FOR SEQUENCE testpub_seq0; +RESET client_min_messages; +-- should be able to add schema to 'FOR SEQUENCE' publication +ALTER PUBLICATION testpub_forsequence ADD SEQUENCES IN SCHEMA pub_test; +\dRp+ testpub_forsequence + Publication testpub_forsequence + Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Sequences | Via root +--------------------------+------------+---------------+---------+---------+---------+-----------+-----------+---------- + regress_publication_user | f | f | t | t | t | t | t | f +Sequences: + "public.testpub_seq0" +Sequences from schemas: + "pub_test" + +-- add sequence from the schema we already added +ALTER PUBLICATION testpub_forsequence ADD SEQUENCE pub_test.testpub_seq1; +-- fail - can't add sequence using ADD TABLE command +ALTER PUBLICATION testpub_forsequence ADD TABLE pub_test.testpub_seq1; +ERROR: object type does not match type expected by command +-- should be able to drop schema from 'FOR SEQUENCE' publication +ALTER PUBLICATION testpub_forsequence DROP SEQUENCES IN SCHEMA pub_test; +\dRp+ testpub_forsequence + Publication testpub_forsequence + Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Sequences | Via root +--------------------------+------------+---------------+---------+---------+---------+-----------+-----------+---------- + regress_publication_user | f | f | t | t | t | t | t | f +Sequences: + "pub_test.testpub_seq1" + "public.testpub_seq0" + +-- should be able to set schema to 'FOR SEQUENCE' publication +ALTER PUBLICATION testpub_forsequence SET SEQUENCES IN SCHEMA pub_test; +\dRp+ testpub_forsequence + Publication testpub_forsequence + Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Sequences | Via root +--------------------------+------------+---------------+---------+---------+---------+-----------+-----------+---------- + regress_publication_user | f | f | t | t | t | t | t | f +Sequences from schemas: + "pub_test" + +SET client_min_messages = 'ERROR'; +CREATE PUBLICATION testpub_forschema FOR SEQUENCES IN SCHEMA pub_test; +RESET client_min_messages; +-- should be able to set sequence to schema publication +ALTER PUBLICATION testpub_forschema SET SEQUENCE pub_test.testpub_seq1; +\dRp+ testpub_forschema + Publication testpub_forschema + Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Sequences | Via root +--------------------------+------------+---------------+---------+---------+---------+-----------+-----------+---------- + regress_publication_user | f | f | t | t | t | t | t | f +Sequences: + "pub_test.testpub_seq1" + +SELECT pubname, puballtables, puballsequences FROM pg_publication WHERE pubname = 'testpub_forallsequences'; + pubname | puballtables | puballsequences +-------------------------+--------------+----------------- + testpub_forallsequences | f | t +(1 row) + +\d+ pub_test.testpub_seq1 + Sequence "pub_test.testpub_seq1" + Type | Start | Minimum | Maximum | Increment | Cycles? | Cache +--------+-------+---------+---------------------+-----------+---------+------- + bigint | 1 | 1 | 9223372036854775807 | 1 | no | 1 +Publications: + "testpub_forallsequences" + "testpub_forschema" + "testpub_forsequence" + +\dRp+ testpub_forallsequences + Publication testpub_forallsequences + Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Sequences | Via root +--------------------------+------------+---------------+---------+---------+---------+-----------+-----------+---------- + regress_publication_user | f | t | t | f | f | f | t | f +(1 row) + +DROP SEQUENCE testpub_seq0, pub_test.testpub_seq1, testpub_seq2; +DROP PUBLICATION testpub_forallsequences, testpub_forsequence, testpub_forschema; +-- publication testing multiple sequences at the same time +CREATE SEQUENCE testpub_seq1; +CREATE SEQUENCE testpub_seq2; +SET client_min_messages = 'ERROR'; +CREATE PUBLICATION testpub_multi FOR SEQUENCE testpub_seq1, testpub_seq2; +RESET client_min_messages; +\dRp+ testpub_multi + Publication testpub_multi + Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Sequences | Via root +--------------------------+------------+---------------+---------+---------+---------+-----------+-----------+---------- + regress_publication_user | f | f | t | t | t | t | t | f +Sequences: + "public.testpub_seq1" + "public.testpub_seq2" + +DROP PUBLICATION testpub_multi; +DROP SEQUENCE testpub_seq1; +DROP SEQUENCE testpub_seq2; +-- Publication mixing tables and sequences +SET client_min_messages = 'ERROR'; +CREATE PUBLICATION testpub_mix; +RESET client_min_messages; +CREATE SEQUENCE testpub_seq1; +CREATE SEQUENCE pub_test.testpub_seq2; +ALTER PUBLICATION testpub_mix ADD SEQUENCE testpub_seq1, TABLE testpub_tbl1; +\dRp+ testpub_mix + Publication testpub_mix + Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Sequences | Via root +--------------------------+------------+---------------+---------+---------+---------+-----------+-----------+---------- + regress_publication_user | f | f | t | t | t | t | t | f +Tables: + "public.testpub_tbl1" +Sequences: + "public.testpub_seq1" + +ALTER PUBLICATION testpub_mix ADD SEQUENCES IN SCHEMA pub_test, TABLES IN SCHEMA pub_test; +\dRp+ testpub_mix + Publication testpub_mix + Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Sequences | Via root +--------------------------+------------+---------------+---------+---------+---------+-----------+-----------+---------- + regress_publication_user | f | f | t | t | t | t | t | f +Tables: + "public.testpub_tbl1" +Tables from schemas: + "pub_test" +Sequences: + "public.testpub_seq1" +Sequences from schemas: + "pub_test" + +ALTER PUBLICATION testpub_mix DROP SEQUENCES IN SCHEMA pub_test; +\dRp+ testpub_mix + Publication testpub_mix + Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Sequences | Via root +--------------------------+------------+---------------+---------+---------+---------+-----------+-----------+---------- + regress_publication_user | f | f | t | t | t | t | t | f +Tables: + "public.testpub_tbl1" +Tables from schemas: + "pub_test" +Sequences: + "public.testpub_seq1" + +ALTER PUBLICATION testpub_mix DROP TABLES IN SCHEMA pub_test; +\dRp+ testpub_mix + Publication testpub_mix + Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Sequences | Via root +--------------------------+------------+---------------+---------+---------+---------+-----------+-----------+---------- + regress_publication_user | f | f | t | t | t | t | t | f +Tables: + "public.testpub_tbl1" +Sequences: + "public.testpub_seq1" + +DROP PUBLICATION testpub_mix; +DROP SEQUENCE testpub_seq1; +DROP SEQUENCE pub_test.testpub_seq2; +-- make sure we replicate only the correct relation type +CREATE SCHEMA pub_test1; +CREATE SEQUENCE pub_test1.test_seq1; +CREATE TABLE pub_test1.test_tbl1 (a int primary key, b int); +CREATE SCHEMA pub_test2; +CREATE SEQUENCE pub_test2.test_seq2; +CREATE TABLE pub_test2.test_tbl2 (a int primary key, b int); +SET client_min_messages = 'ERROR'; +CREATE PUBLICATION testpub_schemas; +RESET client_min_messages; +-- add tables from one schema, sequences from the other +ALTER PUBLICATION testpub_schemas ADD TABLES IN SCHEMA pub_test2; +ALTER PUBLICATION testpub_schemas ADD SEQUENCES IN SCHEMA pub_test1; +\dRp+ testpub_schemas + Publication testpub_schemas + Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Sequences | Via root +--------------------------+------------+---------------+---------+---------+---------+-----------+-----------+---------- + regress_publication_user | f | f | t | t | t | t | t | f +Tables from schemas: + "pub_test2" +Sequences from schemas: + "pub_test1" + +\dn+ pub_test1 + List of schemas + Name | Owner | Access privileges | Description +-----------+--------------------------+-------------------+------------- + pub_test1 | regress_publication_user | | +Publications: + "testpub_schemas" (sequences) + +\dn+ pub_test2 + List of schemas + Name | Owner | Access privileges | Description +-----------+--------------------------+-------------------+------------- + pub_test2 | regress_publication_user | | +Publications: + "testpub_schemas" (tables) + +\d+ pub_test1.test_seq1; + Sequence "pub_test1.test_seq1" + Type | Start | Minimum | Maximum | Increment | Cycles? | Cache +--------+-------+---------+---------------------+-----------+---------+------- + bigint | 1 | 1 | 9223372036854775807 | 1 | no | 1 +Publications: + "testpub_schemas" + +\d+ pub_test1.test_tbl1; + Table "pub_test1.test_tbl1" + Column | Type | Collation | Nullable | Default | Storage | Stats target | Description +--------+---------+-----------+----------+---------+---------+--------------+------------- + a | integer | | not null | | plain | | + b | integer | | | | plain | | +Indexes: + "test_tbl1_pkey" PRIMARY KEY, btree (a) + +\d+ pub_test2.test_seq2; + Sequence "pub_test2.test_seq2" + Type | Start | Minimum | Maximum | Increment | Cycles? | Cache +--------+-------+---------+---------------------+-----------+---------+------- + bigint | 1 | 1 | 9223372036854775807 | 1 | no | 1 + +\d+ pub_test2.test_tbl2; + Table "pub_test2.test_tbl2" + Column | Type | Collation | Nullable | Default | Storage | Stats target | Description +--------+---------+-----------+----------+---------+---------+--------------+------------- + a | integer | | not null | | plain | | + b | integer | | | | plain | | +Indexes: + "test_tbl2_pkey" PRIMARY KEY, btree (a) +Publications: + "testpub_schemas" + +-- add the other object type from each schema +ALTER PUBLICATION testpub_schemas ADD TABLES IN SCHEMA pub_test1; +ALTER PUBLICATION testpub_schemas ADD SEQUENCES IN SCHEMA pub_test2; +\dRp+ testpub_schemas + Publication testpub_schemas + Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Sequences | Via root +--------------------------+------------+---------------+---------+---------+---------+-----------+-----------+---------- + regress_publication_user | f | f | t | t | t | t | t | f +Tables from schemas: + "pub_test1" + "pub_test2" +Sequences from schemas: + "pub_test1" + "pub_test2" + +\dn+ pub_test1 + List of schemas + Name | Owner | Access privileges | Description +-----------+--------------------------+-------------------+------------- + pub_test1 | regress_publication_user | | +Publications: + "testpub_schemas" (sequences) + "testpub_schemas" (tables) + +\dn+ pub_test2 + List of schemas + Name | Owner | Access privileges | Description +-----------+--------------------------+-------------------+------------- + pub_test2 | regress_publication_user | | +Publications: + "testpub_schemas" (tables) + "testpub_schemas" (sequences) + +\d+ pub_test1.test_seq1; + Sequence "pub_test1.test_seq1" + Type | Start | Minimum | Maximum | Increment | Cycles? | Cache +--------+-------+---------+---------------------+-----------+---------+------- + bigint | 1 | 1 | 9223372036854775807 | 1 | no | 1 +Publications: + "testpub_schemas" + +\d+ pub_test1.test_tbl1; + Table "pub_test1.test_tbl1" + Column | Type | Collation | Nullable | Default | Storage | Stats target | Description +--------+---------+-----------+----------+---------+---------+--------------+------------- + a | integer | | not null | | plain | | + b | integer | | | | plain | | +Indexes: + "test_tbl1_pkey" PRIMARY KEY, btree (a) +Publications: + "testpub_schemas" + +\d+ pub_test2.test_seq2; + Sequence "pub_test2.test_seq2" + Type | Start | Minimum | Maximum | Increment | Cycles? | Cache +--------+-------+---------+---------------------+-----------+---------+------- + bigint | 1 | 1 | 9223372036854775807 | 1 | no | 1 +Publications: + "testpub_schemas" + +\d+ pub_test2.test_tbl2; + Table "pub_test2.test_tbl2" + Column | Type | Collation | Nullable | Default | Storage | Stats target | Description +--------+---------+-----------+----------+---------+---------+--------------+------------- + a | integer | | not null | | plain | | + b | integer | | | | plain | | +Indexes: + "test_tbl2_pkey" PRIMARY KEY, btree (a) +Publications: + "testpub_schemas" + +-- now drop the object type added first +ALTER PUBLICATION testpub_schemas DROP TABLES IN SCHEMA pub_test2; +ALTER PUBLICATION testpub_schemas DROP SEQUENCES IN SCHEMA pub_test1; +\dRp+ testpub_schemas + Publication testpub_schemas + Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Sequences | Via root +--------------------------+------------+---------------+---------+---------+---------+-----------+-----------+---------- + regress_publication_user | f | f | t | t | t | t | t | f +Tables from schemas: + "pub_test1" +Sequences from schemas: + "pub_test2" + +\dn+ pub_test1 + List of schemas + Name | Owner | Access privileges | Description +-----------+--------------------------+-------------------+------------- + pub_test1 | regress_publication_user | | +Publications: + "testpub_schemas" (tables) + +\dn+ pub_test2 + List of schemas + Name | Owner | Access privileges | Description +-----------+--------------------------+-------------------+------------- + pub_test2 | regress_publication_user | | +Publications: + "testpub_schemas" (sequences) + +\d+ pub_test1.test_seq1; + Sequence "pub_test1.test_seq1" + Type | Start | Minimum | Maximum | Increment | Cycles? | Cache +--------+-------+---------+---------------------+-----------+---------+------- + bigint | 1 | 1 | 9223372036854775807 | 1 | no | 1 + +\d+ pub_test1.test_tbl1; + Table "pub_test1.test_tbl1" + Column | Type | Collation | Nullable | Default | Storage | Stats target | Description +--------+---------+-----------+----------+---------+---------+--------------+------------- + a | integer | | not null | | plain | | + b | integer | | | | plain | | +Indexes: + "test_tbl1_pkey" PRIMARY KEY, btree (a) +Publications: + "testpub_schemas" + +\d+ pub_test2.test_seq2; + Sequence "pub_test2.test_seq2" + Type | Start | Minimum | Maximum | Increment | Cycles? | Cache +--------+-------+---------+---------------------+-----------+---------+------- + bigint | 1 | 1 | 9223372036854775807 | 1 | no | 1 +Publications: + "testpub_schemas" + +\d+ pub_test2.test_tbl2; + Table "pub_test2.test_tbl2" + Column | Type | Collation | Nullable | Default | Storage | Stats target | Description +--------+---------+-----------+----------+---------+---------+--------------+------------- + a | integer | | not null | | plain | | + b | integer | | | | plain | | +Indexes: + "test_tbl2_pkey" PRIMARY KEY, btree (a) + +-- add a different schema (not including the already published sequences) +ALTER PUBLICATION testpub_schemas ADD TABLE pub_test2.test_tbl2; +ALTER PUBLICATION testpub_schemas ADD SEQUENCE pub_test1.test_seq1; +\dRp+ testpub_schemas + Publication testpub_schemas + Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Sequences | Via root +--------------------------+------------+---------------+---------+---------+---------+-----------+-----------+---------- + regress_publication_user | f | f | t | t | t | t | t | f +Tables: + "pub_test2.test_tbl2" +Tables from schemas: + "pub_test1" +Sequences: + "pub_test1.test_seq1" +Sequences from schemas: + "pub_test2" + +\d+ pub_test1.test_seq1; + Sequence "pub_test1.test_seq1" + Type | Start | Minimum | Maximum | Increment | Cycles? | Cache +--------+-------+---------+---------------------+-----------+---------+------- + bigint | 1 | 1 | 9223372036854775807 | 1 | no | 1 +Publications: + "testpub_schemas" + +\d+ pub_test1.test_tbl1; + Table "pub_test1.test_tbl1" + Column | Type | Collation | Nullable | Default | Storage | Stats target | Description +--------+---------+-----------+----------+---------+---------+--------------+------------- + a | integer | | not null | | plain | | + b | integer | | | | plain | | +Indexes: + "test_tbl1_pkey" PRIMARY KEY, btree (a) +Publications: + "testpub_schemas" + +\d+ pub_test2.test_seq2; + Sequence "pub_test2.test_seq2" + Type | Start | Minimum | Maximum | Increment | Cycles? | Cache +--------+-------+---------+---------------------+-----------+---------+------- + bigint | 1 | 1 | 9223372036854775807 | 1 | no | 1 +Publications: + "testpub_schemas" + +\d+ pub_test2.test_tbl2; + Table "pub_test2.test_tbl2" + Column | Type | Collation | Nullable | Default | Storage | Stats target | Description +--------+---------+-----------+----------+---------+---------+--------------+------------- + a | integer | | not null | | plain | | + b | integer | | | | plain | | +Indexes: + "test_tbl2_pkey" PRIMARY KEY, btree (a) +Publications: + "testpub_schemas" + +-- now drop the explicitly added objects again +ALTER PUBLICATION testpub_schemas DROP TABLE pub_test2.test_tbl2; +ALTER PUBLICATION testpub_schemas DROP SEQUENCE pub_test1.test_seq1; +\dRp+ testpub_schemas + Publication testpub_schemas + Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Sequences | Via root +--------------------------+------------+---------------+---------+---------+---------+-----------+-----------+---------- + regress_publication_user | f | f | t | t | t | t | t | f +Tables from schemas: + "pub_test1" +Sequences from schemas: + "pub_test2" + +\d+ pub_test1.test_seq1; + Sequence "pub_test1.test_seq1" + Type | Start | Minimum | Maximum | Increment | Cycles? | Cache +--------+-------+---------+---------------------+-----------+---------+------- + bigint | 1 | 1 | 9223372036854775807 | 1 | no | 1 + +\d+ pub_test1.test_tbl1; + Table "pub_test1.test_tbl1" + Column | Type | Collation | Nullable | Default | Storage | Stats target | Description +--------+---------+-----------+----------+---------+---------+--------------+------------- + a | integer | | not null | | plain | | + b | integer | | | | plain | | +Indexes: + "test_tbl1_pkey" PRIMARY KEY, btree (a) +Publications: + "testpub_schemas" + +\d+ pub_test2.test_seq2; + Sequence "pub_test2.test_seq2" + Type | Start | Minimum | Maximum | Increment | Cycles? | Cache +--------+-------+---------+---------------------+-----------+---------+------- + bigint | 1 | 1 | 9223372036854775807 | 1 | no | 1 +Publications: + "testpub_schemas" + +\d+ pub_test2.test_tbl2; + Table "pub_test2.test_tbl2" + Column | Type | Collation | Nullable | Default | Storage | Stats target | Description +--------+---------+-----------+----------+---------+---------+--------------+------------- + a | integer | | not null | | plain | | + b | integer | | | | plain | | +Indexes: + "test_tbl2_pkey" PRIMARY KEY, btree (a) + +DROP PUBLICATION testpub_schemas; +DROP TABLE pub_test1.test_tbl1, pub_test2.test_tbl2; +DROP SEQUENCE pub_test1.test_seq1, pub_test2.test_seq2; +DROP SCHEMA pub_test1, pub_test2; -- Tests for partitioned tables SET client_min_messages = 'ERROR'; CREATE PUBLICATION testpub_forparted; @@ -245,10 +749,10 @@ UPDATE testpub_parted1 SET a = 1; -- only parent is listed as being in publication, not the partition ALTER PUBLICATION testpub_forparted ADD TABLE testpub_parted; \dRp+ testpub_forparted - Publication testpub_forparted - Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root ---------------------------+------------+---------+---------+---------+-----------+---------- - regress_publication_user | f | t | t | t | t | f + Publication testpub_forparted + Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Sequences | Via root +--------------------------+------------+---------------+---------+---------+---------+-----------+-----------+---------- + regress_publication_user | f | f | t | t | t | t | t | f Tables: "public.testpub_parted" @@ -263,10 +767,10 @@ ALTER TABLE testpub_parted DETACH PARTITION testpub_parted1; UPDATE testpub_parted1 SET a = 1; ALTER PUBLICATION testpub_forparted SET (publish_via_partition_root = true); \dRp+ testpub_forparted - Publication testpub_forparted - Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root ---------------------------+------------+---------+---------+---------+-----------+---------- - regress_publication_user | f | t | t | t | t | t + Publication testpub_forparted + Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Sequences | Via root +--------------------------+------------+---------------+---------+---------+---------+-----------+-----------+---------- + regress_publication_user | f | f | t | t | t | t | t | t Tables: "public.testpub_parted" @@ -295,10 +799,10 @@ SET client_min_messages = 'ERROR'; CREATE PUBLICATION testpub5 FOR TABLE testpub_rf_tbl1, testpub_rf_tbl2 WHERE (c <> 'test' AND d < 5) WITH (publish = 'insert'); RESET client_min_messages; \dRp+ testpub5 - Publication testpub5 - Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root ---------------------------+------------+---------+---------+---------+-----------+---------- - regress_publication_user | f | t | f | f | f | f + Publication testpub5 + Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Sequences | Via root +--------------------------+------------+---------------+---------+---------+---------+-----------+-----------+---------- + regress_publication_user | f | f | t | f | f | f | f | f Tables: "public.testpub_rf_tbl1" "public.testpub_rf_tbl2" WHERE ((c <> 'test'::text) AND (d < 5)) @@ -311,10 +815,10 @@ Tables: ALTER PUBLICATION testpub5 ADD TABLE testpub_rf_tbl3 WHERE (e > 1000 AND e < 2000); \dRp+ testpub5 - Publication testpub5 - Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root ---------------------------+------------+---------+---------+---------+-----------+---------- - regress_publication_user | f | t | f | f | f | f + Publication testpub5 + Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Sequences | Via root +--------------------------+------------+---------------+---------+---------+---------+-----------+-----------+---------- + regress_publication_user | f | f | t | f | f | f | f | f Tables: "public.testpub_rf_tbl1" "public.testpub_rf_tbl2" WHERE ((c <> 'test'::text) AND (d < 5)) @@ -330,10 +834,10 @@ Publications: ALTER PUBLICATION testpub5 DROP TABLE testpub_rf_tbl2; \dRp+ testpub5 - Publication testpub5 - Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root ---------------------------+------------+---------+---------+---------+-----------+---------- - regress_publication_user | f | t | f | f | f | f + Publication testpub5 + Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Sequences | Via root +--------------------------+------------+---------------+---------+---------+---------+-----------+-----------+---------- + regress_publication_user | f | f | t | f | f | f | f | f Tables: "public.testpub_rf_tbl1" "public.testpub_rf_tbl3" WHERE ((e > 1000) AND (e < 2000)) @@ -341,10 +845,10 @@ Tables: -- remove testpub_rf_tbl1 and add testpub_rf_tbl3 again (another WHERE expression) ALTER PUBLICATION testpub5 SET TABLE testpub_rf_tbl3 WHERE (e > 300 AND e < 500); \dRp+ testpub5 - Publication testpub5 - Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root ---------------------------+------------+---------+---------+---------+-----------+---------- - regress_publication_user | f | t | f | f | f | f + Publication testpub5 + Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Sequences | Via root +--------------------------+------------+---------------+---------+---------+---------+-----------+-----------+---------- + regress_publication_user | f | f | t | f | f | f | f | f Tables: "public.testpub_rf_tbl3" WHERE ((e > 300) AND (e < 500)) @@ -377,10 +881,10 @@ SET client_min_messages = 'ERROR'; CREATE PUBLICATION testpub_syntax1 FOR TABLE testpub_rf_tbl1, ONLY testpub_rf_tbl3 WHERE (e < 999) WITH (publish = 'insert'); RESET client_min_messages; \dRp+ testpub_syntax1 - Publication testpub_syntax1 - Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root ---------------------------+------------+---------+---------+---------+-----------+---------- - regress_publication_user | f | t | f | f | f | f + Publication testpub_syntax1 + Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Sequences | Via root +--------------------------+------------+---------------+---------+---------+---------+-----------+-----------+---------- + regress_publication_user | f | f | t | f | f | f | f | f Tables: "public.testpub_rf_tbl1" "public.testpub_rf_tbl3" WHERE (e < 999) @@ -390,10 +894,10 @@ SET client_min_messages = 'ERROR'; CREATE PUBLICATION testpub_syntax2 FOR TABLE testpub_rf_tbl1, testpub_rf_schema1.testpub_rf_tbl5 WHERE (h < 999) WITH (publish = 'insert'); RESET client_min_messages; \dRp+ testpub_syntax2 - Publication testpub_syntax2 - Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root ---------------------------+------------+---------+---------+---------+-----------+---------- - regress_publication_user | f | t | f | f | f | f + Publication testpub_syntax2 + Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Sequences | Via root +--------------------------+------------+---------------+---------+---------+---------+-----------+-----------+---------- + regress_publication_user | f | f | t | f | f | f | f | f Tables: "public.testpub_rf_tbl1" "testpub_rf_schema1.testpub_rf_tbl5" WHERE (h < 999) @@ -508,10 +1012,10 @@ CREATE PUBLICATION testpub6 FOR TABLES IN SCHEMA testpub_rf_schema2; ALTER PUBLICATION testpub6 SET TABLES IN SCHEMA testpub_rf_schema2, TABLE testpub_rf_schema2.testpub_rf_tbl6 WHERE (i < 99); RESET client_min_messages; \dRp+ testpub6 - Publication testpub6 - Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root ---------------------------+------------+---------+---------+---------+-----------+---------- - regress_publication_user | f | t | t | t | t | f + Publication testpub6 + Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Sequences | Via root +--------------------------+------------+---------------+---------+---------+---------+-----------+-----------+---------- + regress_publication_user | f | f | t | t | t | t | t | f Tables: "testpub_rf_schema2.testpub_rf_tbl6" WHERE (i < 99) Tables from schemas: @@ -725,10 +1229,10 @@ CREATE PUBLICATION testpub_table_ins WITH (publish = 'insert, truncate'); RESET client_min_messages; ALTER PUBLICATION testpub_table_ins ADD TABLE testpub_tbl5 (a); -- ok \dRp+ testpub_table_ins - Publication testpub_table_ins - Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root ---------------------------+------------+---------+---------+---------+-----------+---------- - regress_publication_user | f | t | f | f | t | f + Publication testpub_table_ins + Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Sequences | Via root +--------------------------+------------+---------------+---------+---------+---------+-----------+-----------+---------- + regress_publication_user | f | f | t | f | f | t | f | f Tables: "public.testpub_tbl5" (a) @@ -902,10 +1406,10 @@ CREATE TABLE testpub_tbl_both_filters (a int, b int, c int, PRIMARY KEY (a,c)); ALTER TABLE testpub_tbl_both_filters REPLICA IDENTITY USING INDEX testpub_tbl_both_filters_pkey; ALTER PUBLICATION testpub_both_filters ADD TABLE testpub_tbl_both_filters (a,c) WHERE (c != 1); \dRp+ testpub_both_filters - Publication testpub_both_filters - Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root ---------------------------+------------+---------+---------+---------+-----------+---------- - regress_publication_user | f | t | t | t | t | f + Publication testpub_both_filters + Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Sequences | Via root +--------------------------+------------+---------------+---------+---------+---------+-----------+-----------+---------- + regress_publication_user | f | f | t | t | t | t | t | f Tables: "public.testpub_tbl_both_filters" (a, c) WHERE (c <> 1) @@ -1110,10 +1614,10 @@ ERROR: relation "testpub_tbl1" is already member of publication "testpub_fortbl CREATE PUBLICATION testpub_fortbl FOR TABLE testpub_tbl1; ERROR: publication "testpub_fortbl" already exists \dRp+ testpub_fortbl - Publication testpub_fortbl - Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root ---------------------------+------------+---------+---------+---------+-----------+---------- - regress_publication_user | f | t | t | t | t | f + Publication testpub_fortbl + Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Sequences | Via root +--------------------------+------------+---------------+---------+---------+---------+-----------+-----------+---------- + regress_publication_user | f | f | t | t | t | t | t | f Tables: "pub_test.testpub_nopk" "public.testpub_tbl1" @@ -1153,10 +1657,10 @@ Not-null constraints: "testpub_tbl1_id_not_null" NOT NULL "id" \dRp+ testpub_default - Publication testpub_default - Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root ---------------------------+------------+---------+---------+---------+-----------+---------- - regress_publication_user | f | t | t | t | f | f + Publication testpub_default + Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Sequences | Via root +--------------------------+------------+---------------+---------+---------+---------+-----------+-----------+---------- + regress_publication_user | f | f | t | t | t | f | t | f Tables: "pub_test.testpub_nopk" "public.testpub_tbl1" @@ -1236,10 +1740,10 @@ REVOKE CREATE ON DATABASE regression FROM regress_publication_user2; DROP TABLE testpub_parted; DROP TABLE testpub_tbl1; \dRp+ testpub_default - Publication testpub_default - Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root ---------------------------+------------+---------+---------+---------+-----------+---------- - regress_publication_user | f | t | t | t | f | f + Publication testpub_default + Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Sequences | Via root +--------------------------+------------+---------------+---------+---------+---------+-----------+-----------+---------- + regress_publication_user | f | f | t | t | t | f | t | f (1 row) -- fail - must be owner of publication @@ -1249,20 +1753,20 @@ ERROR: must be owner of publication testpub_default RESET ROLE; ALTER PUBLICATION testpub_default RENAME TO testpub_foo; \dRp testpub_foo - List of publications - Name | Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root --------------+--------------------------+------------+---------+---------+---------+-----------+---------- - testpub_foo | regress_publication_user | f | t | t | t | f | f + List of publications + Name | Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Sequences | Via root +-------------+--------------------------+------------+---------------+---------+---------+---------+-----------+-----------+---------- + testpub_foo | regress_publication_user | f | f | t | t | t | f | t | f (1 row) -- rename back to keep the rest simple ALTER PUBLICATION testpub_foo RENAME TO testpub_default; ALTER PUBLICATION testpub_default OWNER TO regress_publication_user2; \dRp testpub_default - List of publications - Name | Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root ------------------+---------------------------+------------+---------+---------+---------+-----------+---------- - testpub_default | regress_publication_user2 | f | t | t | t | f | f + List of publications + Name | Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Sequences | Via root +-----------------+---------------------------+------------+---------------+---------+---------+---------+-----------+-----------+---------- + testpub_default | regress_publication_user2 | f | f | t | t | t | f | t | f (1 row) -- adding schemas and tables @@ -1278,19 +1782,19 @@ CREATE TABLE "CURRENT_SCHEMA"."CURRENT_SCHEMA"(id int); SET client_min_messages = 'ERROR'; CREATE PUBLICATION testpub1_forschema FOR TABLES IN SCHEMA pub_test1; \dRp+ testpub1_forschema - Publication testpub1_forschema - Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root ---------------------------+------------+---------+---------+---------+-----------+---------- - regress_publication_user | f | t | t | t | t | f + Publication testpub1_forschema + Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Sequences | Via root +--------------------------+------------+---------------+---------+---------+---------+-----------+-----------+---------- + regress_publication_user | f | f | t | t | t | t | t | f Tables from schemas: "pub_test1" CREATE PUBLICATION testpub2_forschema FOR TABLES IN SCHEMA pub_test1, pub_test2, pub_test3; \dRp+ testpub2_forschema - Publication testpub2_forschema - Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root ---------------------------+------------+---------+---------+---------+-----------+---------- - regress_publication_user | f | t | t | t | t | f + Publication testpub2_forschema + Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Sequences | Via root +--------------------------+------------+---------------+---------+---------+---------+-----------+-----------+---------- + regress_publication_user | f | f | t | t | t | t | t | f Tables from schemas: "pub_test1" "pub_test2" @@ -1304,44 +1808,44 @@ CREATE PUBLICATION testpub6_forschema FOR TABLES IN SCHEMA "CURRENT_SCHEMA", CUR CREATE PUBLICATION testpub_fortable FOR TABLE "CURRENT_SCHEMA"."CURRENT_SCHEMA"; RESET client_min_messages; \dRp+ testpub3_forschema - Publication testpub3_forschema - Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root ---------------------------+------------+---------+---------+---------+-----------+---------- - regress_publication_user | f | t | t | t | t | f + Publication testpub3_forschema + Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Sequences | Via root +--------------------------+------------+---------------+---------+---------+---------+-----------+-----------+---------- + regress_publication_user | f | f | t | t | t | t | t | f Tables from schemas: "public" \dRp+ testpub4_forschema - Publication testpub4_forschema - Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root ---------------------------+------------+---------+---------+---------+-----------+---------- - regress_publication_user | f | t | t | t | t | f + Publication testpub4_forschema + Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Sequences | Via root +--------------------------+------------+---------------+---------+---------+---------+-----------+-----------+---------- + regress_publication_user | f | f | t | t | t | t | t | f Tables from schemas: "CURRENT_SCHEMA" \dRp+ testpub5_forschema - Publication testpub5_forschema - Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root ---------------------------+------------+---------+---------+---------+-----------+---------- - regress_publication_user | f | t | t | t | t | f + Publication testpub5_forschema + Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Sequences | Via root +--------------------------+------------+---------------+---------+---------+---------+-----------+-----------+---------- + regress_publication_user | f | f | t | t | t | t | t | f Tables from schemas: "CURRENT_SCHEMA" "public" \dRp+ testpub6_forschema - Publication testpub6_forschema - Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root ---------------------------+------------+---------+---------+---------+-----------+---------- - regress_publication_user | f | t | t | t | t | f + Publication testpub6_forschema + Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Sequences | Via root +--------------------------+------------+---------------+---------+---------+---------+-----------+-----------+---------- + regress_publication_user | f | f | t | t | t | t | t | f Tables from schemas: "CURRENT_SCHEMA" "public" \dRp+ testpub_fortable - Publication testpub_fortable - Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root ---------------------------+------------+---------+---------+---------+-----------+---------- - regress_publication_user | f | t | t | t | t | f + Publication testpub_fortable + Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Sequences | Via root +--------------------------+------------+---------------+---------+---------+---------+-----------+-----------+---------- + regress_publication_user | f | f | t | t | t | t | t | f Tables: "CURRENT_SCHEMA.CURRENT_SCHEMA" @@ -1375,10 +1879,10 @@ ERROR: schema "testpub_view" does not exist -- dropping the schema should reflect the change in publication DROP SCHEMA pub_test3; \dRp+ testpub2_forschema - Publication testpub2_forschema - Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root ---------------------------+------------+---------+---------+---------+-----------+---------- - regress_publication_user | f | t | t | t | t | f + Publication testpub2_forschema + Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Sequences | Via root +--------------------------+------------+---------------+---------+---------+---------+-----------+-----------+---------- + regress_publication_user | f | f | t | t | t | t | t | f Tables from schemas: "pub_test1" "pub_test2" @@ -1386,20 +1890,20 @@ Tables from schemas: -- renaming the schema should reflect the change in publication ALTER SCHEMA pub_test1 RENAME to pub_test1_renamed; \dRp+ testpub2_forschema - Publication testpub2_forschema - Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root ---------------------------+------------+---------+---------+---------+-----------+---------- - regress_publication_user | f | t | t | t | t | f + Publication testpub2_forschema + Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Sequences | Via root +--------------------------+------------+---------------+---------+---------+---------+-----------+-----------+---------- + regress_publication_user | f | f | t | t | t | t | t | f Tables from schemas: "pub_test1_renamed" "pub_test2" ALTER SCHEMA pub_test1_renamed RENAME to pub_test1; \dRp+ testpub2_forschema - Publication testpub2_forschema - Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root ---------------------------+------------+---------+---------+---------+-----------+---------- - regress_publication_user | f | t | t | t | t | f + Publication testpub2_forschema + Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Sequences | Via root +--------------------------+------------+---------------+---------+---------+---------+-----------+-----------+---------- + regress_publication_user | f | f | t | t | t | t | t | f Tables from schemas: "pub_test1" "pub_test2" @@ -1407,10 +1911,10 @@ Tables from schemas: -- alter publication add schema ALTER PUBLICATION testpub1_forschema ADD TABLES IN SCHEMA pub_test2; \dRp+ testpub1_forschema - Publication testpub1_forschema - Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root ---------------------------+------------+---------+---------+---------+-----------+---------- - regress_publication_user | f | t | t | t | t | f + Publication testpub1_forschema + Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Sequences | Via root +--------------------------+------------+---------------+---------+---------+---------+-----------+-----------+---------- + regress_publication_user | f | f | t | t | t | t | t | f Tables from schemas: "pub_test1" "pub_test2" @@ -1419,10 +1923,10 @@ Tables from schemas: ALTER PUBLICATION testpub1_forschema ADD TABLES IN SCHEMA non_existent_schema; ERROR: schema "non_existent_schema" does not exist \dRp+ testpub1_forschema - Publication testpub1_forschema - Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root ---------------------------+------------+---------+---------+---------+-----------+---------- - regress_publication_user | f | t | t | t | t | f + Publication testpub1_forschema + Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Sequences | Via root +--------------------------+------------+---------------+---------+---------+---------+-----------+-----------+---------- + regress_publication_user | f | f | t | t | t | t | t | f Tables from schemas: "pub_test1" "pub_test2" @@ -1431,10 +1935,10 @@ Tables from schemas: ALTER PUBLICATION testpub1_forschema ADD TABLES IN SCHEMA pub_test1; ERROR: schema "pub_test1" is already member of publication "testpub1_forschema" \dRp+ testpub1_forschema - Publication testpub1_forschema - Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root ---------------------------+------------+---------+---------+---------+-----------+---------- - regress_publication_user | f | t | t | t | t | f + Publication testpub1_forschema + Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Sequences | Via root +--------------------------+------------+---------------+---------+---------+---------+-----------+-----------+---------- + regress_publication_user | f | f | t | t | t | t | t | f Tables from schemas: "pub_test1" "pub_test2" @@ -1442,10 +1946,10 @@ Tables from schemas: -- alter publication drop schema ALTER PUBLICATION testpub1_forschema DROP TABLES IN SCHEMA pub_test2; \dRp+ testpub1_forschema - Publication testpub1_forschema - Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root ---------------------------+------------+---------+---------+---------+-----------+---------- - regress_publication_user | f | t | t | t | t | f + Publication testpub1_forschema + Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Sequences | Via root +--------------------------+------------+---------------+---------+---------+---------+-----------+-----------+---------- + regress_publication_user | f | f | t | t | t | t | t | f Tables from schemas: "pub_test1" @@ -1453,10 +1957,10 @@ Tables from schemas: ALTER PUBLICATION testpub1_forschema DROP TABLES IN SCHEMA pub_test2; ERROR: tables from schema "pub_test2" are not part of the publication \dRp+ testpub1_forschema - Publication testpub1_forschema - Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root ---------------------------+------------+---------+---------+---------+-----------+---------- - regress_publication_user | f | t | t | t | t | f + Publication testpub1_forschema + Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Sequences | Via root +--------------------------+------------+---------------+---------+---------+---------+-----------+-----------+---------- + regress_publication_user | f | f | t | t | t | t | t | f Tables from schemas: "pub_test1" @@ -1464,29 +1968,29 @@ Tables from schemas: ALTER PUBLICATION testpub1_forschema DROP TABLES IN SCHEMA non_existent_schema; ERROR: schema "non_existent_schema" does not exist \dRp+ testpub1_forschema - Publication testpub1_forschema - Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root ---------------------------+------------+---------+---------+---------+-----------+---------- - regress_publication_user | f | t | t | t | t | f + Publication testpub1_forschema + Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Sequences | Via root +--------------------------+------------+---------------+---------+---------+---------+-----------+-----------+---------- + regress_publication_user | f | f | t | t | t | t | t | f Tables from schemas: "pub_test1" -- drop all schemas ALTER PUBLICATION testpub1_forschema DROP TABLES IN SCHEMA pub_test1; \dRp+ testpub1_forschema - Publication testpub1_forschema - Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root ---------------------------+------------+---------+---------+---------+-----------+---------- - regress_publication_user | f | t | t | t | t | f + Publication testpub1_forschema + Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Sequences | Via root +--------------------------+------------+---------------+---------+---------+---------+-----------+-----------+---------- + regress_publication_user | f | f | t | t | t | t | t | f (1 row) -- alter publication set multiple schema ALTER PUBLICATION testpub1_forschema SET TABLES IN SCHEMA pub_test1, pub_test2; \dRp+ testpub1_forschema - Publication testpub1_forschema - Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root ---------------------------+------------+---------+---------+---------+-----------+---------- - regress_publication_user | f | t | t | t | t | f + Publication testpub1_forschema + Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Sequences | Via root +--------------------------+------------+---------------+---------+---------+---------+-----------+-----------+---------- + regress_publication_user | f | f | t | t | t | t | t | f Tables from schemas: "pub_test1" "pub_test2" @@ -1495,10 +1999,10 @@ Tables from schemas: ALTER PUBLICATION testpub1_forschema SET TABLES IN SCHEMA non_existent_schema; ERROR: schema "non_existent_schema" does not exist \dRp+ testpub1_forschema - Publication testpub1_forschema - Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root ---------------------------+------------+---------+---------+---------+-----------+---------- - regress_publication_user | f | t | t | t | t | f + Publication testpub1_forschema + Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Sequences | Via root +--------------------------+------------+---------------+---------+---------+---------+-----------+-----------+---------- + regress_publication_user | f | f | t | t | t | t | t | f Tables from schemas: "pub_test1" "pub_test2" @@ -1507,10 +2011,10 @@ Tables from schemas: -- removing the duplicate schemas ALTER PUBLICATION testpub1_forschema SET TABLES IN SCHEMA pub_test1, pub_test1; \dRp+ testpub1_forschema - Publication testpub1_forschema - Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root ---------------------------+------------+---------+---------+---------+-----------+---------- - regress_publication_user | f | t | t | t | t | f + Publication testpub1_forschema + Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Sequences | Via root +--------------------------+------------+---------------+---------+---------+---------+-----------+-----------+---------- + regress_publication_user | f | f | t | t | t | t | t | f Tables from schemas: "pub_test1" @@ -1589,18 +2093,18 @@ SET client_min_messages = 'ERROR'; CREATE PUBLICATION testpub3_forschema; RESET client_min_messages; \dRp+ testpub3_forschema - Publication testpub3_forschema - Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root ---------------------------+------------+---------+---------+---------+-----------+---------- - regress_publication_user | f | t | t | t | t | f + Publication testpub3_forschema + Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Sequences | Via root +--------------------------+------------+---------------+---------+---------+---------+-----------+-----------+---------- + regress_publication_user | f | f | t | t | t | t | t | f (1 row) ALTER PUBLICATION testpub3_forschema SET TABLES IN SCHEMA pub_test1; \dRp+ testpub3_forschema - Publication testpub3_forschema - Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root ---------------------------+------------+---------+---------+---------+-----------+---------- - regress_publication_user | f | t | t | t | t | f + Publication testpub3_forschema + Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Sequences | Via root +--------------------------+------------+---------------+---------+---------+---------+-----------+-----------+---------- + regress_publication_user | f | f | t | t | t | t | t | f Tables from schemas: "pub_test1" @@ -1610,20 +2114,20 @@ CREATE PUBLICATION testpub_forschema_fortable FOR TABLES IN SCHEMA pub_test1, TA CREATE PUBLICATION testpub_fortable_forschema FOR TABLE pub_test2.tbl1, TABLES IN SCHEMA pub_test1; RESET client_min_messages; \dRp+ testpub_forschema_fortable - Publication testpub_forschema_fortable - Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root ---------------------------+------------+---------+---------+---------+-----------+---------- - regress_publication_user | f | t | t | t | t | f + Publication testpub_forschema_fortable + Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Sequences | Via root +--------------------------+------------+---------------+---------+---------+---------+-----------+-----------+---------- + regress_publication_user | f | f | t | t | t | t | t | f Tables: "pub_test2.tbl1" Tables from schemas: "pub_test1" \dRp+ testpub_fortable_forschema - Publication testpub_fortable_forschema - Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root ---------------------------+------------+---------+---------+---------+-----------+---------- - regress_publication_user | f | t | t | t | t | f + Publication testpub_fortable_forschema + Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Sequences | Via root +--------------------------+------------+---------------+---------+---------+---------+-----------+-----------+---------- + regress_publication_user | f | f | t | t | t | t | t | f Tables: "pub_test2.tbl1" Tables from schemas: @@ -1667,40 +2171,85 @@ CREATE SCHEMA sch1; CREATE SCHEMA sch2; CREATE TABLE sch1.tbl1 (a int) PARTITION BY RANGE(a); CREATE TABLE sch2.tbl1_part1 PARTITION OF sch1.tbl1 FOR VALUES FROM (1) to (10); +CREATE SEQUENCE sch1.seq1; +CREATE SEQUENCE sch2.seq2; -- Schema publication that does not include the schema that has the parent table CREATE PUBLICATION pub FOR TABLES IN SCHEMA sch2 WITH (PUBLISH_VIA_PARTITION_ROOT=1); +ALTER PUBLICATION pub ADD SEQUENCES IN SCHEMA sch2; SELECT * FROM pg_publication_tables; pubname | schemaname | tablename | attnames | rowfilter ---------+------------+------------+----------+----------- pub | sch2 | tbl1_part1 | {a} | (1 row) +SELECT * FROM pg_publication_sequences; + pubname | schemaname | sequencename +---------+------------+-------------- + pub | sch2 | seq2 +(1 row) + DROP PUBLICATION pub; -- Table publication that does not include the parent table CREATE PUBLICATION pub FOR TABLE sch2.tbl1_part1 WITH (PUBLISH_VIA_PARTITION_ROOT=1); +ALTER PUBLICATION pub ADD SEQUENCE sch2.seq2; SELECT * FROM pg_publication_tables; pubname | schemaname | tablename | attnames | rowfilter ---------+------------+------------+----------+----------- pub | sch2 | tbl1_part1 | {a} | (1 row) +SELECT * FROM pg_publication_sequences; + pubname | schemaname | sequencename +---------+------------+-------------- + pub | sch2 | seq2 +(1 row) + -- Table publication that includes both the parent table and the child table ALTER PUBLICATION pub ADD TABLE sch1.tbl1; +ALTER PUBLICATION pub ADD SEQUENCE sch1.seq1; SELECT * FROM pg_publication_tables; pubname | schemaname | tablename | attnames | rowfilter ---------+------------+-----------+----------+----------- pub | sch1 | tbl1 | {a} | (1 row) +SELECT * FROM pg_publication_sequences; + pubname | schemaname | sequencename +---------+------------+-------------- + pub | sch1 | seq1 + pub | sch2 | seq2 +(2 rows) + DROP PUBLICATION pub; -- Schema publication that does not include the schema that has the parent table CREATE PUBLICATION pub FOR TABLES IN SCHEMA sch2 WITH (PUBLISH_VIA_PARTITION_ROOT=0); +ALTER PUBLICATION pub ADD SEQUENCE sch1.seq1; SELECT * FROM pg_publication_tables; pubname | schemaname | tablename | attnames | rowfilter ---------+------------+------------+----------+----------- pub | sch2 | tbl1_part1 | {a} | (1 row) +SELECT * FROM pg_publication_sequences; + pubname | schemaname | sequencename +---------+------------+-------------- + pub | sch1 | seq1 +(1 row) + +DROP PUBLICATION pub; +-- Sequence publication +CREATE PUBLICATION pub FOR SEQUENCE sch2.seq2; +SELECT * FROM pg_publication_tables; + pubname | schemaname | tablename | attnames | rowfilter +---------+------------+-----------+----------+----------- +(0 rows) + +SELECT * FROM pg_publication_sequences; + pubname | schemaname | sequencename +---------+------------+-------------- + pub | sch2 | seq2 +(1 row) + DROP PUBLICATION pub; -- Table publication that does not include the parent table CREATE PUBLICATION pub FOR TABLE sch2.tbl1_part1 WITH (PUBLISH_VIA_PARTITION_ROOT=0); @@ -1710,14 +2259,26 @@ SELECT * FROM pg_publication_tables; pub | sch2 | tbl1_part1 | {a} | (1 row) +SELECT * FROM pg_publication_sequences; + pubname | schemaname | sequencename +---------+------------+-------------- +(0 rows) + -- Table publication that includes both the parent table and the child table ALTER PUBLICATION pub ADD TABLE sch1.tbl1; +ALTER PUBLICATION pub ADD SEQUENCES IN SCHEMA sch2; SELECT * FROM pg_publication_tables; pubname | schemaname | tablename | attnames | rowfilter ---------+------------+------------+----------+----------- pub | sch2 | tbl1_part1 | {a} | (1 row) +SELECT * FROM pg_publication_sequences; + pubname | schemaname | sequencename +---------+------------+-------------- + pub | sch2 | seq2 +(1 row) + DROP PUBLICATION pub; DROP TABLE sch2.tbl1_part1; DROP TABLE sch1.tbl1; @@ -1733,9 +2294,81 @@ SELECT * FROM pg_publication_tables; pub | sch1 | tbl1 | {a} | (1 row) +SELECT * FROM pg_publication_sequences; + pubname | schemaname | sequencename +---------+------------+-------------- +(0 rows) + +DROP PUBLICATION pub; +-- Schema publication +CREATE PUBLICATION pub FOR SEQUENCE sch2.seq2; +SELECT * FROM pg_publication_tables; + pubname | schemaname | tablename | attnames | rowfilter +---------+------------+-----------+----------+----------- +(0 rows) + +SELECT * FROM pg_publication_sequences; + pubname | schemaname | sequencename +---------+------------+-------------- + pub | sch2 | seq2 +(1 row) + +DROP PUBLICATION pub; +-- Sequence publication +CREATE PUBLICATION pub FOR SEQUENCES IN SCHEMA sch2; +SELECT * FROM pg_publication_tables; + pubname | schemaname | tablename | attnames | rowfilter +---------+------------+-----------+----------+----------- +(0 rows) + +SELECT * FROM pg_publication_sequences; + pubname | schemaname | sequencename +---------+------------+-------------- + pub | sch2 | seq2 +(1 row) + +ALTER PUBLICATION pub ADD SEQUENCE sch1.seq1; +SELECT * FROM pg_publication_tables; + pubname | schemaname | tablename | attnames | rowfilter +---------+------------+-----------+----------+----------- +(0 rows) + +SELECT * FROM pg_publication_sequences; + pubname | schemaname | sequencename +---------+------------+-------------- + pub | sch1 | seq1 + pub | sch2 | seq2 +(2 rows) + +ALTER PUBLICATION pub DROP SEQUENCE sch1.seq1; +SELECT * FROM pg_publication_tables; + pubname | schemaname | tablename | attnames | rowfilter +---------+------------+-----------+----------+----------- +(0 rows) + +SELECT * FROM pg_publication_sequences; + pubname | schemaname | sequencename +---------+------------+-------------- + pub | sch2 | seq2 +(1 row) + +ALTER PUBLICATION pub ADD SEQUENCES IN SCHEMA sch1; +SELECT * FROM pg_publication_tables; + pubname | schemaname | tablename | attnames | rowfilter +---------+------------+-----------+----------+----------- +(0 rows) + +SELECT * FROM pg_publication_sequences; + pubname | schemaname | sequencename +---------+------------+-------------- + pub | sch1 | seq1 + pub | sch2 | seq2 +(2 rows) + RESET client_min_messages; DROP PUBLICATION pub; DROP TABLE sch1.tbl1; +DROP SEQUENCE sch1.seq1, sch2.seq2; DROP SCHEMA sch1 cascade; DROP SCHEMA sch2 cascade; RESET SESSION AUTHORIZATION; diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 1442c43d9c5..92cbe4fa71b 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1441,6 +1441,14 @@ pg_prepared_xacts| SELECT p.transaction, FROM ((pg_prepared_xact() p(transaction, gid, prepared, ownerid, dbid) LEFT JOIN pg_authid u ON ((p.ownerid = u.oid))) LEFT JOIN pg_database d ON ((p.dbid = d.oid))); +pg_publication_sequences| SELECT p.pubname, + n.nspname AS schemaname, + c.relname AS sequencename + FROM pg_publication p, + LATERAL pg_get_publication_sequences((p.pubname)::text) gps(relid), + (pg_class c + JOIN pg_namespace n ON ((n.oid = c.relnamespace))) + WHERE (c.oid = gps.relid); pg_publication_tables| SELECT p.pubname, n.nspname AS schemaname, c.relname AS tablename, diff --git a/src/test/regress/sql/object_address.sql b/src/test/regress/sql/object_address.sql index 1a6c61f49d5..5c6cdeb05df 100644 --- a/src/test/regress/sql/object_address.sql +++ b/src/test/regress/sql/object_address.sql @@ -50,6 +50,7 @@ CREATE TRANSFORM FOR int LANGUAGE SQL ( SET client_min_messages = 'ERROR'; CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable; CREATE PUBLICATION addr_pub_schema FOR TABLES IN SCHEMA addr_nsp; +CREATE PUBLICATION addr_pub_schema2 FOR SEQUENCES IN SCHEMA addr_nsp; RESET client_min_messages; CREATE SUBSCRIPTION regress_addr_sub CONNECTION '' PUBLICATION bar WITH (connect = false, slot_name = NONE); CREATE STATISTICS addr_nsp.gentable_stat ON a, b FROM addr_nsp.gentable; @@ -206,7 +207,8 @@ WITH objects (type, name, args) AS (VALUES ('transform', '{int}', '{sql}'), ('access method', '{btree}', '{}'), ('publication', '{addr_pub}', '{}'), - ('publication namespace', '{addr_nsp}', '{addr_pub_schema}'), + ('publication namespace', '{addr_nsp}', '{addr_pub_schema, t}'), + ('publication namespace', '{addr_nsp}', '{addr_pub_schema2, s}'), ('publication relation', '{addr_nsp, gentable}', '{addr_pub}'), ('subscription', '{regress_addr_sub}', '{}'), ('statistics object', '{addr_nsp, gentable_stat}', '{}') @@ -227,6 +229,7 @@ ORDER BY addr1.classid, addr1.objid, addr1.objsubid; DROP FOREIGN DATA WRAPPER addr_fdw CASCADE; DROP PUBLICATION addr_pub; DROP PUBLICATION addr_pub_schema; +DROP PUBLICATION addr_pub_schema2; DROP SUBSCRIPTION regress_addr_sub; DROP SCHEMA addr_nsp CASCADE; diff --git a/src/test/regress/sql/publication.sql b/src/test/regress/sql/publication.sql index d5051a5e746..016b58c67ec 100644 --- a/src/test/regress/sql/publication.sql +++ b/src/test/regress/sql/publication.sql @@ -27,7 +27,7 @@ CREATE PUBLICATION testpub_xxx WITH (publish_via_partition_root = 'true', publis \dRp -ALTER PUBLICATION testpub_default SET (publish = 'insert, update, delete'); +ALTER PUBLICATION testpub_default SET (publish = 'insert, update, delete, sequence'); \dRp @@ -46,6 +46,8 @@ ALTER PUBLICATION testpub_foralltables SET (publish = 'insert, update'); CREATE TABLE testpub_tbl2 (id serial primary key, data text); -- fail - can't add to for all tables publication ALTER PUBLICATION testpub_foralltables ADD TABLE testpub_tbl2; +-- fail - can't add a table using ADD SEQUENCE command +ALTER PUBLICATION testpub_foralltables ADD SEQUENCE testpub_tbl2; -- fail - can't drop from all tables publication ALTER PUBLICATION testpub_foralltables DROP TABLE testpub_tbl2; -- fail - can't add to for all tables publication @@ -117,6 +119,188 @@ RESET client_min_messages; DROP TABLE testpub_tbl3, testpub_tbl3a; DROP PUBLICATION testpub3, testpub4; +--- adding sequences +CREATE SEQUENCE testpub_seq0; +CREATE SEQUENCE pub_test.testpub_seq1; + +SET client_min_messages = 'ERROR'; +CREATE PUBLICATION testpub_forallsequences FOR ALL SEQUENCES WITH (publish = 'sequence'); +RESET client_min_messages; +ALTER PUBLICATION testpub_forallsequences SET (publish = 'insert, sequence'); + +CREATE SEQUENCE testpub_seq2; +-- fail - can't add to for all sequences publication +ALTER PUBLICATION testpub_forallsequences ADD SEQUENCE testpub_seq2; +-- fail - can't drop from all sequences publication +ALTER PUBLICATION testpub_forallsequences DROP SEQUENCE testpub_seq2; +-- fail - can't add to for all sequences publication +ALTER PUBLICATION testpub_forallsequences SET SEQUENCE pub_test.testpub_seq1; + +-- fail - can't add schema to 'FOR ALL SEQUENCES' publication +ALTER PUBLICATION testpub_forallsequences ADD SEQUENCES IN SCHEMA pub_test; +-- fail - can't drop schema from 'FOR ALL SEQUENCES' publication +ALTER PUBLICATION testpub_forallsequences DROP SEQUENCES IN SCHEMA pub_test; +-- fail - can't set schema to 'FOR ALL SEQUENCES' publication +ALTER PUBLICATION testpub_forallsequences SET SEQUENCES IN SCHEMA pub_test; + +SET client_min_messages = 'ERROR'; +CREATE PUBLICATION testpub_forsequence FOR SEQUENCE testpub_seq0; +RESET client_min_messages; +-- should be able to add schema to 'FOR SEQUENCE' publication +ALTER PUBLICATION testpub_forsequence ADD SEQUENCES IN SCHEMA pub_test; +\dRp+ testpub_forsequence +-- add sequence from the schema we already added +ALTER PUBLICATION testpub_forsequence ADD SEQUENCE pub_test.testpub_seq1; +-- fail - can't add sequence using ADD TABLE command +ALTER PUBLICATION testpub_forsequence ADD TABLE pub_test.testpub_seq1; +-- should be able to drop schema from 'FOR SEQUENCE' publication +ALTER PUBLICATION testpub_forsequence DROP SEQUENCES IN SCHEMA pub_test; +\dRp+ testpub_forsequence +-- should be able to set schema to 'FOR SEQUENCE' publication +ALTER PUBLICATION testpub_forsequence SET SEQUENCES IN SCHEMA pub_test; +\dRp+ testpub_forsequence + +SET client_min_messages = 'ERROR'; +CREATE PUBLICATION testpub_forschema FOR SEQUENCES IN SCHEMA pub_test; +RESET client_min_messages; +-- should be able to set sequence to schema publication +ALTER PUBLICATION testpub_forschema SET SEQUENCE pub_test.testpub_seq1; +\dRp+ testpub_forschema + +SELECT pubname, puballtables, puballsequences FROM pg_publication WHERE pubname = 'testpub_forallsequences'; +\d+ pub_test.testpub_seq1 +\dRp+ testpub_forallsequences +DROP SEQUENCE testpub_seq0, pub_test.testpub_seq1, testpub_seq2; +DROP PUBLICATION testpub_forallsequences, testpub_forsequence, testpub_forschema; + + +-- publication testing multiple sequences at the same time +CREATE SEQUENCE testpub_seq1; +CREATE SEQUENCE testpub_seq2; + +SET client_min_messages = 'ERROR'; +CREATE PUBLICATION testpub_multi FOR SEQUENCE testpub_seq1, testpub_seq2; +RESET client_min_messages; + +\dRp+ testpub_multi + +DROP PUBLICATION testpub_multi; +DROP SEQUENCE testpub_seq1; +DROP SEQUENCE testpub_seq2; + + +-- Publication mixing tables and sequences +SET client_min_messages = 'ERROR'; +CREATE PUBLICATION testpub_mix; +RESET client_min_messages; + +CREATE SEQUENCE testpub_seq1; +CREATE SEQUENCE pub_test.testpub_seq2; + +ALTER PUBLICATION testpub_mix ADD SEQUENCE testpub_seq1, TABLE testpub_tbl1; +\dRp+ testpub_mix + +ALTER PUBLICATION testpub_mix ADD SEQUENCES IN SCHEMA pub_test, TABLES IN SCHEMA pub_test; +\dRp+ testpub_mix + +ALTER PUBLICATION testpub_mix DROP SEQUENCES IN SCHEMA pub_test; +\dRp+ testpub_mix + +ALTER PUBLICATION testpub_mix DROP TABLES IN SCHEMA pub_test; +\dRp+ testpub_mix + +DROP PUBLICATION testpub_mix; +DROP SEQUENCE testpub_seq1; +DROP SEQUENCE pub_test.testpub_seq2; + + +-- make sure we replicate only the correct relation type +CREATE SCHEMA pub_test1; +CREATE SEQUENCE pub_test1.test_seq1; +CREATE TABLE pub_test1.test_tbl1 (a int primary key, b int); + +CREATE SCHEMA pub_test2; +CREATE SEQUENCE pub_test2.test_seq2; +CREATE TABLE pub_test2.test_tbl2 (a int primary key, b int); + +SET client_min_messages = 'ERROR'; +CREATE PUBLICATION testpub_schemas; +RESET client_min_messages; + +-- add tables from one schema, sequences from the other +ALTER PUBLICATION testpub_schemas ADD TABLES IN SCHEMA pub_test2; +ALTER PUBLICATION testpub_schemas ADD SEQUENCES IN SCHEMA pub_test1; + +\dRp+ testpub_schemas + +\dn+ pub_test1 +\dn+ pub_test2 + +\d+ pub_test1.test_seq1; +\d+ pub_test1.test_tbl1; + +\d+ pub_test2.test_seq2; +\d+ pub_test2.test_tbl2; + +-- add the other object type from each schema +ALTER PUBLICATION testpub_schemas ADD TABLES IN SCHEMA pub_test1; +ALTER PUBLICATION testpub_schemas ADD SEQUENCES IN SCHEMA pub_test2; + +\dRp+ testpub_schemas + +\dn+ pub_test1 +\dn+ pub_test2 + +\d+ pub_test1.test_seq1; +\d+ pub_test1.test_tbl1; + +\d+ pub_test2.test_seq2; +\d+ pub_test2.test_tbl2; + +-- now drop the object type added first +ALTER PUBLICATION testpub_schemas DROP TABLES IN SCHEMA pub_test2; +ALTER PUBLICATION testpub_schemas DROP SEQUENCES IN SCHEMA pub_test1; + +\dRp+ testpub_schemas + +\dn+ pub_test1 +\dn+ pub_test2 + +\d+ pub_test1.test_seq1; +\d+ pub_test1.test_tbl1; + +\d+ pub_test2.test_seq2; +\d+ pub_test2.test_tbl2; + +-- add a different schema (not including the already published sequences) +ALTER PUBLICATION testpub_schemas ADD TABLE pub_test2.test_tbl2; +ALTER PUBLICATION testpub_schemas ADD SEQUENCE pub_test1.test_seq1; + +\dRp+ testpub_schemas + +\d+ pub_test1.test_seq1; +\d+ pub_test1.test_tbl1; + +\d+ pub_test2.test_seq2; +\d+ pub_test2.test_tbl2; + +-- now drop the explicitly added objects again +ALTER PUBLICATION testpub_schemas DROP TABLE pub_test2.test_tbl2; +ALTER PUBLICATION testpub_schemas DROP SEQUENCE pub_test1.test_seq1; + +\dRp+ testpub_schemas + +\d+ pub_test1.test_seq1; +\d+ pub_test1.test_tbl1; + +\d+ pub_test2.test_seq2; +\d+ pub_test2.test_tbl2; + +DROP PUBLICATION testpub_schemas; +DROP TABLE pub_test1.test_tbl1, pub_test2.test_tbl2; +DROP SEQUENCE pub_test1.test_seq1, pub_test2.test_seq2; +DROP SCHEMA pub_test1, pub_test2; + -- Tests for partitioned tables SET client_min_messages = 'ERROR'; CREATE PUBLICATION testpub_forparted; @@ -1052,32 +1236,51 @@ CREATE SCHEMA sch1; CREATE SCHEMA sch2; CREATE TABLE sch1.tbl1 (a int) PARTITION BY RANGE(a); CREATE TABLE sch2.tbl1_part1 PARTITION OF sch1.tbl1 FOR VALUES FROM (1) to (10); +CREATE SEQUENCE sch1.seq1; +CREATE SEQUENCE sch2.seq2; -- Schema publication that does not include the schema that has the parent table CREATE PUBLICATION pub FOR TABLES IN SCHEMA sch2 WITH (PUBLISH_VIA_PARTITION_ROOT=1); +ALTER PUBLICATION pub ADD SEQUENCES IN SCHEMA sch2; SELECT * FROM pg_publication_tables; +SELECT * FROM pg_publication_sequences; DROP PUBLICATION pub; -- Table publication that does not include the parent table CREATE PUBLICATION pub FOR TABLE sch2.tbl1_part1 WITH (PUBLISH_VIA_PARTITION_ROOT=1); +ALTER PUBLICATION pub ADD SEQUENCE sch2.seq2; SELECT * FROM pg_publication_tables; +SELECT * FROM pg_publication_sequences; -- Table publication that includes both the parent table and the child table ALTER PUBLICATION pub ADD TABLE sch1.tbl1; +ALTER PUBLICATION pub ADD SEQUENCE sch1.seq1; SELECT * FROM pg_publication_tables; +SELECT * FROM pg_publication_sequences; DROP PUBLICATION pub; -- Schema publication that does not include the schema that has the parent table CREATE PUBLICATION pub FOR TABLES IN SCHEMA sch2 WITH (PUBLISH_VIA_PARTITION_ROOT=0); +ALTER PUBLICATION pub ADD SEQUENCE sch1.seq1; +SELECT * FROM pg_publication_tables; +SELECT * FROM pg_publication_sequences; + +DROP PUBLICATION pub; +-- Sequence publication +CREATE PUBLICATION pub FOR SEQUENCE sch2.seq2; SELECT * FROM pg_publication_tables; +SELECT * FROM pg_publication_sequences; DROP PUBLICATION pub; -- Table publication that does not include the parent table CREATE PUBLICATION pub FOR TABLE sch2.tbl1_part1 WITH (PUBLISH_VIA_PARTITION_ROOT=0); SELECT * FROM pg_publication_tables; +SELECT * FROM pg_publication_sequences; -- Table publication that includes both the parent table and the child table ALTER PUBLICATION pub ADD TABLE sch1.tbl1; +ALTER PUBLICATION pub ADD SEQUENCES IN SCHEMA sch2; SELECT * FROM pg_publication_tables; +SELECT * FROM pg_publication_sequences; DROP PUBLICATION pub; DROP TABLE sch2.tbl1_part1; @@ -1090,10 +1293,36 @@ CREATE TABLE sch1.tbl1_part3 (a int) PARTITION BY RANGE(a); ALTER TABLE sch1.tbl1 ATTACH PARTITION sch1.tbl1_part3 FOR VALUES FROM (20) to (30); CREATE PUBLICATION pub FOR TABLES IN SCHEMA sch1 WITH (PUBLISH_VIA_PARTITION_ROOT=1); SELECT * FROM pg_publication_tables; +SELECT * FROM pg_publication_sequences; + +DROP PUBLICATION pub; +-- Schema publication +CREATE PUBLICATION pub FOR SEQUENCE sch2.seq2; +SELECT * FROM pg_publication_tables; +SELECT * FROM pg_publication_sequences; + +DROP PUBLICATION pub; +-- Sequence publication +CREATE PUBLICATION pub FOR SEQUENCES IN SCHEMA sch2; +SELECT * FROM pg_publication_tables; +SELECT * FROM pg_publication_sequences; + +ALTER PUBLICATION pub ADD SEQUENCE sch1.seq1; +SELECT * FROM pg_publication_tables; +SELECT * FROM pg_publication_sequences; + +ALTER PUBLICATION pub DROP SEQUENCE sch1.seq1; +SELECT * FROM pg_publication_tables; +SELECT * FROM pg_publication_sequences; + +ALTER PUBLICATION pub ADD SEQUENCES IN SCHEMA sch1; +SELECT * FROM pg_publication_tables; +SELECT * FROM pg_publication_sequences; RESET client_min_messages; DROP PUBLICATION pub; DROP TABLE sch1.tbl1; +DROP SEQUENCE sch1.seq1, sch2.seq2; DROP SCHEMA sch1 cascade; DROP SCHEMA sch2 cascade; diff --git a/src/test/subscription/t/034_sequences.pl b/src/test/subscription/t/034_sequences.pl new file mode 100644 index 00000000000..1744acad21d --- /dev/null +++ b/src/test/subscription/t/034_sequences.pl @@ -0,0 +1,207 @@ + +# Copyright (c) 2021, PostgreSQL Global Development Group + +# This tests that sequences are replicated correctly by logical replication +use strict; +use warnings; +use PostgreSQL::Test::Cluster; +use PostgreSQL::Test::Utils; +use Test::More; + +# Initialize publisher node +my $node_publisher = PostgreSQL::Test::Cluster->new('publisher'); +$node_publisher->init(allows_streaming => 'logical'); +$node_publisher->start; + +# Create subscriber node +my $node_subscriber = PostgreSQL::Test::Cluster->new('subscriber'); +$node_subscriber->init(allows_streaming => 'logical'); +$node_subscriber->start; + +# Create some preexisting content on publisher +my $ddl = qq( + CREATE TABLE seq_test (v BIGINT); + CREATE SEQUENCE s; +); + +# Setup structure on the publisher +$node_publisher->safe_psql('postgres', $ddl); + +# Create some the same structure on subscriber, and an extra sequence that +# we'll create on the publisher later +$ddl = qq( + CREATE TABLE seq_test (v BIGINT); + CREATE SEQUENCE s; + CREATE SEQUENCE s2; +); + +$node_subscriber->safe_psql('postgres', $ddl); + +# Setup logical replication +my $publisher_connstr = $node_publisher->connstr . ' dbname=postgres'; +$node_publisher->safe_psql('postgres', + "CREATE PUBLICATION seq_pub"); + +$node_publisher->safe_psql('postgres', + "ALTER PUBLICATION seq_pub ADD SEQUENCE s"); + +$node_subscriber->safe_psql('postgres', + "CREATE SUBSCRIPTION seq_sub CONNECTION '$publisher_connstr' PUBLICATION seq_pub" +); + +$node_publisher->wait_for_catchup('seq_sub'); + +# Wait for initial sync to finish as well +my $synced_query = + "SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('s', 'r');"; +$node_subscriber->poll_query_until('postgres', $synced_query) + or die "Timed out while waiting for subscriber to synchronize data"; + +# Insert initial test data +$node_publisher->safe_psql( + 'postgres', qq( + -- generate a number of values using the sequence + INSERT INTO seq_test SELECT nextval('s') FROM generate_series(1,100); +)); + +$node_publisher->wait_for_catchup('seq_sub'); + +# Check the data on subscriber +my $result = $node_subscriber->safe_psql( + 'postgres', qq( + SELECT * FROM s; +)); + +is( $result, '132|0|t', + 'initial test data replicated'); + + +# advance the sequence in a rolled-back transaction - the rollback +# does not wait for the replication, so we could see any intermediate state +# so do something else after the test, to ensure we wait for everything +$node_publisher->safe_psql( + 'postgres', qq( + BEGIN; + INSERT INTO seq_test SELECT nextval('s') FROM generate_series(1,100); + ROLLBACK; + INSERT INTO seq_test VALUES (-1); +)); + +$node_publisher->wait_for_catchup('seq_sub'); + +# Check the data on subscriber +$result = $node_subscriber->safe_psql( + 'postgres', qq( + SELECT * FROM s; +)); + +is( $result, '231|0|t', + 'advance sequence in rolled-back transaction'); + + +# create a new sequence and roll it back - should not be replicated, due to +# the transactional behavior +$node_publisher->safe_psql( + 'postgres', qq( + BEGIN; + CREATE SEQUENCE s2; + ALTER PUBLICATION seq_pub ADD SEQUENCE s2; + INSERT INTO seq_test SELECT nextval('s2') FROM generate_series(1,100); + ROLLBACK; +)); + +# Refresh publication after sequence is added to publication +$result = $node_subscriber->safe_psql( + 'postgres', qq( + ALTER SUBSCRIPTION seq_sub REFRESH PUBLICATION +)); + +$node_publisher->wait_for_catchup('seq_sub'); + +# Check the data on subscriber +$result = $node_subscriber->safe_psql( + 'postgres', qq( + SELECT * FROM s2; +)); + +is( $result, '1|0|f', + 'create new sequence and roll it back'); + + +# create a new sequence, advance it in a rolled-back transaction, but commit +# the create - the advance should be replicated nevertheless +$node_publisher->safe_psql( + 'postgres', qq( + BEGIN; + ALTER SEQUENCE s RESTART WITH 1000; + SAVEPOINT sp1; + INSERT INTO seq_test SELECT nextval('s') FROM generate_series(1,100); + ROLLBACK TO sp1; + COMMIT; +)); + +$node_publisher->wait_for_catchup('seq_sub'); + +# Wait for sync of the second sequence we just added to finish +$synced_query = + "SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('s', 'r');"; +$node_subscriber->poll_query_until('postgres', $synced_query) + or die "Timed out while waiting for subscriber to synchronize data"; + +# Check the data on subscriber +$result = $node_subscriber->safe_psql( + 'postgres', qq( + SELECT * FROM s; +)); + +is( $result, '1131|0|t', + 'create sequence, advance it in rolled-back transaction, but commit the create'); + + +# advance the new sequence in a transaction, and roll it back - the rollback +# does not wait for the replication, so we could see any intermediate state +# so do something else after the test, to ensure we wait for everything +$node_publisher->safe_psql( + 'postgres', qq( + BEGIN; + INSERT INTO seq_test SELECT nextval('s') FROM generate_series(1,100); + ROLLBACK; + INSERT INTO seq_test VALUES (-1); +)); + +$node_publisher->wait_for_catchup('seq_sub'); + +# Check the data on subscriber +$result = $node_subscriber->safe_psql( + 'postgres', qq( + SELECT * FROM s; +)); + +is( $result, '1230|0|t', + 'advance the new sequence in a transaction and roll it back'); + + +# advance the sequence in a subtransaction - the subtransaction gets rolled +# back, but commit the main one - the changes should still be replicated +$node_publisher->safe_psql( + 'postgres', qq( + BEGIN; + SAVEPOINT s1; + INSERT INTO seq_test SELECT nextval('s') FROM generate_series(1,100); + ROLLBACK TO s1; + COMMIT; +)); + +$node_publisher->wait_for_catchup('seq_sub'); + +# Check the data on subscriber +$result = $node_subscriber->safe_psql( + 'postgres', qq( + SELECT * FROM s; +)); + +is( $result, '1329|0|t', + 'advance sequence in a subtransaction'); + + +done_testing(); diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index b74f87d2cbf..877fded6b58 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -1499,6 +1499,7 @@ LogicalRepRelId LogicalRepRelMapEntry LogicalRepRelation LogicalRepRollbackPreparedTxnData +LogicalRepSequence LogicalRepStreamAbortData LogicalRepTupleData LogicalRepTyp -- 2.41.0