WIP: pushing parser hooks through SPI and plancache - Mailing list pgsql-hackers

From Tom Lane
Subject WIP: pushing parser hooks through SPI and plancache
Date
Msg-id 17405.1257352028@sss.pgh.pa.us
Whole thread Raw
Responses Re: WIP: pushing parser hooks through SPI and plancache
List pgsql-hackers
Attached is a draft patch for the next step in my project of
reimplementing plpgsql's parsing.  This makes the recently added
parser hooks accessible for callers that are using SPI (like
plpgsql) and provides support for using such hooks in cacheable
plans.  As proof of concept, plpgsql is modified to use the hooks.
plpgsql is still inserting $n symbols textually, but the "back end"
of the parsing process now goes through the ParamRef hook instead of
using a fixed parameter-type array, and then execution only fetches
actually-referenced parameters, using a hook added to ParamListInfo.

Although there's a lot left to be done, this already cures the
"if (TG_OP = 'INSERT' and NEW.foo ..."  problem, as illustrated by the
changed regression test.

As I previously proposed, the core concept is to add a "meta hook"
that installs parser hook functions after a ParseState is created:
    typedef void (*ParserSetupHook) (struct ParseState *pstate, void *arg);
In this way the APIs won't need to change if we add more parser hooks
later.

It turns out that this typedef is needed in two relatively low-level
.h files: nodes/params.h and utils/plancache.h.  My original idea had
been to define the hook typedef in parser/parse_node.h where struct
ParseState is defined.  But that would have required pulling a boatload
of parser headers into these two .h files, which seems like a bad idea
(it might even lead to circular includes).  For the moment I've worked
around this by putting the typedef into nodes/params.h itself, but I
can't say I find that a pleasing solution.  Has anyone got a better
idea?  Should we make a parser/something header that just provides that
typedef?

BTW, the reason nodes/params.h needs it is that I added an instance
of the parser hook to ParamListInfo, which might seem a bit bizarre
since that's an execution-time data structure.  The reason for this is
that we need to support things like plpgsql's
    for r in explain select ...
Here, the EXPLAIN utility command is going to receive some parameters
that it has to pass down to the parsing of the command-to-explain.
So a caller that is using a parser hook needs to pass it to EXPLAIN,
and that goes through ProcessUtility and some other layers.  If we
don't include the parser hook in ParamListInfo then we'll be adding
it as a separate parameter in a lot of places, and that didn't seem
to be helpful.

One other thing I'm not too happy with here is the name of this new
SPI function:

+ extern SPIPlanPtr SPI_prepare_with_hook(const char *src,
+                       ParserSetupHook parserSetup,
+                       void *parserSetupArg,
+                       int cursorOptions);

Anybody have a better idea for that?

            regards, tom lane

Index: doc/src/sgml/spi.sgml
===================================================================
RCS file: /cvsroot/pgsql/doc/src/sgml/spi.sgml,v
retrieving revision 1.65
diff -c -r1.65 spi.sgml
*** doc/src/sgml/spi.sgml    5 Aug 2009 19:31:50 -0000    1.65
--- doc/src/sgml/spi.sgml    4 Nov 2009 16:05:28 -0000
***************
*** 1386,1399 ****
     <function>SPI_execute</function> if successful.
    </para>
   </refsect1>

   <refsect1>
!   <title>Notes</title>

    <para>
!    If one of the objects (a table, function, etc.) referenced by the
!    prepared plan is dropped during the session then the result of
!    <function>SPI_execute_plan</function> for this plan will be unpredictable.
    </para>
   </refsect1>
  </refentry>
--- 1386,1485 ----
     <function>SPI_execute</function> if successful.
    </para>
   </refsect1>
+ </refentry>
+
+ <!-- *********************************************** -->
+
+ <refentry id="spi-spi-execute-plan-with-paramlist">
+  <refmeta>
+   <refentrytitle>SPI_execute_plan_with_paramlist</refentrytitle>
+   <manvolnum>3</manvolnum>
+  </refmeta>
+
+  <refnamediv>
+   <refname>SPI_execute_plan_with_paramlist</refname>
+   <refpurpose>execute a plan prepared by <function>SPI_prepare</function></refpurpose>
+  </refnamediv>
+
+  <indexterm><primary>SPI_execute_plan_with_paramlist</primary></indexterm>
+
+  <refsynopsisdiv>
+ <synopsis>
+ int SPI_execute_plan_with_paramlist(SPIPlanPtr <parameter>plan</parameter>,
+                                     ParamListInfo <parameter>params</parameter>,
+                                     bool <parameter>read_only</parameter>,
+                                     long <parameter>count</parameter>)
+ </synopsis>
+  </refsynopsisdiv>

   <refsect1>
!   <title>Description</title>

    <para>
!    <function>SPI_execute_plan_with_paramlist</function> executes a plan
!    prepared by <function>SPI_prepare</function>.
!    This function is equivalent to <function>SPI_execute_plan</function>
!    except that information about the parameter values to be passed to the
!    query is presented differently.  The <literal>ParamListInfo</>
!    representation can be convenient for passing down values that are
!    already available in that format.  It also supports use of dynamic
!    parameter sets via hook functions specified in <literal>ParamListInfo</>.
!   </para>
!  </refsect1>
!
!  <refsect1>
!   <title>Arguments</title>
!
!   <variablelist>
!    <varlistentry>
!     <term><literal>SPIPlanPtr <parameter>plan</parameter></literal></term>
!     <listitem>
!      <para>
!       execution plan (returned by <function>SPI_prepare</function>)
!      </para>
!     </listitem>
!    </varlistentry>
!
!    <varlistentry>
!     <term><literal>ParamListInfo <parameter>params</parameter></literal></term>
!     <listitem>
!      <para>
!       data structure containing parameter types and values; NULL if none
!      </para>
!     </listitem>
!    </varlistentry>
!
!    <varlistentry>
!     <term><literal>bool <parameter>read_only</parameter></literal></term>
!     <listitem>
!      <para>
!       <literal>true</> for read-only execution
!      </para>
!     </listitem>
!    </varlistentry>
!
!    <varlistentry>
!     <term><literal>long <parameter>count</parameter></literal></term>
!     <listitem>
!      <para>
!       maximum number of rows to process or return
!      </para>
!     </listitem>
!    </varlistentry>
!   </variablelist>
!  </refsect1>
!
!  <refsect1>
!   <title>Return Value</title>
!
!   <para>
!    The return value is the same as for <function>SPI_execute_plan</function>.
!   </para>
!
!   <para>
!    <varname>SPI_processed</varname> and
!    <varname>SPI_tuptable</varname> are set as in
!    <function>SPI_execute_plan</function> if successful.
    </para>
   </refsect1>
  </refentry>
***************
*** 1543,1549 ****
    </para>

    <para>
!    The passed-in data will be copied into the cursor's portal, so it
     can be freed while the cursor still exists.
    </para>
   </refsect1>
--- 1629,1635 ----
    </para>

    <para>
!    The passed-in parameter data will be copied into the cursor's portal, so it
     can be freed while the cursor still exists.
    </para>
   </refsect1>
***************
*** 1667,1673 ****
    </para>

    <para>
!    The passed-in data will be copied into the cursor's portal, so it
     can be freed while the cursor still exists.
    </para>
   </refsect1>
--- 1753,1759 ----
    </para>

    <para>
!    The passed-in parameter data will be copied into the cursor's portal, so it
     can be freed while the cursor still exists.
    </para>
   </refsect1>
***************
*** 1770,1775 ****
--- 1856,1959 ----

  <!-- *********************************************** -->

+ <refentry id="spi-spi-cursor-open-with-paramlist">
+  <refmeta>
+   <refentrytitle>SPI_cursor_open_with_paramlist</refentrytitle>
+   <manvolnum>3</manvolnum>
+  </refmeta>
+
+  <refnamediv>
+   <refname>SPI_cursor_open_with_paramlist</refname>
+   <refpurpose>set up a cursor using parameters</refpurpose>
+  </refnamediv>
+
+  <indexterm><primary>SPI_cursor_open_with_paramlist</primary></indexterm>
+
+  <refsynopsisdiv>
+ <synopsis>
+ Portal SPI_cursor_open_with_paramlist(const char *<parameter>name</parameter>,
+                                       SPIPlanPtr <parameter>plan</parameter>,
+                                       ParamListInfo <parameter>params</parameter>,
+                                       bool <parameter>read_only</parameter>)
+ </synopsis>
+  </refsynopsisdiv>
+
+  <refsect1>
+   <title>Description</title>
+
+   <para>
+    <function>SPI_cursor_open_with_paramlist</function> sets up a cursor
+    (internally, a portal) that will execute a plan prepared by
+    <function>SPI_prepare</function>.
+    This function is equivalent to <function>SPI_cursor_open</function>
+    except that information about the parameter values to be passed to the
+    query is presented differently.  The <literal>ParamListInfo</>
+    representation can be convenient for passing down values that are
+    already available in that format.  It also supports use of dynamic
+    parameter sets via hook functions specified in <literal>ParamListInfo</>.
+   </para>
+
+   <para>
+    The passed-in parameter data will be copied into the cursor's portal, so it
+    can be freed while the cursor still exists.
+   </para>
+  </refsect1>
+
+  <refsect1>
+   <title>Arguments</title>
+
+   <variablelist>
+    <varlistentry>
+     <term><literal>const char * <parameter>name</parameter></literal></term>
+     <listitem>
+      <para>
+       name for portal, or <symbol>NULL</symbol> to let the system
+       select a name
+      </para>
+     </listitem>
+    </varlistentry>
+
+    <varlistentry>
+     <term><literal>SPIPlanPtr <parameter>plan</parameter></literal></term>
+     <listitem>
+      <para>
+       execution plan (returned by <function>SPI_prepare</function>)
+      </para>
+     </listitem>
+    </varlistentry>
+
+    <varlistentry>
+     <term><literal>ParamListInfo <parameter>params</parameter></literal></term>
+     <listitem>
+      <para>
+       data structure containing parameter types and values; NULL if none
+      </para>
+     </listitem>
+    </varlistentry>
+
+    <varlistentry>
+     <term><literal>bool <parameter>read_only</parameter></literal></term>
+     <listitem>
+      <para>
+       <literal>true</> for read-only execution
+      </para>
+     </listitem>
+    </varlistentry>
+   </variablelist>
+  </refsect1>
+
+  <refsect1>
+   <title>Return Value</title>
+
+   <para>
+    Pointer to portal containing the cursor.  Note there is no error
+    return convention; any error will be reported via <function>elog</>.
+   </para>
+  </refsect1>
+ </refentry>
+
+ <!-- *********************************************** -->
+
  <refentry id="spi-spi-cursor-find">
   <refmeta>
    <refentrytitle>SPI_cursor_find</refentrytitle>
Index: src/backend/commands/explain.c
===================================================================
RCS file: /cvsroot/pgsql/src/backend/commands/explain.c,v
retrieving revision 1.192
diff -c -r1.192 explain.c
*** src/backend/commands/explain.c    12 Oct 2009 18:10:41 -0000    1.192
--- src/backend/commands/explain.c    4 Nov 2009 16:05:28 -0000
***************
*** 107,114 ****
               ParamListInfo params, DestReceiver *dest)
  {
      ExplainState es;
-     Oid           *param_types;
-     int            num_params;
      TupOutputState *tstate;
      List       *rewritten;
      ListCell   *lc;
--- 107,112 ----
***************
*** 150,158 ****
                              opt->defname)));
      }

-     /* Convert parameter type data to the form parser wants */
-     getParamListTypes(params, ¶m_types, &num_params);
-
      /*
       * Run parse analysis and rewrite.    Note this also acquires sufficient
       * locks on the source table(s).
--- 148,153 ----
***************
*** 163,170 ****
       * executed repeatedly.  (See also the same hack in DECLARE CURSOR and
       * PREPARE.)  XXX FIXME someday.
       */
!     rewritten = pg_analyze_and_rewrite((Node *) copyObject(stmt->query),
!                                        queryString, param_types, num_params);

      /* emit opening boilerplate */
      ExplainBeginOutput(&es);
--- 158,167 ----
       * executed repeatedly.  (See also the same hack in DECLARE CURSOR and
       * PREPARE.)  XXX FIXME someday.
       */
!     rewritten = pg_analyze_and_rewrite_params((Node *) copyObject(stmt->query),
!                                               queryString,
!                                               (ParserSetupHook) setupParserWithParams,
!                                               params);

      /* emit opening boilerplate */
      ExplainBeginOutput(&es);
Index: src/backend/commands/prepare.c
===================================================================
RCS file: /cvsroot/pgsql/src/backend/commands/prepare.c,v
retrieving revision 1.99
diff -c -r1.99 prepare.c
*** src/backend/commands/prepare.c    10 Aug 2009 05:46:50 -0000    1.99
--- src/backend/commands/prepare.c    4 Nov 2009 16:05:28 -0000
***************
*** 379,384 ****
--- 379,389 ----
      paramLI = (ParamListInfo)
          palloc(sizeof(ParamListInfoData) +
                 (num_params - 1) *sizeof(ParamExternData));
+     /* 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;

      i = 0;
Index: src/backend/executor/execCurrent.c
===================================================================
RCS file: /cvsroot/pgsql/src/backend/executor/execCurrent.c,v
retrieving revision 1.12
diff -c -r1.12 execCurrent.c
*** src/backend/executor/execCurrent.c    26 Oct 2009 02:26:29 -0000    1.12
--- src/backend/executor/execCurrent.c    4 Nov 2009 16:05:28 -0000
***************
*** 217,225 ****
      {
          ParamExternData *prm = ¶mInfo->params[paramId - 1];

          if (OidIsValid(prm->ptype) && !prm->isnull)
          {
!             Assert(prm->ptype == REFCURSOROID);
              /* We know that refcursor uses text's I/O routines */
              return TextDatumGetCString(prm->value);
          }
--- 217,237 ----
      {
          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)
          {
!             /* safety check in case hook did something unexpected */
!             if (prm->ptype != REFCURSOROID)
!                 ereport(ERROR,
!                         (errcode(ERRCODE_DATATYPE_MISMATCH),
!                          errmsg("type of parameter %d (%s) does not match that when preparing the plan (%s)",
!                                 paramId,
!                                 format_type_be(prm->ptype),
!                                 format_type_be(REFCURSOROID))));
!
              /* We know that refcursor uses text's I/O routines */
              return TextDatumGetCString(prm->value);
          }
