From 265830febb6e22b92dc181b97d773b0ab1b2e338 Mon Sep 17 00:00:00 2001
From: Atsushi Torikoshi <torikoshia@oss.nttdata.com>
Date: Wed, 15 Jul 2026 18:34:21 +0900
Subject: [PATCH v2] Fix LSN of logical decoding message

Report startptr, rather than endptr, as the LSN of a decoded
logical message. The logical replication protocol documentation
describes this field as the LSN of the logical decoding message, but
the current implementation reports the end of its WAL record.

This distinction matters to logical decoding consumers. A consumer
typically records the LSN up to which decoded output has been
successfully processed and uses it as the restart point after a crash
or other failure. PostgreSQL reports startptr when decoding DML
records, but reports endptr only for decoding
pg_logical_emit_message(). Consumers that handle logical messages
in the same way as other decoded records can therefore store an LSN
past the message it has just processed and restart from that later
position after a failure, potentially resulting in data loss.

The regression test is adjusted accordingly. The test emits only a
single non-transactional logical message, which is decoded with
xid = 0, so filtering on xid = 0 is sufficient to identify the
message without matching its LSN.
---
 src/backend/replication/logical/decode.c | 2 +-
 src/test/subscription/t/020_messages.pl  | 4 ++--
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/src/backend/replication/logical/decode.c b/src/backend/replication/logical/decode.c
index c944be4ac83..09efc15e454 100644
--- a/src/backend/replication/logical/decode.c
+++ b/src/backend/replication/logical/decode.c
@@ -668,7 +668,7 @@ logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
 	if (!message->transactional)
 		snapshot = SnapBuildGetOrBuildSnapshot(builder);
 
-	ReorderBufferQueueMessage(ctx->reorder, xid, snapshot, buf->endptr,
+	ReorderBufferQueueMessage(ctx->reorder, xid, snapshot, buf->origptr,
 							  message->transactional,
 							  message->message, /* first part of message is
 												 * prefix */
diff --git a/src/test/subscription/t/020_messages.pl b/src/test/subscription/t/020_messages.pl
index ac518849156..bdedd5ff812 100644
--- a/src/test/subscription/t/020_messages.pl
+++ b/src/test/subscription/t/020_messages.pl
@@ -94,7 +94,7 @@ is($result, qq(),
 
 $node_publisher->safe_psql('postgres', "INSERT INTO tab_test VALUES (1)");
 
-my $message_lsn = $node_publisher->safe_psql('postgres',
+$node_publisher->safe_psql('postgres',
 	"SELECT pg_logical_emit_message(false, 'pgoutput', 'a non-transactional message')"
 );
 
@@ -107,7 +107,7 @@ $result = $node_publisher->safe_psql(
 			'proto_version', '1',
 			'publication_names', 'tap_pub',
 			'messages', 'true')
-		WHERE lsn = '$message_lsn' AND xid = 0
+		WHERE xid = 0
 ));
 
 is($result, qq(77|0), 'non-transactional message on slot is M');
-- 
2.48.1

