From f3239ed6c0f196be5b495a586e6b390465d0326d Mon Sep 17 00:00:00 2001
From: Amit Langote <amitlan@postgresql.org>
Date: Mon, 22 Sep 2025 17:01:29 +0900
Subject: [PATCH v3 5/9] WIP: Add EEOPs and helpers for TupleBatch processing

Introduce new EEOP cases to fetch attributes into TupleBatch
vectors:
- EEOP_{INNER,OUTER,SCAN}_FETCHSOME_BATCH
- EEOP_BUILD_{INNER,OUTER,SCAN}_BATCH_VECTOR

Add ExecBuild{Inner,Outer,Scan}BatchVector() helpers to populate
column vectors (values, nulls, nrows, hasnull) from a TupleBatch.
Extend ExprContext with inner_batch, outer_batch, and scan_batch
fields so expression programs can access active batches directly.

Add slot_getsomeattrs_batch() to prefetch attributes across all
slots in a TupleBatch, similar to slot_getsomeattrs() for one slot.
---
 src/backend/executor/execExprInterp.c | 127 +++++++++++++++++++++++++-
 src/backend/executor/execTuples.c     |  32 +++++++
 src/backend/jit/llvm/llvmjit_expr.c   |  86 +++++++++++++++++
 src/backend/jit/llvm/llvmjit_types.c  |   4 +
 src/include/executor/execExpr.h       |  45 ++++++++-
 src/include/executor/tuptable.h       |   2 +
 src/include/nodes/execnodes.h         |  24 +++--
 7 files changed, 310 insertions(+), 10 deletions(-)

diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 0e1a74976f7..68629ad7991 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -59,6 +59,7 @@
 #include "access/heaptoast.h"
 #include "catalog/pg_type.h"
 #include "commands/sequence.h"
+#include "executor/execBatch.h"
 #include "executor/execExpr.h"
 #include "executor/nodeSubplan.h"
 #include "funcapi.h"
@@ -188,6 +189,11 @@ static pg_attribute_always_inline void ExecAggPlainTransByRef(AggState *aggstate
 															  int setno);
 static char *ExecGetJsonValueItemString(JsonbValue *item, bool *resnull);
 
