Обсуждение: Letting plpgsql in on the fun with the new expression eval stuff

Поиск
Список
Период
Сортировка

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

От
Tom Lane
Дата:
I'm looking at ways to get plpgsql expression evaluation to go faster,
and one thing I'm noticing is the rather large overhead of going through
ExecEvalParamExtern and plpgsql_param_fetch to get to the useful work
(exec_eval_datum).  We've ameliorated that for DTYPE_VAR variables
by keeping a pre-set-up copy of their values in a ParamListInfo struct,
but that's pretty ugly and carries a bunch of costs of its own.

What I'm wondering about, given the v10 redesign of expression evaluation,
is whether we couldn't be smarter about this by allowing plpgsql (or other
external users) to skip the ParamListInfo representation altogether, and
instead compile Param references into calls to evaluation functions that
are better tailored to the problem of fetching the desired value.

In the existing execution infrastructure, what seems to make sense is to
have an ExprEvalStep type that has functionality like EEOP_PARAM_EXTERN,
but includes a function pointer to a plpgsql-supplied function having the
same signature as ExecEvalParamExtern.  So the execution would look more
or less like

        EEO_CASE(EEOP_PARAM_CALLBACK)
        {
            op->eval_param(state, op, econtext);
            EEO_NEXT();
        }

and there'd need to be some extra fields (at least a void*) in the op
struct where plpgsql could keep private data.

The JIT stuff you're working on could just compile an equivalent of the
above, although in the very long term maybe there would be some advantage
to letting add-on modules compile specialized code for such steps.

The immediate problem is how can ExecInitExpr generate such a step?
It can't itself know what to put into the function ptr or the additional
fields.  There has to be a way for it to call a plpgsql-supplied
support routine that can construct the eval step.  (And we have to
export ExprEvalPushStep, though that doesn't seem like a problem.)

For compiling full-fledged query trees, what I think we could do is
add a method (function pointer) to ParamListInfo and have ExecInitExpr
invoke plan->state->es_param_list_info->compile_param if that's set.
However, that solution doesn't immediately work for compiling simple
expressions because we pass a null "parent" pointer when building
those.

I thought about instantiating a dummy PlanState and EState to use
just for carrying this info, but that seems pretty ugly.  Another
way we could do it is to invent ExecInitExprWithParams() that takes
an additional ParamListInfo pointer, and use that.  Rather than
adding yet one more parameter that has to be passed down through
ExecInitExprRec, I suggest that we could waste a bit of space in
struct ExprState and store that value there.  Maybe do the same
with the parent pointer so as to reduce the number of recursive
parameters.

I've not written any code around this idea yet, and am not sure
if it conflicts with what you're trying to do for JIT or further out.
Comments, better ideas?

            regards, tom lane


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

От
Andres Freund
Дата:
Hi,

Cool to see you looking at that, I think there's quite some optimization
potential around.  I've to reread a bunch of plpgsql code, it's not
exactly an area of the code I'm intimately familiar with.


On 2017-12-19 13:00:41 -0500, Tom Lane wrote:
> I'm looking at ways to get plpgsql expression evaluation to go faster,
> and one thing I'm noticing is the rather large overhead of going through
> ExecEvalParamExtern and plpgsql_param_fetch to get to the useful work
> (exec_eval_datum).

What's the workload you're testing? I'm mildly surprised to see
ExecEvalParamExtern() show up, rather than just plpgsql_param_fetch() &
exec_eval_datum(). Or were you just listing that to specify the
callpath?


> We've ameliorated that for DTYPE_VAR variables
> by keeping a pre-set-up copy of their values in a ParamListInfo struct,
> but that's pretty ugly and carries a bunch of costs of its own.

Just to make sure I understand correctly, you're talking about
setup_unshared_param_list() / setup_param_list()?


> What I'm wondering about, given the v10 redesign of expression evaluation,
> is whether we couldn't be smarter about this by allowing plpgsql (or other
> external users) to skip the ParamListInfo representation altogether, and
> instead compile Param references into calls to evaluation functions that
> are better tailored to the problem of fetching the desired value.

