From ed1555a53a1fd6fbc3870f0da578d53a265abb43 Mon Sep 17 00:00:00 2001
From: Atsushi Torikoshi <torikoshia@oss.nttdata.com>
Date: Wed, 12 Aug 2026 15:39:02 +0900
Subject: [PATCH v3] Use start LSN for transactional logical decoding messages

Logical decoding currently reports the end LSN of a logical message
record for both transactional and non-transactional messages. This
differs from regular transactional changes, for which the reported LSN
identifies the start of the WAL record.

This difference can matter to logical decoding consumers that persist
the LSN of decoded changes and use it to determine where to resume
decoding after an unexpected interruption.

For transactional logical messages, this patch uses the start LSN, as
is done for other transactional changes.

For non-transactional logical messages, it continues to use the end
LSN. Such messages are processed independently of a surrounding
transaction, so the end LSN can be used as their confirmed flush
position.
---
 doc/src/sgml/protocol.sgml               | 3 ++-
 src/backend/replication/logical/decode.c | 7 ++++++-
 2 files changed, 8 insertions(+), 2 deletions(-)

diff --git a/doc/src/sgml/protocol.sgml b/doc/src/sgml/protocol.sgml
index 49f81676712..a65a15ad9c7 100644
--- a/doc/src/sgml/protocol.sgml
+++ b/doc/src/sgml/protocol.sgml
@@ -6678,7 +6678,8 @@ psql "dbname=postgres replication=database" -c "IDENTIFY_SYSTEM;"
        <term>Int64 (XLogRecPtr)</term>
        <listitem>
         <para>
-         The LSN of the logical decoding message.
+         The start LSN of the logical decoding message for transactional
+         messages, or its end LSN for non-transactional messages.
         </para>
        </listitem>
       </varlistentry>
diff --git a/src/backend/replication/logical/decode.c b/src/backend/replication/logical/decode.c
index c944be4ac83..fd727a7ec7f 100644
--- a/src/backend/replication/logical/decode.c
+++ b/src/backend/replication/logical/decode.c
@@ -668,7 +668,12 @@ logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
 	if (!message->transactional)
 		snapshot = SnapBuildGetOrBuildSnapshot(builder);
 
-	ReorderBufferQueueMessage(ctx->reorder, xid, snapshot, buf->endptr,
+	/*
+	 * Non-transactional messages are processed as separate transactions on
+	 * the receiver, so use endptr as the confirmed flush position for them.
+	 */
+	ReorderBufferQueueMessage(ctx->reorder, xid, snapshot,
+							  message->transactional ? buf->origptr : buf->endptr,
 							  message->transactional,
 							  message->message, /* first part of message is
 												 * prefix */
-- 
2.48.1

