From 25a2dc6de70f7492c4f2c6fec6ee686a5e780b5a Mon Sep 17 00:00:00 2001
From: Amit Langote <amitlan@postgresql.org>
Date: Fri, 3 Jul 2026 12:12:41 +0900
Subject: [PATCH v8 2/3] Add table AM and slot interface for batched scans

Add an interface by which a table AM can deliver tuples to the executor a
batch at a time while the executor continues to consume them one tuple at
a time through the existing slot interface, amortizing the per-tuple cost
of the AM call.

TableAmRoutine gains:

  const TupleTableSlotOps *(*batch_slot_callbacks)(Relation rel);
  bool (*scan_getnextbatch)(TableScanDesc scan, ScanDirection direction,
                            TupleTableSlot *slot);

batch_slot_callbacks returns the slot ops for the AM's batch slot type.
scan_getnextbatch fetches the AM's next batch into that slot and resets
its cursor to the start; what constitutes a batch is defined by the AM.
The caller must fully consume the current batch, via the slot's
batch_next (below), before calling scan_getnextbatch again.
table_scan_getnextbatch() and table_slot_batch_callbacks() are the inline
wrappers.

TupleTableSlotOps gains:

  bool (*batch_next)(TupleTableSlot *slot, ScanDirection direction);

Advance the slot to the next tuple of the batch it currently holds, in
the given scan direction.  After it returns true the slot behaves as an
ordinary single-tuple slot: getsomeattrs, getsysattr, materialize and so
on operate on the current tuple.  Returns false when the batch is
exhausted.  slot_batch_next() is the inline wrapper.
---
 src/include/access/tableam.h    | 49 +++++++++++++++++++++++++++++++++
 src/include/executor/tuptable.h | 23 ++++++++++++++++
 2 files changed, 72 insertions(+)

diff --git a/src/include/access/tableam.h b/src/include/access/tableam.h
index f2c36696bca..50cc312c822 100644
--- a/src/include/access/tableam.h
+++ b/src/include/access/tableam.h
@@ -334,6 +334,12 @@ typedef struct TableAmRoutine
 	 */
 	const TupleTableSlotOps *(*slot_callbacks) (Relation rel);
 
+	/*
+	 * Return the TupleTableSlotOps for the batch slot type used by
+	 * this AM.  NULL if the AM does not support batching.
+	 */
+	const TupleTableSlotOps *(*batch_slot_callbacks) (Relation rel);
+
 
 	/* ------------------------------------------------------------------------
 	 * Table scan callbacks.
@@ -384,6 +390,25 @@ typedef struct TableAmRoutine
 									 ScanDirection direction,
 									 TupleTableSlot *slot);
 
+	/*
+	 * Fetch the next batch of tuples from the scan into the given slot.
+	 *
+	 * What constitutes a batch is defined by the AM.  For heap, it is
+	 * all visible tuples on one heap page.  The AM populates the slot's
+	 * batch state and resets the cursor to the start.  The slot's
+	 * batch_next callback is then used to iterate through individual
+	 * tuples.
+	 *
+	 * Returns true if a non-empty batch was produced, false when the
+	 * scan is exhausted.
+	 *
+	 * The caller must fully consume the previous batch before calling
+	 * again.
+	 */
+	bool		(*scan_getnextbatch) (TableScanDesc scan,
+									  ScanDirection direction,
+									  TupleTableSlot *slot);
+
 	/*-----------
 	 * Optional functions to provide scanning for ranges of ItemPointers.
 	 * Implementations must either provide both of these functions, or neither
@@ -1104,6 +1129,30 @@ table_scan_getnextslot(TableScanDesc sscan, ScanDirection direction, TupleTableS
 	return sscan->rs_rd->rd_tableam->scan_getnextslot(sscan, direction, slot);
 }
 
+/*
+ * table_scan_getnextbatch
+ */
+static inline bool
+table_scan_getnextbatch(TableScanDesc sscan,
+						ScanDirection direction,
+						TupleTableSlot *slot)
+{
+	slot->tts_tableOid = RelationGetRelid(sscan->rs_rd);
+
+	return sscan->rs_rd->rd_tableam->scan_getnextbatch(sscan,
+													   direction,
+													   slot);
+}
+
+/*
+ * table_slot_batch_callbacks
+ */
+static inline const TupleTableSlotOps *
+table_slot_batch_callbacks(Relation rel)
+{
+	return rel->rd_tableam->batch_slot_callbacks(rel);
+}
+
 /* ----------------------------------------------------------------------------
  * TID Range scanning related functions.
  * ----------------------------------------------------------------------------
diff --git a/src/include/executor/tuptable.h b/src/include/executor/tuptable.h
index 3db6c9c9bd0..890115314b0 100644
--- a/src/include/executor/tuptable.h
+++ b/src/include/executor/tuptable.h
@@ -15,6 +15,7 @@
 #define TUPTABLE_H
 
 #include "access/htup.h"
+#include "access/sdir.h"
 #include "access/sysattr.h"
 #include "access/tupdesc.h"
 #include "storage/buf.h"
@@ -239,6 +240,16 @@ struct TupleTableSlotOps
 	 * with the minimal tuple without the need for an additional allocation.
 	 */
 	MinimalTuple (*copy_minimal_tuple) (TupleTableSlot *slot, Size extra);
+
+	/*
+	 * Advance to the next tuple in a batch.  Returns true if a tuple
+	 * is available, false when the batch is exhausted.  After returning
+	 * true, the slot behaves as a regular single-tuple slot for
+	 * getsomeattrs, getsysattr, etc.
+	 *
+	 *	NULL for non-batch slot types.
+	 */
+	bool		(*batch_next)(TupleTableSlot *slot, ScanDirection direction);
 };
 
 /*
@@ -553,6 +564,18 @@ ExecCopySlot(TupleTableSlot *dstslot, TupleTableSlot *srcslot)
 	return dstslot;
 }
 
+/*
+ * slot_batch_next
+ */
+static inline bool
+slot_batch_next(TupleTableSlot *slot, ScanDirection direction)
+{
+	Assert(slot->tts_ops->batch_next != NULL);
+
+	return slot->tts_ops->batch_next(slot, direction);
+}
+
+
 #endif							/* FRONTEND */
 
 #endif							/* TUPTABLE_H */
-- 
2.47.3

