From 029ed90d8398828f8ab6df7d70cce9c84f453411 Mon Sep 17 00:00:00 2001
From: Alexander Korotkov <akorotkov@postgresql.org>
Date: Mon, 3 Aug 2026 00:14:57 +0200
Subject: [PATCH v3 2/3] 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.

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             |  33 ++++
 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      |  51 ++++++
 src/test/regress/sql/partition_split.sql      |  48 ++++++
 6 files changed, 402 insertions(+)

diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index b8246a7ee48..04ab3d08bbd 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -1281,6 +1281,24 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
       dependencies are not silently lost during merge.
      </para>
 
+     <para>
+      The new partition takes its replica identity from the merged partitions
+      when they all use the same simple setting
+      (<literal>DEFAULT</literal>, <literal>FULL</literal> or
+      <literal>NOTHING</literal>).  If they use different settings, or use
+      <literal>REPLICA IDENTITY USING INDEX</literal>, 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
+      <literal>FOR TABLES IN SCHEMA</literal> as the schema of the partitions
+      being merged.
+     </para>
+
      <para>
       Moving rows into the new partition does not emit logical replication
       messages, in the same way that <command>CLUSTER</command> or
@@ -1401,6 +1419,21 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
       from the source partition's indexes.
      </para>
 
+     <para>
+      The new partitions take their replica identity from the split partition,
+      unless it uses <literal>REPLICA IDENTITY USING INDEX</literal>, 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
+      <literal>FOR TABLES IN SCHEMA</literal> as the schema of the partition
+      being split.
+     </para>
+
      <para>
       Moving rows into the new partitions does not emit logical replication
       messages, in the same way that <command>CLUSTER</command> or
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 0eb85c1be17..46396117006 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 add the new partition to the publication after the operation."));
+
+		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("Set the replica identity of the new partition explicitly after the operation."));
+
+		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("Set the replica identity of the new partition explicitly 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..c00cd5b5599 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:  Set the replica identity of the new partition explicitly after the operation.
+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 add the new partition to the publication after the operation.
+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..3f7d49b2204 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 add the new partition to the publication after the operation.
+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..63f1ccd0fba 100644
--- a/src/test/regress/sql/partition_merge.sql
+++ b/src/test/regress/sql/partition_merge.sql
@@ -839,6 +839,57 @@ 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

