From dc2f02f6b26717b774f9438cddf692139e285c5f Mon Sep 17 00:00:00 2001
From: Andres Freund <andres@anarazel.de>
Date: Wed, 22 Feb 2023 11:03:02 -0800
Subject: [PATCH v1] heavily-WIP: partition specific subplan plans

---
 src/include/executor/execExpr.h           |  1 +
 src/include/nodes/execnodes.h             | 17 +++++++
 src/include/nodes/params.h                |  2 +-
 src/backend/executor/execExpr.c           | 15 ++++++
 src/backend/executor/execExprInterp.c     | 33 +++++++++++--
 src/backend/executor/execParallel.c       |  2 +-
 src/backend/executor/execPartition.c      | 12 +++++
 src/backend/executor/execUtils.c          |  1 +
 src/backend/executor/nodeCtescan.c        |  2 +-
 src/backend/executor/nodeRecursiveunion.c |  2 +-
 src/backend/executor/nodeSubplan.c        | 57 +++++++++++++++++------
 src/backend/executor/nodeWorktablescan.c  |  2 +-
 12 files changed, 122 insertions(+), 24 deletions(-)

diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h
index 86e1ac1e65c..6d2f66bbb93 100644
--- a/src/include/executor/execExpr.h
+++ b/src/include/executor/execExpr.h
@@ -379,6 +379,7 @@ typedef struct ExprEvalStep
 		{
 			int			paramid;	/* numeric ID for parameter */
 			Oid			paramtype;	/* OID of parameter's datatype */
+			int			paramthing; /* offset into EState->es_param_exec_plans[paramid][] */
 		}			param;
 
 		/* for EEOP_PARAM_CALLBACK */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 20f4c8b35f3..252c8ba7820 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -650,6 +650,21 @@ typedef struct EState
 	ParamListInfo es_param_list_info;	/* values of external params */
 	ParamExecData *es_param_exec_vals;	/* values of internal params */
 
+	/*
+	 * two level list: es_param_exec_plans[paramid][target relation]
+	 */
+	List	   *es_param_exec_plans;
+
+	/*
+	 * Subplans may end up being partition specific. For that we need to have
+	 * some sort of identifier to index into
+	 * ->es_param_exec_plans[paramid]. That index needs to be available when
+	 * building the expression. We don't have a good identifier for all
+	 * partitions, so we assign these sequentially.
+	 */
+	int			es_param_exec_target_thing_current;
+	int			es_param_exec_target_thing_next;
+
 	QueryEnvironment *es_queryEnv;	/* query environment */
 
 	/* Other working state: */
@@ -972,6 +987,8 @@ typedef struct SubPlanState
 	FmgrInfo   *lhs_hash_funcs; /* hash functions for lefthand datatype(s) */
 	FmgrInfo   *cur_eq_funcs;	/* equality functions for LHS vs. table */
 	ExprState  *cur_eq_comp;	/* equality comparator for LHS vs. table */
