From 0d810ceed77e394883ab0e95eafe36051b546040 Mon Sep 17 00:00:00 2001
From: Amit Langote <amitlan@postgresql.org>
Date: Thu, 5 Mar 2026 17:42:19 +0900
Subject: [PATCH v6 2/5] Add RowBatch infrastructure for batched tuple
 processing

Introduce RowBatch, a data carrier that allows table AMs to deliver
multiple rows per call and the executor to process them as a group.

RowBatch separates three concerns:

  - am_payload: opaque, AM-owned storage (e.g. HeapBatch with pinned
    page and tuple headers).  The AM allocates this in its
    scan_begin_batch callback.

  - slots[]: TupleTableSlot array, created by RowBatchCreateSlots()
    with AM-appropriate slot ops.  Populated from am_payload by
    ops->materialize_into_slots when the executor needs tuple data.

  - max_rows: executor-set upper bound that the AM respects when
    filling a batch.

RowBatch does not own selection/filtering state.  Which rows survive
qual evaluation is the executor's concern, tracked separately in
scan node state.  This keeps RowBatch focused on the AM-to-executor
data transfer boundary.

RowBatchOps provides a vtable for AM-specific operations; currently
only materialize_into_slots is defined.
---
 src/backend/executor/Makefile       |  1 +
 src/backend/executor/execRowBatch.c | 54 ++++++++++++++++++
 src/backend/executor/meson.build    |  1 +
 src/include/executor/execRowBatch.h | 88 +++++++++++++++++++++++++++++
 src/tools/pgindent/typedefs.list    |  2 +
 5 files changed, 146 insertions(+)
 create mode 100644 src/backend/executor/execRowBatch.c
 create mode 100644 src/include/executor/execRowBatch.h

diff --git a/src/backend/executor/Makefile b/src/backend/executor/Makefile
index 11118d0ce02..99a00e762f6 100644
--- a/src/backend/executor/Makefile
+++ b/src/backend/executor/Makefile
@@ -15,6 +15,7 @@ include $(top_builddir)/src/Makefile.global
 OBJS = \
 	execAmi.o \
 	execAsync.o \
+	execRowBatch.o \
 	execCurrent.o \
 	execExpr.o \
 	execExprInterp.o \
diff --git a/src/backend/executor/execRowBatch.c b/src/backend/executor/execRowBatch.c
new file mode 100644
index 00000000000..6a298813bd8
--- /dev/null
+++ b/src/backend/executor/execRowBatch.c
@@ -0,0 +1,54 @@
+/*-------------------------------------------------------------------------
+ *
+ * execRowBatch.c
+ *		Helpers for RowBatch
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *	  src/backend/executor/execRowBatch.c
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "executor/execRowBatch.h"
+
+/*
+ * RowBatchCreate
+ *		Allocate and initialize a new RowBatch envelope.
+ */
+RowBatch *
+RowBatchCreate(int max_rows)
+{
+	RowBatch   *b;
+
+	Assert(max_rows > 0);
+
+	b = palloc(sizeof(RowBatch));
+	b->am_payload = NULL;
+	b->ops = NULL;
+	b->max_rows = max_rows;
+	b->nrows = 0;
+	b->pos = 0;
+	b->materialized = false;
+	b->slot = NULL;
+
+	return b;
+}
+
+/*
+ * RowBatchReset
+ *		Reset an existing RowBatch envelope to empty.
+ */
+void
+RowBatchReset(RowBatch *b, bool drop_slots)
+{
+	Assert(b != NULL);
+
+	b->nrows = 0;
+	b->pos = 0;
+	b->materialized = false;
+	/* b->slot belongs to the owning PlanState node */
+}
diff --git a/src/backend/executor/meson.build b/src/backend/executor/meson.build
index dc45be0b2ce..fd0bf80bacd 100644
--- a/src/backend/executor/meson.build
+++ b/src/backend/executor/meson.build
@@ -3,6 +3,7 @@
 backend_sources += files(
   'execAmi.c',
   'execAsync.c',
+  'execRowBatch.c',
   'execCurrent.c',
   'execExpr.c',
   'execExprInterp.c',
diff --git a/src/include/executor/execRowBatch.h b/src/include/executor/execRowBatch.h
new file mode 100644
index 00000000000..021fdeecc73
--- /dev/null
+++ b/src/include/executor/execRowBatch.h
@@ -0,0 +1,88 @@
+/*-------------------------------------------------------------------------
+ *
+ * execRowBatch.h
+ *		Executor batch envelope for passing row batch state upward
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *	  src/include/executor/execRowBatch.h
+ *-------------------------------------------------------------------------
+ */
+#ifndef EXECROWBATCH_H
+#define EXECROWBATCH_H
+
+#include "executor/tuptable.h"
+
+typedef struct RowBatchOps RowBatchOps;
+
+/*
+ * RowBatch
+ *
+ * Data carrier from table AM to executor. The AM populates am_payload
+ * and nrows via scan_getnextbatch(). The executor calls ops->materialize_all
+ * to populate slots[] when it needs tuple data.
+ *
+ * Selection state (which rows survived qual eval) is owned by the executor,
+ * not the batch.
+ */
+typedef struct RowBatch
+{
+	void	   *am_payload;
+	const RowBatchOps *ops;
+
+	int			max_rows;			/* executor-set upper bound */
+	int			nrows;				/* rows TAM put in */
+	int			pos;				/* iteration position */
+	bool		materialized;		/* tuples in slots valid? */
+
+	TupleTableSlot *slot;			/* row view */
+} RowBatch;
+
+/*
+ * RowBatchOps -- AM-specific operations on a RowBatch.
+ *
+ * Table AMs set b->ops during scan_begin_batch to provide
+ * callbacks that the executor uses to access batch contents.
+ *
+ * repoint_slot re-points the batch's single slot to the tuple at
+ * index idx within the current batch.  The slot remains valid until
+ * the next call or until the batch is exhausted.
+ *
+ * Additional callbacks can be added here as new AMs or executor
+ * features require them.
+ */
+typedef struct RowBatchOps
+{
+	void		(*repoint_slot) (RowBatch *b, int idx);
+} RowBatchOps;
+
+/* Create/teardown */
+extern RowBatch *RowBatchCreate(int max_rows);
+extern void RowBatchReset(RowBatch *b, bool drop_slots);
+
+/* Validation */
+static inline bool
+RowBatchIsValid(RowBatch *b)
+{
+	return b != NULL && b->max_rows > 0;
+}
+
+/* Iteration over materialized slots */
+static inline bool
+RowBatchHasMore(RowBatch *b)
+{
+	return b->pos < b->nrows;
+}
+
+static inline TupleTableSlot *
+RowBatchGetNextSlot(RowBatch *b)
+{
+	if (b->pos >= b->nrows)
+		return NULL;
+	b->ops->repoint_slot(b, b->pos++);
+	return b->slot;
+}
+
+#endif	/* EXECROWBATCH_H */
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 52f8603a7be..a2b0b1d99d4 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2663,6 +2663,8 @@ RoleSpec
 RoleSpecType
 RoleStmtType
 RollupData
+RowBatch
+RowBatchOps
 RowCompareExpr
 RowExpr
 RowIdentityVarInfo
-- 
2.47.3

