From ce735ea792bdf40dddf1d38b64c48da1f37e3fdc Mon Sep 17 00:00:00 2001
From: Peter Geoghegan <pg@bowt.ie>
Date: Wed, 25 Mar 2026 00:22:17 -0400
Subject: [PATCH v18 01/19] Move heapam_handler.c index scan code to new file.

Move the heapam index fetch callbacks (index_fetch_begin,
index_fetch_reset, index_fetch_end, and index_fetch_tuple) into a
dedicated file.  This is a purely mechanical move with no functional
changes.

Upcoming work to add a slot-based table AM interface for index scans
will substantially expand this code.  Keeping it in heapam_handler.c
would clutter a file whose primary role is to wire up the TableAmRoutine
callbacks.  Bitmap heap scans and sequential scans would benefit from
similar separation in the future.

Author: Peter Geoghegan <pg@bowt.ie>
Reviewed-By: Andres Freund <andres@anarazel.de>
Discussion: https://postgr.es/m/bmbrkiyjxoal6o5xadzv5bveoynrt3x37wqch7w3jnwumkq2yo@b4zmtnrfs4mh
---
 src/include/access/heapam.h                |   9 ++
 src/backend/access/heap/Makefile           |   1 +
 src/backend/access/heap/heapam_handler.c   | 109 ------------------
 src/backend/access/heap/heapam_indexscan.c | 126 +++++++++++++++++++++
 src/backend/access/heap/meson.build        |   1 +
 5 files changed, 137 insertions(+), 109 deletions(-)
 create mode 100644 src/backend/access/heap/heapam_indexscan.c

diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h
index 9b4032030..62f15e0b1 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -432,6 +432,15 @@ extern void simple_heap_update(Relation relation, const ItemPointerData *otid,
 extern TransactionId heap_index_delete_tuples(Relation rel,
 											  TM_IndexDeleteOp *delstate);
 
+/* in heap/heapam_indexscan.c */
+extern IndexFetchTableData *heapam_index_fetch_begin(Relation rel);
+extern void heapam_index_fetch_reset(IndexFetchTableData *scan);
+extern void heapam_index_fetch_end(IndexFetchTableData *scan);
+extern bool heapam_index_fetch_tuple(struct IndexFetchTableData *scan,
+									 ItemPointer tid, Snapshot snapshot,
+									 TupleTableSlot *slot, bool *heap_continue,
+									 bool *all_dead);
+
 /* in heap/pruneheap.c */
 extern void heap_page_prune_opt(Relation relation, Buffer buffer,
 								Buffer *vmbuffer);
diff --git a/src/backend/access/heap/Makefile b/src/backend/access/heap/Makefile
index 394534172..1d27ccb91 100644
--- a/src/backend/access/heap/Makefile
+++ b/src/backend/access/heap/Makefile
@@ -15,6 +15,7 @@ include $(top_builddir)/src/Makefile.global
 OBJS = \
 	heapam.o \
 	heapam_handler.o \
+	heapam_indexscan.o \
 	heapam_visibility.o \
 	heapam_xlog.o \
 	heaptoast.o \
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index d40878928..eff276f0d 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -75,115 +75,6 @@ heapam_slot_callbacks(Relation relation)
 }
 
 
