parser-hooks-part2.patch

text/x-patch

Filename: parser-hooks-part2.patch
Type: text/x-patch
Part: 0
Message: WIP: pushing parser hooks through SPI and plancache

Patch

Format: context
File+
doc/src/sgml/spi.sgml 190 6
src/backend/commands/explain.c 4 7
src/backend/commands/prepare.c 5 0
src/backend/executor/execCurrent.c 13 1
src/backend/executor/execQual.c 13 1
src/backend/executor/functions.c 5 0
src/backend/executor/spi.c 168 72
src/backend/nodes/params.c 52 26
src/backend/tcop/postgres.c 51 0
src/backend/utils/cache/plancache.c 43 10
src/include/executor/spi.h 9 0
src/include/executor/spi_priv.h 2 0
src/include/nodes/params.h 25 4
src/include/tcop/tcopprot.h 4 0
src/include/utils/plancache.h 6 0
src/pl/plpgsql/src/gram.y 26 96
src/pl/plpgsql/src/pl_comp.c 7 31
src/pl/plpgsql/src/pl_exec.c 328 198
src/pl/plpgsql/src/pl_funcs.c 1 20
src/pl/plpgsql/src/pl_handler.c 8 1
src/pl/plpgsql/src/plpgsql.h 16 22
src/test/regress/expected/plpgsql.out 3 5
src/test/regress/sql/plpgsql.sql 3 5
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, &param_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 = &paramInfo->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 = &paramInfo->params[paramId - 1];
  
+ 		/* give hook a chance in case parameter is dynamic */
+ 		if (!OidIsValid(prm->ptype) && paramInfo->paramFetch != NULL)
+ 			(*paramInfo->paramFetch) (paramInfo, paramId);
+ 
  		if (OidIsValid(prm->ptype) && !prm->isnull)
  		{
! 			/* 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 = &paramInfo->params[thisParamId - 1];
  
  			if (OidIsValid(prm->ptype))
  			{
! 				Assert(prm->ptype == expression->paramtype);
  				*isNull = prm->isnull;
  				return prm->value;
  			}
--- 882,902 ----
  		{
  			ParamExternData *prm = &paramInfo->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 = &paramLI->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, &paramTypLen, &paramTypByVal);
! 				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 = &params->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 = &params->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], &paramval, &paramisnull);
! 	}
  
  	/*
  	 * 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 = &paramLI->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],
! 						&paramtypeid, &values[i], &paramisnull);
! 		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 = &params->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);