From 85be7bf520fc2746f4ee73a0744c7aa04da55e52 Mon Sep 17 00:00:00 2001
From: Peter Geoghegan <pg@bowt.ie>
Date: Tue, 10 Mar 2026 14:40:35 -0400
Subject: [PATCH v19 03/18] heapam: Track heap block in IndexFetchHeapData.

Add an explicit BlockNumber field (xs_blk) to IndexFetchHeapData that
tracks which heap block is currently pinned in xs_cbuf.

heapam_index_fetch_tuple now uses xs_blk to determine when buffer
switching is needed, replacing the previous approach that compared
buffer identities via ReleaseAndReadBuffer on every non-HOT-chain call.

This is preparatory work for an upcoming commit that will add index
prefetching using a read stream.  Delegating the release of a currently
pinned buffer to ReleaseAndReadBuffer won't work anymore -- at least not
when the next buffer that the scan needs to pin is one returned by
read_stream_next_buffer (not a buffer returned by ReadBuffer).

Author: Peter Geoghegan <pg@bowt.ie>
Reviewed-By: Andres Freund <andres@anarazel.de>
Discussion: https://postgr.es/m/CAH2-Wz=g=JTSyDB4UtB5su2ZcvsS7VbP+ZMvvaG6ABoCb+s8Lw@mail.gmail.com
---
 src/include/access/heapam.h                |  5 +--
 src/backend/access/heap/heapam_indexscan.c | 37 +++++++++++++---------
 2 files changed, 25 insertions(+), 17 deletions(-)

diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h
index a06624142..f91f0e2fb 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -122,10 +122,11 @@ typedef struct IndexFetchHeapData
 	IndexFetchTableData xs_base;	/* AM independent part of the descriptor */
 
 	/*
-	 * Current heap buffer in scan, if any. NB: if xs_cbuf is not
-	 * InvalidBuffer, we hold a pin on that buffer.
+	 * Current heap buffer in scan (and its block number), if any.  NB: if
+	 * xs_blk is not InvalidBlockNumber, we hold a pin in xs_cbuf.
 	 */
 	Buffer		xs_cbuf;
+	BlockNumber xs_blk;
 
 	/* Current heap block's corresponding page in the visibility map */
 	Buffer		xs_vmbuffer;
diff --git a/src/backend/access/heap/heapam_indexscan.c b/src/backend/access/heap/heapam_indexscan.c
index f7e43d567..4b5e2b30d 100644
--- a/src/backend/access/heap/heapam_indexscan.c
+++ b/src/backend/access/heap/heapam_indexscan.c
@@ -48,6 +48,7 @@ heapam_index_fetch_begin(Relation rel, uint32 flags)
 	hscan->xs_base.rel = rel;
 	hscan->xs_base.flags = flags;
 	hscan->xs_cbuf = InvalidBuffer;
+	hscan->xs_blk = InvalidBlockNumber;
 	hscan->xs_vmbuffer = InvalidBuffer;
 
 	return &hscan->xs_base;
@@ -62,6 +63,7 @@ heapam_index_fetch_reset(IndexFetchTableData *scan)
 	{
 		ReleaseBuffer(hscan->xs_cbuf);
 		hscan->xs_cbuf = InvalidBuffer;
+		hscan->xs_blk = InvalidBlockNumber;
 	}
 
 	if (BufferIsValid(hscan->xs_vmbuffer))
@@ -292,25 +294,30 @@ heapam_index_fetch_tuple_impl(struct IndexFetchTableData *scan,
 
 	Assert(TTS_IS_BUFFERTUPLE(slot));
 
-	/* We can skip the buffer-switching logic if we're in mid-HOT chain. */
-	if (!*heap_continue)
+	/* We can skip the buffer-switching logic if we're on the same page. */
+	if (hscan->xs_blk != ItemPointerGetBlockNumber(tid))
 	{
-		/* Switch to correct buffer if we don't have it already */
-		Buffer		prev_buf = hscan->xs_cbuf;
+		Assert(!*heap_continue);
 
-		hscan->xs_cbuf = ReleaseAndReadBuffer(hscan->xs_cbuf,
-											  hscan->xs_base.rel,
-											  ItemPointerGetBlockNumber(tid));
+		/* Remember this buffer's block number for next time */
+		hscan->xs_blk = ItemPointerGetBlockNumber(tid);
+
+		if (BufferIsValid(hscan->xs_cbuf))
+			ReleaseBuffer(hscan->xs_cbuf);
+
+		hscan->xs_cbuf = ReadBuffer(hscan->xs_base.rel, hscan->xs_blk);
 
 		/*
-		 * Prune page, but only if we weren't already on this page
+		 * Prune page when it is pinned for the first time
 		 */
-		if (prev_buf != hscan->xs_cbuf)
-			heap_page_prune_opt(hscan->xs_base.rel, hscan->xs_cbuf,
-								&hscan->xs_vmbuffer,
-								hscan->xs_base.flags & SO_HINT_REL_READ_ONLY);
+		heap_page_prune_opt(hscan->xs_base.rel, hscan->xs_cbuf,
+							&hscan->xs_vmbuffer,
+							hscan->xs_base.flags & SO_HINT_REL_READ_ONLY);
 	}
 
+	Assert(BufferGetBlockNumber(hscan->xs_cbuf) == hscan->xs_blk);
+	Assert(hscan->xs_blk == ItemPointerGetBlockNumber(tid));
+
 	/* Obtain share-lock on the buffer so we can examine visibility */
 	LockBuffer(hscan->xs_cbuf, BUFFER_LOCK_SHARE);
 	got_heap_tuple = heap_hot_search_buffer(tid,
@@ -416,11 +423,11 @@ heapam_index_getnext_slot(IndexScanDesc scan, ScanDirection direction,
 					 */
 					if (unlikely(xs_visited_pages_limit > 0))
 					{
-						BlockNumber blk = ItemPointerGetBlockNumber(tid);
+						Assert(hscan->xs_blk == ItemPointerGetBlockNumber(tid));
 
-						if (blk != last_visited_block)
+						if (hscan->xs_blk != last_visited_block)
 						{
-							last_visited_block = blk;
+							last_visited_block = hscan->xs_blk;
 							if (++n_visited_pages > xs_visited_pages_limit)
 								return false;	/* give up */
 						}
-- 
2.53.0

