From e8f3e9a4d71f0075af5e93abb9715f2fe0b268c2 Mon Sep 17 00:00:00 2001
From: Rui Zhao <zhaorui126@gmail.com>
Date: Sat, 19 Sep 2026 23:53:05 +0800
Subject: [PATCH v5 4/4] Fix XID count in initial decoding snapshots

In v5-0001, newxcnt is incremented even for committed XIDs that are
not added to newxip. This counts uninitialized array entries as part
of the converted snapshot. Increment the count only when storing an
XID in the array.

Extend the USE_SNAPSHOT test to check that every XID in the resulting
snapshot lies in [xmin, xmax).
---
 src/backend/replication/logical/snapbuild.c     |  4 +---
 src/test/recovery/t/057_snapshot_commit_race.pl | 10 +++++++++-
 2 files changed, 10 insertions(+), 4 deletions(-)

diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index 261f25a5cd..c03428b5e1 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -517,7 +517,7 @@ SnapBuildInitialSnapshot(SnapBuild *builder)
 						(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
 						 errmsg("initial slot snapshot too large")));
 
-			newxip[newxcnt] = xid;
+			newxip[newxcnt++] = xid;
 		}
 		else
 		{
@@ -532,8 +532,6 @@ SnapBuildInitialSnapshot(SnapBuild *builder)
 				XactLockTableWait(xid, NULL, NULL, XLTW_None);
 		}
 
-		newxcnt++;
-
 		TransactionIdAdvance(xid);
 	}
 
diff --git a/src/test/recovery/t/057_snapshot_commit_race.pl b/src/test/recovery/t/057_snapshot_commit_race.pl
index af11ad2869..a0d23d9a8c 100644
--- a/src/test/recovery/t/057_snapshot_commit_race.pl
+++ b/src/test/recovery/t/057_snapshot_commit_race.pl
@@ -106,7 +106,7 @@ is($state, 'waiting for s4',
 
 # If the slot got created without waiting, use its snapshot right away:
 # the scan takes s4 as aborted and sets hint bits accordingly, which is
-# what the last two checks then report.
+# what the two row checks below then report.
 if ($state eq 'slot created')
 {
 	$walsender->query_until(qr/test_decoding/, '');
@@ -132,6 +132,14 @@ is( $node->safe_psql('postgres', 'SELECT i, j FROM tab ORDER BY i'),
 	"1|2\n3|3",
 	"a new session sees the transaction's changes");
 
+is( $walsender->query_safe(
+		q(WITH s AS (SELECT pg_current_snapshot() AS snap)
+		  SELECT count(*) FROM s, LATERAL pg_snapshot_xip(snap) AS x(xid)
+		  WHERE xid < pg_snapshot_xmin(snap) OR xid >= pg_snapshot_xmax(snap))
+	),
+	'0',
+	'snapshot XIDs are all within xmin and xmax');
+
 $walsender->query_safe('ROLLBACK');
 $walsender->quit;
 $s2->quit;
-- 
2.43.7

