diff --git a/src/backend/commands/prepare.c b/src/backend/commands/prepare.c
index be7222f..6e90912 100644
*** a/src/backend/commands/prepare.c
--- b/src/backend/commands/prepare.c
*************** EvaluateParams(PreparedStatement *pstmt,
*** 399,408 ****
  	/* we have static list of params, so no hooks needed */
  	paramLI->paramFetch = NULL;
  	paramLI->paramFetchArg = NULL;
  	paramLI->parserSetup = NULL;
  	paramLI->parserSetupArg = NULL;
  	paramLI->numParams = num_params;
- 	paramLI->paramMask = NULL;
  
  	i = 0;
  	foreach(l, exprstates)
--- 399,409 ----
  	/* we have static list of params, so no hooks needed */
  	paramLI->paramFetch = NULL;
  	paramLI->paramFetchArg = NULL;
+ 	paramLI->paramCompile = NULL;
+ 	paramLI->paramCompileArg = NULL;
  	paramLI->parserSetup = NULL;
  	paramLI->parserSetupArg = NULL;
  	paramLI->numParams = num_params;
  
  	i = 0;
  	foreach(l, exprstates)
diff --git a/src/backend/executor/execCurrent.c b/src/backend/executor/execCurrent.c
index a3e962e..eaeb3a2 100644
*** a/src/backend/executor/execCurrent.c
--- b/src/backend/executor/execCurrent.c
*************** fetch_cursor_param_value(ExprContext *ec
*** 216,226 ****
  	if (paramInfo &&
  		paramId > 0 && paramId <= paramInfo->numParams)
  	{
! 		ParamExternData *prm = &paramInfo->params[paramId - 1];
  
  		/* give hook a chance in case parameter is dynamic */
! 		if (!OidIsValid(prm->ptype) && paramInfo->paramFetch != NULL)
! 			paramInfo->paramFetch(paramInfo, paramId);
  
  		if (OidIsValid(prm->ptype) && !prm->isnull)
  		{
--- 216,229 ----
  	if (paramInfo &&
  		paramId > 0 && paramId <= paramInfo->numParams)
  	{
! 		ParamExternData *prm;
! 		ParamExternData prmdata;
  
  		/* give hook a chance in case parameter is dynamic */
! 		if (paramInfo->paramFetch != NULL)
! 			prm = paramInfo->paramFetch(paramInfo, paramId, false, &prmdata);
! 		else
! 			prm = &paramInfo->params[paramId - 1];
  
  		if (OidIsValid(prm->ptype) && !prm->isnull)
  		{
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index e083961..94ebe76 100644
*** a/src/backend/executor/execExpr.c
--- b/src/backend/executor/execExpr.c
*************** typedef struct LastAttnumInfo
*** 55,76 ****
  } LastAttnumInfo;
  
  static void ExecReadyExpr(ExprState *state);
! static void ExecInitExprRec(Expr *node, PlanState *parent, ExprState *state,
  				Datum *resv, bool *resnull);
- static void ExprEvalPushStep(ExprState *es, const ExprEvalStep *s);
  static void ExecInitFunc(ExprEvalStep *scratch, Expr *node, List *args,
! 			 Oid funcid, Oid inputcollid, PlanState *parent,
  			 ExprState *state);
  static void ExecInitExprSlots(ExprState *state, Node *node);
  static bool get_last_attnums_walker(Node *node, LastAttnumInfo *info);
  static void ExecInitWholeRowVar(ExprEvalStep *scratch, Var *variable,
! 					PlanState *parent);
  static void ExecInitArrayRef(ExprEvalStep *scratch, ArrayRef *aref,
! 				 PlanState *parent, ExprState *state,
  				 Datum *resv, bool *resnull);
  static bool isAssignmentIndirectionExpr(Expr *expr);
  static void ExecInitCoerceToDomain(ExprEvalStep *scratch, CoerceToDomain *ctest,
! 					   PlanState *parent, ExprState *state,
  					   Datum *resv, bool *resnull);
  
  
--- 55,75 ----
  } LastAttnumInfo;
  
  static void ExecReadyExpr(ExprState *state);
! static void ExecInitExprRec(Expr *node, ExprState *state,
  				Datum *resv, bool *resnull);
  static void ExecInitFunc(ExprEvalStep *scratch, Expr *node, List *args,
! 			 Oid funcid, Oid inputcollid,
  			 ExprState *state);
  static void ExecInitExprSlots(ExprState *state, Node *node);
  static bool get_last_attnums_walker(Node *node, LastAttnumInfo *info);
  static void ExecInitWholeRowVar(ExprEvalStep *scratch, Var *variable,
! 					ExprState *state);
  static void ExecInitArrayRef(ExprEvalStep *scratch, ArrayRef *aref,
! 				 ExprState *state,
  				 Datum *resv, bool *resnull);
  static bool isAssignmentIndirectionExpr(Expr *expr);
  static void ExecInitCoerceToDomain(ExprEvalStep *scratch, CoerceToDomain *ctest,
! 					   ExprState *state,
  					   Datum *resv, bool *resnull);
  
  
*************** ExecInitExpr(Expr *node, PlanState *pare
*** 122,133 ****
  	/* Initialize ExprState with empty step list */
  	state = makeNode(ExprState);
  	state->expr = node;
  
  	/* Insert EEOP_*_FETCHSOME steps as needed */
  	ExecInitExprSlots(state, (Node *) node);
  
  	/* Compile the expression proper */
! 	ExecInitExprRec(node, parent, state, &state->resvalue, &state->resnull);
  
  	/* Finally, append a DONE step */
  	scratch.opcode = EEOP_DONE;
--- 121,171 ----
  	/* Initialize ExprState with empty step list */
  	state = makeNode(ExprState);
  	state->expr = node;
+ 	state->parent = parent;
+ 	state->ext_params = NULL;
  
  	/* Insert EEOP_*_FETCHSOME steps as needed */
  	ExecInitExprSlots(state, (Node *) node);
  
  	/* Compile the expression proper */
! 	ExecInitExprRec(node, state, &state->resvalue, &state->resnull);
! 
! 	/* Finally, append a DONE step */
! 	scratch.opcode = EEOP_DONE;
! 	ExprEvalPushStep(state, &scratch);
! 
! 	ExecReadyExpr(state);
! 
! 	return state;
! }
! 
! /*
!  * ExecInitExprWithParams: prepare a standalone expression tree for execution
!  *
!  * This is the same as ExecInitExpr, except that there is no parent PlanState,
!  * and instead we may have a ParamListInfo describing PARAM_EXTERN Params.
!  */
! ExprState *
! ExecInitExprWithParams(Expr *node, ParamListInfo ext_params)
! {
! 	ExprState  *state;
! 	ExprEvalStep scratch;
! 
! 	/* Special case: NULL expression produces a NULL ExprState pointer */
! 	if (node == NULL)
! 		return NULL;
! 
! 	/* Initialize ExprState with empty step list */
! 	state = makeNode(ExprState);
! 	state->expr = node;
! 	state->parent = NULL;
! 	state->ext_params = ext_params;
! 
! 	/* Insert EEOP_*_FETCHSOME steps as needed */
! 	ExecInitExprSlots(state, (Node *) node);
! 
! 	/* Compile the expression proper */
! 	ExecInitExprRec(node, state, &state->resvalue, &state->resnull);
  
  	/* Finally, append a DONE step */
  	scratch.opcode = EEOP_DONE;
*************** ExecInitQual(List *qual, PlanState *pare
*** 172,177 ****
--- 210,218 ----
  
  	state = makeNode(ExprState);
  	state->expr = (Expr *) qual;
+ 	state->parent = parent;
+ 	state->ext_params = NULL;
+ 
  	/* mark expression as to be used with ExecQual() */
  	state->flags = EEO_FLAG_IS_QUAL;
  
*************** ExecInitQual(List *qual, PlanState *pare
*** 198,204 ****
  		Expr	   *node = (Expr *) lfirst(lc);
  
  		/* first evaluate expression */
! 		ExecInitExprRec(node, parent, state, &state->resvalue, &state->resnull);
  
  		/* then emit EEOP_QUAL to detect if it's false (or null) */
  		scratch.d.qualexpr.jumpdone = -1;
--- 239,245 ----
  		Expr	   *node = (Expr *) lfirst(lc);
  
  		/* first evaluate expression */
! 		ExecInitExprRec(node, state, &state->resvalue, &state->resnull);
  
  		/* then emit EEOP_QUAL to detect if it's false (or null) */
  		scratch.d.qualexpr.jumpdone = -1;
*************** ExecBuildProjectionInfo(List *targetList
*** 314,319 ****
--- 355,363 ----
  	projInfo->pi_state.tag.type = T_ExprState;
  	state = &projInfo->pi_state;
  	state->expr = (Expr *) targetList;
+ 	state->parent = parent;
+ 	state->ext_params = NULL;
+ 
  	state->resultslot = slot;
  
  	/* Insert EEOP_*_FETCHSOME steps as needed */
*************** ExecBuildProjectionInfo(List *targetList
*** 398,404 ****
  			 * matter) can change between executions.  We instead evaluate
  			 * into the ExprState's resvalue/resnull and then move.
  			 */
! 			ExecInitExprRec(tle->expr, parent, state,
  							&state->resvalue, &state->resnull);
  
  			/*
--- 442,448 ----
  			 * matter) can change between executions.  We instead evaluate
  			 * into the ExprState's resvalue/resnull and then move.
  			 */
! 			ExecInitExprRec(tle->expr, state,
  							&state->resvalue, &state->resnull);
  
  			/*
*************** ExecReadyExpr(ExprState *state)
*** 581,592 ****
   * possibly recursing into sub-expressions of node.
   *
   * node - expression to evaluate
-  * parent - parent executor node (or NULL if a standalone expression)
   * state - ExprState to whose ->steps to append the necessary operations
   * resv / resnull - where to store the result of the node into
   */
  static void
! ExecInitExprRec(Expr *node, PlanState *parent, ExprState *state,
  				Datum *resv, bool *resnull)
  {
  	ExprEvalStep scratch;
--- 625,635 ----
   * possibly recursing into sub-expressions of node.
   *
   * node - expression to evaluate
   * state - ExprState to whose ->steps to append the necessary operations
   * resv / resnull - where to store the result of the node into
   */
  static void
! ExecInitExprRec(Expr *node, ExprState *state,
  				Datum *resv, bool *resnull)
  {
  	ExprEvalStep scratch;
*************** ExecInitExprRec(Expr *node, PlanState *p
*** 609,615 ****
  				if (variable->varattno == InvalidAttrNumber)
  				{
  					/* whole-row Var */
! 					ExecInitWholeRowVar(&scratch, variable, parent);
  				}
  				else if (variable->varattno <= 0)
  				{
--- 652,658 ----
  				if (variable->varattno == InvalidAttrNumber)
  				{
  					/* whole-row Var */
! 					ExecInitWholeRowVar(&scratch, variable, state);
  				}
  				else if (variable->varattno <= 0)
  				{
*************** ExecInitExprRec(Expr *node, PlanState *p
*** 674,679 ****
--- 717,723 ----
  		case T_Param:
  			{
  				Param	   *param = (Param *) node;
+ 				ParamListInfo params;
  
  				switch (param->paramkind)
  				{
*************** ExecInitExprRec(Expr *node, PlanState *p
*** 681,699 ****
  						scratch.opcode = EEOP_PARAM_EXEC;
  						scratch.d.param.paramid = param->paramid;
  						scratch.d.param.paramtype = param->paramtype;
  						break;
  					case PARAM_EXTERN:
! 						scratch.opcode = EEOP_PARAM_EXTERN;
! 						scratch.d.param.paramid = param->paramid;
! 						scratch.d.param.paramtype = param->paramtype;
  						break;
  					default:
  						elog(ERROR, "unrecognized paramkind: %d",
  							 (int) param->paramkind);
  						break;
  				}
- 
- 				ExprEvalPushStep(state, &scratch);
  				break;
  			}
  
--- 725,765 ----
  						scratch.opcode = EEOP_PARAM_EXEC;
  						scratch.d.param.paramid = param->paramid;
  						scratch.d.param.paramtype = param->paramtype;
+ 						ExprEvalPushStep(state, &scratch);
  						break;
  					case PARAM_EXTERN:
! 
! 						/*
! 						 * If we have a relevant ParamCompileHook, use it;
! 						 * otherwise compile a standard EEOP_PARAM_EXTERN
! 						 * step.  ext_params, if supplied, takes precedence
! 						 * over info from the parent node's EState (if any).
! 						 */
! 						if (state->ext_params)
! 							params = state->ext_params;
! 						else if (state->parent &&
! 								 state->parent->state)
! 							params = state->parent->state->es_param_list_info;
! 						else
! 							params = NULL;
! 						if (params && params->paramCompile)
! 						{
! 							params->paramCompile(params, param, state,
! 												 resv, resnull);
! 						}
! 						else
! 						{
! 							scratch.opcode = EEOP_PARAM_EXTERN;
! 							scratch.d.param.paramid = param->paramid;
! 							scratch.d.param.paramtype = param->paramtype;
! 							ExprEvalPushStep(state, &scratch);
! 						}
  						break;
  					default:
  						elog(ERROR, "unrecognized paramkind: %d",
  							 (int) param->paramkind);
  						break;
  				}
  				break;
  			}
  
*************** ExecInitExprRec(Expr *node, PlanState *p
*** 706,714 ****
  				scratch.d.aggref.astate = astate;
  				astate->aggref = aggref;
  
! 				if (parent && IsA(parent, AggState))
  				{
! 					AggState   *aggstate = (AggState *) parent;
  
  					aggstate->aggs = lcons(astate, aggstate->aggs);
  					aggstate->numaggs++;
--- 772,780 ----
  				scratch.d.aggref.astate = astate;
  				astate->aggref = aggref;
  
! 				if (state->parent && IsA(state->parent, AggState))
  				{
! 					AggState   *aggstate = (AggState *) state->parent;
  
  					aggstate->aggs = lcons(astate, aggstate->aggs);
  					aggstate->numaggs++;
*************** ExecInitExprRec(Expr *node, PlanState *p
*** 728,741 ****
  				GroupingFunc *grp_node = (GroupingFunc *) node;
  				Agg		   *agg;
  
! 				if (!parent || !IsA(parent, AggState) ||
! 					!IsA(parent->plan, Agg))
  					elog(ERROR, "GroupingFunc found in non-Agg plan node");
  
  				scratch.opcode = EEOP_GROUPING_FUNC;
! 				scratch.d.grouping_func.parent = (AggState *) parent;
  
! 				agg = (Agg *) (parent->plan);
  
  				if (agg->groupingSets)
  					scratch.d.grouping_func.clauses = grp_node->cols;
--- 794,807 ----
  				GroupingFunc *grp_node = (GroupingFunc *) node;
  				Agg		   *agg;
  
! 				if (!state->parent || !IsA(state->parent, AggState) ||
! 					!IsA(state->parent->plan, Agg))
  					elog(ERROR, "GroupingFunc found in non-Agg plan node");
  
  				scratch.opcode = EEOP_GROUPING_FUNC;
! 				scratch.d.grouping_func.parent = (AggState *) state->parent;
  
! 				agg = (Agg *) (state->parent->plan);
  
  				if (agg->groupingSets)
  					scratch.d.grouping_func.clauses = grp_node->cols;
*************** ExecInitExprRec(Expr *node, PlanState *p
*** 753,761 ****
  
  				wfstate->wfunc = wfunc;
  
! 				if (parent && IsA(parent, WindowAggState))
  				{
! 					WindowAggState *winstate = (WindowAggState *) parent;
  					int			nfuncs;
  
  					winstate->funcs = lcons(wfstate, winstate->funcs);
--- 819,827 ----
  
  				wfstate->wfunc = wfunc;
  
! 				if (state->parent && IsA(state->parent, WindowAggState))
  				{
! 					WindowAggState *winstate = (WindowAggState *) state->parent;
  					int			nfuncs;
  
  					winstate->funcs = lcons(wfstate, winstate->funcs);
*************** ExecInitExprRec(Expr *node, PlanState *p
*** 764,772 ****
  						winstate->numaggs++;
  
  					/* for now initialize agg using old style expressions */
! 					wfstate->args = ExecInitExprList(wfunc->args, parent);
  					wfstate->aggfilter = ExecInitExpr(wfunc->aggfilter,
! 													  parent);
  
  					/*
  					 * Complain if the windowfunc's arguments contain any
--- 830,839 ----
  						winstate->numaggs++;
  
  					/* for now initialize agg using old style expressions */
! 					wfstate->args = ExecInitExprList(wfunc->args,
! 													 state->parent);
  					wfstate->aggfilter = ExecInitExpr(wfunc->aggfilter,
! 													  state->parent);
  
  					/*
  					 * Complain if the windowfunc's arguments contain any
*************** ExecInitExprRec(Expr *node, PlanState *p
*** 795,801 ****
  			{
  				ArrayRef   *aref = (ArrayRef *) node;
  
! 				ExecInitArrayRef(&scratch, aref, parent, state, resv, resnull);
  				break;
  			}
  
--- 862,868 ----
  			{
  				ArrayRef   *aref = (ArrayRef *) node;
  
! 				ExecInitArrayRef(&scratch, aref, state, resv, resnull);
  				break;
  			}
  
*************** ExecInitExprRec(Expr *node, PlanState *p
*** 805,811 ****
  
  				ExecInitFunc(&scratch, node,
  							 func->args, func->funcid, func->inputcollid,
! 							 parent, state);
  				ExprEvalPushStep(state, &scratch);
  				break;
  			}
--- 872,878 ----
  
  				ExecInitFunc(&scratch, node,
  							 func->args, func->funcid, func->inputcollid,
! 							 state);
  				ExprEvalPushStep(state, &scratch);
  				break;
  			}
*************** ExecInitExprRec(Expr *node, PlanState *p
*** 816,822 ****
  
  				ExecInitFunc(&scratch, node,
  							 op->args, op->opfuncid, op->inputcollid,
! 							 parent, state);
  				ExprEvalPushStep(state, &scratch);
  				break;
  			}
--- 883,889 ----
  
  				ExecInitFunc(&scratch, node,
  							 op->args, op->opfuncid, op->inputcollid,
! 							 state);
  				ExprEvalPushStep(state, &scratch);
  				break;
  			}
*************** ExecInitExprRec(Expr *node, PlanState *p
*** 827,833 ****
  
  				ExecInitFunc(&scratch, node,
  							 op->args, op->opfuncid, op->inputcollid,
! 							 parent, state);
  
  				/*
  				 * Change opcode of call instruction to EEOP_DISTINCT.
--- 894,900 ----
  
  				ExecInitFunc(&scratch, node,
  							 op->args, op->opfuncid, op->inputcollid,
! 							 state);
  
  				/*
  				 * Change opcode of call instruction to EEOP_DISTINCT.
*************** ExecInitExprRec(Expr *node, PlanState *p
*** 849,855 ****
  
  				ExecInitFunc(&scratch, node,
  							 op->args, op->opfuncid, op->inputcollid,
! 							 parent, state);
  
  				/*
  				 * Change opcode of call instruction to EEOP_NULLIF.
--- 916,922 ----
  
  				ExecInitFunc(&scratch, node,
  							 op->args, op->opfuncid, op->inputcollid,
! 							 state);
  
  				/*
  				 * Change opcode of call instruction to EEOP_NULLIF.
*************** ExecInitExprRec(Expr *node, PlanState *p
*** 896,902 ****
  										 opexpr->inputcollid, NULL, NULL);
  
  				/* Evaluate scalar directly into left function argument */
! 				ExecInitExprRec(scalararg, parent, state,
  								&fcinfo->arg[0], &fcinfo->argnull[0]);
  
  				/*
--- 963,969 ----
  										 opexpr->inputcollid, NULL, NULL);
  
  				/* Evaluate scalar directly into left function argument */
! 				ExecInitExprRec(scalararg, state,
  								&fcinfo->arg[0], &fcinfo->argnull[0]);
  
  				/*
*************** ExecInitExprRec(Expr *node, PlanState *p
*** 905,911 ****
  				 * be overwritten by EEOP_SCALARARRAYOP, and will not be
  				 * passed to any other expression.
  				 */
! 				ExecInitExprRec(arrayarg, parent, state, resv, resnull);
  
  				/* And perform the operation */
  				scratch.opcode = EEOP_SCALARARRAYOP;
--- 972,978 ----
  				 * be overwritten by EEOP_SCALARARRAYOP, and will not be
  				 * passed to any other expression.
  				 */
! 				ExecInitExprRec(arrayarg, state, resv, resnull);
  
  				/* And perform the operation */
  				scratch.opcode = EEOP_SCALARARRAYOP;
*************** ExecInitExprRec(Expr *node, PlanState *p
*** 949,955 ****
  					Expr	   *arg = (Expr *) lfirst(lc);
  
  					/* Evaluate argument into our output variable */
! 					ExecInitExprRec(arg, parent, state, resv, resnull);
  
  					/* Perform the appropriate step type */
  					switch (boolexpr->boolop)
--- 1016,1022 ----
  					Expr	   *arg = (Expr *) lfirst(lc);
  
  					/* Evaluate argument into our output variable */
! 					ExecInitExprRec(arg, state, resv, resnull);
  
  					/* Perform the appropriate step type */
  					switch (boolexpr->boolop)
*************** ExecInitExprRec(Expr *node, PlanState *p
*** 1009,1021 ****
  				SubPlan    *subplan = (SubPlan *) node;
  				SubPlanState *sstate;
  
! 				if (!parent)
  					elog(ERROR, "SubPlan found with no parent plan");
  
! 				sstate = ExecInitSubPlan(subplan, parent);
  
! 				/* add SubPlanState nodes to parent->subPlan */
! 				parent->subPlan = lappend(parent->subPlan, sstate);
  
  				scratch.opcode = EEOP_SUBPLAN;
  				scratch.d.subplan.sstate = sstate;
--- 1076,1089 ----
  				SubPlan    *subplan = (SubPlan *) node;
  				SubPlanState *sstate;
  
! 				if (!state->parent)
  					elog(ERROR, "SubPlan found with no parent plan");
  
! 				sstate = ExecInitSubPlan(subplan, state->parent);
  
! 				/* add SubPlanState nodes to state->parent->subPlan */
! 				state->parent->subPlan = lappend(state->parent->subPlan,
! 												 sstate);
  
  				scratch.opcode = EEOP_SUBPLAN;
  				scratch.d.subplan.sstate = sstate;
*************** ExecInitExprRec(Expr *node, PlanState *p
*** 1029,1038 ****
  				AlternativeSubPlan *asplan = (AlternativeSubPlan *) node;
  				AlternativeSubPlanState *asstate;
  
! 				if (!parent)
  					elog(ERROR, "AlternativeSubPlan found with no parent plan");
  
! 				asstate = ExecInitAlternativeSubPlan(asplan, parent);
  
  				scratch.opcode = EEOP_ALTERNATIVE_SUBPLAN;
  				scratch.d.alternative_subplan.asstate = asstate;
--- 1097,1106 ----
  				AlternativeSubPlan *asplan = (AlternativeSubPlan *) node;
  				AlternativeSubPlanState *asstate;
  
! 				if (!state->parent)
  					elog(ERROR, "AlternativeSubPlan found with no parent plan");
  
! 				asstate = ExecInitAlternativeSubPlan(asplan, state->parent);
  
  				scratch.opcode = EEOP_ALTERNATIVE_SUBPLAN;
  				scratch.d.alternative_subplan.asstate = asstate;
*************** ExecInitExprRec(Expr *node, PlanState *p
*** 1046,1052 ****
  				FieldSelect *fselect = (FieldSelect *) node;
  
  				/* evaluate row/record argument into result area */
! 				ExecInitExprRec(fselect->arg, parent, state, resv, resnull);
  
  				/* and extract field */
  				scratch.opcode = EEOP_FIELDSELECT;
--- 1114,1120 ----
  				FieldSelect *fselect = (FieldSelect *) node;
  
  				/* evaluate row/record argument into result area */
! 				ExecInitExprRec(fselect->arg, state, resv, resnull);
  
  				/* and extract field */
  				scratch.opcode = EEOP_FIELDSELECT;
*************** ExecInitExprRec(Expr *node, PlanState *p
*** 1083,1089 ****
  				*descp = NULL;
  
  				/* emit code to evaluate the composite input value */
! 				ExecInitExprRec(fstore->arg, parent, state, resv, resnull);
  
  				/* next, deform the input tuple into our workspace */
  				scratch.opcode = EEOP_FIELDSTORE_DEFORM;
--- 1151,1157 ----
  				*descp = NULL;
  
  				/* emit code to evaluate the composite input value */
! 				ExecInitExprRec(fstore->arg, state, resv, resnull);
  
  				/* next, deform the input tuple into our workspace */
  				scratch.opcode = EEOP_FIELDSTORE_DEFORM;
*************** ExecInitExprRec(Expr *node, PlanState *p
*** 1134,1140 ****
  					state->innermost_caseval = &values[fieldnum - 1];
  					state->innermost_casenull = &nulls[fieldnum - 1];
  
! 					ExecInitExprRec(e, parent, state,
  									&values[fieldnum - 1],
  									&nulls[fieldnum - 1]);
  
--- 1202,1208 ----
  					state->innermost_caseval = &values[fieldnum - 1];
  					state->innermost_casenull = &nulls[fieldnum - 1];
  
! 					ExecInitExprRec(e, state,
  									&values[fieldnum - 1],
  									&nulls[fieldnum - 1]);
  
*************** ExecInitExprRec(Expr *node, PlanState *p
*** 1158,1164 ****
  				/* relabel doesn't need to do anything at runtime */
  				RelabelType *relabel = (RelabelType *) node;
  
! 				ExecInitExprRec(relabel->arg, parent, state, resv, resnull);
  				break;
  			}
  
--- 1226,1232 ----
  				/* relabel doesn't need to do anything at runtime */
  				RelabelType *relabel = (RelabelType *) node;
  
! 				ExecInitExprRec(relabel->arg, state, resv, resnull);
  				break;
  			}
  
*************** ExecInitExprRec(Expr *node, PlanState *p
*** 1171,1177 ****
  				FunctionCallInfo fcinfo_in;
  
  				/* evaluate argument into step's result area */
! 				ExecInitExprRec(iocoerce->arg, parent, state, resv, resnull);
  
  				/*
  				 * Prepare both output and input function calls, to be
--- 1239,1245 ----
  				FunctionCallInfo fcinfo_in;
  
  				/* evaluate argument into step's result area */
! 				ExecInitExprRec(iocoerce->arg, state, resv, resnull);
  
  				/*
  				 * Prepare both output and input function calls, to be
*************** ExecInitExprRec(Expr *node, PlanState *p
*** 1228,1234 ****
  				ExprState  *elemstate;
  
  				/* evaluate argument into step's result area */
! 				ExecInitExprRec(acoerce->arg, parent, state, resv, resnull);
  
  				resultelemtype = get_element_type(acoerce->resulttype);
  				if (!OidIsValid(resultelemtype))
--- 1296,1302 ----
  				ExprState  *elemstate;
  
  				/* evaluate argument into step's result area */
! 				ExecInitExprRec(acoerce->arg, state, resv, resnull);
  
  				resultelemtype = get_element_type(acoerce->resulttype);
  				if (!OidIsValid(resultelemtype))
*************** ExecInitExprRec(Expr *node, PlanState *p
*** 1244,1253 ****
  				 */
  				elemstate = makeNode(ExprState);
  				elemstate->expr = acoerce->elemexpr;
  				elemstate->innermost_caseval = (Datum *) palloc(sizeof(Datum));
  				elemstate->innermost_casenull = (bool *) palloc(sizeof(bool));
  
! 				ExecInitExprRec(acoerce->elemexpr, parent, elemstate,
  								&elemstate->resvalue, &elemstate->resnull);
  
  				if (elemstate->steps_len == 1 &&
--- 1312,1324 ----
  				 */
  				elemstate = makeNode(ExprState);
  				elemstate->expr = acoerce->elemexpr;
+ 				elemstate->parent = state->parent;
+ 				elemstate->ext_params = state->ext_params;
+ 
  				elemstate->innermost_caseval = (Datum *) palloc(sizeof(Datum));
  				elemstate->innermost_casenull = (bool *) palloc(sizeof(bool));
  
! 				ExecInitExprRec(acoerce->elemexpr, elemstate,
  								&elemstate->resvalue, &elemstate->resnull);
  
  				if (elemstate->steps_len == 1 &&
*************** ExecInitExprRec(Expr *node, PlanState *p
*** 1290,1296 ****
  				ConvertRowtypeExpr *convert = (ConvertRowtypeExpr *) node;
  
  				/* evaluate argument into step's result area */
! 				ExecInitExprRec(convert->arg, parent, state, resv, resnull);
  
  				/* and push conversion step */
  				scratch.opcode = EEOP_CONVERT_ROWTYPE;
--- 1361,1367 ----
  				ConvertRowtypeExpr *convert = (ConvertRowtypeExpr *) node;
  
  				/* evaluate argument into step's result area */
! 				ExecInitExprRec(convert->arg, state, resv, resnull);
  
  				/* and push conversion step */
  				scratch.opcode = EEOP_CONVERT_ROWTYPE;
*************** ExecInitExprRec(Expr *node, PlanState *p
*** 1324,1330 ****
  					caseval = palloc(sizeof(Datum));
  					casenull = palloc(sizeof(bool));
  
! 					ExecInitExprRec(caseExpr->arg, parent, state,
  									caseval, casenull);
  
  					/*
--- 1395,1401 ----
  					caseval = palloc(sizeof(Datum));
  					casenull = palloc(sizeof(bool));
  
! 					ExecInitExprRec(caseExpr->arg, state,
  									caseval, casenull);
  
  					/*
*************** ExecInitExprRec(Expr *node, PlanState *p
*** 1375,1381 ****
  					state->innermost_casenull = casenull;
  
  					/* evaluate condition into CASE's result variables */
! 					ExecInitExprRec(when->expr, parent, state, resv, resnull);
  
  					state->innermost_caseval = save_innermost_caseval;
  					state->innermost_casenull = save_innermost_casenull;
--- 1446,1452 ----
  					state->innermost_casenull = casenull;
  
  					/* evaluate condition into CASE's result variables */
! 					ExecInitExprRec(when->expr, state, resv, resnull);
  
  					state->innermost_caseval = save_innermost_caseval;
  					state->innermost_casenull = save_innermost_casenull;
*************** ExecInitExprRec(Expr *node, PlanState *p
*** 1390,1396 ****
  					 * If WHEN result is true, evaluate THEN result, storing
  					 * it into the CASE's result variables.
  					 */
! 					ExecInitExprRec(when->result, parent, state, resv, resnull);
  
  					/* Emit JUMP step to jump to end of CASE's code */
  					scratch.opcode = EEOP_JUMP;
--- 1461,1467 ----
  					 * If WHEN result is true, evaluate THEN result, storing
  					 * it into the CASE's result variables.
  					 */
! 					ExecInitExprRec(when->result, state, resv, resnull);
  
  					/* Emit JUMP step to jump to end of CASE's code */
  					scratch.opcode = EEOP_JUMP;
*************** ExecInitExprRec(Expr *node, PlanState *p
*** 1415,1421 ****
  				Assert(caseExpr->defresult);
  
  				/* evaluate ELSE expr into CASE's result variables */
! 				ExecInitExprRec(caseExpr->defresult, parent, state,
  								resv, resnull);
  
  				/* adjust jump targets */
--- 1486,1492 ----
  				Assert(caseExpr->defresult);
  
  				/* evaluate ELSE expr into CASE's result variables */
! 				ExecInitExprRec(caseExpr->defresult, state,
  								resv, resnull);
  
  				/* adjust jump targets */
*************** ExecInitExprRec(Expr *node, PlanState *p
*** 1484,1490 ****
  				{
  					Expr	   *e = (Expr *) lfirst(lc);
  
! 					ExecInitExprRec(e, parent, state,
  									&scratch.d.arrayexpr.elemvalues[elemoff],
  									&scratch.d.arrayexpr.elemnulls[elemoff]);
  					elemoff++;
--- 1555,1561 ----
  				{
  					Expr	   *e = (Expr *) lfirst(lc);
  
! 					ExecInitExprRec(e, state,
  									&scratch.d.arrayexpr.elemvalues[elemoff],
  									&scratch.d.arrayexpr.elemnulls[elemoff]);
  					elemoff++;
*************** ExecInitExprRec(Expr *node, PlanState *p
*** 1578,1584 ****
  					}
  
  					/* Evaluate column expr into appropriate workspace slot */
! 					ExecInitExprRec(e, parent, state,
  									&scratch.d.row.elemvalues[i],
  									&scratch.d.row.elemnulls[i]);
  					i++;
--- 1649,1655 ----
  					}
  
  					/* Evaluate column expr into appropriate workspace slot */
! 					ExecInitExprRec(e, state,
  									&scratch.d.row.elemvalues[i],
  									&scratch.d.row.elemnulls[i]);
  					i++;
*************** ExecInitExprRec(Expr *node, PlanState *p
*** 1667,1675 ****
  					 */
  
  					/* evaluate left and right args directly into fcinfo */
! 					ExecInitExprRec(left_expr, parent, state,
  									&fcinfo->arg[0], &fcinfo->argnull[0]);
! 					ExecInitExprRec(right_expr, parent, state,
  									&fcinfo->arg[1], &fcinfo->argnull[1]);
  
  					scratch.opcode = EEOP_ROWCOMPARE_STEP;
--- 1738,1746 ----
  					 */
  
  					/* evaluate left and right args directly into fcinfo */
! 					ExecInitExprRec(left_expr, state,
  									&fcinfo->arg[0], &fcinfo->argnull[0]);
! 					ExecInitExprRec(right_expr, state,
  									&fcinfo->arg[1], &fcinfo->argnull[1]);
  
  					scratch.opcode = EEOP_ROWCOMPARE_STEP;
*************** ExecInitExprRec(Expr *node, PlanState *p
*** 1738,1744 ****
  					Expr	   *e = (Expr *) lfirst(lc);
  
  					/* evaluate argument, directly into result datum */
! 					ExecInitExprRec(e, parent, state, resv, resnull);
  
  					/* if it's not null, skip to end of COALESCE expr */
  					scratch.opcode = EEOP_JUMP_IF_NOT_NULL;
--- 1809,1815 ----
  					Expr	   *e = (Expr *) lfirst(lc);
  
  					/* evaluate argument, directly into result datum */
! 					ExecInitExprRec(e, state, resv, resnull);
  
  					/* if it's not null, skip to end of COALESCE expr */
  					scratch.opcode = EEOP_JUMP_IF_NOT_NULL;
*************** ExecInitExprRec(Expr *node, PlanState *p
*** 1820,1826 ****
  				{
  					Expr	   *e = (Expr *) lfirst(lc);
  
! 					ExecInitExprRec(e, parent, state,
  									&scratch.d.minmax.values[off],
  									&scratch.d.minmax.nulls[off]);
  					off++;
--- 1891,1897 ----
  				{
  					Expr	   *e = (Expr *) lfirst(lc);
  
! 					ExecInitExprRec(e, state,
  									&scratch.d.minmax.values[off],
  									&scratch.d.minmax.nulls[off]);
  					off++;
*************** ExecInitExprRec(Expr *node, PlanState *p
*** 1886,1892 ****
  				{
  					Expr	   *e = (Expr *) lfirst(arg);
  
! 					ExecInitExprRec(e, parent, state,
  									&scratch.d.xmlexpr.named_argvalue[off],
  									&scratch.d.xmlexpr.named_argnull[off]);
  					off++;
--- 1957,1963 ----
  				{
  					Expr	   *e = (Expr *) lfirst(arg);
  
! 					ExecInitExprRec(e, state,
  									&scratch.d.xmlexpr.named_argvalue[off],
  									&scratch.d.xmlexpr.named_argnull[off]);
  					off++;
*************** ExecInitExprRec(Expr *node, PlanState *p
*** 1897,1903 ****
  				{
  					Expr	   *e = (Expr *) lfirst(arg);
  
! 					ExecInitExprRec(e, parent, state,
  									&scratch.d.xmlexpr.argvalue[off],
  									&scratch.d.xmlexpr.argnull[off]);
  					off++;
--- 1968,1974 ----
  				{
  					Expr	   *e = (Expr *) lfirst(arg);
  
! 					ExecInitExprRec(e, state,
  									&scratch.d.xmlexpr.argvalue[off],
  									&scratch.d.xmlexpr.argnull[off]);
  					off++;
*************** ExecInitExprRec(Expr *node, PlanState *p
*** 1935,1941 ****
  				scratch.d.nulltest_row.argdesc = NULL;
  
  				/* first evaluate argument into result variable */
! 				ExecInitExprRec(ntest->arg, parent, state,
  								resv, resnull);
  
  				/* then push the test of that argument */
--- 2006,2012 ----
  				scratch.d.nulltest_row.argdesc = NULL;
  
  				/* first evaluate argument into result variable */
! 				ExecInitExprRec(ntest->arg, state,
  								resv, resnull);
  
  				/* then push the test of that argument */
*************** ExecInitExprRec(Expr *node, PlanState *p
*** 1953,1959 ****
  				 * and will get overwritten by the below EEOP_BOOLTEST_IS_*
  				 * step.
  				 */
! 				ExecInitExprRec(btest->arg, parent, state, resv, resnull);
  
  				switch (btest->booltesttype)
  				{
--- 2024,2030 ----
  				 * and will get overwritten by the below EEOP_BOOLTEST_IS_*
  				 * step.
  				 */
! 				ExecInitExprRec(btest->arg, state, resv, resnull);
  
  				switch (btest->booltesttype)
  				{
*************** ExecInitExprRec(Expr *node, PlanState *p
*** 1990,1996 ****
  			{
  				CoerceToDomain *ctest = (CoerceToDomain *) node;
  
! 				ExecInitCoerceToDomain(&scratch, ctest, parent, state,
  									   resv, resnull);
  				break;
  			}
--- 2061,2067 ----
  			{
  				CoerceToDomain *ctest = (CoerceToDomain *) node;
  
! 				ExecInitCoerceToDomain(&scratch, ctest, state,
  									   resv, resnull);
  				break;
  			}
*************** ExecInitExprRec(Expr *node, PlanState *p
*** 2046,2052 ****
   * Note that this potentially re-allocates es->steps, therefore no pointer
   * into that array may be used while the expression is still being built.
   */
! static void
  ExprEvalPushStep(ExprState *es, const ExprEvalStep *s)
  {
  	if (es->steps_alloc == 0)
--- 2117,2123 ----
   * Note that this potentially re-allocates es->steps, therefore no pointer
   * into that array may be used while the expression is still being built.
   */
! void
  ExprEvalPushStep(ExprState *es, const ExprEvalStep *s)
  {
  	if (es->steps_alloc == 0)
*************** ExprEvalPushStep(ExprState *es, const Ex
*** 2074,2080 ****
   */
  static void
  ExecInitFunc(ExprEvalStep *scratch, Expr *node, List *args, Oid funcid,
! 			 Oid inputcollid, PlanState *parent, ExprState *state)
  {
  	int			nargs = list_length(args);
  	AclResult	aclresult;
--- 2145,2151 ----
   */
  static void
  ExecInitFunc(ExprEvalStep *scratch, Expr *node, List *args, Oid funcid,
! 			 Oid inputcollid, ExprState *state)
  {
  	int			nargs = list_length(args);
  	AclResult	aclresult;
*************** ExecInitFunc(ExprEvalStep *scratch, Expr
*** 2126,2133 ****
  		ereport(ERROR,
  				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
  				 errmsg("set-valued function called in context that cannot accept a set"),
! 				 parent ? executor_errposition(parent->state,
! 											   exprLocation((Node *) node)) : 0));
  
  	/* Build code to evaluate arguments directly into the fcinfo struct */
  	argno = 0;
--- 2197,2205 ----
  		ereport(ERROR,
  				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
  				 errmsg("set-valued function called in context that cannot accept a set"),
! 				 state->parent ?
! 				 executor_errposition(state->parent->state,
! 									  exprLocation((Node *) node)) : 0));
  
  	/* Build code to evaluate arguments directly into the fcinfo struct */
  	argno = 0;
*************** ExecInitFunc(ExprEvalStep *scratch, Expr
*** 2148,2154 ****
  		}
  		else
  		{
! 			ExecInitExprRec(arg, parent, state,
  							&fcinfo->arg[argno], &fcinfo->argnull[argno]);
  		}
  		argno++;
--- 2220,2226 ----
  		}
  		else
  		{
! 			ExecInitExprRec(arg, state,
  							&fcinfo->arg[argno], &fcinfo->argnull[argno]);
  		}
  		argno++;
*************** get_last_attnums_walker(Node *node, Last
*** 2260,2266 ****
   * The caller still has to push the step.
   */
  static void
! ExecInitWholeRowVar(ExprEvalStep *scratch, Var *variable, PlanState *parent)
  {
  	/* fill in all but the target */
  	scratch->opcode = EEOP_WHOLEROW;
--- 2332,2338 ----
   * The caller still has to push the step.
   */
  static void
! ExecInitWholeRowVar(ExprEvalStep *scratch, Var *variable, ExprState *state)
  {
  	/* fill in all but the target */
  	scratch->opcode = EEOP_WHOLEROW;
*************** ExecInitWholeRowVar(ExprEvalStep *scratc
*** 2282,2298 ****
  	 * can occur in such expressions, but they will always be referencing
  	 * table rows.)
  	 */
! 	if (parent)
  	{
  		PlanState  *subplan = NULL;
  
! 		switch (nodeTag(parent))
  		{
  			case T_SubqueryScanState:
! 				subplan = ((SubqueryScanState *) parent)->subplan;
  				break;
  			case T_CteScanState:
! 				subplan = ((CteScanState *) parent)->cteplanstate;
  				break;
  			default:
  				break;
--- 2354,2370 ----
  	 * can occur in such expressions, but they will always be referencing
  	 * table rows.)
  	 */
! 	if (state->parent)
  	{
  		PlanState  *subplan = NULL;
  
! 		switch (nodeTag(state->parent))
  		{
  			case T_SubqueryScanState:
! 				subplan = ((SubqueryScanState *) state->parent)->subplan;
  				break;
  			case T_CteScanState:
! 				subplan = ((CteScanState *) state->parent)->cteplanstate;
  				break;
  			default:
  				break;
*************** ExecInitWholeRowVar(ExprEvalStep *scratc
*** 2321,2327 ****
  				scratch->d.wholerow.junkFilter =
  					ExecInitJunkFilter(subplan->plan->targetlist,
  									   ExecGetResultType(subplan)->tdhasoid,
! 									   ExecInitExtraTupleSlot(parent->state));
  			}
  		}
  	}
--- 2393,2399 ----
  				scratch->d.wholerow.junkFilter =
  					ExecInitJunkFilter(subplan->plan->targetlist,
  									   ExecGetResultType(subplan)->tdhasoid,
! 									   ExecInitExtraTupleSlot(state->parent->state));
  			}
  		}
  	}
*************** ExecInitWholeRowVar(ExprEvalStep *scratc
*** 2331,2337 ****
   * Prepare evaluation of an ArrayRef expression.
   */
  static void
! ExecInitArrayRef(ExprEvalStep *scratch, ArrayRef *aref, PlanState *parent,
  				 ExprState *state, Datum *resv, bool *resnull)
  {
  	bool		isAssignment = (aref->refassgnexpr != NULL);
--- 2403,2409 ----
   * Prepare evaluation of an ArrayRef expression.
   */
  static void
! ExecInitArrayRef(ExprEvalStep *scratch, ArrayRef *aref,
  				 ExprState *state, Datum *resv, bool *resnull)
  {
  	bool		isAssignment = (aref->refassgnexpr != NULL);
*************** ExecInitArrayRef(ExprEvalStep *scratch, 
*** 2355,2361 ****
  	 * be overwritten by the final EEOP_ARRAYREF_FETCH/ASSIGN step, which is
  	 * pushed last.
  	 */
! 	ExecInitExprRec(aref->refexpr, parent, state, resv, resnull);
  
  	/*
  	 * If refexpr yields NULL, and it's a fetch, then result is NULL.  We can
--- 2427,2433 ----
  	 * be overwritten by the final EEOP_ARRAYREF_FETCH/ASSIGN step, which is
  	 * pushed last.
  	 */
! 	ExecInitExprRec(aref->refexpr, state, resv, resnull);
  
  	/*
  	 * If refexpr yields NULL, and it's a fetch, then result is NULL.  We can
*************** ExecInitArrayRef(ExprEvalStep *scratch, 
*** 2401,2407 ****
  		arefstate->upperprovided[i] = true;
  
  		/* Each subscript is evaluated into subscriptvalue/subscriptnull */
! 		ExecInitExprRec(e, parent, state,
  						&arefstate->subscriptvalue, &arefstate->subscriptnull);
  
  		/* ... and then ARRAYREF_SUBSCRIPT saves it into step's workspace */
--- 2473,2479 ----
  		arefstate->upperprovided[i] = true;
  
  		/* Each subscript is evaluated into subscriptvalue/subscriptnull */
! 		ExecInitExprRec(e, state,
  						&arefstate->subscriptvalue, &arefstate->subscriptnull);
  
  		/* ... and then ARRAYREF_SUBSCRIPT saves it into step's workspace */
*************** ExecInitArrayRef(ExprEvalStep *scratch, 
*** 2434,2440 ****
  		arefstate->lowerprovided[i] = true;
  
  		/* Each subscript is evaluated into subscriptvalue/subscriptnull */
! 		ExecInitExprRec(e, parent, state,
  						&arefstate->subscriptvalue, &arefstate->subscriptnull);
  
  		/* ... and then ARRAYREF_SUBSCRIPT saves it into step's workspace */
--- 2506,2512 ----
  		arefstate->lowerprovided[i] = true;
  
  		/* Each subscript is evaluated into subscriptvalue/subscriptnull */
! 		ExecInitExprRec(e, state,
  						&arefstate->subscriptvalue, &arefstate->subscriptnull);
  
  		/* ... and then ARRAYREF_SUBSCRIPT saves it into step's workspace */
*************** ExecInitArrayRef(ExprEvalStep *scratch, 
*** 2488,2494 ****
  		state->innermost_casenull = &arefstate->prevnull;
  
  		/* evaluate replacement value into replacevalue/replacenull */
! 		ExecInitExprRec(aref->refassgnexpr, parent, state,
  						&arefstate->replacevalue, &arefstate->replacenull);
  
  		state->innermost_caseval = save_innermost_caseval;
--- 2560,2566 ----
  		state->innermost_casenull = &arefstate->prevnull;
  
  		/* evaluate replacement value into replacevalue/replacenull */
! 		ExecInitExprRec(aref->refassgnexpr, state,
  						&arefstate->replacevalue, &arefstate->replacenull);
  
  		state->innermost_caseval = save_innermost_caseval;
*************** isAssignmentIndirectionExpr(Expr *expr)
*** 2566,2573 ****
   */
  static void
  ExecInitCoerceToDomain(ExprEvalStep *scratch, CoerceToDomain *ctest,
! 					   PlanState *parent, ExprState *state,
! 					   Datum *resv, bool *resnull)
  {
  	ExprEvalStep scratch2;
  	DomainConstraintRef *constraint_ref;
--- 2638,2644 ----
   */
  static void
  ExecInitCoerceToDomain(ExprEvalStep *scratch, CoerceToDomain *ctest,
! 					   ExprState *state, Datum *resv, bool *resnull)
  {
  	ExprEvalStep scratch2;
  	DomainConstraintRef *constraint_ref;
*************** ExecInitCoerceToDomain(ExprEvalStep *scr
*** 2587,2593 ****
  	 * if there's constraint failures there'll be errors, otherwise it's what
  	 * needs to be returned.
  	 */
! 	ExecInitExprRec(ctest->arg, parent, state, resv, resnull);
  
  	/*
  	 * Note: if the argument is of varlena type, it could be a R/W expanded
--- 2658,2664 ----
  	 * if there's constraint failures there'll be errors, otherwise it's what
  	 * needs to be returned.
  	 */
! 	ExecInitExprRec(ctest->arg, state, resv, resnull);
  
  	/*
  	 * Note: if the argument is of varlena type, it could be a R/W expanded
*************** ExecInitCoerceToDomain(ExprEvalStep *scr
*** 2684,2690 ****
  				state->innermost_domainnull = domainnull;
  
  				/* evaluate check expression value */
! 				ExecInitExprRec(con->check_expr, parent, state,
  								scratch->d.domaincheck.checkvalue,
  								scratch->d.domaincheck.checknull);
  
--- 2755,2761 ----
  				state->innermost_domainnull = domainnull;
  
  				/* evaluate check expression value */
! 				ExecInitExprRec(con->check_expr, state,
  								scratch->d.domaincheck.checkvalue,
  								scratch->d.domaincheck.checknull);
  
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 6c4612d..0c3f668 100644
*** a/src/backend/executor/execExprInterp.c
--- b/src/backend/executor/execExprInterp.c
*************** ExecInterpExpr(ExprState *state, ExprCon
*** 335,340 ****
--- 335,341 ----
  		&&CASE_EEOP_BOOLTEST_IS_NOT_FALSE,
  		&&CASE_EEOP_PARAM_EXEC,
  		&&CASE_EEOP_PARAM_EXTERN,
+ 		&&CASE_EEOP_PARAM_CALLBACK,
  		&&CASE_EEOP_CASE_TESTVAL,
  		&&CASE_EEOP_MAKE_READONLY,
  		&&CASE_EEOP_IOCOERCE,
*************** ExecInterpExpr(ExprState *state, ExprCon
*** 1047,1052 ****
--- 1048,1060 ----
  			EEO_NEXT();
  		}
  
+ 		EEO_CASE(EEOP_PARAM_CALLBACK)
+ 		{
+ 			/* allow an extension module to supply a PARAM_EXTERN value */
+ 			op->d.cparam.paramfunc(state, op, econtext);
+ 			EEO_NEXT();
+ 		}
+ 
  		EEO_CASE(EEOP_CASE_TESTVAL)
  		{
  			/*
*************** ExecEvalParamExtern(ExprState *state, Ex
*** 1967,1977 ****
  	if (likely(paramInfo &&
  			   paramId > 0 && paramId <= paramInfo->numParams))
  	{
! 		ParamExternData *prm = &paramInfo->params[paramId - 1];
  
  		/* give hook a chance in case parameter is dynamic */
! 		if (!OidIsValid(prm->ptype) && paramInfo->paramFetch != NULL)
! 			paramInfo->paramFetch(paramInfo, paramId);
  
  		if (likely(OidIsValid(prm->ptype)))
  		{
--- 1975,1988 ----
  	if (likely(paramInfo &&
  			   paramId > 0 && paramId <= paramInfo->numParams))
  	{
! 		ParamExternData *prm;
! 		ParamExternData prmdata;
  
  		/* give hook a chance in case parameter is dynamic */
! 		if (paramInfo->paramFetch != NULL)
! 			prm = paramInfo->paramFetch(paramInfo, paramId, false, &prmdata);
! 		else
! 			prm = &paramInfo->params[paramId - 1];
  
  		if (likely(OidIsValid(prm->ptype)))
  		{
diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c
index 3caa343..527f7d8 100644
*** a/src/backend/executor/functions.c
--- b/src/backend/executor/functions.c
*************** postquel_sub_params(SQLFunctionCachePtr 
*** 914,923 ****
  			/* we have static list of params, so no hooks needed */
  			paramLI->paramFetch = NULL;
  			paramLI->paramFetchArg = NULL;
  			paramLI->parserSetup = NULL;
  			paramLI->parserSetupArg = NULL;
  			paramLI->numParams = nargs;
- 			paramLI->paramMask = NULL;
  			fcache->paramLI = paramLI;
  		}
  		else
--- 914,924 ----
  			/* we have static list of params, so no hooks needed */
  			paramLI->paramFetch = NULL;
  			paramLI->paramFetchArg = NULL;
+ 			paramLI->paramCompile = NULL;
+ 			paramLI->paramCompileArg = NULL;
  			paramLI->parserSetup = NULL;
  			paramLI->parserSetupArg = NULL;
  			paramLI->numParams = nargs;
  			fcache->paramLI = paramLI;
  		}
  		else
diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c
index f3da2dd..977f317 100644
*** a/src/backend/executor/spi.c
--- b/src/backend/executor/spi.c
*************** _SPI_convert_params(int nargs, Oid *argt
*** 2259,2268 ****
  		/* we have static list of params, so no hooks needed */
  		paramLI->paramFetch = NULL;
  		paramLI->paramFetchArg = NULL;
  		paramLI->parserSetup = NULL;
  		paramLI->parserSetupArg = NULL;
  		paramLI->numParams = nargs;
- 		paramLI->paramMask = NULL;
  
  		for (i = 0; i < nargs; i++)
  		{
--- 2259,2269 ----
  		/* we have static list of params, so no hooks needed */
  		paramLI->paramFetch = NULL;
  		paramLI->paramFetchArg = NULL;
+ 		paramLI->paramCompile = NULL;
+ 		paramLI->paramCompileArg = NULL;
  		paramLI->parserSetup = NULL;
  		paramLI->parserSetupArg = NULL;
  		paramLI->numParams = nargs;
  
  		for (i = 0; i < nargs; i++)
  		{
diff --git a/src/backend/nodes/params.c b/src/backend/nodes/params.c
index 51429af..94acdf4 100644
*** a/src/backend/nodes/params.c
--- b/src/backend/nodes/params.c
*************** copyParamList(ParamListInfo from)
*** 48,79 ****
  	retval = (ParamListInfo) palloc(size);
  	retval->paramFetch = NULL;
  	retval->paramFetchArg = NULL;
  	retval->parserSetup = NULL;
  	retval->parserSetupArg = NULL;
  	retval->numParams = from->numParams;
- 	retval->paramMask = NULL;
  
  	for (i = 0; i < from->numParams; i++)
  	{
! 		ParamExternData *oprm = &from->params[i];
  		ParamExternData *nprm = &retval->params[i];
  		int16		typLen;
  		bool		typByVal;
  
- 		/* Ignore parameters we don't need, to save cycles and space. */
- 		if (from->paramMask != NULL &&
- 			!bms_is_member(i, from->paramMask))
- 		{
- 			nprm->value = (Datum) 0;
- 			nprm->isnull = true;
- 			nprm->pflags = 0;
- 			nprm->ptype = InvalidOid;
- 			continue;
- 		}
- 
  		/* give hook a chance in case parameter is dynamic */
! 		if (!OidIsValid(oprm->ptype) && from->paramFetch != NULL)
! 			from->paramFetch(from, i + 1);
  
  		/* flat-copy the parameter info */
  		*nprm = *oprm;
--- 48,72 ----
  	retval = (ParamListInfo) palloc(size);
  	retval->paramFetch = NULL;
  	retval->paramFetchArg = NULL;
+ 	retval->paramCompile = NULL;
+ 	retval->paramCompileArg = NULL;
  	retval->parserSetup = NULL;
  	retval->parserSetupArg = NULL;
  	retval->numParams = from->numParams;
  
  	for (i = 0; i < from->numParams; i++)
  	{
! 		ParamExternData *oprm;
  		ParamExternData *nprm = &retval->params[i];
+ 		ParamExternData prmdata;
  		int16		typLen;
  		bool		typByVal;
  
  		/* give hook a chance in case parameter is dynamic */
! 		if (from->paramFetch != NULL)
! 			oprm = from->paramFetch(from, i + 1, false, &prmdata);
! 		else
! 			oprm = &from->params[i];
  
  		/* flat-copy the parameter info */
  		*nprm = *oprm;
*************** EstimateParamListSpace(ParamListInfo par
*** 102,123 ****
  
  	for (i = 0; i < paramLI->numParams; i++)
  	{
! 		ParamExternData *prm = &paramLI->params[i];
  		Oid			typeOid;
  		int16		typLen;
  		bool		typByVal;
  
! 		/* Ignore parameters we don't need, to save cycles and space. */
! 		if (paramLI->paramMask != NULL &&
! 			!bms_is_member(i, paramLI->paramMask))
! 			typeOid = InvalidOid;
  		else
! 		{
! 			/* give hook a chance in case parameter is dynamic */
! 			if (!OidIsValid(prm->ptype) && paramLI->paramFetch != NULL)
! 				paramLI->paramFetch(paramLI, i + 1);
! 			typeOid = prm->ptype;
! 		}
  
  		sz = add_size(sz, sizeof(Oid)); /* space for type OID */
  		sz = add_size(sz, sizeof(uint16));	/* space for pflags */
--- 95,113 ----
  
  	for (i = 0; i < paramLI->numParams; i++)
  	{
! 		ParamExternData *prm;
! 		ParamExternData prmdata;
  		Oid			typeOid;
  		int16		typLen;
  		bool		typByVal;
  
! 		/* give hook a chance in case parameter is dynamic */
! 		if (paramLI->paramFetch != NULL)
! 			prm = paramLI->paramFetch(paramLI, i + 1, false, &prmdata);
  		else
! 			prm = &paramLI->params[i];
! 
! 		typeOid = prm->ptype;
  
  		sz = add_size(sz, sizeof(Oid)); /* space for type OID */
  		sz = add_size(sz, sizeof(uint16));	/* space for pflags */
*************** SerializeParamList(ParamListInfo paramLI
*** 171,192 ****
  	/* Write each parameter in turn. */
  	for (i = 0; i < nparams; i++)
  	{
! 		ParamExternData *prm = &paramLI->params[i];
  		Oid			typeOid;
  		int16		typLen;
  		bool		typByVal;
  
! 		/* Ignore parameters we don't need, to save cycles and space. */
! 		if (paramLI->paramMask != NULL &&
! 			!bms_is_member(i, paramLI->paramMask))
! 			typeOid = InvalidOid;
  		else
! 		{
! 			/* give hook a chance in case parameter is dynamic */
! 			if (!OidIsValid(prm->ptype) && paramLI->paramFetch != NULL)
! 				paramLI->paramFetch(paramLI, i + 1);
! 			typeOid = prm->ptype;
! 		}
  
  		/* Write type OID. */
  		memcpy(*start_address, &typeOid, sizeof(Oid));
--- 161,179 ----
  	/* Write each parameter in turn. */
  	for (i = 0; i < nparams; i++)
  	{
! 		ParamExternData *prm;
! 		ParamExternData prmdata;
  		Oid			typeOid;
  		int16		typLen;
  		bool		typByVal;
  
! 		/* give hook a chance in case parameter is dynamic */
! 		if (paramLI->paramFetch != NULL)
! 			prm = paramLI->paramFetch(paramLI, i + 1, false, &prmdata);
  		else
! 			prm = &paramLI->params[i];
! 
! 		typeOid = prm->ptype;
  
  		/* Write type OID. */
  		memcpy(*start_address, &typeOid, sizeof(Oid));
*************** RestoreParamList(char **start_address)
*** 237,246 ****
  	paramLI = (ParamListInfo) palloc(size);
  	paramLI->paramFetch = NULL;
  	paramLI->paramFetchArg = NULL;
  	paramLI->parserSetup = NULL;
  	paramLI->parserSetupArg = NULL;
  	paramLI->numParams = nparams;
- 	paramLI->paramMask = NULL;
  
  	for (i = 0; i < nparams; i++)
  	{
--- 224,234 ----
  	paramLI = (ParamListInfo) palloc(size);
  	paramLI->paramFetch = NULL;
  	paramLI->paramFetchArg = NULL;
+ 	paramLI->paramCompile = NULL;
+ 	paramLI->paramCompileArg = NULL;
  	paramLI->parserSetup = NULL;
  	paramLI->parserSetupArg = NULL;
  	paramLI->numParams = nparams;
  
  	for (i = 0; i < nparams; i++)
  	{
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 6a2d5ad..9ca384d 100644
*** a/src/backend/optimizer/util/clauses.c
--- b/src/backend/optimizer/util/clauses.c
*************** eval_const_expressions_mutator(Node *nod
*** 2513,2526 ****
  		case T_Param:
  			{
  				Param	   *param = (Param *) node;
  
  				/* Look to see if we've been given a value for this Param */
  				if (param->paramkind == PARAM_EXTERN &&
! 					context->boundParams != NULL &&
  					param->paramid > 0 &&
! 					param->paramid <= context->boundParams->numParams)
  				{
! 					ParamExternData *prm = &context->boundParams->params[param->paramid - 1];
  
  					if (OidIsValid(prm->ptype))
  					{
--- 2513,2539 ----
  		case T_Param:
  			{
  				Param	   *param = (Param *) node;
+ 				ParamListInfo paramLI = context->boundParams;
  
  				/* Look to see if we've been given a value for this Param */
  				if (param->paramkind == PARAM_EXTERN &&
! 					paramLI != NULL &&
  					param->paramid > 0 &&
! 					param->paramid <= paramLI->numParams)
  				{
! 					ParamExternData *prm;
! 					ParamExternData prmdata;
! 
! 					/*
! 					 * Give hook a chance in case parameter is dynamic.  Tell
! 					 * it that this fetch is speculative, so it should avoid
! 					 * erroring out if parameter is unavailable.
! 					 */
! 					if (paramLI->paramFetch != NULL)
! 						prm = paramLI->paramFetch(paramLI, param->paramid,
! 												  true, &prmdata);
! 					else
! 						prm = &paramLI->params[param->paramid - 1];
  
  					if (OidIsValid(prm->ptype))
  					{
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 1ae9ac2..1b24ddd 100644
*** a/src/backend/tcop/postgres.c
--- b/src/backend/tcop/postgres.c
*************** exec_bind_message(StringInfo input_messa
*** 1646,1655 ****
  		/* we have static list of params, so no hooks needed */
  		params->paramFetch = NULL;
  		params->paramFetchArg = NULL;
  		params->parserSetup = NULL;
  		params->parserSetupArg = NULL;
  		params->numParams = numParams;
- 		params->paramMask = NULL;
  
  		for (paramno = 0; paramno < numParams; paramno++)
  		{
--- 1646,1656 ----
  		/* we have static list of params, so no hooks needed */
  		params->paramFetch = NULL;
  		params->paramFetchArg = NULL;
+ 		params->paramCompile = NULL;
+ 		params->paramCompileArg = NULL;
  		params->parserSetup = NULL;
  		params->parserSetupArg = NULL;
  		params->numParams = numParams;
  
  		for (paramno = 0; paramno < numParams; paramno++)
  		{
*************** errdetail_params(ParamListInfo params)
*** 2211,2216 ****
--- 2212,2220 ----
  		MemoryContext oldcontext;
  		int			paramno;
  
+ 		/* This code doesn't support dynamic param lists */
+ 		Assert(params->paramFetch == NULL);
+ 
  		/* Make sure any trash is generated in MessageContext */
  		oldcontext = MemoryContextSwitchTo(MessageContext);
  
diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h
index 5bbb63a..080252f 100644
*** a/src/include/executor/execExpr.h
--- b/src/include/executor/execExpr.h
***************
*** 16,22 ****
  
  #include "nodes/execnodes.h"
  
! /* forward reference to avoid circularity */
  struct ArrayRefState;
  
  /* Bits in ExprState->flags (see also execnodes.h for public flag bits): */
--- 16,23 ----
  
  #include "nodes/execnodes.h"
  
! /* forward references to avoid circularity */
! struct ExprEvalStep;
  struct ArrayRefState;
  
  /* Bits in ExprState->flags (see also execnodes.h for public flag bits): */
*************** struct ArrayRefState;
*** 25,30 ****
--- 26,36 ----
  /* jump-threading is in use */
  #define EEO_FLAG_DIRECT_THREADED			(1 << 2)
  
+ /* Typical API for out-of-line evaluation subroutines */
+ typedef void (*ExecEvalSubroutine) (ExprState *state,
+ 									struct ExprEvalStep *op,
+ 									ExprContext *econtext);
+ 
  /*
   * Discriminator for ExprEvalSteps.
   *
*************** typedef enum ExprEvalOp
*** 131,136 ****
--- 137,143 ----
  	/* evaluate PARAM_EXEC/EXTERN parameters */
  	EEOP_PARAM_EXEC,
  	EEOP_PARAM_EXTERN,
+ 	EEOP_PARAM_CALLBACK,
  
  	/* return CaseTestExpr value */
  	EEOP_CASE_TESTVAL,
*************** typedef struct ExprEvalStep
*** 331,336 ****
--- 338,352 ----
  			Oid			paramtype;	/* OID of parameter's datatype */
  		}			param;
  
+ 		/* for EEOP_PARAM_CALLBACK */
+ 		struct
+ 		{
+ 			ExecEvalSubroutine paramfunc;	/* add-on evaluation subroutine */
+ 			void	   *paramarg;	/* private data for same */
+ 			int			paramid;	/* numeric ID for parameter */
+ 			Oid			paramtype;	/* OID of parameter's datatype */
+ 		}			cparam;
+ 
  		/* for EEOP_CASE_TESTVAL/DOMAIN_TESTVAL */
  		struct
  		{
*************** typedef struct ArrayRefState
*** 598,605 ****
  } ArrayRefState;
  
  
! extern void ExecReadyInterpretedExpr(ExprState *state);
  
  extern ExprEvalOp ExecEvalStepOp(ExprState *state, ExprEvalStep *op);
  
  /*
--- 614,624 ----
  } ArrayRefState;
  
  
! /* functions in execExpr.c */
! extern void ExprEvalPushStep(ExprState *es, const ExprEvalStep *s);
  
+ /* functions in execExprInterp.c */
+ extern void ExecReadyInterpretedExpr(ExprState *state);
  extern ExprEvalOp ExecEvalStepOp(ExprState *state, ExprEvalStep *op);
  
  /*
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index dea9216..2cc74da 100644
*** a/src/include/executor/executor.h
--- b/src/include/executor/executor.h
*************** ExecProcNode(PlanState *node)
*** 247,252 ****
--- 247,253 ----
   * prototypes from functions in execExpr.c
   */
  extern ExprState *ExecInitExpr(Expr *node, PlanState *parent);
+ extern ExprState *ExecInitExprWithParams(Expr *node, ParamListInfo ext_params);
  extern ExprState *ExecInitQual(List *qual, PlanState *parent);
  extern ExprState *ExecInitCheck(List *qual, PlanState *parent);
  extern List *ExecInitExprList(List *nodes, PlanState *parent);
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 1a35c5c..2b1750c 100644
*** a/src/include/nodes/execnodes.h
--- b/src/include/nodes/execnodes.h
***************
*** 32,37 ****
--- 32,43 ----
  #include "storage/condition_variable.h"
  
  
+ struct PlanState;				/* forward references in this file */
+ struct ExprState;
+ struct ExprContext;
+ struct ExprEvalStep;			/* avoid including execExpr.h everywhere */
+ 
+ 
  /* ----------------
   *		ExprState node
   *
***************
*** 39,48 ****
   * It contains instructions (in ->steps) to evaluate the expression.
   * ----------------
   */
- struct ExprState;				/* forward references in this file */
- struct ExprContext;
- struct ExprEvalStep;			/* avoid including execExpr.h everywhere */
- 
  typedef Datum (*ExprStateEvalFunc) (struct ExprState *expression,
  									struct ExprContext *econtext,
  									bool *isNull);
--- 45,50 ----
*************** typedef struct ExprState
*** 84,95 ****
  	Expr	   *expr;
  
  	/*
! 	 * XXX: following only needed during "compilation", could be thrown away.
  	 */
  
  	int			steps_len;		/* number of steps currently */
  	int			steps_alloc;	/* allocated length of steps array */
  
  	Datum	   *innermost_caseval;
  	bool	   *innermost_casenull;
  
--- 86,101 ----
  	Expr	   *expr;
  
  	/*
! 	 * XXX: following fields only needed during "compilation" (ExecInitExpr);
! 	 * could be thrown away afterwards.
  	 */
  
  	int			steps_len;		/* number of steps currently */
  	int			steps_alloc;	/* allocated length of steps array */
  
+ 	struct PlanState *parent;	/* parent PlanState node, if any */
+ 	ParamListInfo ext_params;	/* for compiling PARAM_EXTERN nodes */
+ 
  	Datum	   *innermost_caseval;
  	bool	   *innermost_casenull;
  
*************** typedef struct DomainConstraintState
*** 824,831 ****
   * ----------------------------------------------------------------
   */
  
- struct PlanState;
- 
  /* ----------------
   *	 ExecProcNodeMtd
   *
--- 830,835 ----
diff --git a/src/include/nodes/params.h b/src/include/nodes/params.h
index 55219da..b198db5 100644
*** a/src/include/nodes/params.h
--- b/src/include/nodes/params.h
***************
*** 16,31 ****
  
  /* Forward declarations, to avoid including other headers */
  struct Bitmapset;
  struct ParseState;
  
  
! /* ----------------
   *	  ParamListInfo
   *
!  *	  ParamListInfo arrays are used to pass parameters into the executor
!  *	  for parameterized plans.  Each entry in the array defines the value
!  *	  to be substituted for a PARAM_EXTERN parameter.  The "paramid"
!  *	  of a PARAM_EXTERN Param can range from 1 to numParams.
   *
   *	  Although parameter numbers are normally consecutive, we allow
   *	  ptype == InvalidOid to signal an unused array entry.
--- 16,38 ----
  
  /* Forward declarations, to avoid including other headers */
  struct Bitmapset;
+ struct ExprState;
+ struct Param;
  struct ParseState;
  
  
! /*
   *	  ParamListInfo
   *
!  *	  ParamListInfo structures are used to pass parameters into the executor
!  *	  for parameterized plans.  We support two basic approaches to supplying
!  *	  parameter values, the "static" way and the "dynamic" way.
!  *
!  *	  In the static approach, per-parameter data is stored in an array of
!  *	  ParamExternData structs appended to the ParamListInfo struct.
!  *	  Each entry in the array defines the value to be substituted for a
!  *	  PARAM_EXTERN parameter.  The "paramid" of a PARAM_EXTERN Param
!  *	  can range from 1 to numParams.
   *
   *	  Although parameter numbers are normally consecutive, we allow
   *	  ptype == InvalidOid to signal an unused array entry.
*************** struct ParseState;
*** 35,52 ****
   *	  as a constant (i.e., generate a plan that works only for this value
   *	  of the parameter).
   *
!  *	  There are two hook functions that can be associated with a ParamListInfo
!  *	  array to support dynamic parameter handling.  First, if paramFetch
!  *	  isn't null and the executor requires a value for an invalid parameter
!  *	  (one with ptype == InvalidOid), the paramFetch hook is called to give
!  *	  it a chance to fill in the parameter value.  Second, a parserSetup
!  *	  hook can be supplied to re-instantiate the original parsing hooks if
!  *	  a query needs to be re-parsed/planned (as a substitute for supposing
!  *	  that the current ptype values represent a fixed set of parameter types).
! 
   *	  Although the data structure is really an array, not a list, we keep
   *	  the old typedef name to avoid unnecessary code changes.
!  * ----------------
   */
  
  #define PARAM_FLAG_CONST	0x0001	/* parameter is constant */
--- 42,88 ----
   *	  as a constant (i.e., generate a plan that works only for this value
   *	  of the parameter).
   *
!  *	  In the dynamic approach, all access to parameter values is done through
!  *	  hook functions found in the ParamListInfo struct.  In this case,
!  *	  the ParamExternData array is typically unused and not allocated;
!  *	  but the legal range of paramid is still 1 to numParams.
!  *
   *	  Although the data structure is really an array, not a list, we keep
   *	  the old typedef name to avoid unnecessary code changes.
!  *
!  *	  There are 3 hook functions that can be associated with a ParamListInfo
!  *	  structure:
!  *
!  *	  If paramFetch isn't null, it is called to fetch the ParamExternData
!  *	  for a particular param ID, rather than accessing the relevant element
!  *	  of the ParamExternData array.  This supports the case where the array
!  *	  isn't there at all, as well as cases where the data in the array
!  *	  might be obsolete or lazily evaluated.  paramFetch must return the
!  *	  address of a ParamExternData struct describing the specified param ID;
!  *	  the convention above about ptype == InvalidOid signaling an invalid
!  *	  param ID still applies.  The returned struct can either be placed in
!  *	  the "workspace" supplied by the caller, or it can be in storage
!  *	  controlled by the paramFetch hook if that's more convenient.
!  *	  (In either case, the struct is not expected to be long-lived.)
!  *	  If "speculative" is true, the paramFetch hook should not risk errors
!  *	  in trying to fetch the parameter value, and should report an invalid
!  *	  parameter instead.
!  *
!  *	  If paramCompile isn't null, then it controls what execExpr.c compiles
!  *	  for PARAM_EXTERN Param nodes --- typically, this hook would emit a
!  *	  EEOP_PARAM_CALLBACK step.  This allows unnecessary work to be
!  *	  optimized away in compiled expressions.
!  *
!  *	  If parserSetup isn't null, then it is called to re-instantiate the
!  *	  original parsing hooks when a query needs to be re-parsed/planned.
!  *	  This is especially useful if the types of parameters might change
!  *	  from time to time, since it can replace the need to supply a fixed
!  *	  list of parameter types to the parser.
!  *
!  *	  Notice that the paramFetch and paramCompile hooks are actually passed
!  *	  the ParamListInfo struct's address; they can therefore access all
!  *	  three of the "arg" fields, and the distinction between paramFetchArg
!  *	  and paramCompileArg is rather arbitrary.
   */
  
  #define PARAM_FLAG_CONST	0x0001	/* parameter is constant */
*************** typedef struct ParamExternData
*** 61,67 ****
  
  typedef struct ParamListInfoData *ParamListInfo;
  
! typedef void (*ParamFetchHook) (ParamListInfo params, int paramid);
  
  typedef void (*ParserSetupHook) (struct ParseState *pstate, void *arg);
  
--- 97,109 ----
  
  typedef struct ParamListInfoData *ParamListInfo;
  
! typedef ParamExternData *(*ParamFetchHook) (ParamListInfo params,
! 											int paramid, bool speculative,
! 											ParamExternData *workspace);
! 
! typedef void (*ParamCompileHook) (ParamListInfo params, struct Param *param,
! 								  struct ExprState *state,
! 								  Datum *resv, bool *resnull);
  
  typedef void (*ParserSetupHook) (struct ParseState *pstate, void *arg);
  
*************** typedef struct ParamListInfoData
*** 69,78 ****
  {
  	ParamFetchHook paramFetch;	/* parameter fetch hook */
  	void	   *paramFetchArg;
  	ParserSetupHook parserSetup;	/* parser setup hook */
  	void	   *parserSetupArg;
! 	int			numParams;		/* number of ParamExternDatas following */
! 	struct Bitmapset *paramMask;	/* if non-NULL, can ignore omitted params */
  	ParamExternData params[FLEXIBLE_ARRAY_MEMBER];
  }			ParamListInfoData;
  
--- 111,126 ----
  {
  	ParamFetchHook paramFetch;	/* parameter fetch hook */
  	void	   *paramFetchArg;
+ 	ParamCompileHook paramCompile;	/* parameter compile hook */
+ 	void	   *paramCompileArg;
  	ParserSetupHook parserSetup;	/* parser setup hook */
  	void	   *parserSetupArg;
! 	int			numParams;		/* nominal/maximum # of Params represented */
! 
! 	/*
! 	 * params[] may be of length zero if paramFetch is supplied; otherwise it
! 	 * must be of length numParams.
! 	 */
  	ParamExternData params[FLEXIBLE_ARRAY_MEMBER];
  }			ParamListInfoData;
  
diff --git a/src/pl/plpgsql/src/pl_comp.c b/src/pl/plpgsql/src/pl_comp.c
index 2d7844b..e9eab17 100644
*** a/src/pl/plpgsql/src/pl_comp.c
--- b/src/pl/plpgsql/src/pl_comp.c
*************** plpgsql_adddatum(PLpgSQL_datum *new)
*** 2350,2384 ****
  
  /* ----------
   * plpgsql_finish_datums	Copy completed datum info into function struct.
-  *
-  * This is also responsible for building resettable_datums, a bitmapset
-  * of the dnos of all ROW, REC, and RECFIELD datums in the function.
   * ----------
   */
  static void
  plpgsql_finish_datums(PLpgSQL_function *function)
  {
- 	Bitmapset  *resettable_datums = NULL;
  	int			i;
  
  	function->ndatums = plpgsql_nDatums;
  	function->datums = palloc(sizeof(PLpgSQL_datum *) * plpgsql_nDatums);
  	for (i = 0; i < plpgsql_nDatums; i++)
- 	{
  		function->datums[i] = plpgsql_Datums[i];
- 		switch (function->datums[i]->dtype)
- 		{
- 			case PLPGSQL_DTYPE_ROW:
- 			case PLPGSQL_DTYPE_REC:
- 			case PLPGSQL_DTYPE_RECFIELD:
- 				resettable_datums = bms_add_member(resettable_datums, i);
- 				break;
- 
- 			default:
- 				break;
- 		}
- 	}
- 	function->resettable_datums = resettable_datums;
  }
  
  
--- 2350,2366 ----
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index fa4d573..b8ebd57 100644
*** a/src/pl/plpgsql/src/pl_exec.c
--- b/src/pl/plpgsql/src/pl_exec.c
***************
*** 22,27 ****
--- 22,28 ----
  #include "access/tupconvert.h"
  #include "catalog/pg_proc.h"
  #include "catalog/pg_type.h"
+ #include "executor/execExpr.h"
  #include "executor/spi.h"
  #include "funcapi.h"
  #include "miscadmin.h"
*************** static int exec_for_query(PLpgSQL_execst
*** 268,276 ****
  			   Portal portal, bool prefetch_ok);
  static ParamListInfo setup_param_list(PLpgSQL_execstate *estate,
  				 PLpgSQL_expr *expr);
! static ParamListInfo setup_unshared_param_list(PLpgSQL_execstate *estate,
! 						  PLpgSQL_expr *expr);
! static void plpgsql_param_fetch(ParamListInfo params, int paramid);
  static void exec_move_row(PLpgSQL_execstate *estate,
  			  PLpgSQL_variable *target,
  			  HeapTuple tup, TupleDesc tupdesc);
--- 269,286 ----
  			   Portal portal, bool prefetch_ok);
  static ParamListInfo setup_param_list(PLpgSQL_execstate *estate,
  				 PLpgSQL_expr *expr);
! static ParamExternData *plpgsql_param_fetch(ParamListInfo params,
! 					int paramid, bool speculative,
! 					ParamExternData *workspace);
! static void plpgsql_param_compile(ParamListInfo params, Param *param,
! 					  ExprState *state,
! 					  Datum *resv, bool *resnull);
! static void plpgsql_param_eval_var(ExprState *state, ExprEvalStep *op,
! 					   ExprContext *econtext);
! static void plpgsql_param_eval_var_ro(ExprState *state, ExprEvalStep *op,
! 						  ExprContext *econtext);
! static void plpgsql_param_eval_non_var(ExprState *state, ExprEvalStep *op,
! 						   ExprContext *econtext);
  static void exec_move_row(PLpgSQL_execstate *estate,
  			  PLpgSQL_variable *target,
  			  HeapTuple tup, TupleDesc tupdesc);
*************** exec_stmt_forc(PLpgSQL_execstate *estate
*** 2346,2354 ****
  		exec_prepare_plan(estate, query, curvar->cursor_options);
  
  	/*
! 	 * Set up short-lived ParamListInfo
  	 */
! 	paramLI = setup_unshared_param_list(estate, query);
  
  	/*
  	 * Open the cursor (the paramlist will get copied into the portal)
--- 2356,2364 ----
  		exec_prepare_plan(estate, query, curvar->cursor_options);
  
  	/*
! 	 * Set up ParamListInfo for this query
  	 */
! 	paramLI = setup_param_list(estate, query);
  
  	/*
  	 * Open the cursor (the paramlist will get copied into the portal)
*************** plpgsql_estate_setup(PLpgSQL_execstate *
*** 3440,3456 ****
  	estate->datums = palloc(sizeof(PLpgSQL_datum *) * estate->ndatums);
  	/* caller is expected to fill the datums array */
  
! 	/* initialize ParamListInfo with one entry per datum, all invalid */
  	estate->paramLI = (ParamListInfo)
! 		palloc0(offsetof(ParamListInfoData, params) +
! 				estate->ndatums * sizeof(ParamExternData));
  	estate->paramLI->paramFetch = plpgsql_param_fetch;
  	estate->paramLI->paramFetchArg = (void *) estate;
  	estate->paramLI->parserSetup = (ParserSetupHook) plpgsql_parser_setup;
  	estate->paramLI->parserSetupArg = NULL; /* filled during use */
  	estate->paramLI->numParams = estate->ndatums;
- 	estate->paramLI->paramMask = NULL;
- 	estate->params_dirty = false;
  
  	/* set up for use of appropriate simple-expression EState and cast hash */
  	if (simple_eval_estate)
--- 3450,3465 ----
  	estate->datums = palloc(sizeof(PLpgSQL_datum *) * estate->ndatums);
  	/* caller is expected to fill the datums array */
  
! 	/* initialize our ParamListInfo with appropriate hook functions */
  	estate->paramLI = (ParamListInfo)
! 		palloc(offsetof(ParamListInfoData, params));
  	estate->paramLI->paramFetch = plpgsql_param_fetch;
  	estate->paramLI->paramFetchArg = (void *) estate;
+ 	estate->paramLI->paramCompile = plpgsql_param_compile;
+ 	estate->paramLI->paramCompileArg = NULL;	/* not needed */
  	estate->paramLI->parserSetup = (ParserSetupHook) plpgsql_parser_setup;
  	estate->paramLI->parserSetupArg = NULL; /* filled during use */
  	estate->paramLI->numParams = estate->ndatums;
  
  	/* set up for use of appropriate simple-expression EState and cast hash */
  	if (simple_eval_estate)
*************** exec_stmt_open(PLpgSQL_execstate *estate
*** 4169,4180 ****
  	}
  
  	/*
! 	 * Set up short-lived ParamListInfo
  	 */
! 	paramLI = setup_unshared_param_list(estate, query);
  
  	/*
! 	 * Open the cursor
  	 */
  	portal = SPI_cursor_open_with_paramlist(curname, query->plan,
  											paramLI,
--- 4178,4189 ----
  	}
  
  	/*
! 	 * Set up ParamListInfo for this query
  	 */
! 	paramLI = setup_param_list(estate, query);
  
  	/*
! 	 * Open the cursor (the paramlist will get copied into the portal)
  	 */
  	portal = SPI_cursor_open_with_paramlist(curname, query->plan,
  											paramLI,
*************** exec_run_select(PLpgSQL_execstate *estat
*** 5268,5282 ****
  						  portalP == NULL ? CURSOR_OPT_PARALLEL_OK : 0);
  
  	/*
! 	 * If a portal was requested, put the query into the portal
  	 */
  	if (portalP != NULL)
  	{
- 		/*
- 		 * Set up short-lived ParamListInfo
- 		 */
- 		paramLI = setup_unshared_param_list(estate, expr);
- 
  		*portalP = SPI_cursor_open_with_paramlist(NULL, expr->plan,
  												  paramLI,
  												  estate->readonly_func);
--- 5277,5291 ----
  						  portalP == NULL ? CURSOR_OPT_PARALLEL_OK : 0);
  
  	/*
! 	 * Set up ParamListInfo to pass to executor
! 	 */
! 	paramLI = setup_param_list(estate, expr);
! 
! 	/*
! 	 * If a portal was requested, put the query and paramlist into the portal
  	 */
  	if (portalP != NULL)
  	{
  		*portalP = SPI_cursor_open_with_paramlist(NULL, expr->plan,
  												  paramLI,
  												  estate->readonly_func);
*************** exec_run_select(PLpgSQL_execstate *estat
*** 5288,5298 ****
  	}
  
  	/*
- 	 * Set up ParamListInfo to pass to executor
- 	 */
- 	paramLI = setup_param_list(estate, expr);
- 
- 	/*
  	 * Execute the query
  	 */
  	rc = SPI_execute_plan_with_paramlist(expr->plan, paramLI,
--- 5297,5302 ----
*************** exec_eval_simple_expr(PLpgSQL_execstate 
*** 5504,5510 ****
  	ExprContext *econtext = estate->eval_econtext;
  	LocalTransactionId curlxid = MyProc->lxid;
  	CachedPlan *cplan;
- 	ParamListInfo paramLI;
  	void	   *save_setup_arg;
  	MemoryContext oldcontext;
  
--- 5508,5513 ----
*************** exec_eval_simple_expr(PLpgSQL_execstate 
*** 5552,5557 ****
--- 5555,5568 ----
  	*rettypmod = expr->expr_simple_typmod;
  
  	/*
+ 	 * Set up ParamListInfo to pass to executor.  For safety, save and restore
+ 	 * estate->paramLI->parserSetupArg around our use of the param list.
+ 	 */
+ 	save_setup_arg = estate->paramLI->parserSetupArg;
+ 
+ 	econtext->ecxt_param_list_info = setup_param_list(estate, expr);
+ 
+ 	/*
  	 * Prepare the expression for execution, if it's not been done already in
  	 * the current transaction.  (This will be forced to happen if we called
  	 * exec_save_simple_expr above.)
*************** exec_eval_simple_expr(PLpgSQL_execstate 
*** 5559,5565 ****
  	if (expr->expr_simple_lxid != curlxid)
  	{
  		oldcontext = MemoryContextSwitchTo(estate->simple_eval_estate->es_query_cxt);
! 		expr->expr_simple_state = ExecInitExpr(expr->expr_simple_expr, NULL);
  		expr->expr_simple_in_use = false;
  		expr->expr_simple_lxid = curlxid;
  		MemoryContextSwitchTo(oldcontext);
--- 5570,5578 ----
  	if (expr->expr_simple_lxid != curlxid)
  	{
  		oldcontext = MemoryContextSwitchTo(estate->simple_eval_estate->es_query_cxt);
! 		expr->expr_simple_state =
! 			ExecInitExprWithParams(expr->expr_simple_expr,
! 								   econtext->ecxt_param_list_info);
  		expr->expr_simple_in_use = false;
  		expr->expr_simple_lxid = curlxid;
  		MemoryContextSwitchTo(oldcontext);
*************** exec_eval_simple_expr(PLpgSQL_execstate 
*** 5579,5599 ****
  	}
  
  	/*
- 	 * Set up ParamListInfo to pass to executor.  We need an unshared list if
- 	 * it's going to include any R/W expanded-object pointer.  For safety,
- 	 * save and restore estate->paramLI->parserSetupArg around our use of the
- 	 * param list.
- 	 */
- 	save_setup_arg = estate->paramLI->parserSetupArg;
- 
- 	if (expr->rwparam >= 0)
- 		paramLI = setup_unshared_param_list(estate, expr);
- 	else
- 		paramLI = setup_param_list(estate, expr);
- 
- 	econtext->ecxt_param_list_info = paramLI;
- 
- 	/*
  	 * Mark expression as busy for the duration of the ExecEvalExpr call.
  	 */
  	expr->expr_simple_in_use = true;
--- 5592,5597 ----
*************** exec_eval_simple_expr(PLpgSQL_execstate 
*** 5632,5666 ****
  /*
   * Create a ParamListInfo to pass to SPI
   *
!  * We share a single ParamListInfo array across all SPI calls made from this
!  * estate, except calls creating cursors, which use setup_unshared_param_list
!  * (see its comments for reasons why), and calls that pass a R/W expanded
!  * object pointer.  A shared array is generally OK since any given slot in
!  * the array would need to contain the same current datum value no matter
!  * which query or expression we're evaluating; but of course that doesn't
!  * hold when a specific variable is being passed as a R/W pointer, because
!  * other expressions in the same function probably don't want to do that.
!  *
!  * Note that paramLI->parserSetupArg points to the specific PLpgSQL_expr
!  * being evaluated.  This is not an issue for statement-level callers, but
!  * lower-level callers must save and restore estate->paramLI->parserSetupArg
!  * just in case there's an active evaluation at an outer call level.
   *
!  * The general plan for passing parameters to SPI is that plain VAR datums
!  * always have valid images in the shared param list.  This is ensured by
!  * assign_simple_var(), which also marks those params as PARAM_FLAG_CONST,
!  * allowing the planner to use those values in custom plans.  However, non-VAR
!  * datums cannot conveniently be managed that way.  For one thing, they could
!  * throw errors (for example "no such record field") and we do not want that
!  * to happen in a part of the expression that might never be evaluated at
!  * runtime.  For another thing, exec_eval_datum() may return short-lived
!  * values stored in the estate's eval_mcontext, which will not necessarily
!  * survive to the next SPI operation.  And for a third thing, ROW
!  * and RECFIELD datums' values depend on other datums, and we don't have a
!  * cheap way to track that.  Therefore, param slots for non-VAR datum types
!  * are always reset here and then filled on-demand by plpgsql_param_fetch().
!  * We can save a few cycles by not bothering with the reset loop unless at
!  * least one such param has actually been filled by plpgsql_param_fetch().
   */
  static ParamListInfo
  setup_param_list(PLpgSQL_execstate *estate, PLpgSQL_expr *expr)
--- 5630,5646 ----
  /*
   * Create a ParamListInfo to pass to SPI
   *
!  * We use a single ParamListInfo struct for all SPI calls made from this
!  * estate; it contains no per-param data, just hook functions, so it's
!  * effectively read-only for SPI.
   *
!  * An exception from pure read-only-ness is that the parserSetupArg points
!  * to the specific PLpgSQL_expr being evaluated.  This is not an issue for
!  * statement-level callers, but lower-level callers must save and restore
!  * estate->paramLI->parserSetupArg just in case there's an active evaluation
!  * at an outer call level.  (A plausible alternative design would be to
!  * create a ParamListInfo struct for each PLpgSQL_expr, but for the moment
!  * that seems like a waste of memory.)
   */
  static ParamListInfo
  setup_param_list(PLpgSQL_execstate *estate, PLpgSQL_expr *expr)
*************** setup_param_list(PLpgSQL_execstate *esta
*** 5674,5684 ****
  	Assert(expr->plan != NULL);
  
  	/*
- 	 * Expressions with R/W parameters can't use the shared param list.
- 	 */
- 	Assert(expr->rwparam == -1);
- 
- 	/*
  	 * We only need a ParamListInfo if the expression has parameters.  In
  	 * principle we should test with bms_is_empty(), but we use a not-null
  	 * test because it's faster.  In current usage bits are never removed from
--- 5654,5659 ----
*************** setup_param_list(PLpgSQL_execstate *esta
*** 5690,5714 ****
  		paramLI = estate->paramLI;
  
  		/*
- 		 * If any resettable parameters have been passed to the executor since
- 		 * last time, we need to reset those param slots to "invalid", for the
- 		 * reasons mentioned in the comment above.
- 		 */
- 		if (estate->params_dirty)
- 		{
- 			Bitmapset  *resettable_datums = estate->func->resettable_datums;
- 			int			dno = -1;
- 
- 			while ((dno = bms_next_member(resettable_datums, dno)) >= 0)
- 			{
- 				ParamExternData *prm = &paramLI->params[dno];
- 
- 				prm->ptype = InvalidOid;
- 			}
- 			estate->params_dirty = false;
- 		}
- 
- 		/*
  		 * Set up link to active expr where the hook functions can find it.
  		 * Callers must save and restore parserSetupArg if there is any chance
  		 * that they are interrupting an active use of parameters.
--- 5665,5670 ----
*************** setup_param_list(PLpgSQL_execstate *esta
*** 5716,5727 ****
  		paramLI->parserSetupArg = (void *) expr;
  
  		/*
- 		 * Allow parameters that aren't needed by this expression to be
- 		 * ignored.
- 		 */
- 		paramLI->paramMask = expr->paramnos;
- 
- 		/*
  		 * Also make sure this is set before parser hooks need it.  There is
  		 * no need to save and restore, since the value is always correct once
  		 * set.  (Should be set already, but let's be sure.)
--- 5672,5677 ----
*************** setup_param_list(PLpgSQL_execstate *esta
*** 5741,5855 ****
  }
  
  /*
!  * Create an unshared, short-lived ParamListInfo to pass to SPI
!  *
!  * When creating a cursor, we do not use the shared ParamListInfo array
!  * but create a short-lived one that will contain only params actually
!  * referenced by the query.  The reason for this is that copyParamList() will
!  * be used to copy the parameters into cursor-lifespan storage, and we don't
!  * want it to copy anything that's not used by the specific cursor; that
!  * could result in uselessly copying some large values.
!  *
!  * We also use this for expressions that are passing a R/W object pointer
!  * to some trusted function.  We don't want the R/W pointer to get into the
!  * shared param list, where it could get passed to some less-trusted function.
   *
!  * The result, if not NULL, is in the estate's eval_mcontext.
   *
!  * XXX. Could we use ParamListInfo's new paramMask to avoid creating unshared
!  * parameter lists?
!  */
! static ParamListInfo
! setup_unshared_param_list(PLpgSQL_execstate *estate, PLpgSQL_expr *expr)
! {
! 	ParamListInfo paramLI;
! 
! 	/*
! 	 * We must have created the SPIPlan already (hence, query text has been
! 	 * parsed/analyzed at least once); else we cannot rely on expr->paramnos.
! 	 */
! 	Assert(expr->plan != NULL);
! 
! 	/*
! 	 * We only need a ParamListInfo if the expression has parameters.  In
! 	 * principle we should test with bms_is_empty(), but we use a not-null
! 	 * test because it's faster.  In current usage bits are never removed from
! 	 * expr->paramnos, only added, so this test is correct anyway.
! 	 */
! 	if (expr->paramnos)
! 	{
! 		int			dno;
! 
! 		/* initialize ParamListInfo with one entry per datum, all invalid */
! 		paramLI = (ParamListInfo)
! 			eval_mcontext_alloc0(estate,
! 								 offsetof(ParamListInfoData, params) +
! 								 estate->ndatums * sizeof(ParamExternData));
! 		paramLI->paramFetch = plpgsql_param_fetch;
! 		paramLI->paramFetchArg = (void *) estate;
! 		paramLI->parserSetup = (ParserSetupHook) plpgsql_parser_setup;
! 		paramLI->parserSetupArg = (void *) expr;
! 		paramLI->numParams = estate->ndatums;
! 		paramLI->paramMask = NULL;
! 
! 		/*
! 		 * Instantiate values for "safe" parameters of the expression.  We
! 		 * could skip this and leave them to be filled by plpgsql_param_fetch;
! 		 * but then the values would not be available for query planning,
! 		 * since the planner doesn't call the paramFetch hook.
! 		 */
! 		dno = -1;
! 		while ((dno = bms_next_member(expr->paramnos, dno)) >= 0)
! 		{
! 			PLpgSQL_datum *datum = estate->datums[dno];
! 
! 			if (datum->dtype == PLPGSQL_DTYPE_VAR)
! 			{
! 				PLpgSQL_var *var = (PLpgSQL_var *) datum;
! 				ParamExternData *prm = &paramLI->params[dno];
! 
! 				if (dno == expr->rwparam)
! 					prm->value = var->value;
! 				else
! 					prm->value = MakeExpandedObjectReadOnly(var->value,
! 															var->isnull,
! 															var->datatype->typlen);
! 				prm->isnull = var->isnull;
! 				prm->pflags = PARAM_FLAG_CONST;
! 				prm->ptype = var->datatype->typoid;
! 			}
! 		}
! 
! 		/*
! 		 * Also make sure this is set before parser hooks need it.  There is
! 		 * no need to save and restore, since the value is always correct once
! 		 * set.  (Should be set already, but let's be sure.)
! 		 */
! 		expr->func = estate->func;
! 	}
! 	else
! 	{
! 		/*
! 		 * Expression requires no parameters.  Be sure we represent this case
! 		 * as a NULL ParamListInfo, so that plancache.c knows there is no
! 		 * point in a custom plan.
! 		 */
! 		paramLI = NULL;
! 	}
! 	return paramLI;
! }
! 
! /*
!  * plpgsql_param_fetch		paramFetch callback for dynamic parameter fetch
   */
! static void
! plpgsql_param_fetch(ParamListInfo params, int paramid)
  {
  	int			dno;
  	PLpgSQL_execstate *estate;
  	PLpgSQL_expr *expr;
  	PLpgSQL_datum *datum;
! 	ParamExternData *prm;
  	int32		prmtypmod;
  
  	/* paramid's are 1-based, but dnos are 0-based */
--- 5691,5715 ----
  }
  
  /*
!  * plpgsql_param_fetch		paramFetch callback for dynamic parameter fetch
   *
!  * We always use the caller's workspace to construct the returned struct.
   *
!  * Note: this is no longer used during query execution.  It is used during
!  * planning (with speculative == true) and when the ParamListInfo we supply
!  * to the executor is copied into a cursor portal or transferred to a
!  * parallel child process.
   */
! static ParamExternData *
! plpgsql_param_fetch(ParamListInfo params,
! 					int paramid, bool speculative,
! 					ParamExternData *prm)
  {
  	int			dno;
  	PLpgSQL_execstate *estate;
  	PLpgSQL_expr *expr;
  	PLpgSQL_datum *datum;
! 	bool		ok = true;
  	int32		prmtypmod;
  
  	/* paramid's are 1-based, but dnos are 0-based */
*************** plpgsql_param_fetch(ParamListInfo params
*** 5866,5900 ****
  
  	/*
  	 * Since copyParamList() or SerializeParamList() will try to materialize
! 	 * every single parameter slot, it's important to do nothing when asked
! 	 * for a datum that's not supposed to be used by this SQL expression.
! 	 * Otherwise we risk failures in exec_eval_datum(), or copying a lot more
! 	 * data than necessary.
  	 */
  	if (!bms_is_member(dno, expr->paramnos))
! 		return;
  
! 	if (params == estate->paramLI)
  	{
- 		/*
- 		 * We need to mark the shared params array dirty if we're about to
- 		 * evaluate a resettable datum.
- 		 */
  		switch (datum->dtype)
  		{
  			case PLPGSQL_DTYPE_ROW:
  			case PLPGSQL_DTYPE_REC:
  			case PLPGSQL_DTYPE_RECFIELD:
! 				estate->params_dirty = true;
! 				break;
  
  			default:
  				break;
  		}
  	}
  
! 	/* OK, evaluate the value and store into the appropriate paramlist slot */
! 	prm = &params->params[dno];
  	exec_eval_datum(estate, datum,
  					&prm->ptype, &prmtypmod,
  					&prm->value, &prm->isnull);
--- 5726,5799 ----
  
  	/*
  	 * Since copyParamList() or SerializeParamList() will try to materialize
! 	 * every single parameter slot, it's important to return a dummy param
! 	 * when asked for a datum that's not supposed to be used by this SQL
! 	 * expression.  Otherwise we risk failures in exec_eval_datum(), or
! 	 * copying a lot more data than necessary.
  	 */
  	if (!bms_is_member(dno, expr->paramnos))
! 		ok = false;
  
! 	/*
! 	 * If the access is speculative, we prefer to return no data rather than
! 	 * to fail in exec_eval_datum().  Check the likely failure cases.
! 	 */
! 	else if (speculative)
  	{
  		switch (datum->dtype)
  		{
+ 			case PLPGSQL_DTYPE_VAR:
+ 				/* always safe */
+ 				break;
+ 
  			case PLPGSQL_DTYPE_ROW:
+ 				/* should be safe in all interesting cases */
+ 				break;
+ 
  			case PLPGSQL_DTYPE_REC:
+ 				{
+ 					PLpgSQL_rec *rec = (PLpgSQL_rec *) datum;
+ 
+ 					if (!HeapTupleIsValid(rec->tup))
+ 						ok = false;
+ 					break;
+ 				}
+ 
  			case PLPGSQL_DTYPE_RECFIELD:
! 				{
! 					PLpgSQL_recfield *recfield = (PLpgSQL_recfield *) datum;
! 					PLpgSQL_rec *rec;
! 					int			fno;
! 
! 					rec = (PLpgSQL_rec *) (estate->datums[recfield->recparentno]);
! 					if (!HeapTupleIsValid(rec->tup))
! 						ok = false;
! 					else
! 					{
! 						fno = SPI_fnumber(rec->tupdesc, recfield->fieldname);
! 						if (fno == SPI_ERROR_NOATTRIBUTE)
! 							ok = false;
! 					}
! 					break;
! 				}
  
  			default:
+ 				ok = false;
  				break;
  		}
  	}
  
! 	/* Return "no such parameter" if not ok */
! 	if (!ok)
! 	{
! 		prm->value = (Datum) 0;
! 		prm->isnull = true;
! 		prm->pflags = 0;
! 		prm->ptype = InvalidOid;
! 		return prm;
! 	}
! 
! 	/* OK, evaluate the value and store into the return struct */
  	exec_eval_datum(estate, datum,
  					&prm->ptype, &prmtypmod,
  					&prm->value, &prm->isnull);
*************** plpgsql_param_fetch(ParamListInfo params
*** 5909,5914 ****
--- 5808,5981 ----
  		prm->value = MakeExpandedObjectReadOnly(prm->value,
  												prm->isnull,
  												((PLpgSQL_var *) datum)->datatype->typlen);
+ 
+ 	return prm;
+ }
+ 
+ /*
+  * plpgsql_param_compile		paramCompile callback for plpgsql parameters
+  */
+ static void
+ plpgsql_param_compile(ParamListInfo params, Param *param,
+ 					  ExprState *state,
+ 					  Datum *resv, bool *resnull)
+ {
+ 	PLpgSQL_execstate *estate;
+ 	PLpgSQL_expr *expr;
+ 	int			dno;
+ 	PLpgSQL_datum *datum;
+ 	ExprEvalStep scratch;
+ 
+ 	/* fetch back the hook data */
+ 	estate = (PLpgSQL_execstate *) params->paramFetchArg;
+ 	expr = (PLpgSQL_expr *) params->parserSetupArg;
+ 
+ 	/* paramid's are 1-based, but dnos are 0-based */
+ 	dno = param->paramid - 1;
+ 	Assert(dno >= 0 && dno < estate->ndatums);
+ 
+ 	/* now we can access the target datum */
+ 	datum = estate->datums[dno];
+ 
+ 	scratch.opcode = EEOP_PARAM_CALLBACK;
+ 	scratch.resvalue = resv;
+ 	scratch.resnull = resnull;
+ 
+ 	/* Select appropriate eval function */
+ 	if (datum->dtype == PLPGSQL_DTYPE_VAR)
+ 	{
+ 		if (dno != expr->rwparam &&
+ 			((PLpgSQL_var *) datum)->datatype->typlen == -1)
+ 			scratch.d.cparam.paramfunc = plpgsql_param_eval_var_ro;
+ 		else
+ 			scratch.d.cparam.paramfunc = plpgsql_param_eval_var;
+ 	}
+ 	else
+ 		scratch.d.cparam.paramfunc = plpgsql_param_eval_non_var;
+ 
+ 	/*
+ 	 * Note: it's tempting to use paramarg to store the estate pointer and
+ 	 * thereby save an indirection or two in the eval functions.  But that
+ 	 * doesn't work because the compiled expression might be used with
+ 	 * different estates for the same PL/pgSQL function.
+ 	 */
+ 	scratch.d.cparam.paramarg = NULL;
+ 	scratch.d.cparam.paramid = param->paramid;
+ 	scratch.d.cparam.paramtype = param->paramtype;
+ 	ExprEvalPushStep(state, &scratch);
+ }
+ 
+ /*
+  * plpgsql_param_eval_var		evaluation of EEOP_PARAM_CALLBACK step
+  *
+  * This is specialized to the case of DTYPE_VAR variables for which
+  * we do not need to invoke MakeExpandedObjectReadOnly.
+  */
+ static void
+ plpgsql_param_eval_var(ExprState *state, ExprEvalStep *op,
+ 					   ExprContext *econtext)
+ {
+ 	ParamListInfo params;
+ 	PLpgSQL_execstate *estate;
+ 	int			dno = op->d.cparam.paramid - 1;
+ 	PLpgSQL_var *var;
+ 
+ 	/* fetch back the hook data */
+ 	params = econtext->ecxt_param_list_info;
+ 	estate = (PLpgSQL_execstate *) params->paramFetchArg;
+ 	Assert(dno >= 0 && dno < estate->ndatums);
+ 
+ 	/* now we can access the target datum */
+ 	var = (PLpgSQL_var *) estate->datums[dno];
+ 	Assert(var->dtype == PLPGSQL_DTYPE_VAR);
+ 
+ 	/* inlined version of exec_eval_datum() */
+ 	*op->resvalue = var->value;
+ 	*op->resnull = var->isnull;
+ 
+ 	/* safety check -- an assertion should be sufficient */
+ 	Assert(var->datatype->typoid == op->d.cparam.paramtype);
+ }
+ 
+ /*
+  * plpgsql_param_eval_var_ro		evaluation of EEOP_PARAM_CALLBACK step
+  *
+  * This is specialized to the case of DTYPE_VAR variables for which
+  * we need to invoke MakeExpandedObjectReadOnly.
+  */
+ static void
+ plpgsql_param_eval_var_ro(ExprState *state, ExprEvalStep *op,
+ 						  ExprContext *econtext)
+ {
+ 	ParamListInfo params;
+ 	PLpgSQL_execstate *estate;
+ 	int			dno = op->d.cparam.paramid - 1;
+ 	PLpgSQL_var *var;
+ 
+ 	/* fetch back the hook data */
+ 	params = econtext->ecxt_param_list_info;
+ 	estate = (PLpgSQL_execstate *) params->paramFetchArg;
+ 	Assert(dno >= 0 && dno < estate->ndatums);
+ 
+ 	/* now we can access the target datum */
+ 	var = (PLpgSQL_var *) estate->datums[dno];
+ 	Assert(var->dtype == PLPGSQL_DTYPE_VAR);
+ 
+ 	/*
+ 	 * Inlined version of exec_eval_datum() ... and while we're at it, force
+ 	 * expanded datums to read-only.
+ 	 */
+ 	*op->resvalue = MakeExpandedObjectReadOnly(var->value,
+ 											   var->isnull,
+ 											   -1);
+ 	*op->resnull = var->isnull;
+ 
+ 	/* safety check -- an assertion should be sufficient */
+ 	Assert(var->datatype->typoid == op->d.cparam.paramtype);
+ }
+ 
+ /*
+  * plpgsql_param_eval_non_var		evaluation of EEOP_PARAM_CALLBACK step
+  *
+  * This handles all variable types except DTYPE_VAR.
+  */
+ static void
+ plpgsql_param_eval_non_var(ExprState *state, ExprEvalStep *op,
+ 						   ExprContext *econtext)
+ {
+ 	ParamListInfo params;
+ 	PLpgSQL_execstate *estate;
+ 	int			dno = op->d.cparam.paramid - 1;
+ 	PLpgSQL_datum *datum;
+ 	Oid			datumtype;
+ 	int32		datumtypmod;
+ 
+ 	/* fetch back the hook data */
+ 	params = econtext->ecxt_param_list_info;
+ 	estate = (PLpgSQL_execstate *) params->paramFetchArg;
+ 	Assert(dno >= 0 && dno < estate->ndatums);
+ 
+ 	/* now we can access the target datum */
+ 	datum = estate->datums[dno];
+ 	Assert(datum->dtype != PLPGSQL_DTYPE_VAR);
+ 
+ 	exec_eval_datum(estate, datum,
+ 					&datumtype, &datumtypmod,
+ 					op->resvalue, op->resnull);
+ 
+ 	/* safety check -- needed for, eg, record fields */
+ 	if (unlikely(datumtype != op->d.cparam.paramtype))
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_DATATYPE_MISMATCH),
+ 				 errmsg("type of parameter %d (%s) does not match that when preparing the plan (%s)",
+ 						op->d.cparam.paramid,
+ 						format_type_be(datumtype),
+ 						format_type_be(op->d.cparam.paramtype))));
+ 
+ 	/*
+ 	 * Currently, if the dtype isn't VAR, the value couldn't be a read/write
+ 	 * expanded datum.
+ 	 */
  }
  
  
*************** plpgsql_subxact_cb(SubXactEvent event, S
*** 6875,6888 ****
   * assign_simple_var --- assign a new value to any VAR datum.
   *
   * This should be the only mechanism for assignment to simple variables,
!  * lest we forget to update the paramLI image.
   */
  static void
  assign_simple_var(PLpgSQL_execstate *estate, PLpgSQL_var *var,
  				  Datum newvalue, bool isnull, bool freeable)
  {
- 	ParamExternData *prm;
- 
  	Assert(var->dtype == PLPGSQL_DTYPE_VAR);
  	/* Free the old value if needed */
  	if (var->freeval)
--- 6942,6953 ----
   * assign_simple_var --- assign a new value to any VAR datum.
   *
   * This should be the only mechanism for assignment to simple variables,
!  * lest we do the release of the old value incorrectly.
   */
  static void
  assign_simple_var(PLpgSQL_execstate *estate, PLpgSQL_var *var,
  				  Datum newvalue, bool isnull, bool freeable)
  {
  	Assert(var->dtype == PLPGSQL_DTYPE_VAR);
  	/* Free the old value if needed */
  	if (var->freeval)
*************** assign_simple_var(PLpgSQL_execstate *est
*** 6898,6912 ****
  	var->value = newvalue;
  	var->isnull = isnull;
  	var->freeval = freeable;
- 	/* And update the image in the common parameter list */
- 	prm = &estate->paramLI->params[var->dno];
- 	prm->value = MakeExpandedObjectReadOnly(newvalue,
- 											isnull,
- 											var->datatype->typlen);
- 	prm->isnull = isnull;
- 	/* these might be set already, but let's be sure */
- 	prm->pflags = PARAM_FLAG_CONST;
- 	prm->ptype = var->datatype->typoid;
  }
  
  /*
--- 6963,6968 ----
diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h
index 39bd82a..43d7d7d 100644
*** a/src/pl/plpgsql/src/plpgsql.h
--- b/src/pl/plpgsql/src/plpgsql.h
*************** typedef struct PLpgSQL_function
*** 857,863 ****
  	/* the datums representing the function's local variables */
  	int			ndatums;
  	PLpgSQL_datum **datums;
- 	Bitmapset  *resettable_datums;	/* dnos of non-simple vars */
  
  	/* function body parsetree */
  	PLpgSQL_stmt_block *action;
--- 857,862 ----
*************** typedef struct PLpgSQL_execstate
*** 899,907 ****
  	int			ndatums;
  	PLpgSQL_datum **datums;
  
! 	/* we pass datums[i] to the executor, when needed, in paramLI->params[i] */
  	ParamListInfo paramLI;
- 	bool		params_dirty;	/* T if any resettable datum has been passed */
  
  	/* EState to use for "simple" expression evaluation */
  	EState	   *simple_eval_estate;
--- 898,910 ----
  	int			ndatums;
  	PLpgSQL_datum **datums;
  
! 	/*
! 	 * paramLI is what we use to pass local variable values to the executor.
! 	 * It does not have a ParamExternData array; we just dynamically
! 	 * instantiate parameter data as needed.  By convention, PARAM_EXTERN
! 	 * Params have paramid equal to the dno of the referenced local variable.
! 	 */
  	ParamListInfo paramLI;
  
  	/* EState to use for "simple" expression evaluation */
  	EState	   *simple_eval_estate;