Index: src/backend/executor/execQual.c
===================================================================
RCS file: /cvsroot/pgsql/src/backend/executor/execQual.c,v
retrieving revision 1.253
diff -c -r1.253 execQual.c
*** src/backend/executor/execQual.c    26 Oct 2009 02:26:29 -0000    1.253
--- src/backend/executor/execQual.c    4 Nov 2009 16:05:28 -0000
***************
*** 882,890 ****
          {
              ParamExternData *prm = ¶mInfo->params[thisParamId - 1];

              if (OidIsValid(prm->ptype))
              {
!                 Assert(prm->ptype == expression->paramtype);
                  *isNull = prm->isnull;
                  return prm->value;
              }
--- 882,902 ----
          {
              ParamExternData *prm = ¶mInfo->params[thisParamId - 1];

+             /* give hook a chance in case parameter is dynamic */
+             if (!OidIsValid(prm->ptype) && paramInfo->paramFetch != NULL)
+                 (*paramInfo->paramFetch) (paramInfo, thisParamId);
+
              if (OidIsValid(prm->ptype))
              {
!                 /* safety check in case hook did something unexpected */
!                 if (prm->ptype != expression->paramtype)
!                     ereport(ERROR,
!                             (errcode(ERRCODE_DATATYPE_MISMATCH),
!                              errmsg("type of parameter %d (%s) does not match that when preparing the plan (%s)",
!                                     thisParamId,
!                                     format_type_be(prm->ptype),
!                                     format_type_be(expression->paramtype))));
!
                  *isNull = prm->isnull;
                  return prm->value;
              }
Index: src/backend/executor/functions.c
===================================================================
RCS file: /cvsroot/pgsql/src/backend/executor/functions.c,v
retrieving revision 1.135
diff -c -r1.135 functions.c
*** src/backend/executor/functions.c    11 Jun 2009 17:25:38 -0000    1.135
--- src/backend/executor/functions.c    4 Nov 2009 16:05:28 -0000
***************
*** 526,531 ****
--- 526,536 ----
              /* sizeof(ParamListInfoData) includes the first array element */
              paramLI = (ParamListInfo) palloc(sizeof(ParamListInfoData) +
                                         (nargs - 1) *sizeof(ParamExternData));
+             /* 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;
              fcache->paramLI = paramLI;
          }
Index: src/backend/executor/spi.c
===================================================================
RCS file: /cvsroot/pgsql/src/backend/executor/spi.c,v
retrieving revision 1.210
diff -c -r1.210 spi.c
*** src/backend/executor/spi.c    10 Oct 2009 01:43:47 -0000    1.210
--- src/backend/executor/spi.c    4 Nov 2009 16:05:29 -0000
***************
*** 45,52 ****
  static int    _SPI_curid = -1;

  static Portal SPI_cursor_open_internal(const char *name, SPIPlanPtr plan,
!                          Datum *Values, const char *Nulls,
!                          bool read_only, int pflags);

  static void _SPI_prepare_plan(const char *src, SPIPlanPtr plan,
                    ParamListInfo boundParams);
--- 45,51 ----
  static int    _SPI_curid = -1;

  static Portal SPI_cursor_open_internal(const char *name, SPIPlanPtr plan,
!                          ParamListInfo paramLI, bool read_only);

  static void _SPI_prepare_plan(const char *src, SPIPlanPtr plan,
                    ParamListInfo boundParams);
***************
*** 407,412 ****
--- 406,433 ----
      return SPI_execute_plan(plan, Values, Nulls, false, tcount);
  }

+ /* Execute a previously prepared plan */
+ int
+ SPI_execute_plan_with_paramlist(SPIPlanPtr plan, ParamListInfo params,
+                                 bool read_only, long tcount)
+ {
+     int            res;
+
+     if (plan == NULL || plan->magic != _SPI_PLAN_MAGIC || tcount < 0)
+         return SPI_ERROR_ARGUMENT;
+
+     res = _SPI_begin_call(true);
+     if (res < 0)
+         return res;
+
+     res = _SPI_execute_plan(plan, params,
+                             InvalidSnapshot, InvalidSnapshot,
+                             read_only, true, tcount);
+
+     _SPI_end_call(true);
+     return res;
+ }
+
  /*
   * SPI_execute_snapshot -- identical to SPI_execute_plan, except that we allow
   * the caller to specify exactly which snapshots to use, which will be
***************
*** 483,488 ****
--- 504,511 ----
      plan.cursor_options = 0;
      plan.nargs = nargs;
      plan.argtypes = argtypes;
+     plan.parserSetup = NULL;
+     plan.parserSetupArg = NULL;

      paramLI = _SPI_convert_params(nargs, argtypes,
                                    Values, Nulls,
***************
*** 528,533 ****
--- 551,595 ----
      plan.cursor_options = cursorOptions;
      plan.nargs = nargs;
      plan.argtypes = argtypes;
+     plan.parserSetup = NULL;
+     plan.parserSetupArg = NULL;
+
+     _SPI_prepare_plan(src, &plan, NULL);
+
+     /* copy plan to procedure context */
+     result = _SPI_copy_plan(&plan, _SPI_current->procCxt);
+
+     _SPI_end_call(true);
+
+     return result;
+ }
+
+ SPIPlanPtr
+ SPI_prepare_with_hook(const char *src,
+                       ParserSetupHook parserSetup,
+                       void *parserSetupArg,
+                       int cursorOptions)
+ {
+     _SPI_plan    plan;
+     SPIPlanPtr    result;
+
+     if (src == NULL)
+     {
+         SPI_result = SPI_ERROR_ARGUMENT;
+         return NULL;
+     }
+
+     SPI_result = _SPI_begin_call(true);
+     if (SPI_result < 0)
+         return NULL;
+
+     memset(&plan, 0, sizeof(_SPI_plan));
+     plan.magic = _SPI_PLAN_MAGIC;
+     plan.cursor_options = cursorOptions;
+     plan.nargs = 0;
+     plan.argtypes = NULL;
+     plan.parserSetup = parserSetup;
+     plan.parserSetupArg = parserSetupArg;

      _SPI_prepare_plan(src, &plan, NULL);

***************
*** 954,961 ****
                  Datum *Values, const char *Nulls,
                  bool read_only)
  {
!     return SPI_cursor_open_internal(name, plan, Values, Nulls,
!                                     read_only, 0);
  }


--- 1016,1036 ----
                  Datum *Values, const char *Nulls,
                  bool read_only)
  {
!     Portal        portal;
!     ParamListInfo paramLI;
!
!     /* build transient ParamListInfo in caller's context */
!     paramLI = _SPI_convert_params(plan->nargs, plan->argtypes,
!                                   Values, Nulls,
!                                   0);
!
!     portal = SPI_cursor_open_internal(name, plan, paramLI, read_only);
!
!     /* done with the transient ParamListInfo */
!     if (paramLI)
!         pfree(paramLI);
!
!     return portal;
  }


***************
*** 992,998 ****
--- 1067,1076 ----
      plan.cursor_options = cursorOptions;
      plan.nargs = nargs;
      plan.argtypes = argtypes;
+     plan.parserSetup = NULL;
+     plan.parserSetupArg = NULL;

+     /* build transient ParamListInfo in executor context */
      paramLI = _SPI_convert_params(nargs, argtypes,
                                    Values, Nulls,
                                    PARAM_FLAG_CONST);
***************
*** 1007,1014 ****
      /* SPI_cursor_open_internal must be called in procedure memory context */
      _SPI_procmem();

!     result = SPI_cursor_open_internal(name, &plan, Values, Nulls,
!                                       read_only, PARAM_FLAG_CONST);

      /* And clean up */
      _SPI_curid++;
--- 1085,1091 ----
      /* SPI_cursor_open_internal must be called in procedure memory context */
      _SPI_procmem();

!     result = SPI_cursor_open_internal(name, &plan, paramLI, read_only);

      /* And clean up */
      _SPI_curid++;
***************
*** 1019,1042 ****


  /*
   * SPI_cursor_open_internal()
   *
!  *    Common code for SPI_cursor_open and SPI_cursor_open_with_args
   */
  static Portal
  SPI_cursor_open_internal(const char *name, SPIPlanPtr plan,
!                          Datum *Values, const char *Nulls,
!                          bool read_only, int pflags)
  {
      CachedPlanSource *plansource;
      CachedPlan *cplan;
      List       *stmt_list;
      char       *query_string;
-     ParamListInfo paramLI;
      Snapshot    snapshot;
      MemoryContext oldcontext;
      Portal        portal;
-     int            k;

      /*
       * Check that the plan is something the Portal code will special-case as
--- 1096,1130 ----


  /*
+  * SPI_cursor_open_with_paramlist()
+  *
+  *    Same as SPI_cursor_open except that parameters (if any) are passed
+  *    as a ParamListInfo, which supports dynamic parameter set determination
+  */
+ Portal
+ SPI_cursor_open_with_paramlist(const char *name, SPIPlanPtr plan,
+                                ParamListInfo params, bool read_only)
+ {
+     return SPI_cursor_open_internal(name, plan, params, read_only);
+ }
+
+
+ /*
   * SPI_cursor_open_internal()
   *
!  *    Common code for SPI_cursor_open variants
   */
  static Portal
  SPI_cursor_open_internal(const char *name, SPIPlanPtr plan,
!                          ParamListInfo paramLI, bool read_only)
  {
      CachedPlanSource *plansource;
      CachedPlan *cplan;
      List       *stmt_list;
      char       *query_string;
      Snapshot    snapshot;
      MemoryContext oldcontext;
      Portal        portal;

      /*
       * Check that the plan is something the Portal code will special-case as
***************
*** 1082,1135 ****
          portal = CreatePortal(name, false, false);
      }

-     /*
-      * Prepare to copy stuff into the portal's memory context.  We do all this
-      * copying first, because it could possibly fail (out-of-memory) and we
-      * don't want a failure to occur between RevalidateCachedPlan and
-      * PortalDefineQuery; that would result in leaking our plancache refcount.
-      */
-     oldcontext = MemoryContextSwitchTo(PortalGetHeapMemory(portal));
-
      /* Copy the plan's query string into the portal */
!     query_string = pstrdup(plansource->query_string);
!
!     /* If the plan has parameters, copy them into the portal */
!     if (plan->nargs > 0)
!     {
!         /* sizeof(ParamListInfoData) includes the first array element */
!         paramLI = (ParamListInfo) palloc(sizeof(ParamListInfoData) +
!                                  (plan->nargs - 1) *sizeof(ParamExternData));
!         paramLI->numParams = plan->nargs;
!
!         for (k = 0; k < plan->nargs; k++)
!         {
!             ParamExternData *prm = ¶mLI->params[k];
!
!             prm->ptype = plan->argtypes[k];
!             prm->pflags = pflags;
!             prm->isnull = (Nulls && Nulls[k] == 'n');
!             if (prm->isnull)
!             {
!                 /* nulls just copy */
!                 prm->value = Values[k];
!             }
!             else
!             {
!                 /* pass-by-ref values must be copied into portal context */
!                 int16        paramTypLen;
!                 bool        paramTypByVal;
!
!                 get_typlenbyval(prm->ptype, ¶mTypLen, ¶mTypByVal);
!                 prm->value = datumCopy(Values[k],
!                                        paramTypByVal, paramTypLen);
!             }
!         }
!     }
!     else
!         paramLI = NULL;
!
!     MemoryContextSwitchTo(oldcontext);

      if (plan->saved)
      {
          /* Replan if needed, and increment plan refcount for portal */
--- 1170,1184 ----
          portal = CreatePortal(name, false, false);
      }

      /* Copy the plan's query string into the portal */
!     query_string = MemoryContextStrdup(PortalGetHeapMemory(portal),
!                                        plansource->query_string);

+     /*
+      * Note: we mustn't have any failure occur between RevalidateCachedPlan
+      * and PortalDefineQuery; that would result in leaking our plancache
+      * refcount.
+      */
      if (plan->saved)
      {
          /* Replan if needed, and increment plan refcount for portal */
***************
*** 1221,1226 ****
--- 1270,1288 ----
      }

      /*
+      * If the plan has parameters, copy them into the portal.  Note that
+      * this must be done after revalidating the plan, because in dynamic
+      * parameter cases the set of parameters could have changed during
+      * re-parsing.
+      */
+     if (paramLI)
+     {
+         oldcontext = MemoryContextSwitchTo(PortalGetHeapMemory(portal));
+         paramLI = copyParamList(paramLI);
+         MemoryContextSwitchTo(oldcontext);
+     }
+
+     /*
       * Start portal execution.
       */
      PortalStart(portal, paramLI, snapshot);
***************
*** 1588,1598 ****
  /*
   * Parse and plan a querystring.
   *
!  * At entry, plan->argtypes, plan->nargs, and plan->cursor_options must be
!  * valid.  If boundParams isn't NULL then it represents parameter values
!  * that are made available to the planner (as either estimates or hard values
!  * depending on their PARAM_FLAG_CONST marking).  The boundParams had better
!  * match the param types embedded in the plan!
   *
   * Results are stored into *plan (specifically, plan->plancache_list).
   * Note however that the result trees are all in CurrentMemoryContext
--- 1650,1661 ----
  /*
   * Parse and plan a querystring.
   *
!  * At entry, plan->argtypes and plan->nargs (or alternatively plan->parserSetup
!  * and plan->parserSetupArg) must be valid, as must plan->cursor_options.
!  * If boundParams isn't NULL then it represents parameter values that are made
!  * available to the planner (as either estimates or hard values depending on
!  * their PARAM_FLAG_CONST marking).  The boundParams had better match the
!  * param type information embedded in the plan!
   *
   * Results are stored into *plan (specifically, plan->plancache_list).
   * Note however that the result trees are all in CurrentMemoryContext
***************
*** 1605,1612 ****
      List       *plancache_list;
      ListCell   *list_item;
      ErrorContextCallback spierrcontext;
-     Oid           *argtypes = plan->argtypes;
-     int            nargs = plan->nargs;
      int            cursor_options = plan->cursor_options;

      /*
--- 1668,1673 ----
***************
*** 1623,1630 ****
      raw_parsetree_list = pg_parse_query(src);

      /*
!      * Do parse analysis and rule rewrite for each raw parsetree, then cons up
!      * a phony plancache entry for each one.
       */
      plancache_list = NIL;

--- 1684,1691 ----
      raw_parsetree_list = pg_parse_query(src);

      /*
!      * Do parse analysis, rule rewrite, and planning for each raw parsetree,
!      * then cons up a phony plancache entry for each one.
       */
      plancache_list = NIL;

***************
*** 1635,1643 ****
          CachedPlanSource *plansource;
          CachedPlan *cplan;

!         /* Need a copyObject here to keep parser from modifying raw tree */
!         stmt_list = pg_analyze_and_rewrite(copyObject(parsetree),
!                                            src, argtypes, nargs);
          stmt_list = pg_plan_queries(stmt_list, cursor_options, boundParams);

          plansource = (CachedPlanSource *) palloc0(sizeof(CachedPlanSource));
--- 1696,1722 ----
          CachedPlanSource *plansource;
          CachedPlan *cplan;

!         /*
!          * Parameter datatypes are driven by parserSetup hook if provided,
!          * otherwise we use the fixed parameter list.
!          */
!         if (plan->parserSetup != NULL)
!         {
!             Assert(plan->nargs == 0);
!             /* Need a copyObject here to keep parser from modifying raw tree */
!             stmt_list = pg_analyze_and_rewrite_params(copyObject(parsetree),
!                                                       src,
!                                                       plan->parserSetup,
!                                                       plan->parserSetupArg);
!         }
!         else
!         {
!             /* Need a copyObject here to keep parser from modifying raw tree */
!             stmt_list = pg_analyze_and_rewrite(copyObject(parsetree),
!                                                src,
!                                                plan->argtypes,
!                                                plan->nargs);
!         }
          stmt_list = pg_plan_queries(stmt_list, cursor_options, boundParams);

          plansource = (CachedPlanSource *) palloc0(sizeof(CachedPlanSource));
***************
*** 1647,1654 ****
          /* cast-away-const here is a bit ugly, but there's no reason to copy */
          plansource->query_string = (char *) src;
          plansource->commandTag = CreateCommandTag(parsetree);
!         plansource->param_types = argtypes;
!         plansource->num_params = nargs;
          plansource->fully_planned = true;
          plansource->fixed_result = false;
          /* no need to set search_path, generation or saved_xmin */
--- 1726,1735 ----
          /* cast-away-const here is a bit ugly, but there's no reason to copy */
          plansource->query_string = (char *) src;
          plansource->commandTag = CreateCommandTag(parsetree);
!         plansource->param_types = plan->argtypes;
!         plansource->num_params = plan->nargs;
!         plansource->parserSetup = plan->parserSetup;
!         plansource->parserSetupArg = plan->parserSetupArg;
          plansource->fully_planned = true;
          plansource->fixed_result = false;
          /* no need to set search_path, generation or saved_xmin */
***************
*** 1921,1927 ****
  }

  /*
!  * Convert query parameters to form wanted by planner and executor
   */
  static ParamListInfo
  _SPI_convert_params(int nargs, Oid *argtypes,
--- 2002,2008 ----
  }

  /*
!  * Convert arrays of query parameters to form wanted by planner and executor
   */
  static ParamListInfo
  _SPI_convert_params(int nargs, Oid *argtypes,
***************
*** 1937,1942 ****
--- 2018,2028 ----
          /* sizeof(ParamListInfoData) includes the first array element */
          paramLI = (ParamListInfo) palloc(sizeof(ParamListInfoData) +
                                         (nargs - 1) *sizeof(ParamExternData));
+         /* 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;

          for (i = 0; i < nargs; i++)
***************
*** 2222,2227 ****
--- 2308,2315 ----
      }
      else
          newplan->argtypes = NULL;
+     newplan->parserSetup = plan->parserSetup;
+     newplan->parserSetupArg = plan->parserSetupArg;

      foreach(lc, plan->plancache_list)
      {
***************
*** 2241,2246 ****
--- 2329,2336 ----
          newsource->commandTag = plansource->commandTag;
          newsource->param_types = newplan->argtypes;
          newsource->num_params = newplan->nargs;
+         newsource->parserSetup = newplan->parserSetup;
+         newsource->parserSetupArg = newplan->parserSetupArg;
          newsource->fully_planned = plansource->fully_planned;
          newsource->fixed_result = plansource->fixed_result;
          /* no need to worry about seach_path, generation or saved_xmin */
***************
*** 2298,2303 ****
--- 2388,2395 ----
      }
      else
          newplan->argtypes = NULL;
+     newplan->parserSetup = plan->parserSetup;
+     newplan->parserSetupArg = plan->parserSetupArg;

      foreach(lc, plan->plancache_list)
      {
***************
*** 2317,2322 ****
--- 2409,2418 ----
                                       cplan->stmt_list,
                                       true,
                                       false);
+         if (newplan->parserSetup != NULL)
+             CachedPlanSetHook(newsource,
+                               newplan->parserSetup,
+                               newplan->parserSetupArg);

          newplan->plancache_list = lappend(newplan->plancache_list, newsource);
      }