Yea, that seems to make sense.


> In the existing execution infrastructure, what seems to make sense is to
> have an ExprEvalStep type that has functionality like EEOP_PARAM_EXTERN,
> but includes a function pointer to a plpgsql-supplied function having the
> same signature as ExecEvalParamExtern.  So the execution would look more
> or less like
> 
>         EEO_CASE(EEOP_PARAM_CALLBACK)
>         {
>             op->eval_param(state, op, econtext);
>             EEO_NEXT();
>         }
> 
> and there'd need to be some extra fields (at least a void*) in the op
> struct where plpgsql could keep private data.

I think I'd redo the parameters to the callback slightly, but generally
that sounds sane. Was thinking of something more like

One question I have is how we will re-initialize the relevant state
between exec_simple_expr() calls. I guess the most realistic one is that
the op will have a pointer into an array managed by exec_simple_expr()
that can get reset?


> The JIT stuff you're working on could just compile an equivalent of the
> above, although in the very long term maybe there would be some advantage
> to letting add-on modules compile specialized code for such steps.

Yea, that's reasonable too. I'm not yet 100% sure whether it'll not be
more reasonable to add a few more generic opcodes to the central JITing
that external code can emit and core can handle, or a hook like you
describe is better.


> The immediate problem is how can ExecInitExpr generate such a step?
> It can't itself know what to put into the function ptr or the additional
> fields.  There has to be a way for it to call a plpgsql-supplied
> support routine that can construct the eval step.  (And we have to
> export ExprEvalPushStep, though that doesn't seem like a problem.)

Hm. We could have a version of ExecInitExpr() / ExecInitExprRec that
first gives a callback chance to handle an operation. That'd make
overwriting parts like this quite easy, because we know we want to
handle Param nodes anywhere differently. So we'd not have a Param
specific routine, but the ability to intercept everything, and defer to
ExecInitExprRec() if undesired.


> For compiling full-fledged query trees, what I think we could do is
> add a method (function pointer) to ParamListInfo and have ExecInitExpr
> invoke plan->state->es_param_list_info->compile_param if that's set.
> However, that solution doesn't immediately work for compiling simple
> expressions because we pass a null "parent" pointer when building
> those.
> 
> I thought about instantiating a dummy PlanState and EState to use
> just for carrying this info, but that seems pretty ugly.

Yea, not a fan.


> Another way we could do it is to invent ExecInitExprWithParams() that
> takes an additional ParamListInfo pointer, and use that.  Rather than
> adding yet one more parameter that has to be passed down through
> ExecInitExprRec, I suggest that we could waste a bit of space in
> struct ExprState and store that value there.  Maybe do the same with
> the parent pointer so as to reduce the number of recursive parameters.

I was thinking something slightly different. Namely that we should have
two structs ExprState and ExprBuildState. We can stuff random additions
to ExprBuildState without concerns about increasing ExprState's state.


> I've not written any code around this idea yet, and am not sure
> if it conflicts with what you're trying to do for JIT or further out.
> Comments, better ideas?

I so far see little reason for concern WRT JIT. Implementing support for
a expression step like you describe above is a few minutes worth of
work. There might be some annoying rebasing if the patches conflict, but
even that ought to be manageable.

There *are* some longer term implications that could theoretically
become relevant, although I'm not sure problematic.  In a good number of
workloads the initialization of expression steps and executor trees is
the bottleneck.  The biggest culprit is tupledesc computations, but also
the expression building.  With the latter the problem obviously becomes
a lot "bigger" with JITing - we don't want to recompile functions all
the time.  The point where JIT becomes beneficial is a lot lower if
you've to do it only once per prepared statement, rather than once per
query execution...   So I'm basically saying that in my opinion more
information has to be built at plan time, long term.

Greetings,

Andres Freund


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

От
Tom Lane
Дата:
Andres Freund <andres@anarazel.de> writes:
> On 2017-12-19 13:00:41 -0500, Tom Lane wrote:
>> I'm looking at ways to get plpgsql expression evaluation to go faster,
>> and one thing I'm noticing is the rather large overhead of going through
>> ExecEvalParamExtern and plpgsql_param_fetch to get to the useful work
>> (exec_eval_datum).

> What's the workload you're testing? I'm mildly surprised to see
> ExecEvalParamExtern() show up, rather than just plpgsql_param_fetch() &
> exec_eval_datum(). Or were you just listing that to specify the
> callpath?

I'm using several different test cases, but one that shows up the problem
is

create table foo(a int, b int, c int);
insert into foo select 1,2,3 from generate_series(1,1000000);
vacuum foo;

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;

-- then trace this:
do $$
begin
for i in 1..200 loop
  perform ptest4_rec();
end loop;
end; $$ language plpgsql;

The outer do-block is just to get a run long enough for reliable perf
statistics.  I find these relevant entries in the report:

  Children      Self       Samples  Shared Object  Symbol
+   98.29%     4.44%         10827  postgres       [.] ExecInterpExpr
+   61.82%     3.23%          7878  plpgsql.so     [.] exec_eval_expr
+   34.64%     3.91%          9521  postgres       [.] ExecEvalParamExtern
+   29.78%     4.88%         11892  plpgsql.so     [.] plpgsql_param_fetch
+   23.06%     6.90%         16831  plpgsql.so     [.] exec_eval_datum
+    6.28%     2.89%          7049  plpgsql.so     [.] setup_param_list
+    4.02%     4.01%          9774  postgres       [.] bms_next_member

I think the ridiculous "children" percentage for ExecInterpExpr can be
discounted, because that's a result of essentially everything happening
inside the outer call of ptest4_rec.  But the rest of it can be taken
at face value, and it says that ExecEvalParamExtern and
plpgsql_param_fetch are each pretty expensive; and both of them are just
interface routines doing little useful work.

>> We've ameliorated that for DTYPE_VAR variables
>> by keeping a pre-set-up copy of their values in a ParamListInfo struct,
>> but that's pretty ugly and carries a bunch of costs of its own.

> Just to make sure I understand correctly, you're talking about
> setup_unshared_param_list() / setup_param_list()?

Right, and the stuff in e.g. assign_simple_var() to keep the paramlist
up to date.

>> So the execution would look more or less like
>>         op->eval_param(state, op, econtext);
>> and there'd need to be some extra fields (at least a void*) in the op
>> struct where plpgsql could keep private data.

> I think I'd redo the parameters to the callback slightly, but generally
> that sounds sane. Was thinking of something more like

Um, you left out something here?  I don't mind changing the callback
signature, but it seems like it generally ought to look the same as
the other out-of-line eval functions.

> One question I have is how we will re-initialize the relevant state
> between exec_simple_expr() calls. I guess the most realistic one is that
> the op will have a pointer into an array managed by exec_simple_expr()
> that can get reset?

I'm not seeing anything that needs to be reset?

> Hm. We could have a version of ExecInitExpr() / ExecInitExprRec that
> first gives a callback chance to handle an operation. That'd make
> overwriting parts like this quite easy, because we know we want to
> handle Param nodes anywhere differently. So we'd not have a Param
> specific routine, but the ability to intercept everything, and defer to
> ExecInitExprRec() if undesired.

Yeah, I thought of that idea too, but at least for what I'm trying to
do here, it doesn't seem all that helpful.  The problem is that plpgsql
needs to get its hooks into not only its own direct calls of ExecInitExpr
for "simple" expressions, but also calls that occur within arbitrary plan
trees that are parsed/planned/executed through SPI.  And then things need
to happen entirely differently in parallel child workers, which will have
"flat" copies of the ParamListInfo.  So I really want a hook that's tied
to ParamListInfo, and that only makes much sense for PARAM_EXTERN Params.
There may be use-cases for the more general hook you're talking about,
but I'm not very clear where that would be set up.

>> Another way we could do it is to invent ExecInitExprWithParams() that
>> takes an additional ParamListInfo pointer, and use that.  Rather than
>> adding yet one more parameter that has to be passed down through
>> ExecInitExprRec, I suggest that we could waste a bit of space in
>> struct ExprState and store that value there.  Maybe do the same with
>> the parent pointer so as to reduce the number of recursive parameters.

> I was thinking something slightly different. Namely that we should have
> two structs ExprState and ExprBuildState. We can stuff random additions
> to ExprBuildState without concerns about increasing ExprState's state.

Yeah, perhaps --- there's already some existing fields that don't need
to be kept around past the build phase.  I haven't done it like that
in the patch I'm working on, but no objections if you want to separate
things into two structs.  On the other hand, I doubt it would save
anything, what with palloc's habit of rounding up request sizes.

I have a patch nearly ready to submit, but please clarify your comment
about what you think the callback function signature should be?

            regards, tom lane


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

От
Andres Freund
Дата:
Hi,

On 2017-12-20 12:12:48 -0500, Tom Lane wrote:
> Andres Freund <andres@anarazel.de> writes:
> > What's the workload you're testing? I'm mildly surprised to see
> > ExecEvalParamExtern() show up, rather than just plpgsql_param_fetch() &
> > exec_eval_datum(). Or were you just listing that to specify the
> > callpath?
>
> I'm using several different test cases, but one that shows up the problem
> is [...]

Thanks.


> The outer do-block is just to get a run long enough for reliable perf
> statistics.  I find these relevant entries in the report:
>
>   Children      Self       Samples  Shared Object  Symbol
> +   98.29%     4.44%         10827  postgres       [.] ExecInterpExpr
> +   61.82%     3.23%          7878  plpgsql.so     [.] exec_eval_expr
> +   34.64%     3.91%          9521  postgres       [.] ExecEvalParamExtern
> +   29.78%     4.88%         11892  plpgsql.so     [.] plpgsql_param_fetch
> +   23.06%     6.90%         16831  plpgsql.so     [.] exec_eval_datum
> +    6.28%     2.89%          7049  plpgsql.so     [.] setup_param_list
> +    4.02%     4.01%          9774  postgres       [.] bms_next_member

> I think the ridiculous "children" percentage for ExecInterpExpr can be
> discounted, because that's a result of essentially everything happening
> inside the outer call of ptest4_rec.

Right.

Unfolding this gives:
-   79.73%     5.66%  postgres  postgres            [.] ExecInterpExpr
   - 92.90% ExecInterpExpr
        plpgsql_call_handler
        plpgsql_exec_function
        exec_stmt_block
      - exec_stmts
         - 100.00% exec_for_query
            - 68.11% exec_stmts
               - exec_assign_expr
                  - 60.96% ExecInterpExpr
                     - 99.10% ExecEvalParamExtern
                        - plpgsql_param_fetch
                           + 61.82% SPI_fnumber
                             15.87% SPI_getbinval
                             14.29% nocachegetattr
                             4.18% bms_is_member
                             3.84% SPI_gettypeid
                       0.90% int4pl
                  + 18.95% SPI_plan_get_cached_plan
                    11.48% bms_next_member
                  + 5.13% exec_assign_value
                  + 3.13% ReleaseCachedPlan
            + 16.70% SPI_cursor_fetch
            + 7.19% CreateTupleDescCopy
            + 5.49% heap_copytuple
              1.26% AllocSetFree
   + 6.13% 0xffffffffffffffff
   + 0.71% 0x5624318c8d6f

Which certainly seems interesting. The outer ExecInterpExpr() indeed
doesn't do that much, it's the inner call that's the most relevant
piece.  That so much time is spent in SPI_fnumber() and the functions it
calls, namely strcmp(), certainly doesn't seem right.  I suspect that
without addressing that cost, a lot of the other potential improvements
aren't going to mean much.

Looking at the function cost excluding children:
+    7.79%  postgres  plpgsql.so          [.] exec_assign_expr
+    7.39%  postgres  plpgsql.so          [.] plpgsql_param_fetch
-    6.71%  postgres  libc-2.25.so        [.] __strncmp_sse42
   - __strncmp_sse42
      + 99.97% SPI_fnumber
+    5.66%  postgres  postgres            [.] ExecInterpExpr
-    4.60%  postgres  postgres            [.] bms_next_member
   - bms_next_member
      - 99.98% exec_assign_expr
-    4.59%  postgres  postgres            [.] CreateTupleDescCopy
   - CreateTupleDescCopy
      - 92.93% exec_for_query
           exec_stmts
           exec_stmt_block
           plpgsql_exec_function
           plpgsql_call_handler
+    4.40%  postgres  postgres            [.] AllocSetAlloc
-    3.77%  postgres  postgres            [.] SPI_fnumber
   + SPI_fnumber
+    3.49%  postgres  plpgsql.so          [.] exec_for_query
+    2.93%  postgres  postgres            [.] GetCachedPlan
+    2.90%  postgres  postgres            [.] nocachegetattr
+    2.85%  postgres  postgres            [.] ExecEvalParamExtern
+    2.68%  postgres  postgres            [.] heap_getnext
+    2.64%  postgres  postgres            [.] SPI_getbinval
+    2.39%  postgres  plpgsql.so          [.] exec_assign_value
+    2.22%  postgres  postgres            [.] heap_copytuple
+    2.21%  postgres  plpgsql.so          [.] exec_stmts

The time in exec_assign_expr() is largely spent doing bms_next_member()
via the inlined setup_param_list().

Certainly shows that there's some expression eval related overhead. But
it seems the lowest hanging fruits aren't quite there, and wouldn't
necessarily be addressed by the type of change we're discussing here.

> >> So the execution would look more or less like
> >>         op->eval_param(state, op, econtext);
> >> and there'd need to be some extra fields (at least a void*) in the op
> >> struct where plpgsql could keep private data.
>
> > I think I'd redo the parameters to the callback slightly, but generally
> > that sounds sane. Was thinking of something more like
>
> Um, you left out something here?  I don't mind changing the callback
> signature, but it seems like it generally ought to look the same as
> the other out-of-line eval functions.

Yes, I did. Intercontinental travel does wonders.

I was thinking that it might be better not to expose the exact details
of the expression evaluation step struct to plpgsql etc. I'm kinda
forseeing a bit of further churn there that I don't think other parts of
the code necessarily need to be affected by. We could have the callback
have a signature roughly like
Datum callback(void *private_data, ExprContext econtext, bool *isnull);


> > One question I have is how we will re-initialize the relevant state
> > between exec_simple_expr() calls. I guess the most realistic one is that
> > the op will have a pointer into an array managed by exec_simple_expr()
> > that can get reset?
>
> I'm not seeing anything that needs to be reset?

I was kind of thinking of the params_dirty business in
plpgsql_param_fetch(), setup_param_list() etc. But I'm not quite sure
how you're thinking of representing parameters on the plpgsql side after
changing the callbacks style.


> > Hm. We could have a version of ExecInitExpr() / ExecInitExprRec that
> > first gives a callback chance to handle an operation. [ ... ]
>
> Yeah, I thought of that idea too, but at least for what I'm trying to
> do here, it doesn't seem all that helpful.  [ ... ]

Ah, makes sense.

Greetings,

Andres Freund


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

От
Tom Lane
Дата:
Andres Freund <andres@anarazel.de> writes:
> On 2017-12-20 12:12:48 -0500, Tom Lane wrote:
>> I'm using several different test cases, but one that shows up the problem
>> is [...]

> Which certainly seems interesting. The outer ExecInterpExpr() indeed
> doesn't do that much, it's the inner call that's the most relevant
> piece.  That so much time is spent in SPI_fnumber() and the functions it
> calls, namely strcmp(), certainly doesn't seem right.  I suspect that
> without addressing that cost, a lot of the other potential improvements
> aren't going to mean much.

Right, that's mostly coming from the fact that exec_eval_datum on
a RECFIELD does SPI_fnumber() every time.  I have a patch in the
pipeline to address that, but this business with the expression eval
API is a separable issue (and it stands out a lot more with that
patch in place than it does in HEAD ;-)).

