Re: Letting plpgsql in on the fun with the new expression eval stuff

Поиск
Список
Период
Сортировка
От Tom Lane
Тема Re: Letting plpgsql in on the fun with the new expression eval stuff
Дата
Msg-id 27734.1513802658@sss.pgh.pa.us
обсуждение исходный текст
Ответ на Re: Letting plpgsql in on the fun with the new expression eval stuff  (Tom Lane <tgl@sss.pgh.pa.us>)
Ответы Re: Letting plpgsql in on the fun with the new expression eval stuff  (Tom Lane <tgl@sss.pgh.pa.us>)
Список pgsql-hackers
I wrote:
> Will send a patch in a bit.  I need to write an explanation of what all
> I changed.

OK then.  What the attached patch does is:

* Create a new step type EEOP_PARAM_CALLBACK (if anyone has a better
naming idea, I'm receptive) and add the infrastructure needed for
add-on modules to generate that.  As discussed, the best control
mechanism for that, at least for the immediate use, seems to be to
add another hook function to ParamListInfo, which will be called by
ExecInitExpr if it's supplied and a PARAM_EXTERN Param is found.
For stand-alone expressions, we add a new entry point to allow the
ParamListInfo to be specified directly rather than found in the
parent plan node's EState.

* In passing, remove the "parent" pointer from the arguments to
ExecInitExprRec and related functions, instead storing that pointer
in a transient field in ExprState.  There are now several transient
fields there.  We discussed splitting them out to a different struct,
but that seems like material for a different patch.  I had to do
something here, though, since otherwise I'd have had to pass down
the stand-alone ParamListInfo as another parameter :-(.

* Redesign the API for the ParamListInfo paramFetch hook so that the
ParamExternData array can be entirely virtual.  Typical access to
the info about a PARAM_EXTERN Param now looks like

        if (paramInfo->paramFetch != NULL)
            prm = paramInfo->paramFetch(paramInfo, paramId, ...);
        else
            prm = ¶mInfo->params[paramId - 1];

so that if paramFetch is defined, the core code no longer touches
the params[] array at all, and it doesn't have to be there.
(It still can be there, if paramFetch wants to use it; but the sole
existing user of the hook, plpgsql, doesn't want to.)

* This also lets us get rid of ParamListInfo.paramMask, instead
leaving it to the paramFetch hook to decide which param IDs should
be accessible or not.  plpgsql_param_fetch was already doing the
identical masking check, so having callers do it too seemed quite
redundant and overcomplex.

* While I was at it, I added a "speculative" flag to paramFetch
that the planner can specify as TRUE to avoid unwanted failures.
This solves an ancient problem for plpgsql that it couldn't provide
values of non-DTYPE_VAR variables to the planner for fear of
triggering premature "record not assigned yet" or "field not found"
errors during planning.

* Rework plpgsql to get rid of the need for "unshared" parameter lists,
by dint of turning the single ParamListInfo per estate into a nearly
read-only data structure that doesn't instantiate any per-variable data.
Instead, the paramFetch hook controls access to per-variable data and can
make the right decisions on the fly, replacing the cases that we used to
need multiple ParamListInfos for.  This might perhaps have been a
performance loss on its own, but by using a paramCompile hook we can
bypass plpgsql_param_fetch entirely during normal query execution.
(It's now only called when, eg, we copy the ParamListInfo into a cursor
portal.  copyParamList() or SerializeParamList() effectively instantiate
the virtual parameter array as a simple physical array without a
paramFetch hook, which is what we want in those cases.)  This allows
reverting most of commit 6c82d8d1f, though I kept the cosmetic
code-consolidation aspects of that (eg the assign_simple_var function).

* During compilation of a PARAM_EXTERN expression node, predetermine
as much as we can to select one of several execution routines.


I've done light performance testing on this, mainly comparing the
runtimes for the test functions shown in the second attachment.
I see overall gains that are modest but reproducible (in the range
of a couple percent) for the "row" (named composite type) cases,
and more significant -- around 10% -- for the "record" cases.
I attribute the difference to the fact that the "row" cases use DTYPE_VAR
variables for the fields, which were already pretty well optimized,
whereas the "record" cases use DTYPE_RECFIELD variables which invoked
all that overhead we discussed.  The fact that this isn't losing
on DTYPE_VAR convinces me that it should be a win in all cases.

It'd theoretically be possible to split this into three patches,
one to change the stuff around ExecInitExpr, one to rejigger the
ParamListInfo API, and one to get rid of unshared param lists in
plpgsql.  However, that would require writing some throwaway code
in plpgsql, so I don't especially want to bother.

I have another patch in the pipeline that interacts with this,
so I'd kind of like to get this committed sooner rather than later.

            regards, tom lane

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 = ¶mInfo->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 = ¶mInfo->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 = ¶mInfo->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 = ¶mInfo->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 = ¶mLI->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 = ¶mLI->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 = ¶mLI->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 = ¶mLI->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 = ¶mLI->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 = ¶mLI->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 = ¶mLI->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 = ¶ms->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;
drop table foo;
create table foo(a int, b int, c int);
insert into foo select 1,2,3 from generate_series(1,1000000);
vacuum foo;

set work_mem = '100MB';

\timing on

create or replace function ptest1_rec() returns void as $$
declare r record;
begin
  for r in select * from foo loop
    perform r.a + r.b + r.c;
  end loop;
end$$
language plpgsql stable;

select ptest1_rec();

create or replace function ptest1_row() returns void as $$
declare r foo;
begin
  for r in select * from foo loop
    perform r.a + r.b + r.c;
  end loop;
end$$
language plpgsql stable;

select ptest1_row();

create or replace function ptest2_rec() returns void as $$
declare r record;
begin
  for r in select * from foo loop
    r.a := r.b + r.c;
  end loop;
end$$
language plpgsql stable;

select ptest2_rec();

create or replace function ptest2_row() returns void as $$
declare r foo;
begin
  for r in select * from foo loop
    r.a := r.b + r.c;
  end loop;
end$$
language plpgsql stable;

select ptest2_row();

create or replace function ptest3_rec() returns void as $$
declare r record; t int;
begin
  for r in select * from foo loop
    t := r.b;
  end loop;
end$$
language plpgsql stable;

select ptest3_rec();

create or replace function ptest3_row() returns void as $$
declare r foo; t int;
begin
  for r in select * from foo loop
    t := r.b;
  end loop;
end$$
language plpgsql stable;

select ptest3_row();

create or replace function ptest4_rec() returns void as $$
declare r record; t int;
begin
  for r in select * from foo loop
    t := r.a + r.b + r.c;
  end loop;
end$$
language plpgsql stable;

select ptest4_rec();

create or replace function ptest4_row() returns void as $$
declare r foo; t int;
begin
  for r in select * from foo loop
    t := r.a + r.b + r.c;
  end loop;
end$$
language plpgsql stable;

select ptest4_row();

В списке pgsql-hackers по дате отправления:

Предыдущее
От: Pavel Stehule
Дата:
Сообщение: Re: Tracking of page changes for backup purposes. PTRACK [POC]
Следующее
От: Tomas Vondra
Дата:
Сообщение: Re: Tracking of page changes for backup purposes. PTRACK [POC]