Dear Nisha,

> While testing another feature patch, I came across this base-code
> issue. If a subscriber table's only key is a DEFERRABLE primary key,
> and the published table does not use REPLICA IDENTITY FULL, then
> UPDATE and DELETE apply trips an assertion.

Good catch, I confirmed the same.

> The cause is that two code paths disagree.
> logicalrep_rel_mark_updatable() finds no replica identity bitmap and
> falls back to INDEX_ATTR_BITMAP_PRIMARY_KEY.
> Since commit 270af6f0df7 (pg17), that bitmap includes deferrable
> primary keys, so the relation is marked updatable.
> FindLogicalRepLocalIndex(), however, uses GetRelationIdentityOrPK(),
> which calls RelationGetPrimaryKeyIndex(rel, false) and rejects
> deferrable keys. So it returns InvalidOid.

The analysis looks correct to me.

> The attached patch makes mark_updatable() fall back to the primary key
> only when RelationGetPrimaryKeyIndex(rel, false) returns it, which
> matches the lookup path. With the same test, the subscriber will now
> hit an error:
>   ERROR:  logical replication target relation "public.t" has neither
> REPLICA IDENTITY index nor PRIMARY KEY and published relation does not
> have REPLICA IDENTITY FULL

I could not apply your patch on HEAD as-is, have you had some premise patches?
Anyway, I have one comment.

RelationFindDeletedTupleInfoSeq() also has a fallback code. Per my 
understanding,
the same tuple-detection rule should be used everywhere thus it also should be 
fixed,
right? Like attached.

[1]:
        /*
         * If the relation has a replica identity key or a primary key that is
         * unusable for locating deleted tuples (see
         * IsIndexUsableForFindingDeletedTuple), a full table scan becomes
         * necessary. In such cases, comparing the entire tuple is not required,
         * since the remote tuple might not include all column values. Instead,
         * the indexed columns alone are sufficient to identify the target tuple
         * (see logicalrep_rel_mark_updatable).
         */
        indexbitmap = RelationGetIndexAttrBitmap(rel,
                                                                                
         INDEX_ATTR_BITMAP_IDENTITY_KEY);

        /* fallback to PK if no replica identity */
        if (!indexbitmap)
                indexbitmap = RelationGetIndexAttrBitmap(rel,
                                                                                
                 INDEX_ATTR_BITMAP_PRIMARY_KEY);

Best regards,
Hayato Kuroda
FUJITSU LIMITED

From 29cc11a5640c7423e91879d841b6dca99cd59d55 Mon Sep 17 00:00:00 2001
From: Hayato Kuroda <[email protected]>
Date: Tue, 29 Sep 2026 12:15:06 +0900
Subject: [PATCH v1] Don't treat a deferrable PK in
 RelationFindDeletedTupleInfoSeq

---
 src/backend/executor/execReplication.c | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/src/backend/executor/execReplication.c 
b/src/backend/executor/execReplication.c
index fd9efd94737..625d77e41d4 100644
--- a/src/backend/executor/execReplication.c
+++ b/src/backend/executor/execReplication.c
@@ -591,8 +591,11 @@ RelationFindDeletedTupleInfoSeq(Relation rel, 
TupleTableSlot *searchslot,
        indexbitmap = RelationGetIndexAttrBitmap(rel,
                                                                                
         INDEX_ATTR_BITMAP_IDENTITY_KEY);
 
-       /* fallback to PK if no replica identity */
-       if (!indexbitmap)
+       /*
+        * fallback to PK if no replica identity, but only if the PK is not
+        * deferrable.
+        */
+       if (!indexbitmap && OidIsValid(RelationGetPrimaryKeyIndex(rel, false)))
                indexbitmap = RelationGetIndexAttrBitmap(rel,
                                                                                
                 INDEX_ATTR_BITMAP_PRIMARY_KEY);
 
-- 
2.52.0

Reply via email to