From cf42dee8571e7c584cd1f7435ebe0d08f08be4b9 Mon Sep 17 00:00:00 2001
From: "Chao Li (Evan)" <lic@highgo.com>
Date: Thu, 11 Dec 2025 17:03:37 +0800
Subject: [PATCH v2 1/2] Inherit replica identity for new and attached
 partitions
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

When a replica identity is set on a partitioned table, newly created
partitions and tables attached as partitions should inherit the parent’s
replica identity setting. Previously, such relations kept the default
replica identity, which could lead to inconsistent behavior in logical
replication.

Update StorePartitionBound() to copy relreplident from the parent
relation when marking a table as a partition. This ensures consistent
replica identity semantics across partition hierarchies, including
multi-level partitioning.

Document the behavior in ALTER TABLE ... REPLICA IDENTITY, and add
regression tests covering:

 * partitions created with CREATE TABLE ... PARTITION OF
 * tables attached via ALTER TABLE ... ATTACH PARTITION
 * inheritance through partitioned children and grandchildren

Author: Chao Li <lic@highgo.com>
Discussion: https://postgr.es/m/CAEoWx2nJ71hy8R614HQr7vQhkBReO9AANPODPg0aSQs74eOdLQ@mail.gmail.com
---
 doc/src/sgml/ref/alter_table.sgml             |  5 ++
 src/backend/catalog/heap.c                    |  8 ++-
 .../regress/expected/replica_identity.out     | 69 +++++++++++++++++++
 src/test/regress/sql/replica_identity.sql     | 41 +++++++++++
 4 files changed, 121 insertions(+), 2 deletions(-)

diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index 9abd8037f28..fb9140c8794 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -965,6 +965,11 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
       from the new value; however, if the old value is stored externally, it is
       always logged regardless of whether it changed.
       This option has no effect except when logical replication is in use.
+      When a replica identity is set on a partitioned table, new partitions
+      created with <command>CREATE TABLE ... PARTITION OF</command> and tables
+      attached with <command>ALTER TABLE ... ATTACH PARTITION</command> inherit
+      that replica identity, though it can still be changed for a partition
+      afterwards.
      <variablelist>
       <varlistentry id="sql-altertable-replica-identity-default">
        <term><literal>DEFAULT</literal></term>
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 265cc3e5fbf..4ffc9de064e 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -4036,8 +4036,9 @@ RemovePartitionKeyByRelId(Oid relid)
 
 /*
  * StorePartitionBound
- *		Update pg_class tuple of rel to store the partition bound and set
- *		relispartition to true
+ *		Update pg_class tuple of rel to store the partition bound, set 
+ *		relreplident to the same of the parent and set relispartition
+ *		to true
  *
  * If this is the default partition, also update the default partition OID in
  * pg_partitioned_table.
@@ -4090,6 +4091,9 @@ StorePartitionBound(Relation rel, Relation parent, PartitionBoundSpec *bound)
 	/* Also set the flag */
 	((Form_pg_class) GETSTRUCT(newtuple))->relispartition = true;
 