+	/* identifies offset in Estate->es_param_exec_plans[paramid][offset] */
+	int			target_thing;
 } SubPlanState;
 
 /*
diff --git a/src/include/nodes/params.h b/src/include/nodes/params.h
index ad23113a61c..f78bdf14268 100644
--- a/src/include/nodes/params.h
+++ b/src/include/nodes/params.h
@@ -145,7 +145,7 @@ typedef struct ParamListInfoData
 
 typedef struct ParamExecData
 {
-	void	   *execPlan;		/* should be "SubPlanState *" */
+	bool		needsExecPlan;  /* plan is in EState->es_param_exec_plans */
 	Datum		value;
 	bool		isnull;
 } ParamExecData;
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index 812ead95bc6..74a6b774bd0 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -993,6 +993,21 @@ ExecInitExprRec(Expr *node, ExprState *state,
 						scratch.opcode = EEOP_PARAM_EXEC;
 						scratch.d.param.paramid = param->paramid;
 						scratch.d.param.paramtype = param->paramtype;
+
+						/*
+						 * If we're currently building an expression for a
+						 * partition, a subplan might be specific to the
+						 * current target partition. Unfortunately we can't
+						 * tell yet, because we've not necessarily encountered
+						 * the subplan yet. Need to decide at expression
+						 * evaluation time.
+						 */
+						if (!state->parent)
+							scratch.d.param.paramthing = 0;
+						else
+							scratch.d.param.paramthing =
+								state->parent->state->es_param_exec_target_thing_current;
+
 						ExprEvalPushStep(state, &scratch);
 						break;
 					case PARAM_EXTERN:
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 19351fe34bf..7a6d6bf4579 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -2420,14 +2420,39 @@ void
 ExecEvalParamExec(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
 {
 	ParamExecData *prm;
+	int paramid = op->d.param.paramid;
 
-	prm = &(econtext->ecxt_param_exec_vals[op->d.param.paramid]);
-	if (unlikely(prm->execPlan != NULL))
+	prm = &(econtext->ecxt_param_exec_vals[paramid]);
+
+	if (unlikely(prm->needsExecPlan))
 	{
+		List *param_list;
+		EState	   *estate = econtext->ecxt_estate;
+		SubPlanState *param_plan;
+
+		Assert(list_length(estate->es_param_exec_plans) >= paramid + 1);
+		param_list = list_nth(estate->es_param_exec_plans, paramid);
+
+		/*
+		 * The plan could either be specific to the partitioned table, or
+		 * not. Unfortunately it looks to be hard to determine that in
+		 * execExpr.c. So we just try both - there shouldn't ever be
+		 * conflicts.
+		 */
+		if (list_length(param_list) >= op->d.param.paramthing + 1)
+		{
+			param_plan = castNode(SubPlanState, list_nth(param_list, op->d.param.paramthing));
+		}
+		else
+		{
+			Assert(list_length(param_list) == 1);
+			param_plan = linitial(param_list);
+		}
+
 		/* Parameter not evaluated yet, so go do it */
-		ExecSetParamPlan(prm->execPlan, econtext);
+		ExecSetParamPlan(param_plan, econtext);
 		/* ExecSetParamPlan should have processed this param... */
-		Assert(prm->execPlan == NULL);
+		Assert(!prm->needsExecPlan);
 	}
 	*op->resvalue = prm->value;
 	*op->resnull = prm->isnull;
diff --git a/src/backend/executor/execParallel.c b/src/backend/executor/execParallel.c
index aa3f2834537..2e868425e3f 100644
--- a/src/backend/executor/execParallel.c
+++ b/src/backend/executor/execParallel.c
@@ -429,7 +429,7 @@ RestoreParamExecParams(char *start_address, EState *estate)
 
 		/* Read datum/isnull. */
 		prm->value = datumRestore(&start_address, &prm->isnull);
-		prm->execPlan = NULL;
+		prm->needsExecPlan = false;
 	}
 }
 
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index 651ad24fc10..e93494c7e18 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -510,9 +510,19 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 	MemoryContext oldcxt;
 	AttrMap    *part_attmap = NULL;
 	bool		found_whole_row;
+	size_t		save_target_thing_current = estate->es_param_exec_target_thing_current;
 
 	oldcxt = MemoryContextSwitchTo(proute->memcxt);
 
+	/*
+	 * We can't use partidx or such as an identifier for partition specific
+	 * state, due to multi-level partitioning. So we just assign the
+	 * identifier sequentially. One advantage of that scheme is that we end up
+	 * with far smaller indexes when partition pruning is effective.
+	 */
+	Assert(estate->es_param_exec_target_thing_next != 0);
+	estate->es_param_exec_target_thing_current = estate->es_param_exec_target_thing_next++;
+
 	partrel = table_open(partOid, RowExclusiveLock);
 
 	leaf_part_rri = makeNode(ResultRelInfo);
@@ -959,6 +969,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 	}
 	MemoryContextSwitchTo(oldcxt);
 
+	estate->es_param_exec_target_thing_current = save_target_thing_current;
+
 	return leaf_part_rri;
 }
 
diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index c33a3c0bec7..fef21a1953b 100644
--- a/src/backend/executor/execUtils.c
+++ b/src/backend/executor/execUtils.c
@@ -138,6 +138,7 @@ CreateExecutorState(void)
 
 	estate->es_param_list_info = NULL;
 	estate->es_param_exec_vals = NULL;
+	estate->es_param_exec_target_thing_next = 1;
 
 	estate->es_queryEnv = NULL;
 
