From 2462acd1e12e194487ed7782b9094d291015db17 Mon Sep 17 00:00:00 2001 From: Alexander Korotkov Date: Mon, 3 Aug 2026 00:14:57 +0200 Subject: [PATCH v4 2/4] Peserve replica identity and publications in MERGE/SPLIT PARTITION(s) The new partition(s) created by ALTER TABLE ... MERGE/SPLIT PARTITION are built from the partitioned-table template, so they would default to REPLICA IDENTITY DEFAULT and silently drop out of any publication that the source partitions were directly part of, changing replication behavior without a warning. Carry a uniform, simply-representable replica identity (DEFAULT, FULL or NOTHING) from the source partitions to the new partition(s). Raise an error if the sources disagree, or use an index-based identity that cannot be reproduced automatically, and let the user set it explicitly. Also refuse the operation when any source partition is a direct member of a publication: the new partition would otherwise leave it, and faithfully reproducing per-relation column lists and row filters is ambiguous (especially when several sources are merged). Publications that cover the partitioned root continue to include the new partition, so those are unaffected. For the same reason, refuse to create the new partition in a schema whose FOR TABLES IN SCHEMA publications differ from those of the source partitions: such a move would silently add the relocated rows to, or remove them from, such a publication. The check only triggers when a schema publication is actually involved, so a cross-schema MERGE/SPLIT remains allowed otherwise; publications FOR ALL TABLES, or covering the partitioned table itself, keep covering the new partitions and are unaffected. Also make the error hints name an action that lets the command succeed, rather than one to perform after an operation that did not happen. Document this behavior and add a test coverage. Discussion: https://postgr.es/m/CAN4CZFNCU=t09M=+r2t9hHLJuujdM4oQ8hCK_Sx-GpfiwMAicw@mail.gmail.com --- doc/src/sgml/ref/alter_table.sgml | 35 +++++ src/backend/commands/tablecmds.c | 148 ++++++++++++++++++ src/test/regress/expected/partition_merge.out | 62 ++++++++ src/test/regress/expected/partition_split.out | 60 +++++++ src/test/regress/sql/partition_merge.sql | 50 ++++++ src/test/regress/sql/partition_split.sql | 48 ++++++ 6 files changed, 403 insertions(+) diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml index b8246a7ee48..73e1be7dec8 100644 --- a/doc/src/sgml/ref/alter_table.sgml +++ b/doc/src/sgml/ref/alter_table.sgml @@ -1281,6 +1281,25 @@ WITH ( MODULUS numeric_literal, REM dependencies are not silently lost during merge. + + The new partition takes its replica identity from the merged partitions + when they all use the same simple setting + (DEFAULT, FULL or + NOTHING). If they use different settings, or use + REPLICA IDENTITY USING INDEX, the error is issued + and the command is aborted. Give the partitions being merged a uniform, + non-index replica identity before merging, and set a different replica + identity on the resulting partition afterwards if desired. Likewise, if + any of the partitions being merged is directly part of a publication, the + command is aborted; publish the partitioned table itself instead of the + individual partitions, or remove the partition from the publication before + merging. For the same reason, the new partition cannot be created in a + schema that is not covered by the same publications defined + FOR TABLES IN SCHEMA as the schema of the partitions + being merged. + + + Moving rows into the new partition does not emit logical replication messages, in the same way that CLUSTER or @@ -1401,6 +1420,22 @@ WITH ( MODULUS numeric_literal, REM from the source partition's indexes. + + The new partitions take their replica identity from the split partition, + unless it uses REPLICA IDENTITY USING INDEX, in which + case the error is issued and the command is aborted. Give the partition + being split a non-index replica identity before splitting, and set a + different replica identity on the new partitions afterwards if desired. + Likewise, if the partition being split is directly part of a publication, + the command is rejected; publish the partitioned table itself instead of + the individual partitions, or remove the partition from the publication + before splitting. For the same reason, the new partitions cannot be + created in a schema that is not covered by the same publications defined + FOR TABLES IN SCHEMA as the schema of the partition + being split. + + + Moving rows into the new partitions does not emit logical replication messages, in the same way that CLUSTER or diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 0eb85c1be17..1749ad68f68 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -49,6 +49,7 @@ #include "catalog/pg_opclass.h" #include "catalog/pg_policy.h" #include "catalog/pg_proc.h" +#include "catalog/pg_publication.h" #include "catalog/pg_publication_rel.h" #include "catalog/pg_rewrite.h" #include "catalog/pg_statistic_ext.h" @@ -23353,6 +23354,137 @@ createPartitionTable(List **wqueue, RangeVar *newPartName, return newRel; } +/* + * checkPartitionSchemaPublications: refuse MERGE/SPLIT when the new partition(s) + * would land in a schema whose FOR TABLES IN SCHEMA publications differ from + * those of the source partition(s). + * + * The new partitions are created under the name given in the command, which may + * name a different schema than the source partitions live in. A publication + * defined FOR TABLES IN SCHEMA covers exactly the tables of that schema, so such + * a move would silently add the relocated rows to, or remove them from, that + * publication. Publications FOR ALL TABLES, or covering the partitioned table + * itself, keep covering the new partitions and are therefore not a problem. + * + * 'sourceOids' lists the source partition OIDs, 'newPartRels' the new partition + * Relations. + */ +static void +checkPartitionSchemaPublications(List *sourceOids, List *newPartRels) +{ + foreach_oid(srcOid, sourceOids) + { + Oid srcNsp = get_rel_namespace(srcOid); + List *srcPubs = NIL; + bool srcPubsFetched = false; + + foreach_ptr(RelationData, newrel, newPartRels) + { + Oid newNsp = RelationGetNamespace(newrel); + List *newPubs; + + /* Same schema: publication membership cannot change. */ + if (newNsp == srcNsp) + continue; + + if (!srcPubsFetched) + { + srcPubs = GetSchemaPublications(srcNsp); + srcPubsFetched = true; + } + newPubs = GetSchemaPublications(newNsp); + + /* No schema publication involved, so nothing can change. */ + if (srcPubs == NIL && newPubs == NIL) + continue; + + if (list_length(srcPubs) != list_length(newPubs) || + list_difference_oid(srcPubs, newPubs) != NIL) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot move partition \"%s\" to schema \"%s\" with different publications for tables in schema", + get_rel_name(srcOid), + get_namespace_name(newNsp)), + errdetail("Schema \"%s\" and schema \"%s\" are not covered by the same publications defined FOR TABLES IN SCHEMA, so the new partition would silently join or leave a publication.", + get_namespace_name(srcNsp), + get_namespace_name(newNsp)), + errhint("Create the new partition in the same schema, or publish the partitioned table itself.")); + } + } +} + +/* + * transferPartitionReplicaIdentity: propagate the source partitions' replica + * identity to the new partition(s) created by MERGE/SPLIT, and refuse the + * operation for cases we cannot handle without silently changing replication + * behavior. + * + * The new partitions are built from the partitioned-table template and would + * otherwise default to REPLICA IDENTITY DEFAULT and drop out of any publication + * that the source partitions were directly part of. To avoid silent surprises: + * + * - A uniform, simply-representable replica identity (DEFAULT, FULL or + * NOTHING) is carried over to every new partition. If the sources disagree, + * or use an index-based identity (which cannot be reproduced on the new + * partition automatically), we raise an error and ask the user to set it. + * + * - If any source partition is a direct member of a publication, we refuse the + * operation: the new partition would silently leave the publication, and + * faithfully reproducing per-relation column lists and row filters is + * ambiguous (especially when several sources are merged). Publications that + * cover the partitioned root instead continue to include the new partition. + * + * 'sourceOids' lists the source partition OIDs (still present, not yet dropped); + * 'newPartRels' lists the new partition Relations (exclusively locked). + */ +static void +transferPartitionReplicaIdentity(List *sourceOids, List *newPartRels) +{ + char ri_type = '\0'; + bool ri_seen = false; + + foreach_oid(srcOid, sourceOids) + { + Relation src = table_open(srcOid, NoLock); + + if (GetRelationIncludedPublications(srcOid) != NIL) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot merge or split partition \"%s\" that is directly part of a publication", + RelationGetRelationName(src)), + errhint("Publish the partitioned table instead, or remove the partition from the publication before the operation and add the new partition to it afterwards.")); + + if (!ri_seen) + { + ri_type = src->rd_rel->relreplident; + ri_seen = true; + } + else if (ri_type != src->rd_rel->relreplident) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("partitions being merged have different replica identity settings"), + errhint("Give all partitions being merged the same replica identity before merging.")); + + table_close(src, NoLock); + } + + /* Nothing to carry over, or the new partitions already match. */ + if (!ri_seen || ri_type == REPLICA_IDENTITY_DEFAULT) + return; + + if (ri_type == REPLICA_IDENTITY_INDEX) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot automatically transfer an index-based replica identity to the new partition"), + errhint("Change the replica identity to a non-index one before the operation, then set it on the new partition with ALTER TABLE ... REPLICA IDENTITY USING INDEX.")); + + /* Carry FULL / NOTHING over to each new partition. */ + foreach_ptr(RelationData, newrel, newPartRels) + relation_mark_replica_identity(newrel, ri_type, InvalidOid, true); + + CommandCounterIncrement(); +} + /* * MergePartitionsMoveRows: scan partitions to be merged (mergingPartitions) * of the partitioned table and move rows into the new partition @@ -23903,6 +24035,14 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel, Assert(OidIsValid(ownerId)); newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId); + /* + * Carry the source partitions' replica identity over to the new + * partition, and reject cases that would silently change replication + * behavior. + */ + transferPartitionReplicaIdentity(mergingPartitions, list_make1(newPartRel)); + checkPartitionSchemaPublications(mergingPartitions, list_make1(newPartRel)); + /* * Switch to the table owner's userid, so that any index functions are run * as that user. Also, lockdown security-restricted operations and @@ -24345,6 +24485,14 @@ ATExecSplitPartition(List **wqueue, AlteredTableInfo *tab, Relation rel, newPartRels = lappend(newPartRels, newPartRel); } + /* + * Carry the split partition's replica identity over to the new + * partitions, and reject cases that would silently change replication + * behavior. + */ + transferPartitionReplicaIdentity(list_make1_oid(splitRelOid), newPartRels); + checkPartitionSchemaPublications(list_make1_oid(splitRelOid), newPartRels); + /* * Switch to the table owner's userid, so that any index functions are run * as that user. Also, lockdown security-restricted operations and diff --git a/src/test/regress/expected/partition_merge.out b/src/test/regress/expected/partition_merge.out index ccda2b5843b..0c19e5fa93f 100644 --- a/src/test/regress/expected/partition_merge.out +++ b/src/test/regress/expected/partition_merge.out @@ -1167,6 +1167,68 @@ SELECT reltablespace FROM pg_class WHERE relname = 'tp_merged'; 0 (1 row) +DROP TABLE t; +-- MERGE PARTITIONS carries over a uniform replica identity ... +CREATE TABLE t (i int PRIMARY KEY) PARTITION BY RANGE (i); +CREATE TABLE tp_0_1 PARTITION OF t FOR VALUES FROM (0) TO (1); +CREATE TABLE tp_1_2 PARTITION OF t FOR VALUES FROM (1) TO (2); +ALTER TABLE tp_0_1 REPLICA IDENTITY FULL; +ALTER TABLE tp_1_2 REPLICA IDENTITY FULL; +ALTER TABLE t MERGE PARTITIONS (tp_0_1, tp_1_2) INTO tp_0_2; +SELECT relreplident FROM pg_class WHERE relname = 'tp_0_2' + AND relnamespace = 'partitions_merge_schema'::regnamespace; + relreplident +-------------- + f +(1 row) + +DROP TABLE t; +-- ... but rejects merging partitions with different replica identities. +CREATE TABLE t (i int PRIMARY KEY) PARTITION BY RANGE (i); +CREATE TABLE tp_0_1 PARTITION OF t FOR VALUES FROM (0) TO (1); +CREATE TABLE tp_1_2 PARTITION OF t FOR VALUES FROM (1) TO (2); +ALTER TABLE tp_0_1 REPLICA IDENTITY FULL; +ALTER TABLE t MERGE PARTITIONS (tp_0_1, tp_1_2) INTO tp_0_2; -- fails +ERROR: partitions being merged have different replica identity settings +HINT: Give all partitions being merged the same replica identity before merging. +DROP TABLE t; +-- MERGE PARTITIONS rejects a partition that is directly part of a publication. +CREATE TABLE t (i int PRIMARY KEY) PARTITION BY RANGE (i); +CREATE TABLE tp_0_1 PARTITION OF t FOR VALUES FROM (0) TO (1); +CREATE TABLE tp_1_2 PARTITION OF t FOR VALUES FROM (1) TO (2); +CREATE PUBLICATION pub_merge FOR TABLE tp_0_1; +ALTER TABLE t MERGE PARTITIONS (tp_0_1, tp_1_2) INTO tp_0_2; -- fails +ERROR: cannot merge or split partition "tp_0_1" that is directly part of a publication +HINT: Publish the partitioned table instead, or remove the partition from the publication before the operation and add the new partition to it afterwards. +DROP PUBLICATION pub_merge; +DROP TABLE t; +-- Creating the new partition in another schema is only rejected when that +-- actually changes which FOR TABLES IN SCHEMA publications cover it. +CREATE TABLE t (i int) PARTITION BY RANGE (i); +CREATE TABLE tp_0_1 PARTITION OF t FOR VALUES FROM (0) TO (1); +CREATE TABLE tp_1_2 PARTITION OF t FOR VALUES FROM (1) TO (2); +INSERT INTO t VALUES (0), (1); +-- No such publication, so a cross-schema merge is fine. +ALTER TABLE t MERGE PARTITIONS (tp_0_1, tp_1_2) INTO partitions_merge_schema2.tp_0_2; +SELECT count(*) FROM t; + count +------- + 2 +(1 row) + +DROP TABLE t; +CREATE TABLE t (i int) PARTITION BY RANGE (i); +CREATE TABLE tp_0_1 PARTITION OF t FOR VALUES FROM (0) TO (1); +CREATE TABLE tp_1_2 PARTITION OF t FOR VALUES FROM (1) TO (2); +CREATE PUBLICATION pub_merge FOR TABLES IN SCHEMA partitions_merge_schema; +ALTER TABLE t MERGE PARTITIONS (tp_0_1, tp_1_2) + INTO partitions_merge_schema2.tp_0_2; -- fails +ERROR: cannot move partition "tp_0_1" to schema "partitions_merge_schema2" with different publications for tables in schema +DETAIL: Schema "partitions_merge_schema" and schema "partitions_merge_schema2" are not covered by the same publications defined FOR TABLES IN SCHEMA, so the new partition would silently join or leave a publication. +HINT: Create the new partition in the same schema, or publish the partitioned table itself. +-- Staying in the covered schema is fine. +ALTER TABLE t MERGE PARTITIONS (tp_0_1, tp_1_2) INTO tp_0_2; +DROP PUBLICATION pub_merge; DROP TABLE t; RESET search_path; -- diff --git a/src/test/regress/expected/partition_split.out b/src/test/regress/expected/partition_split.out index 8e245563801..c086f7d2d05 100644 --- a/src/test/regress/expected/partition_split.out +++ b/src/test/regress/expected/partition_split.out @@ -1751,6 +1751,66 @@ SELECT relname, reltablespace FROM pg_class tp_lo | 0 (2 rows) +DROP TABLE t; +-- SPLIT PARTITION carries the split partition's replica identity to the new +-- partitions. +CREATE TABLE t (i int PRIMARY KEY) PARTITION BY RANGE (i); +CREATE TABLE tp_0_2 PARTITION OF t FOR VALUES FROM (0) TO (2); +ALTER TABLE tp_0_2 REPLICA IDENTITY FULL; +ALTER TABLE t SPLIT PARTITION tp_0_2 INTO + (PARTITION tp_0_1 FOR VALUES FROM (0) TO (1), + PARTITION tp_1_2 FOR VALUES FROM (1) TO (2)); +SELECT relname, relreplident FROM pg_class + WHERE relname IN ('tp_0_1', 'tp_1_2') + AND relnamespace = 'partition_split_schema'::regnamespace ORDER BY relname; + relname | relreplident +---------+-------------- + tp_0_1 | f + tp_1_2 | f +(2 rows) + +DROP TABLE t; +-- SPLIT PARTITION rejects a partition that is directly part of a publication. +CREATE TABLE t (i int PRIMARY KEY) PARTITION BY RANGE (i); +CREATE TABLE tp_0_2 PARTITION OF t FOR VALUES FROM (0) TO (2); +CREATE PUBLICATION pub_split FOR TABLE tp_0_2; +ALTER TABLE t SPLIT PARTITION tp_0_2 INTO + (PARTITION tp_0_1 FOR VALUES FROM (0) TO (1), + PARTITION tp_1_2 FOR VALUES FROM (1) TO (2)); -- fails +ERROR: cannot merge or split partition "tp_0_2" that is directly part of a publication +HINT: Publish the partitioned table instead, or remove the partition from the publication before the operation and add the new partition to it afterwards. +DROP PUBLICATION pub_split; +DROP TABLE t; +-- Creating the new partitions in another schema is only rejected when that +-- actually changes which FOR TABLES IN SCHEMA publications cover them. +CREATE TABLE t (i int) PARTITION BY RANGE (i); +CREATE TABLE tp_0_2 PARTITION OF t FOR VALUES FROM (0) TO (2); +INSERT INTO t VALUES (0), (1); +-- No such publication, so a cross-schema split is fine. +ALTER TABLE t SPLIT PARTITION tp_0_2 INTO + (PARTITION partition_split_schema2.tp_0_1 FOR VALUES FROM (0) TO (1), + PARTITION partition_split_schema2.tp_1_2 FOR VALUES FROM (1) TO (2)); +SELECT count(*) FROM t; + count +------- + 2 +(1 row) + +DROP TABLE t; +CREATE TABLE t (i int) PARTITION BY RANGE (i); +CREATE TABLE tp_0_2 PARTITION OF t FOR VALUES FROM (0) TO (2); +CREATE PUBLICATION pub_split FOR TABLES IN SCHEMA partition_split_schema; +ALTER TABLE t SPLIT PARTITION tp_0_2 INTO + (PARTITION partition_split_schema2.tp_0_1 FOR VALUES FROM (0) TO (1), + PARTITION partition_split_schema2.tp_1_2 FOR VALUES FROM (1) TO (2)); -- fails +ERROR: cannot move partition "tp_0_2" to schema "partition_split_schema2" with different publications for tables in schema +DETAIL: Schema "partition_split_schema" and schema "partition_split_schema2" are not covered by the same publications defined FOR TABLES IN SCHEMA, so the new partition would silently join or leave a publication. +HINT: Create the new partition in the same schema, or publish the partitioned table itself. +-- Staying in the covered schema is fine. +ALTER TABLE t SPLIT PARTITION tp_0_2 INTO + (PARTITION tp_0_1 FOR VALUES FROM (0) TO (1), + PARTITION tp_1_2 FOR VALUES FROM (1) TO (2)); +DROP PUBLICATION pub_split; DROP TABLE t; RESET search_path; -- diff --git a/src/test/regress/sql/partition_merge.sql b/src/test/regress/sql/partition_merge.sql index 80dc365b0ce..9c41b252ad3 100644 --- a/src/test/regress/sql/partition_merge.sql +++ b/src/test/regress/sql/partition_merge.sql @@ -839,6 +839,56 @@ SELECT reltablespace FROM pg_class WHERE relname = 'tp_merged'; DROP TABLE t; +-- MERGE PARTITIONS carries over a uniform replica identity ... +CREATE TABLE t (i int PRIMARY KEY) PARTITION BY RANGE (i); +CREATE TABLE tp_0_1 PARTITION OF t FOR VALUES FROM (0) TO (1); +CREATE TABLE tp_1_2 PARTITION OF t FOR VALUES FROM (1) TO (2); +ALTER TABLE tp_0_1 REPLICA IDENTITY FULL; +ALTER TABLE tp_1_2 REPLICA IDENTITY FULL; +ALTER TABLE t MERGE PARTITIONS (tp_0_1, tp_1_2) INTO tp_0_2; +SELECT relreplident FROM pg_class WHERE relname = 'tp_0_2' + AND relnamespace = 'partitions_merge_schema'::regnamespace; +DROP TABLE t; + +-- ... but rejects merging partitions with different replica identities. +CREATE TABLE t (i int PRIMARY KEY) PARTITION BY RANGE (i); +CREATE TABLE tp_0_1 PARTITION OF t FOR VALUES FROM (0) TO (1); +CREATE TABLE tp_1_2 PARTITION OF t FOR VALUES FROM (1) TO (2); +ALTER TABLE tp_0_1 REPLICA IDENTITY FULL; +ALTER TABLE t MERGE PARTITIONS (tp_0_1, tp_1_2) INTO tp_0_2; -- fails +DROP TABLE t; + +-- MERGE PARTITIONS rejects a partition that is directly part of a publication. +CREATE TABLE t (i int PRIMARY KEY) PARTITION BY RANGE (i); +CREATE TABLE tp_0_1 PARTITION OF t FOR VALUES FROM (0) TO (1); +CREATE TABLE tp_1_2 PARTITION OF t FOR VALUES FROM (1) TO (2); +CREATE PUBLICATION pub_merge FOR TABLE tp_0_1; +ALTER TABLE t MERGE PARTITIONS (tp_0_1, tp_1_2) INTO tp_0_2; -- fails +DROP PUBLICATION pub_merge; +DROP TABLE t; + +-- Creating the new partition in another schema is only rejected when that +-- actually changes which FOR TABLES IN SCHEMA publications cover it. +CREATE TABLE t (i int) PARTITION BY RANGE (i); +CREATE TABLE tp_0_1 PARTITION OF t FOR VALUES FROM (0) TO (1); +CREATE TABLE tp_1_2 PARTITION OF t FOR VALUES FROM (1) TO (2); +INSERT INTO t VALUES (0), (1); +-- No such publication, so a cross-schema merge is fine. +ALTER TABLE t MERGE PARTITIONS (tp_0_1, tp_1_2) INTO partitions_merge_schema2.tp_0_2; +SELECT count(*) FROM t; +DROP TABLE t; + +CREATE TABLE t (i int) PARTITION BY RANGE (i); +CREATE TABLE tp_0_1 PARTITION OF t FOR VALUES FROM (0) TO (1); +CREATE TABLE tp_1_2 PARTITION OF t FOR VALUES FROM (1) TO (2); +CREATE PUBLICATION pub_merge FOR TABLES IN SCHEMA partitions_merge_schema; +ALTER TABLE t MERGE PARTITIONS (tp_0_1, tp_1_2) + INTO partitions_merge_schema2.tp_0_2; -- fails +-- Staying in the covered schema is fine. +ALTER TABLE t MERGE PARTITIONS (tp_0_1, tp_1_2) INTO tp_0_2; +DROP PUBLICATION pub_merge; +DROP TABLE t; + RESET search_path; -- diff --git a/src/test/regress/sql/partition_split.sql b/src/test/regress/sql/partition_split.sql index ffd15e7f969..c470c42be71 100644 --- a/src/test/regress/sql/partition_split.sql +++ b/src/test/regress/sql/partition_split.sql @@ -1256,6 +1256,54 @@ SELECT relname, reltablespace FROM pg_class WHERE relname IN ('tp_lo', 'tp_hi') ORDER BY relname; DROP TABLE t; +-- SPLIT PARTITION carries the split partition's replica identity to the new +-- partitions. +CREATE TABLE t (i int PRIMARY KEY) PARTITION BY RANGE (i); +CREATE TABLE tp_0_2 PARTITION OF t FOR VALUES FROM (0) TO (2); +ALTER TABLE tp_0_2 REPLICA IDENTITY FULL; +ALTER TABLE t SPLIT PARTITION tp_0_2 INTO + (PARTITION tp_0_1 FOR VALUES FROM (0) TO (1), + PARTITION tp_1_2 FOR VALUES FROM (1) TO (2)); +SELECT relname, relreplident FROM pg_class + WHERE relname IN ('tp_0_1', 'tp_1_2') + AND relnamespace = 'partition_split_schema'::regnamespace ORDER BY relname; +DROP TABLE t; + +-- SPLIT PARTITION rejects a partition that is directly part of a publication. +CREATE TABLE t (i int PRIMARY KEY) PARTITION BY RANGE (i); +CREATE TABLE tp_0_2 PARTITION OF t FOR VALUES FROM (0) TO (2); +CREATE PUBLICATION pub_split FOR TABLE tp_0_2; +ALTER TABLE t SPLIT PARTITION tp_0_2 INTO + (PARTITION tp_0_1 FOR VALUES FROM (0) TO (1), + PARTITION tp_1_2 FOR VALUES FROM (1) TO (2)); -- fails +DROP PUBLICATION pub_split; +DROP TABLE t; + +-- Creating the new partitions in another schema is only rejected when that +-- actually changes which FOR TABLES IN SCHEMA publications cover them. +CREATE TABLE t (i int) PARTITION BY RANGE (i); +CREATE TABLE tp_0_2 PARTITION OF t FOR VALUES FROM (0) TO (2); +INSERT INTO t VALUES (0), (1); +-- No such publication, so a cross-schema split is fine. +ALTER TABLE t SPLIT PARTITION tp_0_2 INTO + (PARTITION partition_split_schema2.tp_0_1 FOR VALUES FROM (0) TO (1), + PARTITION partition_split_schema2.tp_1_2 FOR VALUES FROM (1) TO (2)); +SELECT count(*) FROM t; +DROP TABLE t; + +CREATE TABLE t (i int) PARTITION BY RANGE (i); +CREATE TABLE tp_0_2 PARTITION OF t FOR VALUES FROM (0) TO (2); +CREATE PUBLICATION pub_split FOR TABLES IN SCHEMA partition_split_schema; +ALTER TABLE t SPLIT PARTITION tp_0_2 INTO + (PARTITION partition_split_schema2.tp_0_1 FOR VALUES FROM (0) TO (1), + PARTITION partition_split_schema2.tp_1_2 FOR VALUES FROM (1) TO (2)); -- fails +-- Staying in the covered schema is fine. +ALTER TABLE t SPLIT PARTITION tp_0_2 INTO + (PARTITION tp_0_1 FOR VALUES FROM (0) TO (1), + PARTITION tp_1_2 FOR VALUES FROM (1) TO (2)); +DROP PUBLICATION pub_split; +DROP TABLE t; + RESET search_path; -- -- 2.55.0