+	/* Copy replication identity from parent if needed */
+	((Form_pg_class) GETSTRUCT(newtuple))->relreplident = parent->rd_rel->relreplident;
+
 	/*
 	 * We already checked for no inheritance children, but reset
 	 * relhassubclass in case it was left over.
diff --git a/src/test/regress/expected/replica_identity.out b/src/test/regress/expected/replica_identity.out
index b9b8dde018f..9766bd5af89 100644
--- a/src/test/regress/expected/replica_identity.out
+++ b/src/test/regress/expected/replica_identity.out
@@ -290,6 +290,73 @@ ALTER TABLE test_replica_identity5 DROP CONSTRAINT test_replica_identity5_pkey;
 ERROR:  constraint "test_replica_identity5_pkey" of relation "test_replica_identity5" does not exist
 ALTER TABLE test_replica_identity5 ALTER b DROP NOT NULL;
 ERROR:  column "b" is in index used as replica identity
+-- New partitions inherit parent's replica identity setting
+CREATE TABLE test_replica_identity6 (a int, b int) PARTITION BY LIST (a);
+ALTER TABLE test_replica_identity6 REPLICA IDENTITY FULL;
+CREATE TABLE test_replica_identity6_p1 PARTITION OF test_replica_identity6
+  FOR VALUES IN (1);
+SELECT relreplident FROM pg_class
+  WHERE oid = 'test_replica_identity6_p1'::regclass;
+ relreplident 
+--------------
+ f
+(1 row)
+
+CREATE TABLE test_replica_identity6_p2 (a int, b int);
+SELECT relreplident FROM pg_class
+  WHERE oid = 'test_replica_identity6_p2'::regclass;
+ relreplident 
+--------------
+ d
+(1 row)
+
+ALTER TABLE test_replica_identity6 ATTACH PARTITION test_replica_identity6_p2
+  FOR VALUES IN (2);
+SELECT relreplident FROM pg_class
+  WHERE oid = 'test_replica_identity6_p2'::regclass;
+ relreplident 
+--------------
+ f
+(1 row)
+
+-- Partitioned child tables and their grandchildren inherit replica identity
+CREATE TABLE test_replica_identity7 (a int, b int) PARTITION BY LIST (a);
+ALTER TABLE test_replica_identity7 REPLICA IDENTITY FULL;
+CREATE TABLE test_replica_identity7_p PARTITION OF test_replica_identity7
+  FOR VALUES IN (1) PARTITION BY LIST (b);
+SELECT relreplident FROM pg_class
+  WHERE oid = 'test_replica_identity7_p'::regclass;
+ relreplident 
+--------------
+ f
+(1 row)
+
+CREATE TABLE test_replica_identity7_p1 PARTITION OF test_replica_identity7_p
+  FOR VALUES IN (1);
+SELECT relreplident FROM pg_class
+  WHERE oid = 'test_replica_identity7_p1'::regclass;
+ relreplident 
+--------------
+ f
+(1 row)
+
+CREATE TABLE test_replica_identity7_leaf (a int, b int);
+SELECT relreplident FROM pg_class
+  WHERE oid = 'test_replica_identity7_leaf'::regclass;
+ relreplident 
+--------------
+ d
+(1 row)
+
+ALTER TABLE test_replica_identity7_p ATTACH PARTITION test_replica_identity7_leaf
+  FOR VALUES IN (2);
+SELECT relreplident FROM pg_class
+  WHERE oid = 'test_replica_identity7_leaf'::regclass;
+ relreplident 
+--------------
+ f
+(1 row)
+
 DROP TABLE test_replica_identity;
 DROP TABLE test_replica_identity2;
 DROP TABLE test_replica_identity3;
@@ -297,3 +364,5 @@ DROP TABLE test_replica_identity4;
 DROP TABLE test_replica_identity5;
 DROP TABLE test_replica_identity_othertable;
 DROP TABLE test_replica_identity_t3;
+DROP TABLE test_replica_identity6;
+DROP TABLE test_replica_identity7;
diff --git a/src/test/regress/sql/replica_identity.sql b/src/test/regress/sql/replica_identity.sql
index 30daec05b71..7c0c7365c26 100644
--- a/src/test/regress/sql/replica_identity.sql
+++ b/src/test/regress/sql/replica_identity.sql
@@ -134,6 +134,45 @@ ALTER TABLE test_replica_identity5 ALTER b SET NOT NULL;
 ALTER TABLE test_replica_identity5 DROP CONSTRAINT test_replica_identity5_pkey;
 ALTER TABLE test_replica_identity5 ALTER b DROP NOT NULL;
 
+-- New partitions inherit parent's replica identity setting
+CREATE TABLE test_replica_identity6 (a int, b int) PARTITION BY LIST (a);
+ALTER TABLE test_replica_identity6 REPLICA IDENTITY FULL;
+
+CREATE TABLE test_replica_identity6_p1 PARTITION OF test_replica_identity6
+  FOR VALUES IN (1);
+SELECT relreplident FROM pg_class
+  WHERE oid = 'test_replica_identity6_p1'::regclass;
+
+CREATE TABLE test_replica_identity6_p2 (a int, b int);
+SELECT relreplident FROM pg_class
+  WHERE oid = 'test_replica_identity6_p2'::regclass;
+ALTER TABLE test_replica_identity6 ATTACH PARTITION test_replica_identity6_p2
+  FOR VALUES IN (2);
+SELECT relreplident FROM pg_class
+  WHERE oid = 'test_replica_identity6_p2'::regclass;
+
+-- Partitioned child tables and their grandchildren inherit replica identity
+CREATE TABLE test_replica_identity7 (a int, b int) PARTITION BY LIST (a);
+ALTER TABLE test_replica_identity7 REPLICA IDENTITY FULL;
+
+CREATE TABLE test_replica_identity7_p PARTITION OF test_replica_identity7
+  FOR VALUES IN (1) PARTITION BY LIST (b);
+SELECT relreplident FROM pg_class
+  WHERE oid = 'test_replica_identity7_p'::regclass;
+
+CREATE TABLE test_replica_identity7_p1 PARTITION OF test_replica_identity7_p
+  FOR VALUES IN (1);
+SELECT relreplident FROM pg_class
+  WHERE oid = 'test_replica_identity7_p1'::regclass;
+
+CREATE TABLE test_replica_identity7_leaf (a int, b int);
+SELECT relreplident FROM pg_class
+  WHERE oid = 'test_replica_identity7_leaf'::regclass;
+ALTER TABLE test_replica_identity7_p ATTACH PARTITION test_replica_identity7_leaf
+  FOR VALUES IN (2);
+SELECT relreplident FROM pg_class
+  WHERE oid = 'test_replica_identity7_leaf'::regclass;
+
 DROP TABLE test_replica_identity;
 DROP TABLE test_replica_identity2;
 DROP TABLE test_replica_identity3;
@@ -141,3 +180,5 @@ DROP TABLE test_replica_identity4;
 DROP TABLE test_replica_identity5;
 DROP TABLE test_replica_identity_othertable;
 DROP TABLE test_replica_identity_t3;
+DROP TABLE test_replica_identity6;
+DROP TABLE test_replica_identity7;
-- 
2.39.5 (Apple Git-154)