Index: src/backend/nodes/params.c
===================================================================
RCS file: /cvsroot/pgsql/src/backend/nodes/params.c,v
retrieving revision 1.11
diff -c -r1.11 params.c
*** src/backend/nodes/params.c    1 Jan 2009 17:23:43 -0000    1.11
--- src/backend/nodes/params.c    4 Nov 2009 16:05:29 -0000
***************
*** 16,21 ****
--- 16,22 ----
  #include "postgres.h"

  #include "nodes/params.h"
+ #include "parser/parse_param.h"
  #include "utils/datum.h"
  #include "utils/lsyscache.h"

***************
*** 24,29 ****
--- 25,35 ----
   * Copy a ParamListInfo structure.
   *
   * The result is allocated in CurrentMemoryContext.
+  *
+  * Note: the intent of this function is to make a static, self-contained
+  * set of parameter values.  If dynamic parameter hooks are present, we
+  * intentionally do not copy them into the result.  Rather, we forcibly
+  * instantiate all available parameter values and copy the datum values.
   */
  ParamListInfo
  copyParamList(ParamListInfo from)
***************
*** 40,93 ****
          (from->numParams - 1) *sizeof(ParamExternData);

      retval = (ParamListInfo) palloc(size);
!     memcpy(retval, from, size);

!     /*
!      * Flat-copy is not good enough for pass-by-ref data values, so make a
!      * pass over the array to copy those.
!      */
!     for (i = 0; i < retval->numParams; i++)
      {
!         ParamExternData *prm = &retval->params[i];
          int16        typLen;
          bool        typByVal;

!         if (prm->isnull || !OidIsValid(prm->ptype))
              continue;
!         get_typlenbyval(prm->ptype, &typLen, &typByVal);
!         prm->value = datumCopy(prm->value, typByVal, typLen);
      }

      return retval;
  }

  /*
!  * Extract an array of parameter type OIDs from a ParamListInfo.
   *
!  * The result is allocated in CurrentMemoryContext.
   */
  void
! getParamListTypes(ParamListInfo params,
!                   Oid **param_types, int *num_params)
  {
!     Oid           *ptypes;
!     int            i;

!     if (params == NULL || params->numParams <= 0)
      {
!         *param_types = NULL;
!         *num_params = 0;
          return;
      }

!     ptypes = (Oid *) palloc(params->numParams * sizeof(Oid));
!     *param_types = ptypes;
!     *num_params = params->numParams;
!
!     for (i = 0; i < params->numParams; i++)
      {
!         ParamExternData *prm = ¶ms->params[i];

!         ptypes[i] = prm->ptype;
      }
  }
--- 46,119 ----
          (from->numParams - 1) *sizeof(ParamExternData);

      retval = (ParamListInfo) palloc(size);
!     retval->paramFetch = NULL;
!     retval->paramFetchArg = NULL;
!     retval->parserSetup = NULL;
!     retval->parserSetupArg = NULL;
!     retval->numParams = from->numParams;

!     for (i = 0; i < from->numParams; i++)
      {
!         ParamExternData *oprm = &from->params[i];
!         ParamExternData *nprm = &retval->params[i];
          int16        typLen;
          bool        typByVal;

!         /* 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;
!
!         /* need datumCopy in case it's a pass-by-reference datatype */
!         if (nprm->isnull || !OidIsValid(nprm->ptype))
              continue;
!         get_typlenbyval(nprm->ptype, &typLen, &typByVal);
!         nprm->value = datumCopy(nprm->value, typByVal, typLen);
      }

      return retval;
  }

  /*
!  * Set up the parser to treat the given list of run-time parameter values
!  * as available external parameters during parsing of a new query.
   *
!  * Note that it's assumed that the input struct and any referenced by-ref
!  * parameter values will survive until the end of parsing.
   */
  void
! setupParserWithParams(struct ParseState *pstate,
!                       ParamListInfo params)
  {
!     if (params == NULL)            /* no params, nothing to do */
!         return;

!     /* If there is a parserSetup hook, it gets to do this */
!     if (params->parserSetup != NULL)
      {
!         (*params->parserSetup) (pstate, params->parserSetupArg);
          return;
      }

!     /* Else, treat any available parameters as being of fixed type */
!     if (params->numParams > 0)
      {
!         Oid           *ptypes;
!         int            i;

!         ptypes = (Oid *) palloc(params->numParams * sizeof(Oid));
!         for (i = 0; i < params->numParams; i++)
!         {
!             ParamExternData *prm = ¶ms->params[i];
!
!             /* give hook a chance in case parameter is dynamic */
!             if (!OidIsValid(prm->ptype) && params->paramFetch != NULL)
!                 (*params->paramFetch) (params, i+1);
!
!             ptypes[i] = prm->ptype;
!         }
!         parse_fixed_parameters(pstate, ptypes, params->numParams);
      }
  }
Index: src/backend/tcop/postgres.c
===================================================================
RCS file: /cvsroot/pgsql/src/backend/tcop/postgres.c,v
retrieving revision 1.574
diff -c -r1.574 postgres.c
*** src/backend/tcop/postgres.c    8 Oct 2009 22:34:57 -0000    1.574
--- src/backend/tcop/postgres.c    4 Nov 2009 16:05:29 -0000
***************
*** 628,633 ****
--- 628,679 ----
  }

  /*
+  * Do parse analysis and rewriting.  This is the same as pg_analyze_and_rewrite
+  * except that external-parameter resolution is determined by parser callback
+  * hooks instead of a fixed list of parameter datatypes.
+  */
+ List *
+ pg_analyze_and_rewrite_params(Node *parsetree,
+                               const char *query_string,
+                               ParserSetupHook parserSetup,
+                               void *parserSetupArg)
+ {
+     ParseState *pstate;
+     Query       *query;
+     List       *querytree_list;
+
+     Assert(query_string != NULL); /* required as of 8.4 */
+
+     TRACE_POSTGRESQL_QUERY_REWRITE_START(query_string);
+
+     /*
+      * (1) Perform parse analysis.
+      */
+     if (log_parser_stats)
+         ResetUsage();
+
+     pstate = make_parsestate(NULL);
+     pstate->p_sourcetext = query_string;
+     (*parserSetup) (pstate, parserSetupArg);
+
+     query = transformStmt(pstate, parsetree);
+
+     free_parsestate(pstate);
+
+     if (log_parser_stats)
+         ShowUsage("PARSE ANALYSIS STATISTICS");
+
+     /*
+      * (2) Rewrite the queries, as necessary
+      */
+     querytree_list = pg_rewrite_query(query);
+
+     TRACE_POSTGRESQL_QUERY_REWRITE_DONE(query_string);
+
+     return querytree_list;
+ }
+
+ /*
   * Perform rewriting of a query produced by parse analysis.
   *
   * Note: query must just have come from the parser, because we do not do
***************
*** 1536,1541 ****
--- 1582,1592 ----
          /* sizeof(ParamListInfoData) includes the first array element */
          params = (ParamListInfo) palloc(sizeof(ParamListInfoData) +
                                     (numParams - 1) *sizeof(ParamExternData));
+         /* 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;

          for (paramno = 0; paramno < numParams; paramno++)
Index: src/backend/utils/cache/plancache.c
===================================================================
RCS file: /cvsroot/pgsql/src/backend/utils/cache/plancache.c,v
retrieving revision 1.30
diff -c -r1.30 plancache.c
*** src/backend/utils/cache/plancache.c    26 Oct 2009 02:26:41 -0000    1.30
--- src/backend/utils/cache/plancache.c    4 Nov 2009 16:05:29 -0000
***************
*** 99,106 ****
   * raw_parse_tree: output of raw_parser()
   * query_string: original query text (as of PG 8.4, must not be NULL)
   * commandTag: compile-time-constant tag for query, or NULL if empty query
!  * param_types: array of parameter type OIDs, or NULL if none
!  * num_params: number of parameters
   * cursor_options: options bitmask that was/will be passed to planner
   * stmt_list: list of PlannedStmts/utility stmts, or list of Query trees
   * fully_planned: are we caching planner or rewriter output?
--- 99,106 ----
   * raw_parse_tree: output of raw_parser()
   * query_string: original query text (as of PG 8.4, must not be NULL)
   * commandTag: compile-time-constant tag for query, or NULL if empty query
!  * param_types: array of fixed parameter type OIDs, or NULL if none
!  * num_params: number of fixed parameters
   * cursor_options: options bitmask that was/will be passed to planner
   * stmt_list: list of PlannedStmts/utility stmts, or list of Query trees
   * fully_planned: are we caching planner or rewriter output?
***************
*** 156,161 ****
--- 156,164 ----
      else
          plansource->param_types = NULL;
      plansource->num_params = num_params;
+     /* For historical reasons, these can be set later with CachedPlanSetHook */
+     plansource->parserSetup = NULL;
+     plansource->parserSetupArg = NULL;
      plansource->cursor_options = cursor_options;
      plansource->fully_planned = fully_planned;
      plansource->fixed_result = fixed_result;
***************
*** 240,245 ****
--- 243,251 ----
      plansource->commandTag = commandTag;        /* no copying needed */
      plansource->param_types = param_types;
      plansource->num_params = num_params;
+     /* For historical reasons, these can be set later with CachedPlanSetHook */
+     plansource->parserSetup = NULL;
+     plansource->parserSetupArg = NULL;
      plansource->cursor_options = cursor_options;
      plansource->fully_planned = fully_planned;
      plansource->fixed_result = fixed_result;