-/* ------------------------------------------------------------------------
- * Index Scan Callbacks for heap AM
- * ------------------------------------------------------------------------
- */
-
-static IndexFetchTableData *
-heapam_index_fetch_begin(Relation rel)
-{
-	IndexFetchHeapData *hscan = palloc0_object(IndexFetchHeapData);
-
-	hscan->xs_base.rel = rel;
-	hscan->xs_cbuf = InvalidBuffer;
-	hscan->xs_vmbuffer = InvalidBuffer;
-
-	return &hscan->xs_base;
-}
-
-static void
-heapam_index_fetch_reset(IndexFetchTableData *scan)
-{
-	IndexFetchHeapData *hscan = (IndexFetchHeapData *) scan;
-
-	if (BufferIsValid(hscan->xs_cbuf))
-	{
-		ReleaseBuffer(hscan->xs_cbuf);
-		hscan->xs_cbuf = InvalidBuffer;
-	}
-
-	if (BufferIsValid(hscan->xs_vmbuffer))
-	{
-		ReleaseBuffer(hscan->xs_vmbuffer);
-		hscan->xs_vmbuffer = InvalidBuffer;
-	}
-}
-
-static void
-heapam_index_fetch_end(IndexFetchTableData *scan)
-{
-	IndexFetchHeapData *hscan = (IndexFetchHeapData *) scan;
-
-	heapam_index_fetch_reset(scan);
-
-	pfree(hscan);
-}
-
-static bool
-heapam_index_fetch_tuple(struct IndexFetchTableData *scan,
-						 ItemPointer tid,
-						 Snapshot snapshot,
-						 TupleTableSlot *slot,
-						 bool *call_again, bool *all_dead)
-{
-	IndexFetchHeapData *hscan = (IndexFetchHeapData *) scan;
-	BufferHeapTupleTableSlot *bslot = (BufferHeapTupleTableSlot *) slot;
-	bool		got_heap_tuple;
-
-	Assert(TTS_IS_BUFFERTUPLE(slot));
-
-	/* We can skip the buffer-switching logic if we're in mid-HOT chain. */
-	if (!*call_again)
-	{
-		/* Switch to correct buffer if we don't have it already */
-		Buffer		prev_buf = hscan->xs_cbuf;
-
-		hscan->xs_cbuf = ReleaseAndReadBuffer(hscan->xs_cbuf,
-											  hscan->xs_base.rel,
-											  ItemPointerGetBlockNumber(tid));
-
-		/*
-		 * Prune page, but only if we weren't already on this page
-		 */
-		if (prev_buf != hscan->xs_cbuf)
-			heap_page_prune_opt(hscan->xs_base.rel, hscan->xs_cbuf,
-								&hscan->xs_vmbuffer);
-	}
-
-	/* 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,
-											hscan->xs_base.rel,
-											hscan->xs_cbuf,
-											snapshot,
-											&bslot->base.tupdata,
-											all_dead,
-											!*call_again);
-	bslot->base.tupdata.t_self = *tid;
-	LockBuffer(hscan->xs_cbuf, BUFFER_LOCK_UNLOCK);
-
-	if (got_heap_tuple)
-	{
-		/*
-		 * Only in a non-MVCC snapshot can more than one member of the HOT
-		 * chain be visible.
-		 */
-		*call_again = !IsMVCCLikeSnapshot(snapshot);
-
-		slot->tts_tableOid = RelationGetRelid(scan->rel);
-		ExecStoreBufferHeapTuple(&bslot->base.tupdata, slot, hscan->xs_cbuf);
-	}
-	else
-	{
-		/* We've reached the end of the HOT chain. */
-		*call_again = false;
-	}
-
-	return got_heap_tuple;
-}
-
-
 /* ------------------------------------------------------------------------
  * Callbacks for non-modifying operations on individual tuples for heap AM
  * ------------------------------------------------------------------------
diff --git a/src/backend/access/heap/heapam_indexscan.c b/src/backend/access/heap/heapam_indexscan.c
new file mode 100644
index 000000000..4e8230834
--- /dev/null
+++ b/src/backend/access/heap/heapam_indexscan.c
@@ -0,0 +1,126 @@
+/*-------------------------------------------------------------------------
+ *
+ * heapam_indexscan.c
+ *	  heap table plain index scan and index-only scan code
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *	  src/backend/access/heap/heapam_indexscan.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/heapam.h"
+
+
+/* ------------------------------------------------------------------------
+ * Index Scan Callbacks for heap AM
+ * ------------------------------------------------------------------------
+ */
+
+IndexFetchTableData *
+heapam_index_fetch_begin(Relation rel)
+{
+	IndexFetchHeapData *hscan = palloc0_object(IndexFetchHeapData);
+
+	hscan->xs_base.rel = rel;
+	hscan->xs_cbuf = InvalidBuffer;
+	hscan->xs_vmbuffer = InvalidBuffer;
+
+	return &hscan->xs_base;
+}
+
+void
+heapam_index_fetch_reset(IndexFetchTableData *scan)
+{
+	IndexFetchHeapData *hscan = (IndexFetchHeapData *) scan;
+
+	if (BufferIsValid(hscan->xs_cbuf))
+	{
+		ReleaseBuffer(hscan->xs_cbuf);
+		hscan->xs_cbuf = InvalidBuffer;
+	}
+
+	if (BufferIsValid(hscan->xs_vmbuffer))
+	{
+		ReleaseBuffer(hscan->xs_vmbuffer);
+		hscan->xs_vmbuffer = InvalidBuffer;
+	}
+}
+
+void
+heapam_index_fetch_end(IndexFetchTableData *scan)
+{
+	IndexFetchHeapData *hscan = (IndexFetchHeapData *) scan;
+
+	heapam_index_fetch_reset(scan);
+
+	pfree(hscan);
+}
+
+bool
+heapam_index_fetch_tuple(struct IndexFetchTableData *scan,
+						 ItemPointer tid,
+						 Snapshot snapshot,
+						 TupleTableSlot *slot,
+						 bool *heap_continue, bool *all_dead)
+{
+	IndexFetchHeapData *hscan = (IndexFetchHeapData *) scan;
+	BufferHeapTupleTableSlot *bslot = (BufferHeapTupleTableSlot *) slot;
+	bool		got_heap_tuple;
+
+	Assert(TTS_IS_BUFFERTUPLE(slot));
+
+	/* We can skip the buffer-switching logic if we're in mid-HOT chain. */
+	if (!*heap_continue)
+	{
+		/* Switch to correct buffer if we don't have it already */
+		Buffer		prev_buf = hscan->xs_cbuf;
+
+		hscan->xs_cbuf = ReleaseAndReadBuffer(hscan->xs_cbuf,
+											  hscan->xs_base.rel,
+											  ItemPointerGetBlockNumber(tid));
+
+		/*
+		 * Prune page, but only if we weren't already on this page
+		 */
+		if (prev_buf != hscan->xs_cbuf)
+			heap_page_prune_opt(hscan->xs_base.rel, hscan->xs_cbuf,
+								&hscan->xs_vmbuffer);
+	}
+
+	/* 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,
+											hscan->xs_base.rel,
+											hscan->xs_cbuf,
+											snapshot,
+											&bslot->base.tupdata,
+											all_dead,
+											!*heap_continue);
+	bslot->base.tupdata.t_self = *tid;
+	LockBuffer(hscan->xs_cbuf, BUFFER_LOCK_UNLOCK);
+
+	if (got_heap_tuple)
+	{
+		/*
+		 * Only in a non-MVCC snapshot can more than one member of the HOT
+		 * chain be visible.
+		 */
+		*heap_continue = !IsMVCCLikeSnapshot(snapshot);
+
+		slot->tts_tableOid = RelationGetRelid(scan->rel);
+		ExecStoreBufferHeapTuple(&bslot->base.tupdata, slot, hscan->xs_cbuf);
+	}
+	else
+	{
+		/* We've reached the end of the HOT chain. */
+		*heap_continue = false;
+	}
+
+	return got_heap_tuple;
+}
diff --git a/src/backend/access/heap/meson.build b/src/backend/access/heap/meson.build
index 92ab8be3d..00ec07d7f 100644
--- a/src/backend/access/heap/meson.build
+++ b/src/backend/access/heap/meson.build
@@ -3,6 +3,7 @@
 backend_sources += files(
   'heapam.c',
   'heapam_handler.c',
+  'heapam_indexscan.c',
   'heapam_visibility.c',
   'heapam_xlog.c',
   'heaptoast.c',
-- 
2.53.0

