From b48b763dd22188fc0b1b7a2933d430b7719d090d Mon Sep 17 00:00:00 2001 From: Vignesh C Date: Fri, 7 Jun 2024 21:12:12 +0530 Subject: [PATCH v20240608] Enable sequence synchronization when creating or modifying subscriptions. Subscriber modifications: Enable sequence synchronization during subscription creation or alteration using the following syntax: CREATE SUBSCRIPTION ... WITH (sequences=true); When a subscription is created with the sequence option enabled, the sequence list from the specified publications in the subscription will be retrieved from the publisher. Each sequence's data will then be copied from the remote publisher sequence to the local subscriber sequence. Since all of these operations are done within a single transaction, if any errors occur during the copying process, the entire transaction will be rolled back. To refresh sequences, use the syntax: ALTER SUBSCRIPTION REFRESH SEQUENCES; During sequence refresh, the sequence list is updated by removing stale sequences and adding any missing sequences. The updated sequence list is then re-synchronized. A new catalog table, pg_subscription_seq, has been introduced for mapping subscriptions to sequences. Additionally, the sequence LSN (Log Sequence Number) is stored, facilitating determination of sequence changes occurring before or after the returned sequence state. Publisher changes: 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. --- doc/src/sgml/catalogs.sgml | 83 ++ doc/src/sgml/ref/alter_publication.sgml | 25 +- doc/src/sgml/ref/alter_subscription.sgml | 20 +- doc/src/sgml/ref/create_publication.sgml | 51 +- doc/src/sgml/ref/create_subscription.sgml | 13 + doc/src/sgml/system-views.sgml | 71 ++ src/backend/catalog/objectaddress.c | 45 +- src/backend/catalog/pg_publication.c | 336 +++++- src/backend/catalog/pg_subscription.c | 56 + src/backend/catalog/system_views.sql | 10 + src/backend/commands/publicationcmds.c | 422 +++++-- src/backend/commands/sequence.c | 156 ++- src/backend/commands/subscriptioncmds.c | 174 ++- src/backend/executor/execReplication.c | 4 +- src/backend/parser/gram.y | 82 +- src/backend/replication/logical/Makefile | 1 + src/backend/replication/logical/meson.build | 1 + .../replication/logical/sequencesync.c | 357 ++++++ src/backend/replication/pgoutput/pgoutput.c | 11 +- src/backend/utils/cache/relcache.c | 28 +- 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 | 43 +- src/include/catalog/Makefile | 3 +- src/include/catalog/meson.build | 1 + src/include/catalog/pg_proc.dat | 13 + src/include/catalog/pg_publication.h | 26 +- .../catalog/pg_publication_namespace.h | 12 +- src/include/catalog/pg_subscription.h | 4 + src/include/catalog/pg_subscription_seq.h | 67 ++ src/include/commands/sequence.h | 1 + src/include/nodes/parsenodes.h | 11 +- src/test/regress/expected/object_address.out | 20 +- src/test/regress/expected/oidjoins.out | 2 + 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 +++- 41 files changed, 3369 insertions(+), 496 deletions(-) create mode 100644 src/backend/replication/logical/sequencesync.c create mode 100644 src/include/catalog/pg_subscription_seq.h diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml index 15f6255d86..38b43d57e1 100644 --- a/doc/src/sgml/catalogs.sgml +++ b/doc/src/sgml/catalogs.sgml @@ -320,6 +320,11 @@ relation state for subscriptions + + pg_subscription_seq + sequence information for subscriptions + + pg_tablespace tablespaces within this database cluster @@ -6449,6 +6454,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 + + @@ -8181,6 +8196,74 @@ SCRAM-SHA-256$<iteration count>:&l + + <structname>pg_subscription_seq</structname> + + + pg_subscription_seq + + + + The catalog pg_subscription_seq contains the + information for each synchronized sequence in each subscription. This is a + many-to-many mapping. + + + + This catalog only contains sequence known to the subscription after running + either CREATE SUBSCRIPTION or + ALTER SUBSCRIPTION ... REFRESH + SEQUENCES. + + + + <structname>pg_subscription_seq</structname> Columns + + + + + Column Type + + + Description + + + + + + + + sssubid oid + (references pg_subscription.oid) + + + Reference to subscription + + + + + + ssseqid oid + (references pg_class.oid) + + + Reference to sequence + + + + + + sssublsn pg_lsn + + + Remote LSN of the sequence for synchronization coordination. + + + + +
+
+ <structname>pg_tablespace</structname> diff --git a/doc/src/sgml/ref/alter_publication.sgml b/doc/src/sgml/ref/alter_publication.sgml index 44ae7e0e87..8caf2135fc 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 476f195622..d423128541 100644 --- a/doc/src/sgml/ref/alter_subscription.sgml +++ b/doc/src/sgml/ref/alter_subscription.sgml @@ -27,6 +27,7 @@ ALTER SUBSCRIPTION name ADD PUBLICA ALTER SUBSCRIPTION name DROP PUBLICATION publication_name [, ...] [ WITH ( publication_option [= value] [, ... ] ) ] ALTER SUBSCRIPTION name REFRESH PUBLICATION [ WITH ( refresh_option [= value] [, ... ] ) ] ALTER SUBSCRIPTION name ENABLE +ALTER SUBSCRIPTION name REFRESH SEQUENCES ALTER SUBSCRIPTION name DISABLE ALTER SUBSCRIPTION name SET ( subscription_parameter [= value] [, ... ] ) ALTER SUBSCRIPTION name SKIP ( skip_option = value ) @@ -153,8 +154,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 that were added to the subscribed-to + publications since CREATE SUBSCRIPTION or the last invocation of REFRESH PUBLICATION. @@ -194,6 +195,16 @@ ALTER SUBSCRIPTION name RENAME TO < + + REFRESH SEQUENCES + + + Fetch missing sequences information from publisher and re-synchronize the + sequence data from the publisher. + + + + ENABLE @@ -228,8 +239,9 @@ ALTER SUBSCRIPTION name RENAME TO < disable_on_error, password_required, run_as_owner, - origin, and - failover. + origin, + failover, and + sequences. Only a superuser can set password_required = false. diff --git a/doc/src/sgml/ref/create_publication.sgml b/doc/src/sgml/ref/create_publication.sgml index fd9c5deac9..ba578eb9aa 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/ref/create_subscription.sgml b/doc/src/sgml/ref/create_subscription.sgml index 740b7d9421..7e1da48da3 100644 --- a/doc/src/sgml/ref/create_subscription.sgml +++ b/doc/src/sgml/ref/create_subscription.sgml @@ -428,6 +428,19 @@ CREATE SUBSCRIPTION subscription_name + + + sequences (boolean) + + + Specifies whether the subscription will request the publisher to + synchronize sequences while creating subscription. Note that for + sequences to be synchronized, the sequence also has to be added to + the publication. + The default is false. + + + diff --git a/doc/src/sgml/system-views.sgml b/doc/src/sgml/system-views.sgml index 8c18bea902..5591ffd78a 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 7b536ac6fd..6d68a51cc2 100644 --- a/src/backend/catalog/objectaddress.c +++ b/src/backend/catalog/objectaddress.c @@ -1920,12 +1920,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)) @@ -1938,10 +1940,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), @@ -2216,7 +2220,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, @@ -2249,6 +2252,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, @@ -2321,6 +2325,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; @@ -2824,11 +2830,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; @@ -2864,6 +2871,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; } @@ -3934,15 +3949,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; } @@ -5785,18 +5802,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 0602398a54..ab57ab6873 100644 --- a/src/backend/catalog/pg_publication.c +++ b/src/backend/catalog/pg_publication.c @@ -58,9 +58,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", @@ -137,7 +138,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; @@ -228,6 +230,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. @@ -248,7 +296,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); @@ -334,7 +382,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; @@ -603,7 +653,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; @@ -615,6 +665,8 @@ publication_add_schema(Oid pubid, Oid schemaid, bool if_not_exists) ObjectAddress myself, referenced; + AssertObjectTypeValid(objectType); + rel = table_open(PublicationNamespaceRelationId, RowExclusiveLock); /* @@ -622,9 +674,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); @@ -650,6 +703,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); @@ -675,7 +730,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); @@ -709,11 +764,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; @@ -721,6 +779,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); @@ -735,11 +795,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); @@ -789,6 +867,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). * @@ -855,28 +970,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; @@ -894,14 +1019,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)); @@ -909,6 +1046,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); } @@ -920,9 +1062,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]; @@ -931,6 +1077,7 @@ GetSchemaPublicationRelations(Oid schemaid, PublicationPartOpt pub_partopt) List *result = NIL; Assert(OidIsValid(schemaid)); + AssertObjectTypeValid(objectType); classRel = table_open(RelationRelationId, AccessShareLock); @@ -951,9 +1098,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; @@ -966,7 +1121,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); @@ -975,28 +1134,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 * @@ -1019,10 +1218,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); @@ -1103,10 +1304,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); @@ -1192,9 +1395,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)); @@ -1254,3 +1458,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/pg_subscription.c b/src/backend/catalog/pg_subscription.c index 9efc9159f2..35b02721b5 100644 --- a/src/backend/catalog/pg_subscription.c +++ b/src/backend/catalog/pg_subscription.c @@ -21,6 +21,7 @@ #include "catalog/indexing.h" #include "catalog/pg_subscription.h" #include "catalog/pg_subscription_rel.h" +#include "catalog/pg_subscription_seq.h" #include "catalog/pg_type.h" #include "miscadmin.h" #include "storage/lmgr.h" @@ -72,6 +73,7 @@ GetSubscription(Oid subid, bool missing_ok) sub->passwordrequired = subform->subpasswordrequired; sub->runasowner = subform->subrunasowner; sub->failover = subform->subfailover; + sub->sequences = subform->subsequences; /* Get conninfo */ datum = SysCacheGetAttrNotNull(SUBSCRIPTIONOID, @@ -551,3 +553,57 @@ GetSubscriptionRelations(Oid subid, bool not_ready) return res; } + + +/* + * Get the sequences for the subscription. + * + * The returned list is palloc'ed in the current memory context. + */ +List * +GetSubscriptionSequences(Oid subid) +{ + List *res = NIL; + Relation rel; + HeapTuple tup; + int nkeys = 0; + ScanKeyData skey[2]; + SysScanDesc scan; + + rel = table_open(SubscriptionSeqRelationId, AccessShareLock); + + ScanKeyInit(&skey[nkeys++], + Anum_pg_subscription_seq_sssubid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(subid)); + + scan = systable_beginscan(rel, InvalidOid, false, + NULL, nkeys, skey); + + while (HeapTupleIsValid(tup = systable_getnext(scan))) + { + Form_pg_subscription_seq subseq; + SubscriptionSeqInfo *seqinfo; + Datum d; + bool isnull; + + subseq = (Form_pg_subscription_seq) GETSTRUCT(tup); + + seqinfo = (SubscriptionSeqInfo *) palloc(sizeof(SubscriptionSeqInfo)); + seqinfo->seqid = subseq->sssubid; + d = SysCacheGetAttr(SUBSCRIPTIONSEQMAP, tup, + Anum_pg_subscription_seq_sssublsn, &isnull); + if (isnull) + seqinfo->lsn = InvalidXLogRecPtr; + else + seqinfo->lsn = DatumGetLSN(d); + + res = lappend(res, seqinfo); + } + + /* Cleanup */ + systable_endscan(scan); + table_close(rel, AccessShareLock); + + return res; +} \ No newline at end of file diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 53047cab5f..97ad8ebb01 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -398,6 +398,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 6ea709988e..a797f81c62 100644 --- a/src/backend/commands/publicationcmds.c +++ b/src/backend/commands/publicationcmds.c @@ -15,6 +15,7 @@ #include "postgres.h" #include "access/htup_details.h" +#include "access/relation.h" #include "access/table.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -61,15 +62,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 @@ -90,6 +93,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 */ @@ -114,6 +118,7 @@ parse_publication_options(ParseState *pstate, pubactions->pubupdate = false; pubactions->pubdelete = false; pubactions->pubtruncate = false; + pubactions->pubsequence = false; *publish_given = true; publish = defGetString(defel); @@ -137,6 +142,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), @@ -164,7 +171,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; @@ -182,13 +190,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); @@ -201,7 +218,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 */ @@ -727,6 +757,7 @@ CheckPubRelationColumnList(char *pubname, List *tables, ObjectAddress CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt) { + ListCell *lc; Relation rel; ObjectAddress myself; Oid puboid; @@ -738,8 +769,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); @@ -748,11 +798,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 */ @@ -782,7 +838,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] = @@ -791,6 +849,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); @@ -808,46 +868,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); } } @@ -910,6 +1012,7 @@ AlterPublicationOptions(ParseState *pstate, AlterPublicationStmt *stmt, AccessShareLock); root_relids = GetPublicationRelations(pubform->oid, + PUB_OBJTYPE_TABLE, PUBLICATION_PART_ROOT); foreach(lc, root_relids) @@ -989,6 +1092,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) @@ -1008,7 +1114,7 @@ AlterPublicationOptions(ParseState *pstate, AlterPublicationStmt *stmt, pubform = (Form_pg_publication) GETSTRUCT(tup); /* Invalidate the relcache. */ - if (pubform->puballtables) + if (pubform->puballtables || pubform->puballsequences) { CacheInvalidateRelcacheAll(); } @@ -1024,6 +1130,7 @@ AlterPublicationOptions(ParseState *pstate, AlterPublicationStmt *stmt, */ if (root_relids == NIL) relids = GetPublicationRelations(pubform->oid, + PUB_OBJTYPE_TABLE, PUBLICATION_PART_ALL); else { @@ -1037,7 +1144,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); @@ -1092,7 +1212,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) { @@ -1103,13 +1223,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; @@ -1226,18 +1347,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); } /* @@ -1247,7 +1368,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); @@ -1269,7 +1391,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) { @@ -1296,13 +1418,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 */ @@ -1315,13 +1437,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); } } @@ -1331,12 +1453,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"))); @@ -1345,13 +1468,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, @@ -1359,6 +1493,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); } /* @@ -1396,14 +1631,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); @@ -1424,9 +1665,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. */ @@ -1494,7 +1742,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); @@ -1530,6 +1778,7 @@ RemovePublicationSchemaById(Oid psoid) * partitions. */ schemaRels = GetSchemaPublicationRelations(pubsch->pnnspid, + pubsch->pntype, PUBLICATION_PART_ALL); InvalidatePublicationRels(schemaRels); @@ -1546,10 +1795,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; @@ -1557,19 +1806,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". @@ -1602,7 +1867,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) @@ -1671,10 +1936,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) @@ -1689,14 +1953,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; @@ -1744,12 +2008,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) { @@ -1778,7 +2042,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; @@ -1823,19 +2087,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, @@ -1851,7 +2115,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; @@ -1861,10 +2125,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) @@ -1919,6 +2184,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 28f8522264..b43b85bee6 100644 --- a/src/backend/commands/sequence.c +++ b/src/backend/commands/sequence.c @@ -45,6 +45,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" @@ -102,7 +103,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, @@ -277,7 +279,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)) @@ -328,6 +330,75 @@ ResetSequence(Oid seq_relid) sequence_close(seq_rel, NoLock); } +/* + * Set a sequence to a specified internal state. + * + * 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, 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); +} + /* * Initialize a sequence's relation with the specified tuple as content * @@ -476,7 +547,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); @@ -551,7 +622,7 @@ SequenceChangePersistence(Oid relid, char newrelpersistence) if (RelationNeedsWAL(seqrel)) GetTopTransactionId(); - (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); @@ -680,7 +751,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); last = next = result = seq->last_value; @@ -976,7 +1047,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, @@ -1180,7 +1251,8 @@ init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel) * 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; @@ -1197,6 +1269,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)); @@ -1804,7 +1883,7 @@ pg_sequence_last_value(PG_FUNCTION_ARGS) HeapTupleData seqtuple; Form_pg_sequence_data seq; - seq = read_seq_tuple(seqrel, &buf, &seqtuple); + seq = read_seq_tuple(seqrel, &buf, &seqtuple, NULL); is_called = seq->is_called; result = seq->last_value; @@ -1819,6 +1898,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 e407428dbc..330470780b 100644 --- a/src/backend/commands/subscriptioncmds.c +++ b/src/backend/commands/subscriptioncmds.c @@ -27,6 +27,7 @@ #include "catalog/pg_database_d.h" #include "catalog/pg_subscription.h" #include "catalog/pg_subscription_rel.h" +#include "catalog/pg_subscription_seq.h" #include "catalog/pg_type.h" #include "commands/dbcommands.h" #include "commands/defrem.h" @@ -72,6 +73,7 @@ #define SUBOPT_FAILOVER 0x00002000 #define SUBOPT_LSN 0x00004000 #define SUBOPT_ORIGIN 0x00008000 +#define SUBOPT_SEQUENCES 0x00010000 /* check if the 'val' has 'bits' set */ #define IsSet(val, bits) (((val) & (bits)) == (bits)) @@ -99,6 +101,7 @@ typedef struct SubOpts bool failover; char *origin; XLogRecPtr lsn; + bool sequences; } SubOpts; static List *fetch_table_list(WalReceiverConn *wrconn, List *publications); @@ -161,6 +164,8 @@ parse_subscription_options(ParseState *pstate, List *stmt_options, opts->failover = false; if (IsSet(supported_opts, SUBOPT_ORIGIN)) opts->origin = pstrdup(LOGICALREP_ORIGIN_ANY); + if (IsSet(supported_opts, SUBOPT_SEQUENCES)) + opts->sequences = false; /* Parse options */ foreach(lc, stmt_options) @@ -366,6 +371,15 @@ parse_subscription_options(ParseState *pstate, List *stmt_options, opts->specified_opts |= SUBOPT_LSN; opts->lsn = lsn; } + else if (IsSet(supported_opts, SUBOPT_SEQUENCES) && + strcmp(defel->defname, "sequences") == 0) + { + if (IsSet(opts->specified_opts, SUBOPT_SEQUENCES)) + errorConflictingDefElem(defel, pstate); + + opts->specified_opts |= SUBOPT_SEQUENCES; + opts->sequences = defGetBoolean(defel); + } else ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), @@ -603,7 +617,8 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt, SUBOPT_SYNCHRONOUS_COMMIT | SUBOPT_BINARY | SUBOPT_STREAMING | SUBOPT_TWOPHASE_COMMIT | SUBOPT_DISABLE_ON_ERR | SUBOPT_PASSWORD_REQUIRED | - SUBOPT_RUN_AS_OWNER | SUBOPT_FAILOVER | SUBOPT_ORIGIN); + SUBOPT_RUN_AS_OWNER | SUBOPT_FAILOVER | SUBOPT_ORIGIN | + SUBOPT_SEQUENCES); parse_subscription_options(pstate, stmt->options, supported_opts, &opts); /* @@ -710,6 +725,7 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt, values[Anum_pg_subscription_subpasswordrequired - 1] = BoolGetDatum(opts.passwordrequired); values[Anum_pg_subscription_subrunasowner - 1] = BoolGetDatum(opts.runasowner); values[Anum_pg_subscription_subfailover - 1] = BoolGetDatum(opts.failover); + values[Anum_pg_subscription_subsequences - 1] = BoolGetDatum(opts.sequences); values[Anum_pg_subscription_subconninfo - 1] = CStringGetTextDatum(conninfo); if (opts.slot_name) @@ -763,6 +779,16 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt, check_publications_origin(wrconn, publications, opts.copy_data, opts.origin, NULL, 0, stmt->subname); + /* + * Add sequences, but only if the subscription explicitly enabled + * them to be replicated. + */ + if (opts.sequences) + { + List *sequences = fetch_sequence_list(wrconn, publications); + copy_subscription_sequences(wrconn, subid, sequences); + } + /* * Set sync state based on if we were asked to do data copy or * not. @@ -1077,6 +1103,131 @@ AlterSubscription_refresh(Subscription *sub, bool copy_data, table_close(rel, NoLock); } +/* + * Refresh the sequences data of the subscription. + */ +static void +AlterSubscription_refreshsequences(Subscription *sub) +{ + char *err; + List *pubseq_names = NIL; + List *subseq_states; + Oid *subseq_local_oids; + Oid *pubseq_local_oids; + ListCell *lc; + int off; + int subrel_count; + Relation rel = NULL; + WalReceiverConn *wrconn; + bool must_use_password; + + if (!sub->sequences) + return; + + /* Load the library providing us libpq calls. */ + load_file("libpqwalreceiver", false); + + /* Try to connect to the publisher. */ + must_use_password = sub->passwordrequired && !sub->ownersuperuser; + wrconn = walrcv_connect(sub->conninfo, true, true, must_use_password, + sub->name, &err); + if (!wrconn) + ereport(ERROR, + (errcode(ERRCODE_CONNECTION_FAILURE), + errmsg("could not connect to the publisher: %s", err))); + + PG_TRY(); + { + /* Get the sequences from the publisher. */ + pubseq_names = fetch_sequence_list(wrconn, sub->publications); + + /* Get local sequence list. */ + subseq_states = GetSubscriptionSequences(sub->oid); + subrel_count = list_length(subseq_states); + + /* + * Build qsorted array of local table oids for faster lookup. This can + * potentiallGetSubscriptionRelationsy contain all tables in the database so speed of lookup + * is important. + */ + subseq_local_oids = palloc(subrel_count * sizeof(Oid)); + off = 0; + foreach(lc, subseq_states) + { + SubscriptionSeqInfo *seqinfo = (SubscriptionSeqInfo *) lfirst(lc); + + subseq_local_oids[off++] = seqinfo->seqid; + } + + qsort(subseq_local_oids, subrel_count, sizeof(Oid), oid_cmp); + + /* + * Walk over the remote tables and try to match them to locally known + * tables. If the table is not known locally create a new state for + * it. + * + * Also builds array of local oids of remote tables for the next step. + */ + off = 0; + pubseq_local_oids = palloc(list_length(pubseq_names) * sizeof(Oid)); + + foreach(lc, pubseq_names) + { + RangeVar *rv = (RangeVar *) lfirst(lc); + Oid relid; + + relid = RangeVarGetRelid(rv, AccessShareLock, false); + + /* Check for supported relkind. */ + CheckSubscriptionRelkind(get_rel_relkind(relid), + rv->schemaname, rv->relname); + + pubseq_local_oids[off++] = relid; + } + + /* + * Next remove state for tables we should not care about anymore using + * the data we collected above + */ + qsort(pubseq_local_oids, list_length(pubseq_names), + sizeof(Oid), oid_cmp); + + for (off = 0; off < subrel_count; off++) + { + Oid relid = subseq_local_oids[off]; + + if (!bsearch(&relid, pubseq_local_oids, + list_length(pubseq_names), sizeof(Oid), oid_cmp)) + { + /* + * This locking ensures that the state of rels won't change + * till we are done with this refresh operation. + */ + if (!rel) + rel = table_open(SubscriptionSeqRelationId, AccessExclusiveLock); + + RemoveSubscriptionRel(sub->oid, relid); + + ereport(DEBUG1, + (errmsg_internal("sequence \"%s.%s\" removed from subscription \"%s\"", + get_namespace_name(get_rel_namespace(relid)), + get_rel_name(relid), + sub->name))); + } + } + + copy_subscription_sequences(wrconn, sub->oid, pubseq_names); + } + PG_FINALLY(); + { + walrcv_disconnect(wrconn); + } + PG_END_TRY(); + + if (rel) + table_close(rel, NoLock); +} + /* * Alter the existing subscription. */ @@ -1263,6 +1414,13 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt, replaces[Anum_pg_subscription_suborigin - 1] = true; } + if (IsSet(opts.specified_opts, SUBOPT_SEQUENCES)) + { + values[Anum_pg_subscription_subsequences - 1] = + BoolGetDatum(opts.sequences); + replaces[Anum_pg_subscription_subsequences - 1] = true; + } + update_tuple = true; break; } @@ -1404,6 +1562,20 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt, break; } + case ALTER_SUBSCRIPTION_REFRESH_SEQUENCES: + { + if (!sub->enabled) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("ALTER SUBSCRIPTION ... REFRESH SEQUENCES is not allowed for disabled subscriptions"))); + + PreventInTransactionBlock(isTopLevel, "ALTER SUBSCRIPTION ... REFRESH SEQUENCES FOR PUBLICATION"); + + AlterSubscription_refreshsequences(sub); + + break; + } + case ALTER_SUBSCRIPTION_REFRESH: { if (!sub->enabled) diff --git a/src/backend/executor/execReplication.c b/src/backend/executor/execReplication.c index d0a89cd577..fdf69e4f28 100644 --- a/src/backend/executor/execReplication.c +++ b/src/backend/executor/execReplication.c @@ -739,7 +739,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 4d582950b7..8eea735441 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -455,7 +455,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 @@ -590,6 +590,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 @@ -10557,12 +10558,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 [, ...] * *****************************************************************************/ @@ -10575,13 +10580,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 @@ -10632,6 +10637,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); @@ -10693,6 +10718,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 ) @@ -10706,7 +10744,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 [, ...] * *****************************************************************************/ @@ -10807,6 +10847,15 @@ AlterSubscriptionStmt: n->options = $6; $$ = (Node *) n; } + | ALTER SUBSCRIPTION name REFRESH SEQUENCES + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + + n->kind = ALTER_SUBSCRIPTION_REFRESH_SEQUENCES; + n->subname = $3; + $$ = (Node *) n; + } | ALTER SUBSCRIPTION name ADD_P PUBLICATION name_list opt_definition { AlterSubscriptionStmt *n = @@ -19435,7 +19484,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) @@ -19486,6 +19536,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/Makefile b/src/backend/replication/logical/Makefile index ba03eeff1c..7621fa8aed 100644 --- a/src/backend/replication/logical/Makefile +++ b/src/backend/replication/logical/Makefile @@ -25,6 +25,7 @@ OBJS = \ proto.o \ relation.o \ reorderbuffer.o \ + sequencesync.o \ slotsync.o \ snapbuild.o \ tablesync.o \ diff --git a/src/backend/replication/logical/meson.build b/src/backend/replication/logical/meson.build index 3dec36a6de..1711fc3248 100644 --- a/src/backend/replication/logical/meson.build +++ b/src/backend/replication/logical/meson.build @@ -11,6 +11,7 @@ backend_sources += files( 'proto.c', 'relation.c', 'reorderbuffer.c', + 'sequencesync.c', 'slotsync.c', 'snapbuild.c', 'tablesync.c', diff --git a/src/backend/replication/logical/sequencesync.c b/src/backend/replication/logical/sequencesync.c new file mode 100644 index 0000000000..639f685c22 --- /dev/null +++ b/src/backend/replication/logical/sequencesync.c @@ -0,0 +1,357 @@ +/*------------------------------------------------------------------------- + * sequencesync.c + * PostgreSQL logical replication: initial sequence synchronization + * + * Copyright (c) 2012-2024, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/backend/replication/logical/sequencesync.c + * + * NOTES + * This file contains code for sequence synchronization for + * logical replication. + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "access/table.h" +#include "catalog/indexing.h" +#include "catalog/namespace.h" +#include "catalog/pg_subscription_seq.h" +#include "commands/sequence.h" +#include "executor/executor.h" +#include "nodes/makefuncs.h" +#include "replication/worker_internal.h" +#include "storage/lmgr.h" +#include "utils/builtins.h" +#include "utils/lsyscache.h" +#include "utils/pg_lsn.h" +#include "utils/rls.h" +#include "utils/syscache.h" + +/* + * Add sequence for a subscription. + */ +static void +AddSubscriptionSequence(Oid subid, Oid relid, XLogRecPtr sublsn) +{ + Relation rel; + HeapTuple tup; + bool nulls[Natts_pg_subscription_seq]; + Datum values[Natts_pg_subscription_seq]; + bool replaces[Natts_pg_subscription_seq]; + + /* Form the tuple. */ + memset(values, 0, sizeof(values)); + memset(nulls, false, sizeof(nulls)); + values[Anum_pg_subscription_seq_sssubid - 1] = ObjectIdGetDatum(subid); + values[Anum_pg_subscription_seq_ssseqid - 1] = ObjectIdGetDatum(relid); + if (sublsn != InvalidXLogRecPtr) + values[Anum_pg_subscription_seq_sssublsn - 1] = LSNGetDatum(sublsn); + else + nulls[Anum_pg_subscription_seq_sssublsn - 1] = true; + + LockSharedObject(SubscriptionRelationId, subid, 0, AccessShareLock); + + rel = table_open(SubscriptionSeqRelationId, RowExclusiveLock); + + /* Try finding existing mapping. */ + tup = SearchSysCacheCopy2(SUBSCRIPTIONSEQMAP, + ObjectIdGetDatum(relid), + ObjectIdGetDatum(subid)); + if (!HeapTupleIsValid(tup)) + { + tup = heap_form_tuple(RelationGetDescr(rel), values, nulls); + + /* Insert tuple into catalog. */ + CatalogTupleInsert(rel, tup); + + heap_freetuple(tup); + } + else + { + memset(replaces, true, sizeof(replaces)); + + tup = heap_modify_tuple(tup, RelationGetDescr(rel), values, nulls, + replaces); + + /* Update the catalog. */ + CatalogTupleUpdate(rel, &tup->t_self, tup); + } + + /* Cleanup. */ + table_close(rel, NoLock); +} + +/* + * Get the list of sequences which belong to specified publications on the + * publisher connection. + */ +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; +} + +/* + * Fetch sequence data (current state) from the remote node, including the + * page LSN. + */ +static int64 +fetch_sequence_data(WalReceiverConn *conn, 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(conn, 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(WalReceiverConn *conn, Relation rel) +{ + StringInfoData cmd; + int64 sequence_value; + XLogRecPtr lsn = InvalidXLogRecPtr; + WalRcvExecResult *res; + Oid tableRow[] = {OIDOID, CHAROID}; + TupleTableSlot *slot; + LogicalRepRelId remoteid; /* unique id of the relation */ + char relkind PG_USED_FOR_ASSERTS_ONLY; + bool isnull; + char *nspname = get_namespace_name(RelationGetNamespace(rel)); + char *relname = RelationGetRelationName(rel); + + /* Fetch Oid. */ + initStringInfo(&cmd); + appendStringInfo(&cmd, "SELECT c.oid, c.relkind" + " FROM pg_catalog.pg_class c" + " INNER JOIN pg_catalog.pg_namespace n" + " ON (c.relnamespace = n.oid)" + " WHERE n.nspname = %s" + " AND c.relname = %s", + quote_literal_cstr(nspname), + quote_literal_cstr(relname)); + res = walrcv_exec(conn, cmd.data, + lengthof(tableRow), tableRow); + + if (res->status != WALRCV_OK_TUPLES) + ereport(ERROR, + (errcode(ERRCODE_CONNECTION_FAILURE), + errmsg("could not fetch table info for table \"%s.%s\" from publisher: %s", + nspname, RelationGetRelationName(rel), res->err))); + + slot = MakeSingleTupleTableSlot(res->tupledesc, &TTSOpsMinimalTuple); + if (!tuplestore_gettupleslot(res->tuplestore, true, false, slot)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("table \"%s.%s\" not found on publisher", + nspname, relname))); + + remoteid = DatumGetObjectId(slot_getattr(slot, 1, &isnull)); + Assert(!isnull); + relkind = DatumGetChar(slot_getattr(slot, 2, &isnull)); + Assert(!isnull); + + ExecDropSingleTupleTableSlot(slot); + walrcv_clear_result(res); + + Assert(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(conn, remoteid, &lsn); + + /* sets the sequences in non-transactional way */ + SetSequence(RelationGetRelid(rel), sequence_value); + + /* return the LSN when the sequence state was set */ + return lsn; +} + +/* + * Copy subscription's sequence data from the publisher. + */ +void +copy_subscription_sequences(WalReceiverConn *conn, Oid subid, List *sequences) +{ + WalRcvExecResult *res; + char slotname[NAMEDATALEN] = {0}; + XLogRecPtr origin_startpos = InvalidXLogRecPtr; + ListCell *lc; + + /* + * Start a transaction in the remote node in REPEATABLE READ mode. This + * ensures that both the replication slot we create (see below) and the + * COPY are consistent with each other. + */ + res = walrcv_exec(conn, + "BEGIN READ ONLY ISOLATION LEVEL REPEATABLE READ", + 0, NULL); + if (res->status != WALRCV_OK_COMMAND) + ereport(ERROR, + (errcode(ERRCODE_CONNECTION_FAILURE), + errmsg("sequence copy could not start transaction on publisher: %s", + res->err))); + walrcv_clear_result(res); + + snprintf(slotname, NAMEDATALEN, "pg_%u_sync_sequences_" UINT64_FORMAT, + subid, GetSystemIdentifier()); + + /* Create a new temporary logical decoding slot */ + walrcv_create_slot(conn, slotname, true /* temporary */ , + false /* two_phase */ , false, /* failover */ + CRS_USE_SNAPSHOT, &origin_startpos); + + foreach(lc, sequences) + { + RangeVar *rv = (RangeVar *) lfirst(lc); + Oid relid; + XLogRecPtr sequence_lsn = InvalidXLogRecPtr; + Relation sequencerel; + + relid = RangeVarGetRelid(rv, AccessShareLock, false); + + /* Check for supported relkind. */ + CheckSubscriptionRelkind(get_rel_relkind(relid), + rv->schemaname, rv->relname); + + sequencerel = table_open(relid, RowExclusiveLock); + + /* + * COPY FROM does not honor RLS policies. That is not a problem for + * subscriptions owned by roles with BYPASSRLS privilege (or superuser, + * who has it implicitly), but other roles should not be able to + * circumvent RLS. Disallow logical replication into RLS enabled + * relations for such roles. + */ + if (check_enable_rls(RelationGetRelid(sequencerel), InvalidOid, false) == RLS_ENABLED) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("user \"%s\" cannot replicate into relation with row-level security enabled: \"%s\"", + GetUserNameFromId(GetUserId(), true), + RelationGetRelationName(sequencerel)))); + + sequence_lsn = copy_sequence(conn, sequencerel); + + AddSubscriptionSequence(subid, relid, sequence_lsn); + + ereport(LOG, + errmsg("logical replication synchronization for subscription \"%s\", sequence \"%s\" has finished", + get_subscription_name(subid, false), RelationGetRelationName(sequencerel))); + table_close(sequencerel, NoLock); + } + + res = walrcv_exec(conn, "COMMIT", 0, NULL); + if (res->status != WALRCV_OK_COMMAND) + ereport(ERROR, + errcode(ERRCODE_CONNECTION_FAILURE), + errmsg("sequence copy could not finish transaction on publisher: %s", + res->err)); + walrcv_clear_result(res); +} diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c index d2b35cfb96..8d737ba9c9 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" @@ -906,9 +907,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. @@ -1997,18 +1999,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. */ diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c index cc9b0c6524..1ddd7644e5 100644 --- a/src/backend/utils/cache/relcache.c +++ b/src/backend/utils/cache/relcache.c @@ -55,6 +55,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" @@ -5687,6 +5688,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 @@ -5717,8 +5720,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. */ @@ -5730,12 +5740,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) { @@ -5754,6 +5775,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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index e324070828..8f0e375cf8 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -4185,10 +4185,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; @@ -4204,23 +4206,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); @@ -4232,10 +4240,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)); @@ -4251,6 +4261,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 = @@ -4259,6 +4271,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); @@ -4304,6 +4318,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) { @@ -4338,6 +4355,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) @@ -4384,6 +4410,7 @@ getPublicationNamespaces(Archive *fout) int i_oid; int i_pnpubid; int i_pnnspid; + int i_pntype; int i, j, ntups; @@ -4395,7 +4422,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); @@ -4405,6 +4432,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)); @@ -4414,6 +4442,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; @@ -4445,6 +4474,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); @@ -4610,7 +4640,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 @@ -4643,6 +4677,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) @@ -4652,8 +4687,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)); @@ -4683,7 +4729,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 865823868f..508fd51bf4 100644 --- a/src/bin/pg_dump/pg_dump.h +++ b/src/bin/pg_dump/pg_dump.h @@ -619,10 +619,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; @@ -648,6 +650,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 d3dd8784d6..b7989e5470 100644 --- a/src/bin/pg_dump/t/002_pg_dump.pl +++ b/src/bin/pg_dump/t/002_pg_dump.pl @@ -2916,7 +2916,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, }, }, @@ -2936,7 +2936,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, }, }, @@ -2945,7 +2945,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, }, }, @@ -3091,6 +3111,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 f67bf0b892..61783ae0cb 100644 --- a/src/bin/psql/describe.c +++ b/src/bin/psql/describe.c @@ -1715,28 +1715,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';", @@ -1745,22 +1736,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)); @@ -1770,6 +1754,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) || '.' ||" @@ -1801,32 +1838,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 */ @@ -2053,6 +2121,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++) { @@ -2979,7 +3052,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" @@ -5076,7 +5149,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" @@ -5102,8 +5175,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); } @@ -6219,7 +6293,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) { @@ -6233,23 +6307,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\"", @@ -6343,6 +6439,7 @@ describePublications(const char *pattern) PGresult *res; bool has_pubtruncate; bool has_pubviaroot; + bool has_pubsequence; PQExpBufferData title; printTableContent cont; @@ -6359,6 +6456,7 @@ describePublications(const char *pattern) has_pubtruncate = (pset.sversion >= 110000); has_pubviaroot = (pset.sversion >= 130000); + has_pubsequence = (pset.sversion >= 170000); initPQExpBuffer(&buf); @@ -6372,6 +6470,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"); @@ -6417,6 +6519,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) @@ -6424,29 +6527,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) { @@ -6477,6 +6594,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; @@ -6488,7 +6606,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)) @@ -6496,6 +6614,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 d453e224d9..2ac6ff6c4d 100644 --- a/src/bin/psql/tab-complete.c +++ b/src/bin/psql/tab-complete.c @@ -1893,11 +1893,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 @@ -1917,11 +1921,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"); @@ -1933,6 +1937,10 @@ psql_completion(const char *text, int start, int end) COMPLETE_WITH("CONNECTION", "ENABLE", "DISABLE", "OWNER TO", "RENAME TO", "REFRESH PUBLICATION", "SET", "SKIP (", "ADD PUBLICATION", "DROP PUBLICATION"); + /* ALTER SUBSCRIPTION REFRESH */ + else if (HeadMatches("ALTER", "SUBSCRIPTION", MatchAny) && + TailMatches("REFRESH")) + COMPLETE_WITH("PUBLICATION", "SEQUENCES"); /* ALTER SUBSCRIPTION REFRESH PUBLICATION */ else if (HeadMatches("ALTER", "SUBSCRIPTION", MatchAny) && TailMatches("REFRESH", "PUBLICATION")) @@ -3159,24 +3167,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("("); @@ -3188,11 +3203,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/Makefile b/src/include/catalog/Makefile index 167f91a6e3..5878810887 100644 --- a/src/include/catalog/Makefile +++ b/src/include/catalog/Makefile @@ -81,7 +81,8 @@ CATALOG_HEADERS := \ pg_publication_namespace.h \ pg_publication_rel.h \ pg_subscription.h \ - pg_subscription_rel.h + pg_subscription_rel.h \ + pg_subscription_seq.h GENERATED_HEADERS := $(CATALOG_HEADERS:%.h=%_d.h) diff --git a/src/include/catalog/meson.build b/src/include/catalog/meson.build index f70d1daba5..9330bd698a 100644 --- a/src/include/catalog/meson.build +++ b/src/include/catalog/meson.build @@ -69,6 +69,7 @@ catalog_headers = [ 'pg_publication_rel.h', 'pg_subscription.h', 'pg_subscription_rel.h', + 'pg_subscription_seq.h', ] # The .dat files we need can just be listed alphabetically. diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 6a5476d3c4..8e68adb01f 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 => '6313', + 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', @@ -11937,6 +11945,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 2f1b6abbfa..ec42386bc4 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; @@ -75,6 +84,7 @@ typedef struct PublicationActions bool pubupdate; bool pubdelete; bool pubtruncate; + bool pubsequence; } PublicationActions; typedef struct PublicationDesc @@ -102,6 +112,7 @@ typedef struct Publication Oid oid; char *name; bool alltables; + bool allsequences; bool pubviaroot; PublicationActions pubactions; } Publication; @@ -133,14 +144,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, @@ -148,11 +160,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 1cfb557684..b98659ac79 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,9 +43,16 @@ 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); MAKE_SYSCACHE(PUBLICATIONNAMESPACE, pg_publication_namespace_oid_index, 64); -MAKE_SYSCACHE(PUBLICATIONNAMESPACEMAP, pg_publication_namespace_pnnspid_pnpubid_index, 64); +MAKE_SYSCACHE(PUBLICATIONNAMESPACEMAP, pg_publication_namespace_pnnspid_pnpubid_pntype_index, 64); #endif /* PG_PUBLICATION_NAMESPACE_H */ diff --git a/src/include/catalog/pg_subscription.h b/src/include/catalog/pg_subscription.h index 0aa14ec4a2..eb9ee15127 100644 --- a/src/include/catalog/pg_subscription.h +++ b/src/include/catalog/pg_subscription.h @@ -98,6 +98,9 @@ CATALOG(pg_subscription,6100,SubscriptionRelationId) BKI_SHARED_RELATION BKI_ROW * slots) in the upstream database are enabled * to be synchronized to the standbys. */ + bool subsequences; /* True if sequences should be requested from + * the publisher */ + #ifdef CATALOG_VARLEN /* variable-length fields start here */ /* Connection string to the publisher */ text subconninfo BKI_FORCE_NOT_NULL; @@ -151,6 +154,7 @@ typedef struct Subscription * (i.e. the main slot and the table sync * slots) in the upstream database are enabled * to be synchronized to the standbys. */ + bool sequences; /* Request sequences from the publisher */ char *conninfo; /* Connection string to the publisher */ char *slotname; /* Name of the replication slot */ char *synccommit; /* Synchronous commit setting for worker */ diff --git a/src/include/catalog/pg_subscription_seq.h b/src/include/catalog/pg_subscription_seq.h new file mode 100644 index 0000000000..d0c03ef58a --- /dev/null +++ b/src/include/catalog/pg_subscription_seq.h @@ -0,0 +1,67 @@ +/* ------------------------------------------------------------------------- + * + * pg_subscription_rel.h + * definition of the system catalog containing the state for each + * replicated sequence in each subscription (pg_subscription_rel) + * + * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/catalog/pg_subscription_seq.h + * + * NOTES + * The Catalog.pm module reads this file and derives schema + * information. + * + * ------------------------------------------------------------------------- + */ +#ifndef PG_SUBSCRIPTION_SEQ_H +#define PG_SUBSCRIPTION_SEQ_H + +#include "access/xlogdefs.h" +#include "catalog/genbki.h" +#include "catalog/pg_subscription_seq_d.h" +#include "nodes/pg_list.h" +#include "replication/walreceiver.h" + +/* ---------------- + * pg_subscription_seq definition. cpp turns this into + * typedef struct FormData_pg_subscription_seq + * ---------------- + */ +CATALOG(pg_subscription_seq,8001,SubscriptionSeqRelationId) +{ + Oid sssubid BKI_LOOKUP(pg_subscription); /* Oid of subscription */ + Oid ssseqid BKI_LOOKUP(pg_class); /* Oid of relation */ + + /* + * Although sssublsn is a fixed-width type, it is allowed to be NULL, so + * we prevent direct C code access to it just as for a varlena field. + */ +#ifdef CATALOG_VARLEN /* variable-length fields start here */ + + XLogRecPtr sssublsn BKI_FORCE_NULL; /* remote LSN of the state change + * used for synchronization + * coordination, or NULL if not + * valid */ +#endif +} FormData_pg_subscription_seq; + +typedef FormData_pg_subscription_seq *Form_pg_subscription_seq; + +DECLARE_UNIQUE_INDEX_PKEY(pg_subscription_seq_ssseqid_sssubid_index, 8002, SubscriptionSeqSsseqidSssubidIndexId, pg_subscription_seq, btree(ssseqid oid_ops, sssubid oid_ops)); + +MAKE_SYSCACHE(SUBSCRIPTIONSEQMAP, pg_subscription_seq_ssseqid_sssubid_index, 64); + +typedef struct SubscriptionSeqInfo +{ + Oid seqid; + XLogRecPtr lsn; +} SubscriptionSeqInfo; + +extern List *GetSubscriptionSequences(Oid subid); +extern List *fetch_sequence_list(WalReceiverConn *wrconn, List *publications); +extern void copy_subscription_sequences(WalReceiverConn *conn, Oid subid, + List *sequences); + +#endif /* PG_SUBSCRIPTION_REL_H */ diff --git a/src/include/commands/sequence.h b/src/include/commands/sequence.h index e88cbee3b5..fad731a733 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, 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 ddfed02db2..afb994ae45 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -4150,6 +4150,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; @@ -4168,7 +4172,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 @@ -4191,7 +4196,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; @@ -4213,6 +4219,7 @@ typedef enum AlterSubscriptionType ALTER_SUBSCRIPTION_ADD_PUBLICATION, ALTER_SUBSCRIPTION_DROP_PUBLICATION, ALTER_SUBSCRIPTION_REFRESH, + ALTER_SUBSCRIPTION_REFRESH_SEQUENCES, ALTER_SUBSCRIPTION_ENABLED, ALTER_SUBSCRIPTION_SKIP, } AlterSubscriptionType; diff --git a/src/test/regress/expected/object_address.out b/src/test/regress/expected/object_address.out index fc42d418bf..9aec2612d0 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/oidjoins.out b/src/test/regress/expected/oidjoins.out index 215eb899be..98c48e19bb 100644 --- a/src/test/regress/expected/oidjoins.out +++ b/src/test/regress/expected/oidjoins.out @@ -266,3 +266,5 @@ NOTICE: checking pg_subscription {subdbid} => pg_database {oid} NOTICE: checking pg_subscription {subowner} => pg_authid {oid} NOTICE: checking pg_subscription_rel {srsubid} => pg_subscription {oid} NOTICE: checking pg_subscription_rel {srrelid} => pg_class {oid} +NOTICE: checking pg_subscription_seq {sssubid} => pg_subscription {oid} +NOTICE: checking pg_subscription_seq {ssseqid} => pg_class {oid} diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index 3bbe4c5f97..d26156a2f1 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -6248,9 +6248,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 30b6371134..6052321911 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" @@ -195,10 +198,10 @@ Publications: "testpub_foralltables" \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; @@ -210,24 +213,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; @@ -243,10 +747,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" @@ -261,10 +765,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" @@ -293,10 +797,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)) @@ -309,10 +813,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)) @@ -328,10 +832,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)) @@ -339,10 +843,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)) @@ -375,10 +879,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) @@ -388,10 +892,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) @@ -506,10 +1010,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: @@ -723,10 +1227,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) @@ -910,10 +1414,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) @@ -1118,10 +1622,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" @@ -1159,10 +1663,10 @@ Publications: "testpub_fortbl" \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" @@ -1240,10 +1744,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 @@ -1253,20 +1757,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 @@ -1282,19 +1786,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" @@ -1308,44 +1812,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" @@ -1379,10 +1883,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" @@ -1390,20 +1894,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" @@ -1411,10 +1915,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" @@ -1423,10 +1927,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" @@ -1435,10 +1939,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" @@ -1446,10 +1950,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" @@ -1457,10 +1961,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" @@ -1468,29 +1972,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" @@ -1499,10 +2003,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" @@ -1511,10 +2015,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" @@ -1593,18 +2097,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" @@ -1614,20 +2118,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: @@ -1671,40 +2175,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); @@ -1714,14 +2263,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; @@ -1737,9 +2298,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 ef658ad740..4acb94ce96 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 1a6c61f49d..5c6cdeb05d 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 479d4f3264..cfa030b812 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; @@ -1061,32 +1245,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; @@ -1099,10 +1302,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; -- 2.34.1