On Wed, Oct 14, 2026 at 8:16 PM Nisha Moond <[email protected]> wrote:
>
> On Tue, Oct 13, 2026 at 3:34 PM Dilip Kumar <[email protected]> wrote:
> >
> > I merged v80-0001 and v80-0002 and adjusted the commit message and
> > test accordingly; I also included Amit's and Hou-san's code
> > refactoring.  Rebased Nisha's V80_2-0003 on top of that as 0002, also
> > merged Shveta's datestyle fix with some modification into the 0002
> >
>
> Thanks for the updated patches. A few comments on v81-0001:
>

There seems to be a bug where the replica_identity column uses index
attribute names, which can be stale or incorrect.
build_index_key_json() takes each JSON key name from the index's tuple
descriptor:

-     tableslot = ExecCopySlot(tableslot, slot);
+     Form_pg_attribute att = TupleDescAttr(indexTupDesc, i);
+
+     if (i > 0)
+        appendStringInfoChar(&result, ',');
+
+     escape_json(&result, NameStr(att->attname));

Index attribute names are copied from the table when the index is
created, and aren't kept in sync afterwards. So the CLT can record a
key under a name that isn't the column's current name. The server log
doesn't have this problem: BuildIndexValueDescription() builds the
names with pg_get_indexdef_columns(), which reads the current column
names from the table. So the CLT and the log disagree in below two
cases.

Case 1: Renamed key column: the CLT records the old name
----------------------------------------------
-- [PUB]
CREATE TABLE r3 (a int PRIMARY KEY, b int);
CREATE PUBLICATION pub3 FOR TABLE r3;

-- [SUB]
CREATE TABLE r3 (a int PRIMARY KEY, b int);
CREATE SUBSCRIPTION sub3 CONNECTION '...' PUBLICATION pub3 WITH
(copy_data = false, conflict_log_destination = 'all');

-- [PUB]
INSERT INTO r3 VALUES (1, 10);
ALTER TABLE r3 RENAME COLUMN a TO z;

-- [SUB]
ALTER TABLE r3 RENAME COLUMN a TO z;
DELETE FROM r3;

-- [PUB] -- trigger conflict
DELETE FROM r3 WHERE z = 1;

CLT:
postgres=# select conflict_type, replica_identity from
pg_conflict.pg_conflict_log_16390;
 conflict_type  | replica_identity
----------------+------------------
 delete_missing | {"a":"1"}

Meanwhile the LOG shows the correct altered name:
LOG:  conflict detected on relation "public.r3": conflict=delete_missing
DETAIL:  Could not find the row to be deleted: replica identity (z)=(1).
~~~

case 2: UNIQUE (a, a) replica identity: a value is recorded under
another column's name
----------------------------------------------
-- [PUB]
CREATE TABLE d3 (a int PRIMARY KEY, a1 int, b int);
CREATE PUBLICATION pub3d FOR TABLE d3;

-- [SUB]
CREATE TABLE d3 (a int NOT NULL, a1 int NOT NULL, b int);
CREATE UNIQUE INDEX d3_aa ON d3 (a, a);
ALTER TABLE d3 REPLICA IDENTITY USING INDEX d3_aa;
CREATE SUBSCRIPTION sub3d CONNECTION '...' PUBLICATION pub3d WITH
(copy_data = false, conflict_log_destination = 'all');

-- [PUB]
INSERT INTO d3 VALUES (1, 99, 0);

-- [SUB]
DELETE FROM d3;

-- [PUB]
DELETE FROM d3;

CLT:
postgres=# select conflict_type, replica_identity from
pg_conflict.pg_conflict_log_16406;
 conflict_type  |  replica_identity
----------------+--------------------
 delete_missing | {"a":"1","a1":"1"}
(1 row)

LOG shows:
LOG:  conflict detected on relation "public.d3": conflict=delete_missing
DETAIL:  Could not find the row to be deleted: replica identity (a, a)=(1, 1).
~~~

How about fixing it like in the attached patch.

--
Thanks,
Nisha
From 2889022316f4929466a071bfc20cb0a478c88159 Mon Sep 17 00:00:00 2001
From: Nisha Moond <[email protected]>
Date: Thu, 15 Oct 2026 14:45:00 +0530
Subject: [PATCH] fix index stale or incorrect key name issue

---
 src/backend/replication/logical/conflict.c | 9 ++++++---
 1 file changed, 6 insertions(+), 3 deletions(-)

diff --git a/src/backend/replication/logical/conflict.c b/src/backend/replication/logical/conflict.c
index 4683253587b..c00e78427ab 100644
--- a/src/backend/replication/logical/conflict.c
+++ b/src/backend/replication/logical/conflict.c
@@ -1053,7 +1053,7 @@ build_index_key_json(EState *estate, Relation localrel,
 					 bool *omitted)
 {
 	Relation	indexDesc;
-	TupleDesc	indexTupDesc;
+	TupleDesc	reltupdesc = RelationGetDescr(localrel);
 	Datum		values[INDEX_MAX_KEYS];
 	bool		isnull[INDEX_MAX_KEYS];
 	StringInfoData result;
@@ -1066,7 +1066,6 @@ build_index_key_json(EState *estate, Relation localrel,
 	Assert(CheckRelationOidLockedByMe(indexid, RowExclusiveLock, true));
 
 	indexDesc = index_open(indexid, NoLock);
-	indexTupDesc = RelationGetDescr(indexDesc);
 
 	build_index_datums_from_slot(estate, localrel, slot, indexDesc, values,
 								 isnull);
@@ -1114,7 +1113,11 @@ build_index_key_json(EState *estate, Relation localrel,
 
 	for (int i = 0; i < indnkeyatts; i++)
 	{
-		Form_pg_attribute att = TupleDescAttr(indexTupDesc, i);
+        Form_pg_attribute att;
+		AttrNumber	attnum = indexDesc->rd_index->indkey.values[i];
+
+		Assert(attnum > 0);		/* RI and PK indexes have no expressions */
+		att = TupleDescAttr(reltupdesc, attnum - 1);
 
 		if (i > 0)
 			appendStringInfoChar(&result, ',');
-- 
2.54.0 (Apple Git-157)

Reply via email to