diff --git a/src/backend/executor/nodeCtescan.c b/src/backend/executor/nodeCtescan.c
index cc4c4243e2f..b6b59410791 100644
--- a/src/backend/executor/nodeCtescan.c
+++ b/src/backend/executor/nodeCtescan.c
@@ -224,7 +224,7 @@ ExecInitCteScan(CteScan *node, EState *estate, int eflags)
 	 * CTEs, particularly the shared tuplestore.
 	 */
 	prmdata = &(estate->es_param_exec_vals[node->cteParam]);
-	Assert(prmdata->execPlan == NULL);
+	Assert(!prmdata->needsExecPlan);
 	Assert(!prmdata->isnull);
 	scanstate->leader = castNode(CteScanState, DatumGetPointer(prmdata->value));
 	if (scanstate->leader == NULL)
diff --git a/src/backend/executor/nodeRecursiveunion.c b/src/backend/executor/nodeRecursiveunion.c
index e7810039341..bafbdf4e2d9 100644
--- a/src/backend/executor/nodeRecursiveunion.c
+++ b/src/backend/executor/nodeRecursiveunion.c
@@ -215,7 +215,7 @@ ExecInitRecursiveUnion(RecursiveUnion *node, EState *estate, int eflags)
 	 * via the Param slot reserved for it.
 	 */
 	prmdata = &(estate->es_param_exec_vals[node->wtParam]);
-	Assert(prmdata->execPlan == NULL);
+	Assert(!prmdata->needsExecPlan);
 	prmdata->value = PointerGetDatum(rustate);
 	prmdata->isnull = false;
 
diff --git a/src/backend/executor/nodeSubplan.c b/src/backend/executor/nodeSubplan.c
index 64af36a1873..16c0998a65d 100644
--- a/src/backend/executor/nodeSubplan.c
+++ b/src/backend/executor/nodeSubplan.c
@@ -270,7 +270,7 @@ ExecScanSubPlan(SubPlanState *node,
 			int			paramid = lfirst_int(l);
 			ParamExecData *prm = &(estate->es_param_exec_vals[paramid]);
 
-			prm->execPlan = node;
+			prm->needsExecPlan = true;
 		}
 		*isNull = true;
 		return (Datum) 0;
@@ -413,7 +413,7 @@ ExecScanSubPlan(SubPlanState *node,
 			ParamExecData *prmdata;
 
 			prmdata = &(econtext->ecxt_param_exec_vals[paramid]);
-			Assert(prmdata->execPlan == NULL);
+			Assert(!prmdata->needsExecPlan);
 			prmdata->value = slot_getattr(slot, col, &(prmdata->isnull));
 			col++;
 		}
@@ -598,7 +598,7 @@ buildSubPlanHash(SubPlanState *node, ExprContext *econtext)
 			ParamExecData *prmdata;
 
 			prmdata = &(innerecontext->ecxt_param_exec_vals[paramid]);
-			Assert(prmdata->execPlan == NULL);
+			Assert(!prmdata->needsExecPlan);
 			prmdata->value = slot_getattr(slot, col,
 										  &(prmdata->isnull));
 			col++;
@@ -846,6 +846,7 @@ ExecInitSubPlan(SubPlan *subplan, PlanState *parent)
 	sstate->tab_collations = NULL;
 	sstate->lhs_hash_funcs = NULL;
 	sstate->cur_eq_funcs = NULL;