***************
*** 275,280 ****
--- 281,307 ----
  }

  /*
+  * CachedPlanSetHook: set up to use parser callback hooks
+  *
+  * Use this when a caller wants to manage parameter information via parser
+  * callbacks rather than a fixed parameter-types list.  Beware that the
+  * information pointed to by parserSetupArg must be valid for as long as
+  * the cached plan might be replanned!
+  */
+ void
+ CachedPlanSetHook(CachedPlanSource *plansource,
+                   ParserSetupHook parserSetup,
+                   void *parserSetupArg)
+ {
+     /* Must not have specified a fixed parameter-types list */
+     Assert(plansource->param_types == NULL);
+     Assert(plansource->num_params == 0);
+     /* OK, save hook info */
+     plansource->parserSetup = parserSetup;
+     plansource->parserSetupArg = parserSetupArg;
+ }
+
+ /*
   * StoreCachedPlan: store a built or rebuilt plan into a plancache entry.
   *
   * Common subroutine for CreateCachedPlan and RevalidateCachedPlan.
***************
*** 466,471 ****
--- 493,499 ----
      if (!plan)
      {
          bool        snapshot_set = false;
+         Node       *rawtree;
          List       *slist;
          TupleDesc    resultDesc;

***************
*** 491,504 ****
          /*
           * Run parse analysis and rule rewriting.  The parser tends to
           * scribble on its input, so we must copy the raw parse tree to
!          * prevent corruption of the cache.  Note that we do not use
!          * parse_analyze_varparams(), assuming that the caller never wants the
!          * parameter types to change from the original values.
!          */
!         slist = pg_analyze_and_rewrite(copyObject(plansource->raw_parse_tree),
!                                        plansource->query_string,
!                                        plansource->param_types,
!                                        plansource->num_params);

          if (plansource->fully_planned)
          {
--- 519,537 ----
          /*
           * Run parse analysis and rule rewriting.  The parser tends to
           * scribble on its input, so we must copy the raw parse tree to
!          * prevent corruption of the cache.
!          */
!         rawtree = copyObject(plansource->raw_parse_tree);
!         if (plansource->parserSetup != NULL)
!             slist = pg_analyze_and_rewrite_params(rawtree,
!                                                   plansource->query_string,
!                                                   plansource->parserSetup,
!                                                   plansource->parserSetupArg);
!         else
!             slist = pg_analyze_and_rewrite(rawtree,
!                                            plansource->query_string,
!                                            plansource->param_types,
!                                            plansource->num_params);

          if (plansource->fully_planned)
          {
Index: src/include/executor/spi.h
===================================================================
RCS file: /cvsroot/pgsql/src/include/executor/spi.h,v
retrieving revision 1.72
diff -c -r1.72 spi.h
*** src/include/executor/spi.h    11 Jun 2009 14:49:11 -0000    1.72
--- src/include/executor/spi.h    4 Nov 2009 16:05:29 -0000
***************
*** 73,78 ****
--- 73,81 ----
  extern int    SPI_execute(const char *src, bool read_only, long tcount);
  extern int SPI_execute_plan(SPIPlanPtr plan, Datum *Values, const char *Nulls,
                   bool read_only, long tcount);
+ extern int SPI_execute_plan_with_paramlist(SPIPlanPtr plan,
+                                            ParamListInfo params,
+                                            bool read_only, long tcount);
  extern int    SPI_exec(const char *src, long tcount);
  extern int SPI_execp(SPIPlanPtr plan, Datum *Values, const char *Nulls,
            long tcount);
***************
*** 88,93 ****
--- 91,100 ----
  extern SPIPlanPtr SPI_prepare(const char *src, int nargs, Oid *argtypes);
  extern SPIPlanPtr SPI_prepare_cursor(const char *src, int nargs, Oid *argtypes,
                     int cursorOptions);
+ extern SPIPlanPtr SPI_prepare_with_hook(const char *src,
+                       ParserSetupHook parserSetup,
+                       void *parserSetupArg,
+                       int cursorOptions);
  extern SPIPlanPtr SPI_saveplan(SPIPlanPtr plan);
  extern int    SPI_freeplan(SPIPlanPtr plan);

***************
*** 122,127 ****
--- 129,136 ----
                            int nargs, Oid *argtypes,
                            Datum *Values, const char *Nulls,
                            bool read_only, int cursorOptions);
+ extern Portal SPI_cursor_open_with_paramlist(const char *name, SPIPlanPtr plan,
+                                ParamListInfo params, bool read_only);
  extern Portal SPI_cursor_find(const char *name);
  extern void SPI_cursor_fetch(Portal portal, bool forward, long count);
  extern void SPI_cursor_move(Portal portal, bool forward, long count);
Index: src/include/executor/spi_priv.h
===================================================================
RCS file: /cvsroot/pgsql/src/include/executor/spi_priv.h,v
retrieving revision 1.32
diff -c -r1.32 spi_priv.h
*** src/include/executor/spi_priv.h    1 Jan 2009 17:23:59 -0000    1.32
--- src/include/executor/spi_priv.h    4 Nov 2009 16:05:29 -0000
***************
*** 68,73 ****
--- 68,75 ----
      int            cursor_options; /* Cursor options used for planning */
      int            nargs;            /* number of plan arguments */
      Oid           *argtypes;        /* Argument types (NULL if nargs is 0) */
+     ParserSetupHook parserSetup;    /* alternative parameter spec method */
+     void       *parserSetupArg;
  } _SPI_plan;

  #endif   /* SPI_PRIV_H */
Index: src/include/nodes/params.h
===================================================================
RCS file: /cvsroot/pgsql/src/include/nodes/params.h,v
retrieving revision 1.38
diff -c -r1.38 params.h
*** src/include/nodes/params.h    1 Jan 2009 17:24:00 -0000    1.38
--- src/include/nodes/params.h    4 Nov 2009 16:05:29 -0000
***************
*** 14,19 ****
--- 14,24 ----
  #ifndef PARAMS_H
  #define PARAMS_H

+ /* To avoid including a pile of parser headers, reference ParseState thus: */
+ struct ParseState;
+
+ typedef void (*ParserSetupHook) (struct ParseState *pstate, void *arg);
+

  /* ----------------
   *      ParamListInfo
***************
*** 26,35 ****
--- 31,50 ----
   *      Although parameter numbers are normally consecutive, we allow
   *      ptype == InvalidOid to signal an unused array entry.
   *
+  *      pflags is a flags field.  Currently the only used bit is:
   *      PARAM_FLAG_CONST signals the planner that it may treat this parameter
   *      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.
   * ----------------
***************
*** 45,58 ****
      Oid            ptype;            /* parameter's datatype, or 0 */
  } ParamExternData;

  typedef struct ParamListInfoData
  {
      int            numParams;        /* number of ParamExternDatas following */
      ParamExternData params[1];    /* VARIABLE LENGTH ARRAY */
  } ParamListInfoData;

- typedef ParamListInfoData *ParamListInfo;
-

  /* ----------------
   *      ParamExecData
--- 60,79 ----
      Oid            ptype;            /* parameter's datatype, or 0 */
  } ParamExternData;

+ typedef struct ParamListInfoData *ParamListInfo;
+
+ typedef void (*ParamFetchHook) (ParamListInfo params, int paramid);
+
  typedef struct ParamListInfoData
  {
+     ParamFetchHook paramFetch;    /* parameter fetch hook */
+     void       *paramFetchArg;
+     ParserSetupHook parserSetup; /* parser setup hook */
+     void       *parserSetupArg;
      int            numParams;        /* number of ParamExternDatas following */
      ParamExternData params[1];    /* VARIABLE LENGTH ARRAY */
  } ParamListInfoData;


  /* ----------------
   *      ParamExecData
***************
*** 82,88 ****
  /* Functions found in src/backend/nodes/params.c */
  extern ParamListInfo copyParamList(ParamListInfo from);

! extern void getParamListTypes(ParamListInfo params,
!                   Oid **param_types, int *num_params);

  #endif   /* PARAMS_H */
--- 103,109 ----
  /* Functions found in src/backend/nodes/params.c */
  extern ParamListInfo copyParamList(ParamListInfo from);

! extern void setupParserWithParams(struct ParseState *pstate,
!                                   ParamListInfo params);

  #endif   /* PARAMS_H */
Index: src/include/tcop/tcopprot.h
===================================================================
RCS file: /cvsroot/pgsql/src/include/tcop/tcopprot.h,v
retrieving revision 1.100
diff -c -r1.100 tcopprot.h
*** src/include/tcop/tcopprot.h    1 Sep 2009 00:09:42 -0000    1.100
--- src/include/tcop/tcopprot.h    4 Nov 2009 16:05:29 -0000
***************
*** 49,54 ****
--- 49,58 ----
  extern List *pg_parse_query(const char *query_string);
  extern List *pg_analyze_and_rewrite(Node *parsetree, const char *query_string,
                         Oid *paramTypes, int numParams);
+ extern List *pg_analyze_and_rewrite_params(Node *parsetree,
+                                            const char *query_string,
+                                            ParserSetupHook parserSetup,
+                                            void *parserSetupArg);
  extern PlannedStmt *pg_plan_query(Query *querytree, int cursorOptions,
                ParamListInfo boundParams);
  extern List *pg_plan_queries(List *querytrees, int cursorOptions,
Index: src/include/utils/plancache.h
===================================================================
RCS file: /cvsroot/pgsql/src/include/utils/plancache.h,v
retrieving revision 1.15
diff -c -r1.15 plancache.h
*** src/include/utils/plancache.h    1 Jan 2009 17:24:02 -0000    1.15
--- src/include/utils/plancache.h    4 Nov 2009 16:05:29 -0000
***************
*** 16,21 ****
--- 16,22 ----
  #define PLANCACHE_H

  #include "access/tupdesc.h"
+ #include "nodes/params.h"

  /*
   * CachedPlanSource represents the portion of a cached plan that persists
***************
*** 50,55 ****
--- 51,58 ----
      const char *commandTag;        /* command tag (a constant!), or NULL */
      Oid           *param_types;    /* array of parameter type OIDs, or NULL */
      int            num_params;        /* length of param_types array */
+     ParserSetupHook parserSetup;    /* alternative parameter spec method */
+     void       *parserSetupArg;
      int            cursor_options; /* cursor options used for planning */
      bool        fully_planned;    /* do we cache planner or rewriter output? */
      bool        fixed_result;    /* disallow change in result tupdesc? */
***************
*** 105,110 ****
--- 108,116 ----
                       bool fully_planned,
                       bool fixed_result,
                       MemoryContext context);
+ extern void CachedPlanSetHook(CachedPlanSource *plansource,
+                               ParserSetupHook parserSetup,
+                               void *parserSetupArg);
  extern void DropCachedPlan(CachedPlanSource *plansource);
  extern CachedPlan *RevalidateCachedPlan(CachedPlanSource *plansource,
                       bool useResOwner);
Index: src/pl/plpgsql/src/gram.y
===================================================================
RCS file: /cvsroot/pgsql/src/pl/plpgsql/src/gram.y,v
retrieving revision 1.128
diff -c -r1.128 gram.y
*** src/pl/plpgsql/src/gram.y    29 Sep 2009 20:05:29 -0000    1.128
--- src/pl/plpgsql/src/gram.y    4 Nov 2009 16:05:29 -0000
***************
*** 109,115 ****
          }                        loop_body;
          List                    *list;
          PLpgSQL_type            *dtype;
!         PLpgSQL_datum            *scalar;    /* a VAR, RECFIELD, or TRIGARG */
          PLpgSQL_variable        *variable;    /* a VAR, REC, or ROW */
          PLpgSQL_var                *var;
          PLpgSQL_row                *row;
--- 109,115 ----
          }                        loop_body;
          List                    *list;
          PLpgSQL_type            *dtype;
!         PLpgSQL_datum            *scalar;    /* a VAR or RECFIELD */
          PLpgSQL_variable        *variable;    /* a VAR, REC, or ROW */
          PLpgSQL_var                *var;
          PLpgSQL_row                *row;
***************
*** 236,242 ****
           */
  %token    T_STRING
  %token    T_NUMBER
! %token    T_SCALAR                /* a VAR, RECFIELD, or TRIGARG */
  %token    T_ROW
  %token    T_RECORD
  %token    T_DTYPE
--- 236,242 ----
           */
  %token    T_STRING
  %token    T_NUMBER
! %token    T_SCALAR                /* a VAR or RECFIELD */
  %token    T_ROW
  %token    T_RECORD
  %token    T_DTYPE
***************
*** 1903,1946 ****
  %%


- #define MAX_EXPR_PARAMS  1024
-
- /*
-  * determine the expression parameter position to use for a plpgsql datum
-  *
-  * It is important that any given plpgsql datum map to just one parameter.
-  * We used to be sloppy and assign a separate parameter for each occurrence
-  * of a datum reference, but that fails for situations such as "select DATUM
-  * from ... group by DATUM".
-  *
-  * The params[] array must be of size MAX_EXPR_PARAMS.
-  */
- static int
- assign_expr_param(int dno, int *params, int *nparams)
- {
-     int        i;
-
-     /* already have an instance of this dno? */
-     for (i = 0; i < *nparams; i++)
-     {
-         if (params[i] == dno)
-             return i+1;
-     }
-     /* check for array overflow */
-     if (*nparams >= MAX_EXPR_PARAMS)
-     {
-         plpgsql_error_lineno = plpgsql_scanner_lineno();
-         ereport(ERROR,
-                 (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
-                  errmsg("too many variables specified in SQL statement")));
-     }
-     /* add new parameter dno to array */
-     params[*nparams] = dno;
-     (*nparams)++;
-     return *nparams;
- }
-
-
  /* Convenience routine to read an expression with one possible terminator */
  PLpgSQL_expr *
  plpgsql_read_expression(int until, const char *expected)
--- 1903,1908 ----
***************
*** 1993,2000 ****
      int                    lno;
      StringInfoData        ds;
      int                    parenlevel = 0;
!     int                    nparams = 0;
!     int                    params[MAX_EXPR_PARAMS];
      char                buf[32];
      PLpgSQL_expr        *expr;

--- 1955,1961 ----
      int                    lno;
      StringInfoData        ds;
      int                    parenlevel = 0;
!     Bitmapset           *paramnos = NULL;
      char                buf[32];
      PLpgSQL_expr        *expr;