>> Um, you left out something here?  I don't mind changing the callback
>> signature, but it seems like it generally ought to look the same as
>> the other out-of-line eval functions.

> Yes, I did. Intercontinental travel does wonders.

> I was thinking that it might be better not to expose the exact details
> of the expression evaluation step struct to plpgsql etc. I'm kinda
> forseeing a bit of further churn there that I don't think other parts of
> the code necessarily need to be affected by. We could have the callback
> have a signature roughly like
> Datum callback(void *private_data, ExprContext econtext, bool *isnull);

I don't especially like that, because it forces the callback to use a
separately allocated private_data area even when the available space
in the op step struct would serve perfectly well.  In my draft patch
I have

--- 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
        {

and it turns out that plpgsql isn't bothering with paramarg because
paramid and paramtype are all it needs.  I doubt that whatever you
have in mind to do to struct ExprEvalStep is likely to be so major
that it changes what we can keep in these fields.

>> I'm not seeing anything that needs to be reset?

> I was kind of thinking of the params_dirty business in
> plpgsql_param_fetch(), setup_param_list() etc. But I'm not quite sure
> how you're thinking of representing parameters on the plpgsql side after
> changing the callbacks style.

Turns out we can just get rid of that junk altogether.  I've redesigned
the ParamListInfo API a bit in service of this, and there no longer seems
to be a need for plpgsql to use a mutable ParamListInfo at all.

Will send a patch in a bit.  I need to write an explanation of what all
I changed.

            regards, tom lane


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

От
Tom Lane
Дата:
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();

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

От
Tom Lane
Дата:
I wrote:
> * 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.

I forgot to mention that I dithered about changing the params field
from a variable-length array to a pointer, ie,

-    ParamExternData params[FLEXIBLE_ARRAY_MEMBER];
+    ParamExternData *params;

Then we could set the pointer to NULL in cases where no physical
array is provided, which would be a good thing in terms of helping
to catch code that hasn't been updated to the new convention.
However, this would force less-than-trivial changes in every place
that creates a ParamListInfo.  For instance, copyParamList might
change from

    size = offsetof(ParamListInfoData, params) +
        from->numParams * sizeof(ParamExternData);

    retval = (ParamListInfo) palloc(size);
    ... fill retval ...

to

    size = MAXALIGN(sizeof(ParamListInfoData)) +
        from->numParams * sizeof(ParamExternData);

    retval = (ParamListInfo) palloc(size);
    retval->params = (ParamExternData *)
        ((char *) retval + MAXALIGN(sizeof(ParamListInfoData)));
    ... fill rest of retval ...

That seemed like a pain in the rear, and easy to get wrong
(although it could be a lot simpler if you didn't mind doing
two pallocs instead of one).

Now there aren't that many places in the core code that do this,
so it wouldn't be very hard to fix them, but I'm concerned about
the possible impact on extension modules.  Creating param lists
seems like something that a lot of things would have code for.

Anyway, I left it as-is, but I'm willing to make the change if
people feel the other way is better.

            regards, tom lane


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

От
Robert Haas
Дата:
On Wed, Dec 20, 2017 at 6:06 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
> Anyway, I left it as-is, but I'm willing to make the change if
> people feel the other way is better.

I feel the other way -- let's not add more pointer indirections if it
isn't really necessary.

-- 
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company


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

От
Tom Lane
Дата:
Robert Haas <robertmhaas@gmail.com> writes:
> On Wed, Dec 20, 2017 at 6:06 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
>> Anyway, I left it as-is, but I'm willing to make the change if
>> people feel the other way is better.

> I feel the other way -- let's not add more pointer indirections if it
> isn't really necessary.

OK --- with any luck, the changes in the paramFetch API will force people
to update relevant code anyway.  It would only be an issue for code that
is not calling paramFetch at all, and such code is broken anyway for
dynamic param lists.

Pushed without touching that issue.

            regards, tom lane