+static pg_attribute_always_inline void ExecBuildBatchVector(ExprState *state,
+															ExprEvalStep *op,
+															ExprContext *econtext,
+															TupleBatch *b);
+
 /*
  * ScalarArrayOpExprHashEntry
  * 		Hash table entry type used during EEOP_HASHED_SCALARARRAYOP
@@ -446,7 +452,6 @@ ExecReadyInterpretedExpr(ExprState *state)
 	state->evalfunc_private = ExecInterpExpr;
 }
 
-
 /*
  * Evaluate expression identified by "state" in the execution context
  * given by "econtext".  *isnull is set to the is-null flag for the result,
@@ -466,6 +471,9 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 	TupleTableSlot *scanslot;
 	TupleTableSlot *oldslot;
 	TupleTableSlot *newslot;
+	TupleBatch *innerbatch;
+	TupleBatch *outerbatch;
+	TupleBatch *scanbatch;
 
 	/*
 	 * This array has to be in the same order as enum ExprEvalOp.
@@ -479,6 +487,9 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 		&&CASE_EEOP_SCAN_FETCHSOME,
 		&&CASE_EEOP_OLD_FETCHSOME,
 		&&CASE_EEOP_NEW_FETCHSOME,
+		&&CASE_EEOP_INNER_FETCHSOME_BATCH,
+		&&CASE_EEOP_OUTER_FETCHSOME_BATCH,
+		&&CASE_EEOP_SCAN_FETCHSOME_BATCH,
 		&&CASE_EEOP_INNER_VAR,
 		&&CASE_EEOP_OUTER_VAR,
 		&&CASE_EEOP_SCAN_VAR,
@@ -592,6 +603,9 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 		&&CASE_EEOP_AGG_PRESORTED_DISTINCT_MULTI,
 		&&CASE_EEOP_AGG_ORDERED_TRANS_DATUM,
 		&&CASE_EEOP_AGG_ORDERED_TRANS_TUPLE,
+		&&CASE_EEOP_BUILD_INNER_BATCH_VECTOR,
+		&&CASE_EEOP_BUILD_OUTER_BATCH_VECTOR,
+		&&CASE_EEOP_BUILD_SCAN_BATCH_VECTOR,
 		&&CASE_EEOP_LAST
 	};
 
@@ -612,6 +626,9 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 	scanslot = econtext->ecxt_scantuple;
 	oldslot = econtext->ecxt_oldtuple;
 	newslot = econtext->ecxt_newtuple;
+	innerbatch = econtext->inner_batch;
+	outerbatch = econtext->outer_batch;
+	scanbatch = econtext->scan_batch;
 
 #if defined(EEO_USE_COMPUTED_GOTO)
 	EEO_DISPATCH();
@@ -658,6 +675,36 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 			EEO_NEXT();
 		}
 
+		EEO_CASE(EEOP_INNER_FETCHSOME_BATCH)
+		{
+			CheckOpSlotCompatibility(op, innerslot);
+
+			Assert(innerbatch);
+			slot_getsomeattrs_batch(innerbatch, op->d.fetch_batch.last_var);
+
+			EEO_NEXT();
+		}
+
+		EEO_CASE(EEOP_OUTER_FETCHSOME_BATCH)
+		{
+			CheckOpSlotCompatibility(op, outerslot);
+
+			Assert(outerbatch);
+			slot_getsomeattrs_batch(outerbatch, op->d.fetch_batch.last_var);
+
+			EEO_NEXT();
+		}
+
+		EEO_CASE(EEOP_SCAN_FETCHSOME_BATCH)
+		{
+			CheckOpSlotCompatibility(op, scanslot);
+
+			Assert(scanbatch);
+			slot_getsomeattrs_batch(scanbatch, op->d.fetch_batch.last_var);
+
+			EEO_NEXT();
+		}
+
 		EEO_CASE(EEOP_OLD_FETCHSOME)
 		{
 			CheckOpSlotCompatibility(op, oldslot);
@@ -2265,6 +2312,30 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 			EEO_NEXT();
 		}
 
+		EEO_CASE(EEOP_BUILD_INNER_BATCH_VECTOR)
+		{
+			/* too complex for an inline implementation */
+			ExecBuildInnerBatchVector(state, op, econtext);
+
+			EEO_NEXT();
+		}
+
+		EEO_CASE(EEOP_BUILD_OUTER_BATCH_VECTOR)
+		{
+			/* too complex for an inline implementation */
+			ExecBuildOuterBatchVector(state, op, econtext);
+
+			EEO_NEXT();
+		}
+
+		EEO_CASE(EEOP_BUILD_SCAN_BATCH_VECTOR)
+		{
+			/* too complex for an inline implementation */
+			ExecBuildScanBatchVector(state, op, econtext);
+
+			EEO_NEXT();
+		}
+
 		EEO_CASE(EEOP_LAST)
 		{
 			/* unreachable */
@@ -5914,3 +5985,57 @@ ExecAggPlainTransByRef(AggState *aggstate, AggStatePerTrans pertrans,
 
 	MemoryContextSwitchTo(oldContext);
 }
+
+void
+ExecBuildInnerBatchVector(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
+{
+	Assert(econtext->inner_batch);
+	ExecBuildBatchVector(state, op, econtext, econtext->inner_batch);
+}
+
+void
+ExecBuildOuterBatchVector(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
+{
+	Assert(econtext->outer_batch);
+	ExecBuildBatchVector(state, op, econtext, econtext->outer_batch);
+}
+
+void
+ExecBuildScanBatchVector(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
+{
+	Assert(econtext->scan_batch);
+	ExecBuildBatchVector(state, op, econtext, econtext->scan_batch);
+}
+
+static pg_attribute_always_inline void
+ExecBuildBatchVector(ExprState *state, ExprEvalStep *op, ExprContext *econtext,
+					 TupleBatch *b)
+{
+	struct BatchVector *bv = op->d.batch_vector.bv;
+	int		i = 0;
+
+	if (bv->ncols == 0)
+		return;
+
+	/* Fetch each requested attribute into column vectors. */
+	TupleBatchRewind(b);
+	while (TupleBatchHasMore(b))
+	{
+		TupleTableSlot *slot = TupleBatchGetNextSlot(b);
+
+		for (int j = 0; j < bv->ncols; j++)
+		{
+			AttrNumber attno = bv->attnos[j];
+			Datum  *cols  = bv->cols[j];
+			bool   *nulls  = bv->nulls[j];
+
+			Assert(attno <= slot->tts_nvalid);
+			cols[i] = slot->tts_values[attno - 1];
+			nulls[i] = slot->tts_isnull[attno - 1];
+			if (!bv->hasnull && nulls[i])
+				bv->hasnull = true;
+		}
+		i++;
+	}
+	bv->nrows = i;
+}
diff --git a/src/backend/executor/execTuples.c b/src/backend/executor/execTuples.c
index 8e02d68824f..86d5dea8f8b 100644
--- a/src/backend/executor/execTuples.c
+++ b/src/backend/executor/execTuples.c
@@ -2111,6 +2111,38 @@ slot_getsomeattrs_int(TupleTableSlot *slot, int attnum)
 	}
 }
 
+void
+slot_getsomeattrs_batch(struct TupleBatch *b, int attnum)
+{
+	while (TupleBatchHasMore(b))
+	{
+		TupleTableSlot *slot = TupleBatchGetNextSlot(b);
+
+		/* Check for caller errors */
+		Assert(attnum > 0);
+
+		if (unlikely(attnum > slot->tts_tupleDescriptor->natts))
+			elog(ERROR, "invalid attribute number %d", attnum);
+
+		/* XXX - there should perhaps also be a batch-level att_nvalid */
+		if (attnum < slot->tts_nvalid)
+			continue;
+
+		/* Fetch as many attributes as possible from the underlying tuple. */
+		slot->tts_ops->getsomeattrs(slot, attnum);
+
+		/*
+		 * If the underlying tuple doesn't have enough attributes, tuple
+		 * descriptor must have the missing attributes.
+		 */
+		if (unlikely(slot->tts_nvalid < attnum))
+		{
+			slot_getmissingattrs(slot, slot->tts_nvalid, attnum);
+			slot->tts_nvalid = attnum;
+		}
+	}
+}
+
 /* ----------------------------------------------------------------
  *		ExecTypeFromTL
  *
diff --git a/src/backend/jit/llvm/llvmjit_expr.c b/src/backend/jit/llvm/llvmjit_expr.c
index 712b35df7e5..848f0b52d6f 100644
--- a/src/backend/jit/llvm/llvmjit_expr.c
+++ b/src/backend/jit/llvm/llvmjit_expr.c
@@ -109,6 +109,11 @@ llvm_compile_expr(ExprState *state)
 	LLVMValueRef v_newslot;
 	LLVMValueRef v_resultslot;
 
+	/* batches */
+	LLVMValueRef v_innerbatch;
+	LLVMValueRef v_outerbatch;
+	LLVMValueRef v_scanbatch;
+
 	/* nulls/values of slots */
 	LLVMValueRef v_innervalues;
 	LLVMValueRef v_innernulls;
@@ -221,6 +226,21 @@ llvm_compile_expr(ExprState *state)
 									 v_state,
 									 FIELDNO_EXPRSTATE_RESULTSLOT,
 									 "v_resultslot");
+	v_innerbatch = l_load_struct_gep(b,
+									 StructExprContext,
+									 v_econtext,
+									 FIELDNO_EXPRCONTEXT_OUTERBATCH,
+									 "v_innerbatch");
+	v_outerbatch = l_load_struct_gep(b,
+									 StructExprContext,
+									 v_econtext,
+									 FIELDNO_EXPRCONTEXT_OUTERBATCH,
+									 "v_outerbatch");
+	v_scanbatch = l_load_struct_gep(b,
+									StructExprContext,
+									v_econtext,
+									FIELDNO_EXPRCONTEXT_SCANBATCH,
+									"v_scanbatch");
 
 	/* build global values/isnull pointers */
 	v_scanvalues = l_load_struct_gep(b,
@@ -439,6 +459,54 @@ llvm_compile_expr(ExprState *state)
 					break;
 				}
 
+			case EEOP_INNER_FETCHSOME_BATCH:
+				{
+					LLVMValueRef params[2];
+
+					params[0] = v_innerbatch;
+					params[1] = l_int32_const(lc, op->d.fetch_batch.last_var);
+
+						l_call(b,
+							   llvm_pg_var_func_type("slot_getsomeattrs_batch"),
+							   llvm_pg_func(mod, "slot_getsomeattrs_batch"),
+							   params, lengthof(params), "");
+
+					LLVMBuildBr(b, opblocks[opno + 1]);
+					break;
+				}
+
+			case EEOP_OUTER_FETCHSOME_BATCH:
+				{
+					LLVMValueRef params[2];
+
+					params[0] = v_outerbatch;
+					params[1] = l_int32_const(lc, op->d.fetch_batch.last_var);
+
+						l_call(b,
+							   llvm_pg_var_func_type("slot_getsomeattrs_batch"),
+							   llvm_pg_func(mod, "slot_getsomeattrs_batch"),
+							   params, lengthof(params), "");
+
+					LLVMBuildBr(b, opblocks[opno + 1]);
+					break;
+				}
+
+			case EEOP_SCAN_FETCHSOME_BATCH:
+				{
+					LLVMValueRef params[2];
+
+					params[0] = v_scanbatch;
+					params[1] = l_int32_const(lc, op->d.fetch_batch.last_var);
+
+						l_call(b,
+							   llvm_pg_var_func_type("slot_getsomeattrs_batch"),
+							   llvm_pg_func(mod, "slot_getsomeattrs_batch"),
+							   params, lengthof(params), "");
+
+					LLVMBuildBr(b, opblocks[opno + 1]);
+					break;
+				}
+
 			case EEOP_INNER_VAR:
 			case EEOP_OUTER_VAR:
 			case EEOP_SCAN_VAR:
@@ -2940,6 +3008,24 @@ llvm_compile_expr(ExprState *state)
 				LLVMBuildBr(b, opblocks[opno + 1]);
 				break;
 
+			case EEOP_BUILD_INNER_BATCH_VECTOR:
+				build_EvalXFunc(b, mod, "ExecBuildInnerBatchVector",
+								v_state, op, v_econtext);
+				LLVMBuildBr(b, opblocks[opno + 1]);
+				break;
+
+			case EEOP_BUILD_OUTER_BATCH_VECTOR:
+				build_EvalXFunc(b, mod, "ExecBuildOuterBatchVector",
+								v_state, op, v_econtext);
+				LLVMBuildBr(b, opblocks[opno + 1]);
+				break;
+
+			case EEOP_BUILD_SCAN_BATCH_VECTOR:
+				build_EvalXFunc(b, mod, "ExecBuildScanBatchVector",
+								v_state, op, v_econtext);
+				LLVMBuildBr(b, opblocks[opno + 1]);
+				break;
+
 			case EEOP_LAST:
 				Assert(false);
 				break;
diff --git a/src/backend/jit/llvm/llvmjit_types.c b/src/backend/jit/llvm/llvmjit_types.c
index 167cd554b9c..6bb527c3f6f 100644
--- a/src/backend/jit/llvm/llvmjit_types.c
+++ b/src/backend/jit/llvm/llvmjit_types.c
@@ -179,7 +179,11 @@ void	   *referenced_functions[] =
 	MakeExpandedObjectReadOnlyInternal,
 	slot_getmissingattrs,
 	slot_getsomeattrs_int,
+	slot_getsomeattrs_batch,
 	strlen,
 	varsize_any,
 	ExecInterpExprStillValid,
+	ExecBuildInnerBatchVector,
+	ExecBuildOuterBatchVector,
+	ExecBuildScanBatchVector,
 };
diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h
index 75366203706..99c86bac702 100644
--- a/src/include/executor/execExpr.h
+++ b/src/include/executor/execExpr.h
@@ -78,6 +78,11 @@ typedef enum ExprEvalOp
 	EEOP_OLD_FETCHSOME,
 	EEOP_NEW_FETCHSOME,
 
+	/* apply slot_getsomeattrs_batch() to corresponding batch */
+	EEOP_INNER_FETCHSOME_BATCH,
+	EEOP_OUTER_FETCHSOME_BATCH,
+	EEOP_SCAN_FETCHSOME_BATCH,
+
 	/* compute non-system Var value */
 	EEOP_INNER_VAR,
 	EEOP_OUTER_VAR,
@@ -292,11 +297,15 @@ typedef enum ExprEvalOp
 	EEOP_AGG_ORDERED_TRANS_DATUM,
 	EEOP_AGG_ORDERED_TRANS_TUPLE,
 
+	/* ExprContext.*_batch -> BatchVector */
+	EEOP_BUILD_INNER_BATCH_VECTOR,
+	EEOP_BUILD_OUTER_BATCH_VECTOR,
+	EEOP_BUILD_SCAN_BATCH_VECTOR,
+
 	/* non-existent operation, used e.g. to check array lengths */
 	EEOP_LAST
 } ExprEvalOp;
 