+	sstate->target_thing = estate->es_param_exec_target_thing_current;
 
 	/*
 	 * If this is an initplan or MULTIEXPR subplan, it has output parameters
@@ -867,8 +868,21 @@ ExecInitSubPlan(SubPlan *subplan, PlanState *parent)
 		{
 			int			paramid = lfirst_int(lst);
 			ParamExecData *prm = &(estate->es_param_exec_vals[paramid]);
+			ListCell	*param_cell;
 
-			prm->execPlan = sstate;
+			prm->needsExecPlan = true;
+
+			while (list_length(estate->es_param_exec_plans) <= paramid)
+				estate->es_param_exec_plans = lappend(estate->es_param_exec_plans, NULL);
+
+			param_cell = list_nth_cell(estate->es_param_exec_plans, paramid);
+
+			while (list_length(lfirst(param_cell)) <= sstate->target_thing)
+				lfirst(param_cell) = lappend(lfirst(param_cell), NULL);
+
+			if (list_nth(lfirst(param_cell), sstate->target_thing) != NULL)
+				elog(WARNING, "overwriting previous subplan exec plan: %u", sstate->target_thing);
+			lfirst(list_nth_cell(lfirst(param_cell), sstate->target_thing)) = sstate;
 		}
 	}
 
@@ -1143,7 +1157,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
 			int			paramid = linitial_int(subplan->setParam);
 			ParamExecData *prm = &(econtext->ecxt_param_exec_vals[paramid]);
 
-			prm->execPlan = NULL;
+			prm->needsExecPlan = false;
 			prm->value = BoolGetDatum(true);
 			prm->isnull = false;
 			found = true;
@@ -1193,7 +1207,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
 			int			paramid = lfirst_int(l);
 			ParamExecData *prm = &(econtext->ecxt_param_exec_vals[paramid]);
 
-			prm->execPlan = NULL;
+			prm->needsExecPlan = false;
 			prm->value = heap_getattr(node->curTuple, i, tdesc,
 									  &(prm->isnull));
 			i++;
@@ -1216,7 +1230,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
 		node->curArray = makeArrayResultAny(astate,
 											econtext->ecxt_per_query_memory,
 											true);
-		prm->execPlan = NULL;
+		prm->needsExecPlan = false;
 		prm->value = node->curArray;
 		prm->isnull = false;
 	}
@@ -1228,7 +1242,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
 			int			paramid = linitial_int(subplan->setParam);
 			ParamExecData *prm = &(econtext->ecxt_param_exec_vals[paramid]);
 
-			prm->execPlan = NULL;
+			prm->needsExecPlan = false;
 			prm->value = BoolGetDatum(false);
 			prm->isnull = false;
 		}
@@ -1240,7 +1254,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
 				int			paramid = lfirst_int(l);
 				ParamExecData *prm = &(econtext->ecxt_param_exec_vals[paramid]);
 
-				prm->execPlan = NULL;
+				prm->needsExecPlan = false;
 				prm->value = (Datum) 0;
 				prm->isnull = true;
 			}
@@ -1268,18 +1282,31 @@ void
 ExecSetParamPlanMulti(const Bitmapset *params, ExprContext *econtext)
 {
 	int			paramid;
+	EState	   *estate = econtext->ecxt_estate;
 
 	paramid = -1;
 	while ((paramid = bms_next_member(params, paramid)) >= 0)
 	{
 		ParamExecData *prm = &(econtext->ecxt_param_exec_vals[paramid]);
 
-		if (prm->execPlan != NULL)
+		if (prm->needsExecPlan)
 		{
-			/* Parameter not evaluated yet, so go do it */
-			ExecSetParamPlan(prm->execPlan, econtext);
-			/* ExecSetParamPlan should have processed this param... */
-			Assert(prm->execPlan == NULL);
+			ListCell *lc;
+			List *param_list;
+
+			Assert(list_length(estate->es_param_exec_plans) >= paramid);
+			param_list = list_nth(estate->es_param_exec_plans, paramid);
+
+			foreach(lc, param_list)
+			{
+				if (lfirst(lc) == NULL)
+					continue;
+
+				/* Parameter not evaluated yet, so go do it */
+				ExecSetParamPlan(lfirst(lc), econtext);
+				/* ExecSetParamPlan should have processed this param... */
+				Assert(!prm->needsExecPlan);
+			}
 		}
 	}
 }
@@ -1321,7 +1348,7 @@ ExecReScanSetParamPlan(SubPlanState *node, PlanState *parent)
 		ParamExecData *prm = &(estate->es_param_exec_vals[paramid]);
 
 		if (subplan->subLinkType != CTE_SUBLINK)
-			prm->execPlan = node;
+			prm->needsExecPlan = node;
 
 		parent->chgParam = bms_add_member(parent->chgParam, paramid);
 	}
diff --git a/src/backend/executor/nodeWorktablescan.c b/src/backend/executor/nodeWorktablescan.c
index 0c13448236a..4202ab74955 100644
--- a/src/backend/executor/nodeWorktablescan.c
+++ b/src/backend/executor/nodeWorktablescan.c
@@ -95,7 +95,7 @@ ExecWorkTableScan(PlanState *pstate)
 		ParamExecData *param;
 
 		param = &(estate->es_param_exec_vals[plan->wtParam]);
-		Assert(param->execPlan == NULL);
+		Assert(!param->needsExecPlan);
 		Assert(!param->isnull);
 		node->rustate = castNode(RecursiveUnionState, DatumGetPointer(param->value));
 		Assert(node->rustate);
-- 
2.38.0