***************
*** 2047,2070 ****
          switch (tok)
          {
              case T_SCALAR:
!                 snprintf(buf, sizeof(buf), " $%d ",
!                          assign_expr_param(yylval.scalar->dno,
!                                            params, &nparams));
                  appendStringInfoString(&ds, buf);
                  break;

              case T_ROW:
!                 snprintf(buf, sizeof(buf), " $%d ",
!                          assign_expr_param(yylval.row->dno,
!                                            params, &nparams));
                  appendStringInfoString(&ds, buf);
                  break;

              case T_RECORD:
!                 snprintf(buf, sizeof(buf), " $%d ",
!                          assign_expr_param(yylval.rec->dno,
!                                            params, &nparams));
                  appendStringInfoString(&ds, buf);
                  break;

              default:
--- 2008,2028 ----
          switch (tok)
          {
              case T_SCALAR:
!                 snprintf(buf, sizeof(buf), " $%d ", yylval.scalar->dno + 1);
                  appendStringInfoString(&ds, buf);
+                 paramnos = bms_add_member(paramnos, yylval.scalar->dno);
                  break;

              case T_ROW:
!                 snprintf(buf, sizeof(buf), " $%d ", yylval.row->dno + 1);
                  appendStringInfoString(&ds, buf);
+                 paramnos = bms_add_member(paramnos, yylval.row->dno);
                  break;

              case T_RECORD:
!                 snprintf(buf, sizeof(buf), " $%d ", yylval.rec->dno + 1);
                  appendStringInfoString(&ds, buf);
+                 paramnos = bms_add_member(paramnos, yylval.rec->dno);
                  break;

              default:
***************
*** 2076,2088 ****
      if (endtoken)
          *endtoken = tok;

!     expr = palloc(sizeof(PLpgSQL_expr) + sizeof(int) * nparams - sizeof(int));
      expr->dtype            = PLPGSQL_DTYPE_EXPR;
      expr->query            = pstrdup(ds.data);
      expr->plan            = NULL;
!     expr->nparams        = nparams;
!     while(nparams-- > 0)
!         expr->params[nparams] = params[nparams];
      pfree(ds.data);

      if (valid_sql)
--- 2034,2044 ----
      if (endtoken)
          *endtoken = tok;

!     expr = palloc0(sizeof(PLpgSQL_expr));
      expr->dtype            = PLPGSQL_DTYPE_EXPR;
      expr->query            = pstrdup(ds.data);
      expr->plan            = NULL;
!     expr->paramnos        = paramnos;
      pfree(ds.data);

      if (valid_sql)
***************
*** 2162,2169 ****
  make_execsql_stmt(const char *sqlstart, int lineno)
  {
      StringInfoData        ds;
!     int                    nparams = 0;
!     int                    params[MAX_EXPR_PARAMS];
      char                buf[32];
      PLpgSQL_stmt_execsql *execsql;
      PLpgSQL_expr        *expr;
--- 2118,2124 ----
  make_execsql_stmt(const char *sqlstart, int lineno)
  {
      StringInfoData        ds;
!     Bitmapset           *paramnos = NULL;
      char                buf[32];
      PLpgSQL_stmt_execsql *execsql;
      PLpgSQL_expr        *expr;
***************
*** 2214,2237 ****
          switch (tok)
          {
              case T_SCALAR:
!                 snprintf(buf, sizeof(buf), " $%d ",
!                          assign_expr_param(yylval.scalar->dno,
!                                            params, &nparams));
                  appendStringInfoString(&ds, buf);
                  break;

              case T_ROW:
!                 snprintf(buf, sizeof(buf), " $%d ",
!                          assign_expr_param(yylval.row->dno,
!                                            params, &nparams));
                  appendStringInfoString(&ds, buf);
                  break;

              case T_RECORD:
!                 snprintf(buf, sizeof(buf), " $%d ",
!                          assign_expr_param(yylval.rec->dno,
!                                            params, &nparams));
                  appendStringInfoString(&ds, buf);
                  break;

              default:
--- 2169,2189 ----
          switch (tok)
          {
              case T_SCALAR:
!                 snprintf(buf, sizeof(buf), " $%d ", yylval.scalar->dno + 1);
                  appendStringInfoString(&ds, buf);
+                 paramnos = bms_add_member(paramnos, yylval.scalar->dno);
                  break;

              case T_ROW:
!                 snprintf(buf, sizeof(buf), " $%d ", yylval.row->dno + 1);
                  appendStringInfoString(&ds, buf);
+                 paramnos = bms_add_member(paramnos, yylval.row->dno);
                  break;

              case T_RECORD:
!                 snprintf(buf, sizeof(buf), " $%d ", yylval.rec->dno + 1);
                  appendStringInfoString(&ds, buf);
+                 paramnos = bms_add_member(paramnos, yylval.rec->dno);
                  break;

              default:
***************
*** 2240,2252 ****
          }
      }

!     expr = palloc(sizeof(PLpgSQL_expr) + sizeof(int) * nparams - sizeof(int));
      expr->dtype            = PLPGSQL_DTYPE_EXPR;
      expr->query            = pstrdup(ds.data);
      expr->plan            = NULL;
!     expr->nparams        = nparams;
!     while(nparams-- > 0)
!         expr->params[nparams] = params[nparams];
      pfree(ds.data);

      check_sql_expr(expr->query);
--- 2192,2202 ----
          }
      }

!     expr = palloc0(sizeof(PLpgSQL_expr));
      expr->dtype            = PLPGSQL_DTYPE_EXPR;
      expr->query            = pstrdup(ds.data);
      expr->plan            = NULL;
!     expr->paramnos        = paramnos;
      pfree(ds.data);

      check_sql_expr(expr->query);
***************
*** 2600,2608 ****
          case PLPGSQL_DTYPE_ARRAYELEM:
              /* always assignable? */
              break;
-         case PLPGSQL_DTYPE_TRIGARG:
-             yyerror("cannot assign to tg_argv");
-             break;
          default:
              elog(ERROR, "unrecognized dtype: %d", datum->dtype);
              break;
--- 2550,2555 ----
***************
*** 3095,3118 ****
          {
              PLpgSQL_case_when *cwt = (PLpgSQL_case_when *) lfirst(l);
              PLpgSQL_expr *expr = cwt->expr;
-             int        nparams = expr->nparams;
-             PLpgSQL_expr *new_expr;
              StringInfoData    ds;

              /* Must add the CASE variable as an extra param to expression */
!             if (nparams >= MAX_EXPR_PARAMS)
!             {
!                 plpgsql_error_lineno = cwt->lineno;
!                 ereport(ERROR,
!                         (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
!                          errmsg("too many variables specified in SQL statement")));
!             }
!
!             new_expr = palloc(sizeof(PLpgSQL_expr) + sizeof(int) * (nparams + 1) - sizeof(int));
!             memcpy(new_expr, expr,
!                    sizeof(PLpgSQL_expr) + sizeof(int) * nparams - sizeof(int));
!             new_expr->nparams = nparams + 1;
!             new_expr->params[nparams] = t_varno;

              /* copy expression query without SELECT keyword (expr->query + 7) */
              Assert(strncmp(expr->query, "SELECT ", 7) == 0);
--- 3042,3051 ----
          {
              PLpgSQL_case_when *cwt = (PLpgSQL_case_when *) lfirst(l);
              PLpgSQL_expr *expr = cwt->expr;
              StringInfoData    ds;

              /* Must add the CASE variable as an extra param to expression */
!             expr->paramnos = bms_add_member(expr->paramnos, t_varno);

              /* copy expression query without SELECT keyword (expr->query + 7) */
              Assert(strncmp(expr->query, "SELECT ", 7) == 0);
***************
*** 3120,3136 ****
              /* And do the string hacking */
              initStringInfo(&ds);

!             appendStringInfo(&ds, "SELECT $%d IN(%s)",
!                                 nparams + 1,
!                                 expr->query + 7);

-             new_expr->query = pstrdup(ds.data);
-
-             pfree(ds.data);
              pfree(expr->query);
!             pfree(expr);

!             cwt->expr = new_expr;
          }
      }

--- 3053,3066 ----
              /* And do the string hacking */
              initStringInfo(&ds);

!             appendStringInfo(&ds, "SELECT $%d IN (%s)",
!                              t_varno + 1,
!                              expr->query + 7);

              pfree(expr->query);
!             expr->query = pstrdup(ds.data);

!             pfree(ds.data);
          }
      }

Index: src/pl/plpgsql/src/pl_comp.c
===================================================================
RCS file: /cvsroot/pgsql/src/pl/plpgsql/src/pl_comp.c,v
retrieving revision 1.139
diff -c -r1.139 pl_comp.c
*** src/pl/plpgsql/src/pl_comp.c    22 Sep 2009 23:43:42 -0000    1.139
--- src/pl/plpgsql/src/pl_comp.c    4 Nov 2009 16:05:29 -0000
***************
*** 624,643 ****
                                           true);
              function->tg_table_name_varno = var->dno;

!
!             /* add variable tg_table_schema */
              var = plpgsql_build_variable("tg_table_schema", 0,
                                           plpgsql_build_datatype(NAMEOID, -1),
                                           true);
              function->tg_table_schema_varno = var->dno;

-
              /* Add the variable tg_nargs */
              var = plpgsql_build_variable("tg_nargs", 0,
                                           plpgsql_build_datatype(INT4OID, -1),
                                           true);
              function->tg_nargs_varno = var->dno;

              break;

          default:
--- 624,647 ----
                                           true);
              function->tg_table_name_varno = var->dno;

!             /* add the variable tg_table_schema */
              var = plpgsql_build_variable("tg_table_schema", 0,
                                           plpgsql_build_datatype(NAMEOID, -1),
                                           true);
              function->tg_table_schema_varno = var->dno;

              /* Add the variable tg_nargs */
              var = plpgsql_build_variable("tg_nargs", 0,
                                           plpgsql_build_datatype(INT4OID, -1),
                                           true);
              function->tg_nargs_varno = var->dno;

+             /* Add the variable tg_argv */
+             var = plpgsql_build_variable("tg_argv", 0,
+                                          plpgsql_build_datatype(TEXTARRAYOID, -1),
+                                          true);
+             function->tg_argv_varno = var->dno;
+
              break;

          default:
***************
*** 932,965 ****
      plpgsql_convert_ident(word, cp, 1);

      /*
-      * Recognize tg_argv when compiling triggers (XXX this sucks, it should be
-      * a regular variable in the namestack)
-      */
-     if (plpgsql_curr_compile->fn_is_trigger)
-     {
-         if (strcmp(cp[0], "tg_argv") == 0)
-         {
-             bool        save_spacescanned = plpgsql_SpaceScanned;
-             PLpgSQL_trigarg *trigarg;
-
-             trigarg = palloc0(sizeof(PLpgSQL_trigarg));
-             trigarg->dtype = PLPGSQL_DTYPE_TRIGARG;
-
-             if (plpgsql_yylex() != '[')
-                 plpgsql_yyerror("expected \"[\"");
-
-             trigarg->argnum = plpgsql_read_expression(']', "]");
-
-             plpgsql_adddatum((PLpgSQL_datum *) trigarg);
-             plpgsql_yylval.scalar = (PLpgSQL_datum *) trigarg;
-
-             plpgsql_SpaceScanned = save_spacescanned;
-             pfree(cp[0]);
-             return T_SCALAR;
-         }
-     }
-
-     /*
       * Do a lookup on the compiler's namestack
       */
      nse = plpgsql_ns_lookup(cp[0], NULL, NULL, NULL);
--- 936,941 ----
Index: src/pl/plpgsql/src/pl_exec.c
===================================================================
RCS file: /cvsroot/pgsql/src/pl/plpgsql/src/pl_exec.c,v
retrieving revision 1.248
diff -c -r1.248 pl_exec.c
*** src/pl/plpgsql/src/pl_exec.c    6 Aug 2009 20:44:31 -0000    1.248
--- src/pl/plpgsql/src/pl_exec.c    4 Nov 2009 16:05:29 -0000
***************
*** 26,31 ****
--- 26,32 ----
  #include "lib/stringinfo.h"
  #include "miscadmin.h"
  #include "nodes/nodeFuncs.h"
+ #include "parser/parse_node.h"
  #include "parser/scansup.h"
  #include "storage/proc.h"
  #include "tcop/tcopprot.h"
***************
*** 154,163 ****
                    Datum value, Oid valtype, bool *isNull);
  static void exec_eval_datum(PLpgSQL_execstate *estate,
                  PLpgSQL_datum *datum,
-                 Oid expectedtypeid,
                  Oid *typeid,
                  Datum *value,
                  bool *isnull);
  static int exec_eval_integer(PLpgSQL_execstate *estate,
                    PLpgSQL_expr *expr,
                    bool *isNull);
--- 155,165 ----
                    Datum value, Oid valtype, bool *isNull);
  static void exec_eval_datum(PLpgSQL_execstate *estate,
                  PLpgSQL_datum *datum,
                  Oid *typeid,
                  Datum *value,
                  bool *isnull);
+ static Oid exec_get_datum_type(PLpgSQL_execstate *estate,
+                                PLpgSQL_datum *datum);
  static int exec_eval_integer(PLpgSQL_execstate *estate,
                    PLpgSQL_expr *expr,
                    bool *isNull);
***************
*** 172,179 ****
                  PLpgSQL_expr *expr, long maxtuples, Portal *portalP);
  static int exec_for_query(PLpgSQL_execstate *estate, PLpgSQL_stmt_forq *stmt,
                 Portal portal, bool prefetch_ok);
! static void eval_expr_params(PLpgSQL_execstate *estate,
!                  PLpgSQL_expr *expr, Datum **p_values, char **p_nulls);
  static void exec_move_row(PLpgSQL_execstate *estate,
                PLpgSQL_rec *rec,
                PLpgSQL_row *row,
--- 174,184 ----
                  PLpgSQL_expr *expr, long maxtuples, Portal *portalP);
  static int exec_for_query(PLpgSQL_execstate *estate, PLpgSQL_stmt_forq *stmt,
                 Portal portal, bool prefetch_ok);
! static ParamListInfo setup_param_list(PLpgSQL_execstate *estate,
!                                       PLpgSQL_expr *expr);
! static void plpgsql_parser_setup(ParseState *pstate, PLpgSQL_expr *expr);
! static Node *plpgsql_param_ref(ParseState *pstate, ParamRef *pref);
! static void plpgsql_param_fetch(ParamListInfo params, int paramid);
  static void exec_move_row(PLpgSQL_execstate *estate,
                PLpgSQL_rec *rec,
                PLpgSQL_row *row,
***************
*** 514,525 ****
--- 519,535 ----

      /*
       * Put the OLD and NEW tuples into record variables
+      *
+      * We make the tupdescs available in both records even though only one
+      * may have a value.  This allows parsing of record references to succeed.
       */
      rec_new = (PLpgSQL_rec *) (estate.datums[func->new_varno]);
      rec_new->freetup = false;
+     rec_new->tupdesc = trigdata->tg_relation->rd_att;
      rec_new->freetupdesc = false;
      rec_old = (PLpgSQL_rec *) (estate.datums[func->old_varno]);
      rec_old->freetup = false;
+     rec_old->tupdesc = trigdata->tg_relation->rd_att;
      rec_old->freetupdesc = false;

      if (TRIGGER_FIRED_FOR_STATEMENT(trigdata->tg_event))
***************
*** 528,557 ****
           * Per-statement triggers don't use OLD/NEW variables
           */
          rec_new->tup = NULL;
-         rec_new->tupdesc = NULL;
          rec_old->tup = NULL;
-         rec_old->tupdesc = NULL;
      }
      else if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
      {
          rec_new->tup = trigdata->tg_trigtuple;
-         rec_new->tupdesc = trigdata->tg_relation->rd_att;
          rec_old->tup = NULL;
-         rec_old->tupdesc = NULL;
      }
      else if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
      {
          rec_new->tup = trigdata->tg_newtuple;
-         rec_new->tupdesc = trigdata->tg_relation->rd_att;
          rec_old->tup = trigdata->tg_trigtuple;
-         rec_old->tupdesc = trigdata->tg_relation->rd_att;
      }
      else if (TRIGGER_FIRED_BY_DELETE(trigdata->tg_event))
      {
          rec_new->tup = NULL;
-         rec_new->tupdesc = NULL;
          rec_old->tup = trigdata->tg_trigtuple;
-         rec_old->tupdesc = trigdata->tg_relation->rd_att;
      }
      else
          elog(ERROR, "unrecognized trigger action: not INSERT, DELETE, or UPDATE");
--- 538,559 ----
***************
*** 631,649 ****
      var->isnull = false;
      var->freeval = false;

!     /*
!      * Store the trigger argument values into the special execution state
!      * variables
!      */
!     estate.err_text = gettext_noop("while storing call arguments into local variables");
!     estate.trig_nargs = trigdata->tg_trigger->tgnargs;
!     if (estate.trig_nargs == 0)
!         estate.trig_argv = NULL;
      else
      {
!         estate.trig_argv = palloc(sizeof(Datum) * estate.trig_nargs);
!         for (i = 0; i < trigdata->tg_trigger->tgnargs; i++)
!             estate.trig_argv[i] = CStringGetTextDatum(trigdata->tg_trigger->tgargs[i]);
      }

      estate.err_text = gettext_noop("during function entry");
--- 633,668 ----
      var->isnull = false;
      var->freeval = false;

!     var = (PLpgSQL_var *) (estate.datums[func->tg_argv_varno]);
!     if (trigdata->tg_trigger->tgnargs > 0)
!     {
!         /*
!          * For historical reasons, tg_argv[] subscripts start at zero not one.
!          * So we can't use construct_array().
!          */
!         int            nelems = trigdata->tg_trigger->tgnargs;
!         Datum       *elems;
!         int            dims[1];
!         int            lbs[1];
!
!         elems = palloc(sizeof(Datum) * nelems);
!         for (i = 0; i < nelems; i++)
!             elems[i] = CStringGetTextDatum(trigdata->tg_trigger->tgargs[i]);
!         dims[0] = nelems;
!         lbs[0] = 0;
!
!         var->value = PointerGetDatum(construct_md_array(elems, NULL,
!                                                         1, dims, lbs,
!                                                         TEXTOID,
!                                                         -1, false, 'i'));
!         var->isnull = false;
!         var->freeval = true;
!     }
      else
      {
!         var->value = (Datum) 0;
!         var->isnull = true;
!         var->freeval = false;
      }

      estate.err_text = gettext_noop("during function entry");