-
 typedef struct ExprEvalStep
 {
 	/*
@@ -331,6 +340,12 @@ typedef struct ExprEvalStep
 			const TupleTableSlotOps *kind;
 		}			fetch;
 
+		struct
+		{
+			/* attribute number up to which to fetch (inclusive) */
+			int			last_var;
+		}			fetch_batch;
+
 		/* for EEOP_INNER/OUTER/SCAN/OLD/NEW_[SYS]VAR */
 		struct
 		{
@@ -769,6 +784,12 @@ typedef struct ExprEvalStep
 			void	   *json_coercion_cache;
 			ErrorSaveContext *escontext;
 		}			jsonexpr_coercion;
+
+		/* for batch vector construction */
+		struct
+		{
+			struct BatchVector *bv;
+		}			batch_vector;
 	}			d;
 } ExprEvalStep;
 
@@ -917,4 +938,26 @@ extern void ExecEvalAggOrderedTransDatum(ExprState *state, ExprEvalStep *op,
 extern void ExecEvalAggOrderedTransTuple(ExprState *state, ExprEvalStep *op,
 										 ExprContext *econtext);
 
+/* ---------- BatchVector stuff ------------- */
+
+/* Vector fetch spec for a list of simple Vars. */
+typedef struct BatchVector
+{
+	/* immutable after BatchVectorCreate */
+	AttrNumber *attnos;		/* [ncols] */
+	int			ncols;
+	int			maxrows;
+	int			last_var;
+
+	/* per batch state */
+	Datum **cols;			/* [ncols][maxbatch] */
+	bool  **nulls;			/* [ncols][maxbatch] */
+	bool	hasnull;		/* is any datum in cols NULL? */
+	int		nrows;			/* #rows loaded into cols/nulls */
+} BatchVector;
+
+extern void ExecBuildInnerBatchVector(ExprState *state, ExprEvalStep *op, ExprContext *econtext);
+extern void ExecBuildOuterBatchVector(ExprState *state, ExprEvalStep *op, ExprContext *econtext);
+extern void ExecBuildScanBatchVector(ExprState *state, ExprEvalStep *op, ExprContext *econtext);
+
 #endif							/* EXEC_EXPR_H */
diff --git a/src/include/executor/tuptable.h b/src/include/executor/tuptable.h
index 43f1d999b91..82369fa6e8e 100644
--- a/src/include/executor/tuptable.h
+++ b/src/include/executor/tuptable.h
@@ -346,6 +346,8 @@ extern Datum ExecFetchSlotHeapTupleDatum(TupleTableSlot *slot);
 extern void slot_getmissingattrs(TupleTableSlot *slot, int startAttNum,
 								 int lastAttNum);
 extern void slot_getsomeattrs_int(TupleTableSlot *slot, int attnum);
+struct TupleBatch;
+extern void slot_getsomeattrs_batch(struct TupleBatch *b, int attnum);
 
 
 #ifndef FRONTEND
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 9b81b842161..fdfe8b4ddaf 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -277,6 +277,14 @@ typedef struct ExprContext
 #define FIELDNO_EXPRCONTEXT_OUTERTUPLE 3
 	TupleTableSlot *ecxt_outertuple;
 
+	/* For batched evaluation using batch-aware EEOPs */
+#define FIELDNO_EXPRCONTEXT_INNERBATCH 4
+	TupleBatch	   *inner_batch;
+#define FIELDNO_EXPRCONTEXT_OUTERBATCH 5
+	TupleBatch	   *outer_batch;
+#define FIELDNO_EXPRCONTEXT_SCANBATCH 6
+	TupleBatch	   *scan_batch;
+
 	/* Memory contexts for expression evaluation --- see notes above */
 	MemoryContext ecxt_per_query_memory;
 	MemoryContext ecxt_per_tuple_memory;
@@ -289,27 +297,27 @@ typedef struct ExprContext
 	 * Values to substitute for Aggref nodes in the expressions of an Agg
 	 * node, or for WindowFunc nodes within a WindowAgg node.
 	 */
-#define FIELDNO_EXPRCONTEXT_AGGVALUES 8
+#define FIELDNO_EXPRCONTEXT_AGGVALUES 11
 	Datum	   *ecxt_aggvalues; /* precomputed values for aggs/windowfuncs */
-#define FIELDNO_EXPRCONTEXT_AGGNULLS 9
+#define FIELDNO_EXPRCONTEXT_AGGNULLS 12
 	bool	   *ecxt_aggnulls;	/* null flags for aggs/windowfuncs */
 
 	/* Value to substitute for CaseTestExpr nodes in expression */
-#define FIELDNO_EXPRCONTEXT_CASEDATUM 10
+#define FIELDNO_EXPRCONTEXT_CASEDATUM 13
 	Datum		caseValue_datum;
-#define FIELDNO_EXPRCONTEXT_CASENULL 11
+#define FIELDNO_EXPRCONTEXT_CASENULL 14
 	bool		caseValue_isNull;
 
 	/* Value to substitute for CoerceToDomainValue nodes in expression */
-#define FIELDNO_EXPRCONTEXT_DOMAINDATUM 12
+#define FIELDNO_EXPRCONTEXT_DOMAINDATUM 15
 	Datum		domainValue_datum;
-#define FIELDNO_EXPRCONTEXT_DOMAINNULL 13
+#define FIELDNO_EXPRCONTEXT_DOMAINNULL 16
 	bool		domainValue_isNull;
 
 	/* Tuples that OLD/NEW Var nodes in RETURNING may refer to */
-#define FIELDNO_EXPRCONTEXT_OLDTUPLE 14
+#define FIELDNO_EXPRCONTEXT_OLDTUPLE 17
 	TupleTableSlot *ecxt_oldtuple;
-#define FIELDNO_EXPRCONTEXT_NEWTUPLE 15
+#define FIELDNO_EXPRCONTEXT_NEWTUPLE 18
 	TupleTableSlot *ecxt_newtuple;
 
 	/* Link to containing EState (NULL if a standalone ExprContext) */
-- 
2.47.3