***************
*** 756,765 ****
  {
      PLpgSQL_execstate *estate = (PLpgSQL_execstate *) arg;

-     /* safety check, shouldn't happen */
-     if (estate->err_func == NULL)
-         return;
-
      /* if we are doing RAISE, don't report its location */
      if (estate->err_text == raise_skip_msg)
          return;
--- 775,780 ----
***************
*** 784,790 ****
               * local variable initialization"
               */
              errcontext("PL/pgSQL function \"%s\" line %d %s",
!                        estate->err_func->fn_name,
                         estate->err_stmt->lineno,
                         _(estate->err_text));
          }
--- 799,805 ----
               * local variable initialization"
               */
              errcontext("PL/pgSQL function \"%s\" line %d %s",
!                        estate->func->fn_name,
                         estate->err_stmt->lineno,
                         _(estate->err_text));
          }
***************
*** 795,801 ****
               * arguments into local variables"
               */
              errcontext("PL/pgSQL function \"%s\" %s",
!                        estate->err_func->fn_name,
                         _(estate->err_text));
          }
      }
--- 810,816 ----
               * arguments into local variables"
               */
              errcontext("PL/pgSQL function \"%s\" %s",
!                        estate->func->fn_name,
                         _(estate->err_text));
          }
      }
***************
*** 803,815 ****
      {
          /* translator: last %s is a plpgsql statement type name */
          errcontext("PL/pgSQL function \"%s\" line %d at %s",
!                    estate->err_func->fn_name,
                     estate->err_stmt->lineno,
                     plpgsql_stmt_typename(estate->err_stmt));
      }
      else
          errcontext("PL/pgSQL function \"%s\"",
!                    estate->err_func->fn_name);
  }


--- 818,830 ----
      {
          /* translator: last %s is a plpgsql statement type name */
          errcontext("PL/pgSQL function \"%s\" line %d at %s",
!                    estate->func->fn_name,
                     estate->err_stmt->lineno,
                     plpgsql_stmt_typename(estate->err_stmt));
      }
      else
          errcontext("PL/pgSQL function \"%s\"",
!                    estate->func->fn_name);
  }


***************
*** 856,862 ****
          case PLPGSQL_DTYPE_ROW:
          case PLPGSQL_DTYPE_RECFIELD:
          case PLPGSQL_DTYPE_ARRAYELEM:
-         case PLPGSQL_DTYPE_TRIGARG:

              /*
               * These datum records are read-only at runtime, so no need to
--- 871,876 ----
***************
*** 977,986 ****
                      if (rec->freetup)
                      {
                          heap_freetuple(rec->tup);
-                         FreeTupleDesc(rec->tupdesc);
                          rec->freetup = false;
                      }
!
                      rec->tup = NULL;
                      rec->tupdesc = NULL;
                  }
--- 991,1003 ----
                      if (rec->freetup)
                      {
                          heap_freetuple(rec->tup);
                          rec->freetup = false;
                      }
!                     if (rec->freetupdesc)
!                     {
!                         FreeTupleDesc(rec->tupdesc);
!                         rec->freetupdesc = false;
!                     }
                      rec->tup = NULL;
                      rec->tupdesc = NULL;
                  }
***************
*** 1885,1894 ****
      PLpgSQL_var *curvar;
      char       *curname = NULL;
      PLpgSQL_expr *query;
      Portal        portal;
      int            rc;
-     Datum       *values;
-     char       *nulls;

      /* ----------
       * Get the cursor variable and if it has an assigned name, check
--- 1902,1910 ----
      PLpgSQL_var *curvar;
      char       *curname = NULL;
      PLpgSQL_expr *query;
+     ParamListInfo paramLI;
      Portal        portal;
      int            rc;

      /* ----------
       * Get the cursor variable and if it has an assigned name, check
***************
*** 1954,1972 ****
          exec_prepare_plan(estate, query, curvar->cursor_options);

      /*
!      * Now build up the values and nulls arguments for SPI_execute_plan()
       */
!     eval_expr_params(estate, query, &values, &nulls);

      /*
!      * Open the cursor
       */
!     portal = SPI_cursor_open(curname, query->plan, values, nulls,
!                              estate->readonly_func);
      if (portal == NULL)
          elog(ERROR, "could not open cursor: %s",
               SPI_result_code_string(SPI_result));

      /*
       * If cursor variable was NULL, store the generated portal name in it
       */
--- 1970,1994 ----
          exec_prepare_plan(estate, query, curvar->cursor_options);

      /*
!      * Set up ParamListInfo (note this is only carrying a hook function,
!      * not any actual data values, at this point)
       */
!     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,
!                                             estate->readonly_func);
      if (portal == NULL)
          elog(ERROR, "could not open cursor: %s",
               SPI_result_code_string(SPI_result));

+     /* don't need paramlist any more */
+     if (paramLI)
+         pfree(paramLI);
+
      /*
       * If cursor variable was NULL, store the generated portal name in it
       */
***************
*** 1992,1999 ****
          curvar->isnull = true;
      }

-     pfree(values);
-     pfree(nulls);
      if (curname)
          pfree(curname);

--- 2014,2019 ----
***************
*** 2599,2604 ****
--- 2619,2629 ----
                       PLpgSQL_function *func,
                       ReturnSetInfo *rsi)
  {
+     /* this link will be restored at exit from plpgsql_call_handler */
+     func->cur_estate = estate;
+
+     estate->func = func;
+
      estate->retval = (Datum) 0;
      estate->retisnull = true;
      estate->rettype = InvalidOid;
***************
*** 2616,2624 ****
      estate->tuple_store_cxt = NULL;
      estate->rsi = rsi;

-     estate->trig_nargs = 0;
-     estate->trig_argv = NULL;
-
      estate->found_varno = func->found_varno;
      estate->ndatums = func->ndatums;
      estate->datums = palloc(sizeof(PLpgSQL_datum *) * estate->ndatums);
--- 2641,2646 ----
***************
*** 2627,2637 ****
      estate->eval_tuptable = NULL;
      estate->eval_processed = 0;
      estate->eval_lastoid = InvalidOid;

-     estate->err_func = func;
      estate->err_stmt = NULL;
      estate->err_text = NULL;

      /*
       * Create an EState and ExprContext for evaluation of simple expressions.
       */
--- 2649,2662 ----
      estate->eval_tuptable = NULL;
      estate->eval_processed = 0;
      estate->eval_lastoid = InvalidOid;
+     estate->eval_econtext = NULL;
+     estate->cur_expr = NULL;

      estate->err_stmt = NULL;
      estate->err_text = NULL;

+     estate->plugin_info = NULL;
+
      /*
       * Create an EState and ExprContext for evaluation of simple expressions.
       */
***************
*** 2682,2712 ****
  exec_prepare_plan(PLpgSQL_execstate *estate,
                    PLpgSQL_expr *expr, int cursorOptions)
  {
-     int            i;
      SPIPlanPtr    plan;
-     Oid           *argtypes;

      /*
!      * We need a temporary argtypes array to load with data. (The finished
!      * plan structure will contain a copy of it.)
       */
!     argtypes = (Oid *) palloc(expr->nparams * sizeof(Oid));
!
!     for (i = 0; i < expr->nparams; i++)
!     {
!         Datum        paramval;
!         bool        paramisnull;
!
!         exec_eval_datum(estate, estate->datums[expr->params[i]],
!                         InvalidOid,
!                         &argtypes[i], ¶mval, ¶misnull);
!     }

      /*
       * Generate and save the plan
       */
!     plan = SPI_prepare_cursor(expr->query, expr->nparams, argtypes,
!                               cursorOptions);
      if (plan == NULL)
      {
          /* Some SPI errors deserve specific error messages */
--- 2707,2727 ----
  exec_prepare_plan(PLpgSQL_execstate *estate,
                    PLpgSQL_expr *expr, int cursorOptions)
  {
      SPIPlanPtr    plan;

      /*
!      * The grammar can't conveniently set expr->func while building the
!      * parse tree, so make sure it's set before parser hooks need it.
       */
!     expr->func = estate->func;

      /*
       * Generate and save the plan
       */
!     plan = SPI_prepare_with_hook(expr->query,
!                                  (ParserSetupHook) plpgsql_parser_setup,
!                                  (void *) expr,
!                                  cursorOptions);
      if (plan == NULL)
      {
          /* Some SPI errors deserve specific error messages */
***************
*** 2722,2738 ****
                           errmsg("cannot begin/end transactions in PL/pgSQL"),
                           errhint("Use a BEGIN block with an EXCEPTION clause instead.")));
              default:
!                 elog(ERROR, "SPI_prepare_cursor failed for \"%s\": %s",
                       expr->query, SPI_result_code_string(SPI_result));
          }
      }
      expr->plan = SPI_saveplan(plan);
      SPI_freeplan(plan);
-     plan = expr->plan;
-     expr->plan_argtypes = plan->argtypes;
      exec_simple_check_plan(expr);
-
-     pfree(argtypes);
  }


--- 2737,2749 ----
                           errmsg("cannot begin/end transactions in PL/pgSQL"),
                           errhint("Use a BEGIN block with an EXCEPTION clause instead.")));
              default:
!                 elog(ERROR, "SPI_prepare_with_hook failed for \"%s\": %s",
                       expr->query, SPI_result_code_string(SPI_result));
          }
      }
      expr->plan = SPI_saveplan(plan);
      SPI_freeplan(plan);
      exec_simple_check_plan(expr);
  }


***************
*** 2744,2751 ****
  exec_stmt_execsql(PLpgSQL_execstate *estate,
                    PLpgSQL_stmt_execsql *stmt)
  {
!     Datum       *values;
!     char       *nulls;
      long        tcount;
      int            rc;
      PLpgSQL_expr *expr = stmt->sqlstmt;
--- 2755,2761 ----
  exec_stmt_execsql(PLpgSQL_execstate *estate,
                    PLpgSQL_stmt_execsql *stmt)
  {
!     ParamListInfo paramLI;
      long        tcount;
      int            rc;
      PLpgSQL_expr *expr = stmt->sqlstmt;
***************
*** 2782,2790 ****
      }

      /*
!      * Now build up the values and nulls arguments for SPI_execute_plan()
       */
!     eval_expr_params(estate, expr, &values, &nulls);

      /*
       * If we have INTO, then we only need one row back ... but if we have INTO
--- 2792,2801 ----
      }

      /*
!      * Set up ParamListInfo (note this is only carrying a hook function,
!      * not any actual data values, at this point)
       */
!     paramLI = setup_param_list(estate, expr);

      /*
       * If we have INTO, then we only need one row back ... but if we have INTO
***************
*** 2810,2817 ****
      /*
       * Execute the plan
       */
!     rc = SPI_execute_plan(expr->plan, values, nulls,
!                           estate->readonly_func, tcount);

      /*
       * Check for error, and set FOUND if appropriate (for historical reasons
--- 2821,2828 ----
      /*
       * Execute the plan
       */
!     rc = SPI_execute_plan_with_paramlist(expr->plan, paramLI,
!                                          estate->readonly_func, tcount);

      /*
       * Check for error, and set FOUND if appropriate (for historical reasons
***************
*** 2919,2926 ****
                       (rc == SPI_OK_SELECT) ? errhint("If you want to discard the results of a SELECT, use PERFORM
instead."): 0)); 
      }

!     pfree(values);
!     pfree(nulls);

      return PLPGSQL_RC_OK;
  }
--- 2930,2937 ----
                       (rc == SPI_OK_SELECT) ? errhint("If you want to discard the results of a SELECT, use PERFORM
instead."): 0)); 
      }

!     if (paramLI)
!         pfree(paramLI);

      return PLPGSQL_RC_OK;
  }
***************
*** 3142,3149 ****
      char       *curname = NULL;
      PLpgSQL_expr *query;
      Portal        portal;
!     Datum       *values;
!     char       *nulls;
      bool        isnull;

      /* ----------
--- 3153,3159 ----
      char       *curname = NULL;
      PLpgSQL_expr *query;
      Portal        portal;
!     ParamListInfo paramLI;
      bool        isnull;

      /* ----------
***************
*** 3280,3294 ****
      }

      /*
!      * Now build up the values and nulls arguments for SPI_execute_plan()
       */
!     eval_expr_params(estate, query, &values, &nulls);

      /*
       * Open the cursor
       */
!     portal = SPI_cursor_open(curname, query->plan, values, nulls,
!                              estate->readonly_func);
      if (portal == NULL)
          elog(ERROR, "could not open cursor: %s",
               SPI_result_code_string(SPI_result));
--- 3290,3306 ----
      }

      /*
!      * Set up ParamListInfo (note this is only carrying a hook function,
!      * not any actual data values, at this point)
       */
!     paramLI = setup_param_list(estate, query);

      /*
       * Open the cursor
       */
!     portal = SPI_cursor_open_with_paramlist(curname, query->plan,
!                                             paramLI,
!                                             estate->readonly_func);
      if (portal == NULL)
          elog(ERROR, "could not open cursor: %s",
               SPI_result_code_string(SPI_result));
***************
*** 3299,3308 ****
      if (curname == NULL)
          assign_text_var(curvar, portal->name);

-     pfree(values);
-     pfree(nulls);
      if (curname)
          pfree(curname);

      return PLPGSQL_RC_OK;
  }
--- 3311,3320 ----
      if (curname == NULL)
          assign_text_var(curvar, portal->name);

      if (curname)
          pfree(curname);
+     if (paramLI)
+         pfree(paramLI);

      return PLPGSQL_RC_OK;
  }
***************
*** 3755,3761 ****
                  } while (target->dtype == PLPGSQL_DTYPE_ARRAYELEM);

                  /* Fetch current value of array datum */
!                 exec_eval_datum(estate, target, InvalidOid,
                                &arraytypeid, &oldarraydatum, &oldarrayisnull);

                  arrayelemtypeid = get_element_type(arraytypeid);
--- 3767,3773 ----
                  } while (target->dtype == PLPGSQL_DTYPE_ARRAYELEM);

                  /* Fetch current value of array datum */
!                 exec_eval_datum(estate, target,
                                &arraytypeid, &oldarraydatum, &oldarrayisnull);

                  arrayelemtypeid = get_element_type(arraytypeid);
***************
*** 3860,3867 ****
   *
   * The type oid, value in Datum format, and null flag are returned.
   *
-  * If expectedtypeid isn't InvalidOid, it is checked against the actual type.
-  *
   * At present this doesn't handle PLpgSQL_expr or PLpgSQL_arrayelem datums.
   *
   * NOTE: caller must not modify the returned value, since it points right
--- 3872,3877 ----
***************
*** 3872,3878 ****
  static void
  exec_eval_datum(PLpgSQL_execstate *estate,
                  PLpgSQL_datum *datum,
-                 Oid expectedtypeid,
                  Oid *typeid,
                  Datum *value,
                  bool *isnull)
--- 3882,3887 ----
***************
*** 3888,3898 ****
                  *typeid = var->datatype->typoid;
                  *value = var->value;
                  *isnull = var->isnull;
-                 if (expectedtypeid != InvalidOid && expectedtypeid != *typeid)
-                     ereport(ERROR,
-                             (errcode(ERRCODE_DATATYPE_MISMATCH),
-                              errmsg("type of \"%s\" does not match that when preparing the plan",
-                                     var->refname)));
                  break;
              }

--- 3897,3902 ----
***************
*** 3913,3923 ****
                  *typeid = row->rowtupdesc->tdtypeid;
                  *value = HeapTupleGetDatum(tup);
                  *isnull = false;
-                 if (expectedtypeid != InvalidOid && expectedtypeid != *typeid)
-                     ereport(ERROR,
-                             (errcode(ERRCODE_DATATYPE_MISMATCH),
-                              errmsg("type of \"%s\" does not match that when preparing the plan",
-                                     row->refname)));
                  break;
              }

--- 3917,3922 ----
***************
*** 3950,3960 ****
                  *typeid = rec->tupdesc->tdtypeid;
                  *value = HeapTupleGetDatum(&worktup);
                  *isnull = false;
-                 if (expectedtypeid != InvalidOid && expectedtypeid != *typeid)
-                     ereport(ERROR,
-                             (errcode(ERRCODE_DATATYPE_MISMATCH),
-                              errmsg("type of \"%s\" does not match that when preparing the plan",
-                                     rec->refname)));
                  break;
              }

--- 3949,3954 ----
***************
*** 3979,4020 ****
                                      rec->refname, recfield->fieldname)));
                  *typeid = SPI_gettypeid(rec->tupdesc, fno);
                  *value = SPI_getbinval(rec->tup, rec->tupdesc, fno, isnull);
!                 if (expectedtypeid != InvalidOid && expectedtypeid != *typeid)
                      ereport(ERROR,
!                             (errcode(ERRCODE_DATATYPE_MISMATCH),
!                              errmsg("type of \"%s.%s\" does not match that when preparing the plan",
!                                     rec->refname, recfield->fieldname)));
                  break;
              }

!         case PLPGSQL_DTYPE_TRIGARG:
              {
!                 PLpgSQL_trigarg *trigarg = (PLpgSQL_trigarg *) datum;
!                 int            tgargno;

!                 *typeid = TEXTOID;
!                 tgargno = exec_eval_integer(estate, trigarg->argnum, isnull);
!                 if (*isnull || tgargno < 0 || tgargno >= estate->trig_nargs)
!                 {
!                     *value = (Datum) 0;
!                     *isnull = true;
!                 }
!                 else
!                 {
!                     *value = estate->trig_argv[tgargno];
!                     *isnull = false;
!                 }
!                 if (expectedtypeid != InvalidOid && expectedtypeid != *typeid)
                      ereport(ERROR,
!                             (errcode(ERRCODE_DATATYPE_MISMATCH),
!                              errmsg("type of tg_argv[%d] does not match that when preparing the plan",
!                                     tgargno)));
                  break;
              }

          default:
              elog(ERROR, "unrecognized dtype: %d", datum->dtype);
      }
  }

  /* ----------
--- 3973,4066 ----
                                      rec->refname, recfield->fieldname)));
                  *typeid = SPI_gettypeid(rec->tupdesc, fno);
                  *value = SPI_getbinval(rec->tup, rec->tupdesc, fno, isnull);
!                 break;
!             }
!
!         default:
!             elog(ERROR, "unrecognized dtype: %d", datum->dtype);
!     }
! }
!
! /*
!  * exec_get_datum_type                Get datatype of a PLpgSQL_datum
!  *
!  * This is the same logic as in exec_eval_datum, except that it can handle
!  * some cases where exec_eval_datum has to fail.
!  */
! static Oid
! exec_get_datum_type(PLpgSQL_execstate *estate,
!                     PLpgSQL_datum *datum)
! {
!     Oid            typeid;
!
!     switch (datum->dtype)
!     {
!         case PLPGSQL_DTYPE_VAR:
!             {
!                 PLpgSQL_var *var = (PLpgSQL_var *) datum;
!
!                 typeid = var->datatype->typoid;
!                 break;
!             }
!
!         case PLPGSQL_DTYPE_ROW:
!             {
!                 PLpgSQL_row *row = (PLpgSQL_row *) datum;
!
!                 if (!row->rowtupdesc)    /* should not happen */
!                     elog(ERROR, "row variable has no tupdesc");
!                 /* Make sure we have a valid type/typmod setting */
!                 BlessTupleDesc(row->rowtupdesc);
!                 typeid = row->rowtupdesc->tdtypeid;
!                 break;
!             }
!
!         case PLPGSQL_DTYPE_REC:
!             {
!                 PLpgSQL_rec *rec = (PLpgSQL_rec *) datum;
!
!                 if (rec->tupdesc == NULL)
                      ereport(ERROR,
!                           (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
!                            errmsg("record \"%s\" is not assigned yet",
!                                   rec->refname),
!                            errdetail("The tuple structure of a not-yet-assigned record is indeterminate.")));
!                 /* Make sure we have a valid type/typmod setting */
!                 BlessTupleDesc(rec->tupdesc);
!                 typeid = rec->tupdesc->tdtypeid;
                  break;
              }

!         case PLPGSQL_DTYPE_RECFIELD:
              {
!                 PLpgSQL_recfield *recfield = (PLpgSQL_recfield *) datum;
!                 PLpgSQL_rec *rec;
!                 int            fno;

!                 rec = (PLpgSQL_rec *) (estate->datums[recfield->recparentno]);
!                 if (rec->tupdesc == NULL)
                      ereport(ERROR,
!                           (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
!                            errmsg("record \"%s\" is not assigned yet",
!                                   rec->refname),
!                            errdetail("The tuple structure of a not-yet-assigned record is indeterminate.")));
!                 fno = SPI_fnumber(rec->tupdesc, recfield->fieldname);
!                 if (fno == SPI_ERROR_NOATTRIBUTE)
!                     ereport(ERROR,
!                             (errcode(ERRCODE_UNDEFINED_COLUMN),
!                              errmsg("record \"%s\" has no field \"%s\"",
!                                     rec->refname, recfield->fieldname)));
!                 typeid = SPI_gettypeid(rec->tupdesc, fno);
                  break;
              }

          default:
              elog(ERROR, "unrecognized dtype: %d", datum->dtype);
+             typeid = InvalidOid;            /* keep compiler quiet */
+             break;
      }
+
+     return typeid;
  }

  /* ----------
***************
*** 4145,4152 ****
  exec_run_select(PLpgSQL_execstate *estate,
                  PLpgSQL_expr *expr, long maxtuples, Portal *portalP)
  {
!     Datum       *values;
!     char       *nulls;
      int            rc;

      /*
--- 4191,4197 ----
  exec_run_select(PLpgSQL_execstate *estate,
                  PLpgSQL_expr *expr, long maxtuples, Portal *portalP)
  {
!     ParamListInfo paramLI;
      int            rc;

      /*
***************
*** 4156,4185 ****
          exec_prepare_plan(estate, expr, 0);

      /*
!      * Now build up the values and nulls arguments for SPI_execute_plan()
       */
!     eval_expr_params(estate, expr, &values, &nulls);

      /*
       * If a portal was requested, put the query into the portal
       */
      if (portalP != NULL)
      {
!         *portalP = SPI_cursor_open(NULL, expr->plan, values, nulls,
!                                    estate->readonly_func);
          if (*portalP == NULL)
              elog(ERROR, "could not open implicit cursor for query \"%s\": %s",
                   expr->query, SPI_result_code_string(SPI_result));
!         pfree(values);
!         pfree(nulls);
          return SPI_OK_CURSOR;
      }

      /*
       * Execute the query
       */
!     rc = SPI_execute_plan(expr->plan, values, nulls,
!                           estate->readonly_func, maxtuples);
      if (rc != SPI_OK_SELECT)
          ereport(ERROR,
                  (errcode(ERRCODE_SYNTAX_ERROR),
--- 4201,4232 ----
          exec_prepare_plan(estate, expr, 0);

      /*
!      * Set up ParamListInfo (note this is only carrying a hook function,
!      * not any actual data values, at this point)
       */
!     paramLI = setup_param_list(estate, expr);

      /*
       * If a portal was requested, put the query into the portal
       */
      if (portalP != NULL)
      {
!         *portalP = SPI_cursor_open_with_paramlist(NULL, expr->plan,
!                                                   paramLI,
!                                                   estate->readonly_func);
          if (*portalP == NULL)
              elog(ERROR, "could not open implicit cursor for query \"%s\": %s",
                   expr->query, SPI_result_code_string(SPI_result));
!         if (paramLI)
!             pfree(paramLI);
          return SPI_OK_CURSOR;
      }

      /*
       * Execute the query
       */
!     rc = SPI_execute_plan_with_paramlist(expr->plan, paramLI,
!                                          estate->readonly_func, maxtuples);
      if (rc != SPI_OK_SELECT)
          ereport(ERROR,
                  (errcode(ERRCODE_SYNTAX_ERROR),
***************
*** 4191,4198 ****
      estate->eval_processed = SPI_processed;
      estate->eval_lastoid = SPI_lastoid;

!     pfree(values);
!     pfree(nulls);

      return rc;
  }
--- 4238,4245 ----
      estate->eval_processed = SPI_processed;
      estate->eval_lastoid = SPI_lastoid;

!     if (paramLI)
!         pfree(paramLI);

      return rc;
  }
***************
*** 4378,4384 ****
      CachedPlanSource *plansource;
      CachedPlan *cplan;
      ParamListInfo paramLI;
!     int            i;
      MemoryContext oldcontext;

      /*
--- 4425,4431 ----
      CachedPlanSource *plansource;
      CachedPlan *cplan;
      ParamListInfo paramLI;
!     PLpgSQL_expr *save_cur_expr;
      MemoryContext oldcontext;

      /*
***************
*** 4426,4467 ****
      }

      /*
-      * Param list can live in econtext's temporary memory context.
-      *
-      * XXX think about avoiding repeated palloc's for param lists? Beware
-      * however that this routine is re-entrant: exec_eval_datum() can call it
-      * back for subscript evaluation, and so there can be a need to have more
-      * than one active param list.
-      */
-     if (expr->nparams > 0)
-     {
-         /* sizeof(ParamListInfoData) includes the first array element */
-         paramLI = (ParamListInfo)
-             MemoryContextAlloc(econtext->ecxt_per_tuple_memory,
-                                sizeof(ParamListInfoData) +
-                                (expr->nparams - 1) *sizeof(ParamExternData));
-         paramLI->numParams = expr->nparams;
-
-         for (i = 0; i < expr->nparams; i++)
-         {
-             ParamExternData *prm = ¶mLI->params[i];
-             PLpgSQL_datum *datum = estate->datums[expr->params[i]];
-
-             prm->pflags = 0;
-             exec_eval_datum(estate, datum, expr->plan_argtypes[i],
-                             &prm->ptype,
-                             &prm->value, &prm->isnull);
-         }
-     }
-     else
-         paramLI = NULL;
-
-     /*
-      * Now we can safely make the econtext point to the param list.
-      */
-     econtext->ecxt_param_list_info = paramLI;
-
-     /*
       * We have to do some of the things SPI_execute_plan would do, in
       * particular advance the snapshot if we are in a non-read-only function.
       * Without this, stable functions within the expression would fail to see
--- 4473,4478 ----
***************
*** 4477,4493 ****
      }

      /*
       * Finally we can call the executor to evaluate the expression
       */
      *result = ExecEvalExpr(expr->expr_simple_state,
                             econtext,
                             isNull,
                             NULL);
!     MemoryContextSwitchTo(oldcontext);

      if (!estate->readonly_func)
          PopActiveSnapshot();

      SPI_pop();

      /*
--- 4488,4524 ----
      }

      /*
+      * Create the param list in econtext's temporary memory context.
+      * We won't need to free it explicitly, since it will go away at the
+      * next reset of that context.
+      *
+      * XXX think about avoiding repeated palloc's for param lists?  It should
+      * be possible --- this routine isn't re-entrant anymore.
+      *
+      * Just for paranoia's sake, save and restore the prior value of
+      * estate->cur_expr, which setup_param_list() sets.
+      */
+     save_cur_expr = estate->cur_expr;
+
+     paramLI = setup_param_list(estate, expr);
+     econtext->ecxt_param_list_info = paramLI;
+
+     /*
       * Finally we can call the executor to evaluate the expression
       */
      *result = ExecEvalExpr(expr->expr_simple_state,
                             econtext,
                             isNull,
                             NULL);
!
!     /* Assorted cleanup */
!     estate->cur_expr = save_cur_expr;

      if (!estate->readonly_func)
          PopActiveSnapshot();

+     MemoryContextSwitchTo(oldcontext);
+
      SPI_pop();

      /*
***************
*** 4503,4534 ****


  /*
!  * Build up the values and nulls arguments for SPI_execute_plan()
   */
! static void
! eval_expr_params(PLpgSQL_execstate *estate,
!                  PLpgSQL_expr *expr, Datum **p_values, char **p_nulls)
  {
!     Datum       *values;
!     char       *nulls;
!     int            i;
!
!     *p_values = values = (Datum *) palloc(expr->nparams * sizeof(Datum));
!     *p_nulls = nulls = (char *) palloc(expr->nparams * sizeof(char));

!     for (i = 0; i < expr->nparams; i++)
      {
!         PLpgSQL_datum *datum = estate->datums[expr->params[i]];
!         Oid            paramtypeid;
!         bool        paramisnull;
!
!         exec_eval_datum(estate, datum, expr->plan_argtypes[i],
!                         ¶mtypeid, &values[i], ¶misnull);
!         if (paramisnull)
!             nulls[i] = 'n';
!         else
!             nulls[i] = ' ';
      }
  }


--- 4534,4664 ----


  /*
!  * Create a ParamListInfo to pass to SPI
!  *
!  * The ParamListInfo array is initially all zeroes, in particular the
!  * ptype values are all InvalidOid.  This causes the executor to call the
!  * paramFetch hook each time it wants a value.  We thus evaluate only the
!  * parameters actually demanded.
!  *
!  * The result is a locally palloc'd array that should be pfree'd after use;
!  * but note it can be NULL.
   */
! static ParamListInfo
! setup_param_list(PLpgSQL_execstate *estate, PLpgSQL_expr *expr)
  {
!     ParamListInfo paramLI;

!     /*
!      * Could we re-use these arrays instead of palloc'ing a new one each
!      * time?  However, we'd have to zero the array each time anyway,
!      * since new values might have been assigned to the variables.
!      */
!     if (estate->ndatums > 0)
      {
!         /* sizeof(ParamListInfoData) includes the first array element */
!         paramLI = (ParamListInfo)
!             palloc0(sizeof(ParamListInfoData) +
!                     (estate->ndatums - 1) * sizeof(ParamExternData));
!         paramLI->paramFetch = plpgsql_param_fetch;
!         paramLI->paramFetchArg = (void *) estate;
!         paramLI->parserSetup = (ParserSetupHook) plpgsql_parser_setup;
!         paramLI->parserSetupArg = (void *) expr;
!         /* make sure this is set before parser hooks need it */
!         expr->func = estate->func;
!         paramLI->numParams = estate->ndatums;
!
!         /*
!          * Set up link to active expr where the hook functions can find it.
!          * Callers must save and restore cur_expr if there is any chance
!          * that they are interrupting an active use of parameters.
!          */
!         estate->cur_expr = expr;
      }
+     else
+         paramLI = NULL;
+     return paramLI;
+ }
+
+ /*
+  * plpgsql_parser_setup        set up parser hooks for dynamic parameters
+  */
+ static void
+ plpgsql_parser_setup(ParseState *pstate, PLpgSQL_expr *expr)
+ {
+     pstate->p_ref_hook_state = (void *) expr;
+     pstate->p_paramref_hook = plpgsql_param_ref;
+     /* no need to use p_coerce_param_hook */
+ }
+
+ /*
+  * plpgsql_param_ref        parser callback for ParamRefs ($n symbols)
+  */
+ static Node *
+ plpgsql_param_ref(ParseState *pstate, ParamRef *pref)
+ {
+     int            paramno = pref->number;
+     PLpgSQL_expr *expr = (PLpgSQL_expr *) pstate->p_ref_hook_state;
+     PLpgSQL_execstate *estate;
+     Param       *param;
+
+     /* Let's just check parameter number is in range */
+     if (!bms_is_member(paramno-1, expr->paramnos))
+         return NULL;
+
+     /*
+      * We use the function's current estate to resolve parameter data types.
+      * This is really pretty bogus because there is no provision for updating
+      * plans when those types change ...
+      */
+     estate = expr->func->cur_estate;
+     Assert(paramno <= estate->ndatums);
+
+     param = makeNode(Param);
+     param->paramkind = PARAM_EXTERN;
+     param->paramid = paramno;
+     param->paramtype = exec_get_datum_type(estate,
+                                            estate->datums[paramno-1]);
+     param->paramtypmod = -1;
+     param->location = pref->location;
+
+     return (Node *) param;
+ }
+
+ /*
+  * 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;
+
+     /* paramid's are 1-based, but dnos are 0-based */
+     dno = paramid - 1;
+     Assert(dno >= 0 && dno < params->numParams);
+
+     /* fetch back the hook data */
+     estate = (PLpgSQL_execstate *) params->paramFetchArg;
+     expr = estate->cur_expr;
+     Assert(params->numParams == estate->ndatums);
+
+     /*
+      * Do nothing if asked for a value that's not supposed to be used by
+      * this SQL expression.  This avoids unwanted evaluations when functions
+      * such as copyParamList try to materialize all the values.
+      */
+     if (!bms_is_member(dno, expr->paramnos))
+         return;
+
+     /* OK, evaluate the value and store into the appropriate paramlist slot */
+     datum = estate->datums[dno];
+     prm = ¶ms->params[dno];
+     exec_eval_datum(estate, datum,
+                     &prm->ptype, &prm->value, &prm->isnull);
  }


***************
*** 4710,4716 ****
              elog(ERROR, "dropped rowtype entry for non-dropped column");

          exec_eval_datum(estate, estate->datums[row->varnos[i]],
!                         InvalidOid, &fieldtypeid, &dvalues[i], &nulls[i]);
          if (fieldtypeid != tupdesc->attrs[i]->atttypid)
              return NULL;
      }
--- 4840,4846 ----
              elog(ERROR, "dropped rowtype entry for non-dropped column");

          exec_eval_datum(estate, estate->datums[row->varnos[i]],
!                         &fieldtypeid, &dvalues[i], &nulls[i]);
          if (fieldtypeid != tupdesc->attrs[i]->atttypid)
              return NULL;
      }
Index: src/pl/plpgsql/src/pl_funcs.c
===================================================================
RCS file: /cvsroot/pgsql/src/pl/plpgsql/src/pl_funcs.c,v
retrieving revision 1.81
diff -c -r1.81 pl_funcs.c
*** src/pl/plpgsql/src/pl_funcs.c    29 Sep 2009 20:05:29 -0000    1.81
--- src/pl/plpgsql/src/pl_funcs.c    4 Nov 2009 16:05:29 -0000
***************
*** 1148,1168 ****
  static void
  dump_expr(PLpgSQL_expr *expr)
  {
!     int            i;
!
!     printf("'%s", expr->query);
!     if (expr->nparams > 0)
!     {
!         printf(" {");
!         for (i = 0; i < expr->nparams; i++)
!         {
!             if (i > 0)
!                 printf(", ");
!             printf("$%d=%d", i + 1, expr->params[i]);
!         }
!         printf("}");
!     }
!     printf("'");
  }

  void
--- 1148,1154 ----
  static void
  dump_expr(PLpgSQL_expr *expr)
  {
!     printf("'%s'", expr->query);
  }

  void
***************
*** 1240,1250 ****
                  dump_expr(((PLpgSQL_arrayelem *) d)->subscript);
                  printf("\n");
                  break;
-             case PLPGSQL_DTYPE_TRIGARG:
-                 printf("TRIGARG ");
-                 dump_expr(((PLpgSQL_trigarg *) d)->argnum);
-                 printf("\n");
-                 break;
              default:
                  printf("??? unknown data type %d\n", d->dtype);
          }
--- 1226,1231 ----
Index: src/pl/plpgsql/src/pl_handler.c
===================================================================
RCS file: /cvsroot/pgsql/src/pl/plpgsql/src/pl_handler.c,v
retrieving revision 1.46
diff -c -r1.46 pl_handler.c
*** src/pl/plpgsql/src/pl_handler.c    22 Sep 2009 23:43:42 -0000    1.46
--- src/pl/plpgsql/src/pl_handler.c    4 Nov 2009 16:05:29 -0000
***************
*** 68,73 ****
--- 68,74 ----
  plpgsql_call_handler(PG_FUNCTION_ARGS)
  {
      PLpgSQL_function *func;
+     PLpgSQL_execstate *save_cur_estate;
      Datum        retval;
      int            rc;

***************
*** 80,85 ****
--- 81,89 ----
      /* Find or compile the function */
      func = plpgsql_compile(fcinfo, false);

+     /* Must save and restore prior value of cur_estate */
+     save_cur_estate = func->cur_estate;
+
      /* Mark the function as busy, so it can't be deleted from under us */
      func->use_count++;

***************
*** 97,110 ****
      }
      PG_CATCH();
      {
!         /* Decrement use-count and propagate error */
          func->use_count--;
          PG_RE_THROW();
      }
      PG_END_TRY();

      func->use_count--;

      /*
       * Disconnect from SPI manager
       */
--- 101,117 ----
      }
      PG_CATCH();
      {
!         /* Decrement use-count, restore cur_estate, and propagate error */
          func->use_count--;
+         func->cur_estate = save_cur_estate;
          PG_RE_THROW();
      }
      PG_END_TRY();

      func->use_count--;

+     func->cur_estate = save_cur_estate;
+
      /*
       * Disconnect from SPI manager
       */
Index: src/pl/plpgsql/src/plpgsql.h
===================================================================
RCS file: /cvsroot/pgsql/src/pl/plpgsql/src/plpgsql.h,v
retrieving revision 1.117
diff -c -r1.117 plpgsql.h
*** src/pl/plpgsql/src/plpgsql.h    29 Sep 2009 20:05:29 -0000    1.117
--- src/pl/plpgsql/src/plpgsql.h    4 Nov 2009 16:05:29 -0000
***************
*** 22,27 ****
--- 22,28 ----
  #include "fmgr.h"
  #include "commands/trigger.h"
  #include "executor/spi.h"
+ #include "nodes/bitmapset.h"
  #include "utils/tuplestore.h"

  /**********************************************************************
***************
*** 58,65 ****
      PLPGSQL_DTYPE_REC,
      PLPGSQL_DTYPE_RECFIELD,
      PLPGSQL_DTYPE_ARRAYELEM,
!     PLPGSQL_DTYPE_EXPR,
!     PLPGSQL_DTYPE_TRIGARG
  };

  /* ----------
--- 59,65 ----
      PLPGSQL_DTYPE_REC,
      PLPGSQL_DTYPE_RECFIELD,
      PLPGSQL_DTYPE_ARRAYELEM,
!     PLPGSQL_DTYPE_EXPR
  };

  /* ----------
***************
*** 162,169 ****

  /*
   * PLpgSQL_datum is the common supertype for PLpgSQL_expr, PLpgSQL_var,
!  * PLpgSQL_row, PLpgSQL_rec, PLpgSQL_recfield, PLpgSQL_arrayelem, and
!  * PLpgSQL_trigarg
   */
  typedef struct
  {                                /* Generic datum array item        */
--- 162,168 ----

  /*
   * PLpgSQL_datum is the common supertype for PLpgSQL_expr, PLpgSQL_var,
!  * PLpgSQL_row, PLpgSQL_rec, PLpgSQL_recfield, and PLpgSQL_arrayelem
   */
  typedef struct
  {                                /* Generic datum array item        */
***************
*** 189,195 ****
      int            dno;
      char       *query;
      SPIPlanPtr    plan;
!     Oid           *plan_argtypes;
      /* fields for "simple expression" fast-path execution: */
      Expr       *expr_simple_expr;        /* NULL means not a simple expr */
      int            expr_simple_generation; /* plancache generation we checked */
--- 188,198 ----
      int            dno;
      char       *query;
      SPIPlanPtr    plan;
!     Bitmapset  *paramnos;        /* all dnos referenced by this query */
!
!     /* function containing this expr (not set until we first parse query) */
!     struct PLpgSQL_function *func;
!
      /* fields for "simple expression" fast-path execution: */
      Expr       *expr_simple_expr;        /* NULL means not a simple expr */
      int            expr_simple_generation; /* plancache generation we checked */
***************
*** 202,211 ****
       */
      ExprState  *expr_simple_state;
      LocalTransactionId expr_simple_lxid;
-
-     /* params to pass to expr */
-     int            nparams;
-     int            params[1];        /* VARIABLE SIZE ARRAY ... must be last */
  } PLpgSQL_expr;


--- 205,210 ----
***************
*** 285,298 ****


  typedef struct
- {                                /* Positional argument to trigger    */
-     int            dtype;
-     int            dno;
-     PLpgSQL_expr *argnum;
- } PLpgSQL_trigarg;
-
-
- typedef struct
  {                                /* Item in the compilers namestack    */
      int            itemtype;
      int            itemno;
--- 284,289 ----
***************
*** 670,686 ****
      int            tg_table_name_varno;
      int            tg_table_schema_varno;
      int            tg_nargs_varno;

      int            ndatums;
      PLpgSQL_datum **datums;
      PLpgSQL_stmt_block *action;

      unsigned long use_count;
  } PLpgSQL_function;


! typedef struct
  {                                /* Runtime execution data    */
      Datum        retval;
      bool        retisnull;
      Oid            rettype;        /* type of current retval */
--- 661,682 ----
      int            tg_table_name_varno;
      int            tg_table_schema_varno;
      int            tg_nargs_varno;
+     int            tg_argv_varno;

      int            ndatums;
      PLpgSQL_datum **datums;
      PLpgSQL_stmt_block *action;

+     /* these fields change when the function is used */
+     struct PLpgSQL_execstate *cur_estate;
      unsigned long use_count;
  } PLpgSQL_function;


! typedef struct PLpgSQL_execstate
  {                                /* Runtime execution data    */
+     PLpgSQL_function *func;        /* function being executed */
+
      Datum        retval;
      bool        retisnull;
      Oid            rettype;        /* type of current retval */
***************
*** 699,707 ****
      MemoryContext tuple_store_cxt;
      ReturnSetInfo *rsi;

-     int            trig_nargs;
-     Datum       *trig_argv;
-
      int            found_varno;
      int            ndatums;
      PLpgSQL_datum **datums;
--- 695,700 ----
***************
*** 711,721 ****
      uint32        eval_processed;
      Oid            eval_lastoid;
      ExprContext *eval_econtext; /* for executing simple expressions */

      /* status information for error context reporting */
-     PLpgSQL_function *err_func; /* current func */
      PLpgSQL_stmt *err_stmt;        /* current stmt */
      const char *err_text;        /* additional state info */
      void       *plugin_info;    /* reserved for use by optional plugin */
  } PLpgSQL_execstate;

--- 704,715 ----
      uint32        eval_processed;
      Oid            eval_lastoid;
      ExprContext *eval_econtext; /* for executing simple expressions */
+     PLpgSQL_expr *cur_expr;        /* current query/expr being evaluated */

      /* status information for error context reporting */
      PLpgSQL_stmt *err_stmt;        /* current stmt */
      const char *err_text;        /* additional state info */
+
      void       *plugin_info;    /* reserved for use by optional plugin */
  } PLpgSQL_execstate;

Index: src/test/regress/expected/plpgsql.out
===================================================================
RCS file: /cvsroot/pgsql/src/test/regress/expected/plpgsql.out,v
retrieving revision 1.75
diff -c -r1.75 plpgsql.out
*** src/test/regress/expected/plpgsql.out    29 Sep 2009 20:05:29 -0000    1.75
--- src/test/regress/expected/plpgsql.out    4 Nov 2009 16:05:30 -0000
***************
*** 285,295 ****
      if new.slotno < 1 or new.slotno > hubrec.nslots then
          raise exception ''no manual manipulation of HSlot'';
      end if;
!     if tg_op = ''UPDATE'' then
!     if new.hubname != old.hubname then
!         if count(*) > 0 from Hub where name = old.hubname then
!         raise exception ''no manual manipulation of HSlot'';
!         end if;
      end if;
      end if;
      sname := ''HS.'' || trim(new.hubname);
--- 285,293 ----
      if new.slotno < 1 or new.slotno > hubrec.nslots then
          raise exception ''no manual manipulation of HSlot'';
      end if;
!     if tg_op = ''UPDATE'' and new.hubname != old.hubname then
!     if count(*) > 0 from Hub where name = old.hubname then
!         raise exception ''no manual manipulation of HSlot'';
      end if;
      end if;
      sname := ''HS.'' || trim(new.hubname);
Index: src/test/regress/sql/plpgsql.sql
===================================================================
RCS file: /cvsroot/pgsql/src/test/regress/sql/plpgsql.sql,v
retrieving revision 1.63
diff -c -r1.63 plpgsql.sql
*** src/test/regress/sql/plpgsql.sql    29 Sep 2009 20:05:29 -0000    1.63
--- src/test/regress/sql/plpgsql.sql    4 Nov 2009 16:05:30 -0000
***************
*** 347,357 ****
      if new.slotno < 1 or new.slotno > hubrec.nslots then
          raise exception ''no manual manipulation of HSlot'';
      end if;
!     if tg_op = ''UPDATE'' then
!     if new.hubname != old.hubname then
!         if count(*) > 0 from Hub where name = old.hubname then
!         raise exception ''no manual manipulation of HSlot'';
!         end if;
      end if;
      end if;
      sname := ''HS.'' || trim(new.hubname);
--- 347,355 ----
      if new.slotno < 1 or new.slotno > hubrec.nslots then
          raise exception ''no manual manipulation of HSlot'';
      end if;
!     if tg_op = ''UPDATE'' and new.hubname != old.hubname then
!     if count(*) > 0 from Hub where name = old.hubname then
!         raise exception ''no manual manipulation of HSlot'';
      end if;
      end if;
      sname := ''HS.'' || trim(new.hubname);

pgsql-hackers by date:

Previous
From: Peter Eisentraut
Date:
Subject: Re: PL/Python array support
Next
From: Alvaro Herrera
Date:
Subject: Re: WIP: pushing parser hooks through SPI and plancache