diff --git a/doc/src/sgml/spi.sgml b/doc/src/sgml/spi.sgml
index 07fab82badcec38b30bfd45e877ba3ca9e29972e..8d40c60c8e7526473cff6e3f6fa5636a25e08fad 100644
--- a/doc/src/sgml/spi.sgml
+++ b/doc/src/sgml/spi.sgml
@@ -1,4 +1,4 @@
-<!-- $PostgreSQL: pgsql/doc/src/sgml/spi.sgml,v 1.65 2009/08/05 19:31:50 alvherre Exp $ -->
+<!-- $PostgreSQL: pgsql/doc/src/sgml/spi.sgml,v 1.66 2009/11/04 22:26:04 tgl Exp $ -->
 
 <chapter id="spi">
  <title>Server Programming Interface</title>
@@ -861,7 +861,7 @@ SPIPlanPtr SPI_prepare(const char * <parameter>command</parameter>, int <paramet
 
   <para>
    <function>SPI_prepare</function> creates and returns an execution
-   plan for the specified command but doesn't execute the command.
+   plan for the specified command, but doesn't execute the command.
    This function should only be called from a connected procedure.
   </para>
 
@@ -990,7 +990,7 @@ SPIPlanPtr SPI_prepare_cursor(const char * <parameter>command</parameter>, int <
    of the planner's <quote>cursor options</> parameter.  This is a bitmask
    having the values shown in <filename>nodes/parsenodes.h</filename>
    for the <structfield>options</> field of <structname>DeclareCursorStmt</>.
-   <function>SPI_prepare</function> always takes these options as zero.
+   <function>SPI_prepare</function> always takes the cursor options as zero.
   </para>
  </refsect1>
 
@@ -1061,6 +1061,94 @@ SPIPlanPtr SPI_prepare_cursor(const char * <parameter>command</parameter>, int <
 
 <!-- *********************************************** -->
 
+<refentry id="spi-spi-prepare-params">
+ <refmeta>
+  <refentrytitle>SPI_prepare_params</refentrytitle>
+  <manvolnum>3</manvolnum>
+ </refmeta>
+
+ <refnamediv>
+  <refname>SPI_prepare_params</refname>
+  <refpurpose>prepare a plan for a command, without executing it yet</refpurpose>
+ </refnamediv>
+
+ <indexterm><primary>SPI_prepare_params</primary></indexterm>
+
+ <refsynopsisdiv>
+<synopsis>
+SPIPlanPtr SPI_prepare_params(const char * <parameter>command</parameter>,
+                              ParserSetupHook <parameter>parserSetup</parameter>,
+                              void * <parameter>parserSetupArg</parameter>,
+                              int <parameter>cursorOptions</parameter>)
+</synopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+  <title>Description</title>
+
+  <para>
+   <function>SPI_prepare_params</function> creates and returns an execution
+   plan for the specified command, but doesn't execute the command.
+   This function is equivalent to <function>SPI_prepare_cursor</function>,
+   with the addition that the caller can specify parser hook functions
+   to control the parsing of external parameter references.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Arguments</title>
+
+  <variablelist>
+   <varlistentry>
+    <term><literal>const char * <parameter>command</parameter></literal></term>
+    <listitem>
+     <para>
+      command string
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>ParserSetupHook <parameter>parserSetup</parameter></literal></term>
+    <listitem>
+     <para>
+      Parser hook setup function
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>void * <parameter>parserSetupArg</parameter></literal></term>
+    <listitem>
+     <para>
+      passthrough argument for <parameter>parserSetup</parameter>
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>int <parameter>cursorOptions</parameter></literal></term>
+    <listitem>
+     <para>
+      integer bitmask of cursor options; zero produces default behavior
+     </para>
+    </listitem>
+   </varlistentry>
+  </variablelist>
+ </refsect1>
+
+ <refsect1>
+  <title>Return Value</title>
+
+  <para>
+   <function>SPI_prepare_params</function> has the same return conventions as
+   <function>SPI_prepare</function>.
+  </para>
+ </refsect1>
+</refentry>
+
+<!-- *********************************************** -->
+
 <refentry id="spi-spi-getargcount">
  <refmeta>
   <refentrytitle>SPI_getargcount</refentrytitle>
@@ -1386,14 +1474,100 @@ int SPI_execute_plan(SPIPlanPtr <parameter>plan</parameter>, Datum * <parameter>
    <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>Notes</title>
+  <title>Description</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.
+   <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,7 +1717,7 @@ Portal SPI_cursor_open(const char * <parameter>name</parameter>, SPIPlanPtr <par
   </para>
 
   <para>
-   The passed-in data will be copied into the cursor's portal, so it
+   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,7 +1841,7 @@ Portal SPI_cursor_open_with_args(const char *<parameter>name</parameter>,
   </para>
 
   <para>
-   The passed-in data will be copied into the cursor's portal, so it
+   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,6 +1944,104 @@ Portal SPI_cursor_open_with_args(const char *<parameter>name</parameter>,
 
 <!-- *********************************************** -->
 
+<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>
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 1260ca00c237d22f731a9c3dc1b641a9da82f3db..21fa3add4f662352c666c68b7dc7f29d904c87d1 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1994-5, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/commands/explain.c,v 1.192 2009/10/12 18:10:41 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/commands/explain.c,v 1.193 2009/11/04 22:26:04 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -107,8 +107,6 @@ ExplainQuery(ExplainStmt *stmt, const char *queryString,
 			 ParamListInfo params, DestReceiver *dest)
 {
 	ExplainState es;
-	Oid		   *param_types;
-	int			num_params;
 	TupOutputState *tstate;
 	List	   *rewritten;
 	ListCell   *lc;
@@ -150,9 +148,6 @@ ExplainQuery(ExplainStmt *stmt, const char *queryString,
 							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).
@@ -163,8 +158,10 @@ ExplainQuery(ExplainStmt *stmt, const char *queryString,
 	 * 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);
+	rewritten = pg_analyze_and_rewrite_params((Node *) copyObject(stmt->query),
+											  queryString,
+											  (ParserSetupHook) setupParserWithParamList,
+											  params);
 
 	/* emit opening boilerplate */
 	ExplainBeginOutput(&es);
diff --git a/src/backend/commands/prepare.c b/src/backend/commands/prepare.c
index 56a16401f35ed37543be7db40953641e2e3b45cf..021c2daf26d998174d6f5e7f94003e253ce1411a 100644
--- a/src/backend/commands/prepare.c
+++ b/src/backend/commands/prepare.c
@@ -10,7 +10,7 @@
  * Copyright (c) 2002-2009, PostgreSQL Global Development Group
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/commands/prepare.c,v 1.99 2009/08/10 05:46:50 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/commands/prepare.c,v 1.100 2009/11/04 22:26:05 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -379,6 +379,11 @@ EvaluateParams(PreparedStatement *pstmt, List *params,
 	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;
diff --git a/src/backend/executor/execCurrent.c b/src/backend/executor/execCurrent.c
index a4103332c4049cec4167b4ab97da6e6fe0851de6..35dc05a52b8a1ce51978a115fc65c2b7df333bb6 100644
--- a/src/backend/executor/execCurrent.c
+++ b/src/backend/executor/execCurrent.c
@@ -6,7 +6,7 @@
  * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- *	$PostgreSQL: pgsql/src/backend/executor/execCurrent.c,v 1.12 2009/10/26 02:26:29 tgl Exp $
+ *	$PostgreSQL: pgsql/src/backend/executor/execCurrent.c,v 1.13 2009/11/04 22:26:05 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -217,9 +217,21 @@ fetch_param_value(ExprContext *econtext, int paramId)
 	{
 		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)
 		{
-			Assert(prm->ptype == REFCURSOROID);
+			/* 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);
 		}
diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c
index fdfbd999f4f2c457eb3fc0cedb1fc3e1ef9e383b..226e15546fd3a8fef1c67a9060a6de501a410c92 100644
--- a/src/backend/executor/execQual.c
+++ b/src/backend/executor/execQual.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/executor/execQual.c,v 1.253 2009/10/26 02:26:29 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/executor/execQual.c,v 1.254 2009/11/04 22:26:05 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -882,9 +882,21 @@ ExecEvalParam(ExprState *exprstate, ExprContext *econtext,
 		{
 			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))
 			{
-				Assert(prm->ptype == expression->paramtype);
+				/* 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;
 			}
diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c
index fe25798a21e2c8d94422211e6c69e1f8772678ef..2934e51161ea91abac6fcf8e7ba8053c7ca2c649 100644
--- a/src/backend/executor/functions.c
+++ b/src/backend/executor/functions.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/executor/functions.c,v 1.135 2009/06/11 17:25:38 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/executor/functions.c,v 1.136 2009/11/04 22:26:05 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -526,6 +526,11 @@ postquel_sub_params(SQLFunctionCachePtr fcache,
 			/* 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;
 		}
diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c
index e6bb04bc8a8d21cc90a98564695e5ec8e625b8cc..fcea0a1e62386005503b64bcce1de123af2811d5 100644
--- a/src/backend/executor/spi.c
+++ b/src/backend/executor/spi.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/executor/spi.c,v 1.210 2009/10/10 01:43:47 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/executor/spi.c,v 1.211 2009/11/04 22:26:06 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -45,8 +45,7 @@ static int	_SPI_connected = -1;
 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);
+						 ParamListInfo paramLI, bool read_only);
 
 static void _SPI_prepare_plan(const char *src, SPIPlanPtr plan,
 				  ParamListInfo boundParams);
@@ -407,6 +406,28 @@ SPI_execp(SPIPlanPtr plan, Datum *Values, const char *Nulls, long tcount)
 	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,6 +504,8 @@ SPI_execute_with_args(const char *src,
 	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,6 +551,45 @@ SPI_prepare_cursor(const char *src, int nargs, Oid *argtypes,
 	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_params(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,8 +1016,21 @@ SPI_cursor_open(const char *name, SPIPlanPtr plan,
 				Datum *Values, const char *Nulls,
 				bool read_only)
 {
-	return SPI_cursor_open_internal(name, plan, Values, Nulls,
-									read_only, 0);
+	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,7 +1067,10 @@ SPI_cursor_open_with_args(const char *name,
 	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,8 +1085,7 @@ SPI_cursor_open_with_args(const char *name,
 	/* 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);
+	result = SPI_cursor_open_internal(name, &plan, paramLI, read_only);
 
 	/* And clean up */
 	_SPI_curid++;
@@ -1018,25 +1095,36 @@ SPI_cursor_open_with_args(const char *name,
 }
 
 
+/*
+ * 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 and SPI_cursor_open_with_args
+ *	Common code for SPI_cursor_open variants
  */
 static Portal
 SPI_cursor_open_internal(const char *name, SPIPlanPtr plan,
-						 Datum *Values, const char *Nulls,
-						 bool read_only, int pflags)
+						 ParamListInfo paramLI, bool read_only)
 {
 	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
@@ -1082,54 +1170,15 @@ SPI_cursor_open_internal(const char *name, SPIPlanPtr plan,
 		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);
+	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 */
@@ -1220,6 +1269,19 @@ SPI_cursor_open_internal(const char *name, SPIPlanPtr plan,
 		snapshot = GetTransactionSnapshot();
 	}
 
+	/*
+	 * 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.
 	 */
@@ -1588,11 +1650,12 @@ spi_printtup(TupleTableSlot *slot, DestReceiver *self)
 /*
  * 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!
+ * 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,8 +1668,6 @@ _SPI_prepare_plan(const char *src, SPIPlanPtr plan, ParamListInfo boundParams)
 	List	   *plancache_list;
 	ListCell   *list_item;
 	ErrorContextCallback spierrcontext;
-	Oid		   *argtypes = plan->argtypes;
-	int			nargs = plan->nargs;
 	int			cursor_options = plan->cursor_options;
 
 	/*
@@ -1623,8 +1684,8 @@ _SPI_prepare_plan(const char *src, SPIPlanPtr plan, ParamListInfo boundParams)
 	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.
+	 * 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,9 +1696,27 @@ _SPI_prepare_plan(const char *src, SPIPlanPtr plan, ParamListInfo boundParams)
 		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);
+		/*
+		 * 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,8 +1726,10 @@ _SPI_prepare_plan(const char *src, SPIPlanPtr plan, ParamListInfo boundParams)
 		/* 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->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,7 +2002,7 @@ fail:
 }
 
 /*
- * Convert query parameters to form wanted by planner and executor
+ * Convert arrays of query parameters to form wanted by planner and executor
  */
 static ParamListInfo
 _SPI_convert_params(int nargs, Oid *argtypes,
@@ -1937,6 +2018,11 @@ _SPI_convert_params(int nargs, Oid *argtypes,
 		/* 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,6 +2308,8 @@ _SPI_copy_plan(SPIPlanPtr plan, MemoryContext parentcxt)
 	}
 	else
 		newplan->argtypes = NULL;
+	newplan->parserSetup = plan->parserSetup;
+	newplan->parserSetupArg = plan->parserSetupArg;
 
 	foreach(lc, plan->plancache_list)
 	{
@@ -2241,6 +2329,8 @@ _SPI_copy_plan(SPIPlanPtr plan, MemoryContext parentcxt)
 		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,6 +2388,8 @@ _SPI_save_plan(SPIPlanPtr plan)
 	}
 	else
 		newplan->argtypes = NULL;
+	newplan->parserSetup = plan->parserSetup;
+	newplan->parserSetupArg = plan->parserSetupArg;
 
 	foreach(lc, plan->plancache_list)
 	{
@@ -2317,6 +2409,10 @@ _SPI_save_plan(SPIPlanPtr plan)
 									 cplan->stmt_list,
 									 true,
 									 false);
+		if (newplan->parserSetup != NULL)
+			CachedPlanSetParserHook(newsource,
+									newplan->parserSetup,
+									newplan->parserSetupArg);
 
 		newplan->plancache_list = lappend(newplan->plancache_list, newsource);
 	}
diff --git a/src/backend/nodes/params.c b/src/backend/nodes/params.c
index e7eeb2df015f31af5c42ebb8e73a1491aa457d62..111276c348c528dee430f2a5b60fb8cba927bb44 100644
--- a/src/backend/nodes/params.c
+++ b/src/backend/nodes/params.c
@@ -8,7 +8,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/nodes/params.c,v 1.11 2009/01/01 17:23:43 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/nodes/params.c,v 1.12 2009/11/04 22:26:06 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -16,6 +16,7 @@
 #include "postgres.h"
 
 #include "nodes/params.h"
+#include "parser/parse_param.h"
 #include "utils/datum.h"
 #include "utils/lsyscache.h"
 
@@ -24,6 +25,11 @@
  * 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,54 +46,76 @@ copyParamList(ParamListInfo from)
 		(from->numParams - 1) *sizeof(ParamExternData);
 
 	retval = (ParamListInfo) palloc(size);
-	memcpy(retval, from, size);
+	retval->paramFetch = NULL;
+	retval->paramFetchArg = NULL;
+	retval->parserSetup = NULL;
+	retval->parserSetupArg = NULL;
+	retval->numParams = from->numParams;
 
-	/*
-	 * 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++)
+	for (i = 0; i < from->numParams; i++)
 	{
-		ParamExternData *prm = &retval->params[i];
+		ParamExternData *oprm = &from->params[i];
+		ParamExternData *nprm = &retval->params[i];
 		int16		typLen;
 		bool		typByVal;
 
-		if (prm->isnull || !OidIsValid(prm->ptype))
+		/* 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(prm->ptype, &typLen, &typByVal);
-		prm->value = datumCopy(prm->value, typByVal, typLen);
+		get_typlenbyval(nprm->ptype, &typLen, &typByVal);
+		nprm->value = datumCopy(nprm->value, typByVal, typLen);
 	}
 
 	return retval;
 }
 
 /*
- * Extract an array of parameter type OIDs from a ParamListInfo.
+ * Set up the parser to treat the given list of run-time parameters
+ * as available external parameters during parsing of a new query.
  *
- * The result is allocated in CurrentMemoryContext.
+ * Note that the parser doesn't actually care about the *values* of the given
+ * parameters, only about their *types*.  Also, the code that originally
+ * provided the ParamListInfo may have provided a setupHook, which should
+ * override applying parse_fixed_parameters().
  */
 void
-getParamListTypes(ParamListInfo params,
-				  Oid **param_types, int *num_params)
+setupParserWithParamList(struct ParseState *pstate,
+						 ParamListInfo params)
 {
-	Oid		   *ptypes;
-	int			i;
+	if (params == NULL)			/* no params, nothing to do */
+		return;
 
-	if (params == NULL || params->numParams <= 0)
+	/* If there is a parserSetup hook, it gets to do this */
+	if (params->parserSetup != NULL)
 	{
-		*param_types = NULL;
-		*num_params = 0;
+		(*params->parserSetup) (pstate, params->parserSetupArg);
 		return;
 	}
 
-	ptypes = (Oid *) palloc(params->numParams * sizeof(Oid));
-	*param_types = ptypes;
-	*num_params = params->numParams;
-
-	for (i = 0; i < params->numParams; i++)
+	/* Else, treat any available parameters as being of fixed type */
+	if (params->numParams > 0)
 	{
-		ParamExternData *prm = &params->params[i];
+		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;
+			ptypes[i] = prm->ptype;
+		}
+		parse_fixed_parameters(pstate, ptypes, params->numParams);
 	}
 }
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 67deea796242b5c335d0ca22ec86f00fc5c057eb..b6a892a30f558b30532ec73cacbe54b27373d3a4 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/tcop/postgres.c,v 1.574 2009/10/08 22:34:57 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/tcop/postgres.c,v 1.575 2009/11/04 22:26:06 tgl Exp $
  *
  * NOTES
  *	  this is the "main" module of the postgres backend and
@@ -627,6 +627,52 @@ pg_analyze_and_rewrite(Node *parsetree, const char *query_string,
 	return querytree_list;
 }
 
+/*
+ * 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.
  *
@@ -1536,6 +1582,11 @@ exec_bind_message(StringInfo input_message)
 		/* 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++)
diff --git a/src/backend/utils/cache/plancache.c b/src/backend/utils/cache/plancache.c
index eb196ebb56a414516a94ecc58c2bd47f37289e8b..b473b7a1598bf0deffe46026bec99d5d803f376a 100644
--- a/src/backend/utils/cache/plancache.c
+++ b/src/backend/utils/cache/plancache.c
@@ -35,7 +35,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/utils/cache/plancache.c,v 1.30 2009/10/26 02:26:41 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/utils/cache/plancache.c,v 1.31 2009/11/04 22:26:06 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -99,8 +99,8 @@ InitPlanCache(void)
  * 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
+ * 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,6 +156,9 @@ CreateCachedPlan(Node *raw_parse_tree,
 	else
 		plansource->param_types = NULL;
 	plansource->num_params = num_params;
+	/* these can be set later with CachedPlanSetParserHook: */
+	plansource->parserSetup = NULL;
+	plansource->parserSetupArg = NULL;
 	plansource->cursor_options = cursor_options;
 	plansource->fully_planned = fully_planned;
 	plansource->fixed_result = fixed_result;
@@ -240,6 +243,9 @@ FastCreateCachedPlan(Node *raw_parse_tree,
 	plansource->commandTag = commandTag;		/* no copying needed */
 	plansource->param_types = param_types;
 	plansource->num_params = num_params;
+	/* these can be set later with CachedPlanSetParserHook: */
+	plansource->parserSetup = NULL;
+	plansource->parserSetupArg = NULL;
 	plansource->cursor_options = cursor_options;
 	plansource->fully_planned = fully_planned;
 	plansource->fixed_result = fixed_result;
@@ -274,6 +280,27 @@ FastCreateCachedPlan(Node *raw_parse_tree,
 	return plansource;
 }
 
+/*
+ * CachedPlanSetParserHook: 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
+CachedPlanSetParserHook(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.
  *
@@ -466,6 +493,7 @@ RevalidateCachedPlan(CachedPlanSource *plansource, bool useResOwner)
 	if (!plan)
 	{
 		bool		snapshot_set = false;
+		Node	   *rawtree;
 		List	   *slist;
 		TupleDesc	resultDesc;
 
@@ -491,14 +519,19 @@ RevalidateCachedPlan(CachedPlanSource *plansource, bool useResOwner)
 		/*
 		 * 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.
+		 * prevent corruption of the cache.
 		 */
-		slist = pg_analyze_and_rewrite(copyObject(plansource->raw_parse_tree),
-									   plansource->query_string,
-									   plansource->param_types,
-									   plansource->num_params);
+		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)
 		{
diff --git a/src/include/executor/spi.h b/src/include/executor/spi.h
index 5bdde2d524172b37295593786541dd69bf845f92..42ba4e464d120b40724decc444e665ea9d589140 100644
--- a/src/include/executor/spi.h
+++ b/src/include/executor/spi.h
@@ -6,7 +6,7 @@
  * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/executor/spi.h,v 1.72 2009/06/11 14:49:11 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/executor/spi.h,v 1.73 2009/11/04 22:26:06 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -73,6 +73,9 @@ extern void SPI_restore_connection(void);
 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,6 +91,10 @@ extern int SPI_execute_with_args(const char *src,
 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_params(const char *src,
+									 ParserSetupHook parserSetup,
+									 void *parserSetupArg,
+									 int cursorOptions);
 extern SPIPlanPtr SPI_saveplan(SPIPlanPtr plan);
 extern int	SPI_freeplan(SPIPlanPtr plan);
 
@@ -122,6 +129,8 @@ extern Portal SPI_cursor_open_with_args(const char *name,
 						  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);
diff --git a/src/include/executor/spi_priv.h b/src/include/executor/spi_priv.h
index ef50a9013e82bd6296cc7023c47a006c6b4d5467..a0dee126952770a276752fedb41147c17ecdf4bf 100644
--- a/src/include/executor/spi_priv.h
+++ b/src/include/executor/spi_priv.h
@@ -6,7 +6,7 @@
  * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/executor/spi_priv.h,v 1.32 2009/01/01 17:23:59 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/executor/spi_priv.h,v 1.33 2009/11/04 22:26:06 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -68,6 +68,8 @@ typedef struct _SPI_plan
 	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 */
diff --git a/src/include/nodes/params.h b/src/include/nodes/params.h
index adc58710553078c4ab55c9d6e03b02be17c0c058..96cbf5267a4d22958f0a67067fd0e2a2a606c168 100644
--- a/src/include/nodes/params.h
+++ b/src/include/nodes/params.h
@@ -7,13 +7,16 @@
  * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/nodes/params.h,v 1.38 2009/01/01 17:24:00 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/nodes/params.h,v 1.39 2009/11/04 22:26:06 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
 #ifndef PARAMS_H
 #define PARAMS_H
 
+/* To avoid including a pile of parser headers, reference ParseState thus: */
+struct ParseState;
+
 
 /* ----------------
  *	  ParamListInfo
@@ -26,10 +29,20 @@
  *	  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,14 +58,22 @@ typedef struct ParamExternData
 	Oid			ptype;			/* parameter's datatype, or 0 */
 } ParamExternData;
 
+typedef struct ParamListInfoData *ParamListInfo;
+
+typedef void (*ParamFetchHook) (ParamListInfo params, int paramid);
+
+typedef void (*ParserSetupHook) (struct ParseState *pstate, void *arg);
+
 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;
 
-typedef ParamListInfoData *ParamListInfo;
-
 
 /* ----------------
  *	  ParamExecData
@@ -82,7 +103,7 @@ typedef struct ParamExecData
 /* Functions found in src/backend/nodes/params.c */
 extern ParamListInfo copyParamList(ParamListInfo from);
 
-extern void getParamListTypes(ParamListInfo params,
-				  Oid **param_types, int *num_params);
+extern void setupParserWithParamList(struct ParseState *pstate,
+									 ParamListInfo params);
 
 #endif   /* PARAMS_H */
diff --git a/src/include/tcop/tcopprot.h b/src/include/tcop/tcopprot.h
index b1c8b77bd5c35101ca893b3e0646c020a8fac77d..103a22fbc53dbc8508f55756682c197229b1addf 100644
--- a/src/include/tcop/tcopprot.h
+++ b/src/include/tcop/tcopprot.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/tcop/tcopprot.h,v 1.100 2009/09/01 00:09:42 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/tcop/tcopprot.h,v 1.101 2009/11/04 22:26:07 tgl Exp $
  *
  * OLD COMMENTS
  *	  This file was created so that other c files could get the two
@@ -49,6 +49,10 @@ extern List *pg_parse_and_rewrite(const char *query_string,
 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,
diff --git a/src/include/utils/plancache.h b/src/include/utils/plancache.h
index ea919bd456605e86ad657775a5d4eed01cc207a4..68e3f72b195d0b98a342941302515de809050bd2 100644
--- a/src/include/utils/plancache.h
+++ b/src/include/utils/plancache.h
@@ -8,7 +8,7 @@
  * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/utils/plancache.h,v 1.15 2009/01/01 17:24:02 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/utils/plancache.h,v 1.16 2009/11/04 22:26:07 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -16,6 +16,7 @@
 #define PLANCACHE_H
 
 #include "access/tupdesc.h"
+#include "nodes/params.h"
 
 /*
  * CachedPlanSource represents the portion of a cached plan that persists
@@ -50,6 +51,8 @@ typedef struct CachedPlanSource
 	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,6 +108,9 @@ extern CachedPlanSource *FastCreateCachedPlan(Node *raw_parse_tree,
 					 bool fully_planned,
 					 bool fixed_result,
 					 MemoryContext context);
+extern void CachedPlanSetParserHook(CachedPlanSource *plansource,
+									ParserSetupHook parserSetup,
+									void *parserSetupArg);
 extern void DropCachedPlan(CachedPlanSource *plansource);
 extern CachedPlan *RevalidateCachedPlan(CachedPlanSource *plansource,
 					 bool useResOwner);
diff --git a/src/pl/plpgsql/src/gram.y b/src/pl/plpgsql/src/gram.y
index c876b875e62ee6beb56ee6d51bcfcad605cb8244..ec6b285bf2f843bfd0ccf057679978e32047ae42 100644
--- a/src/pl/plpgsql/src/gram.y
+++ b/src/pl/plpgsql/src/gram.y
@@ -9,7 +9,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/pl/plpgsql/src/gram.y,v 1.128 2009/09/29 20:05:29 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/pl/plpgsql/src/gram.y,v 1.129 2009/11/04 22:26:07 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -109,7 +109,7 @@ static List				*read_raise_options(void);
 		}						loop_body;
 		List					*list;
 		PLpgSQL_type			*dtype;
-		PLpgSQL_datum			*scalar;	/* a VAR, RECFIELD, or TRIGARG */
+		PLpgSQL_datum			*scalar;	/* a VAR or RECFIELD */
 		PLpgSQL_variable		*variable;	/* a VAR, REC, or ROW */
 		PLpgSQL_var				*var;
 		PLpgSQL_row				*row;
@@ -236,7 +236,7 @@ static List				*read_raise_options(void);
 		 */
 %token	T_STRING
 %token	T_NUMBER
-%token	T_SCALAR				/* a VAR, RECFIELD, or TRIGARG */
+%token	T_SCALAR				/* a VAR or RECFIELD */
 %token	T_ROW
 %token	T_RECORD
 %token	T_DTYPE
@@ -1903,44 +1903,6 @@ lno				:
 %%
 
 
-#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)
@@ -1993,8 +1955,7 @@ read_sql_construct(int until,
 	int					lno;
 	StringInfoData		ds;
 	int					parenlevel = 0;
-	int					nparams = 0;
-	int					params[MAX_EXPR_PARAMS];
+	Bitmapset		   *paramnos = NULL;
 	char				buf[32];
 	PLpgSQL_expr		*expr;
 
@@ -2047,24 +2008,21 @@ read_sql_construct(int until,
 		switch (tok)
 		{
 			case T_SCALAR:
-				snprintf(buf, sizeof(buf), " $%d ",
-						 assign_expr_param(yylval.scalar->dno,
-										   params, &nparams));
+				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 ",
-						 assign_expr_param(yylval.row->dno,
-										   params, &nparams));
+				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 ",
-						 assign_expr_param(yylval.rec->dno,
-										   params, &nparams));
+				snprintf(buf, sizeof(buf), " $%d ", yylval.rec->dno + 1);
 				appendStringInfoString(&ds, buf);
+				paramnos = bms_add_member(paramnos, yylval.rec->dno);
 				break;
 
 			default:
@@ -2076,13 +2034,11 @@ read_sql_construct(int until,
 	if (endtoken)
 		*endtoken = tok;
 
-	expr = palloc(sizeof(PLpgSQL_expr) + sizeof(int) * nparams - sizeof(int));
+	expr = palloc0(sizeof(PLpgSQL_expr));
 	expr->dtype			= PLPGSQL_DTYPE_EXPR;
 	expr->query			= pstrdup(ds.data);
 	expr->plan			= NULL;
-	expr->nparams		= nparams;
-	while(nparams-- > 0)
-		expr->params[nparams] = params[nparams];
+	expr->paramnos		= paramnos;
 	pfree(ds.data);
 
 	if (valid_sql)
@@ -2162,8 +2118,7 @@ static PLpgSQL_stmt *
 make_execsql_stmt(const char *sqlstart, int lineno)
 {
 	StringInfoData		ds;
-	int					nparams = 0;
-	int					params[MAX_EXPR_PARAMS];
+	Bitmapset		   *paramnos = NULL;
 	char				buf[32];
 	PLpgSQL_stmt_execsql *execsql;
 	PLpgSQL_expr		*expr;
@@ -2214,24 +2169,21 @@ make_execsql_stmt(const char *sqlstart, int lineno)
 		switch (tok)
 		{
 			case T_SCALAR:
-				snprintf(buf, sizeof(buf), " $%d ",
-						 assign_expr_param(yylval.scalar->dno,
-										   params, &nparams));
+				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 ",
-						 assign_expr_param(yylval.row->dno,
-										   params, &nparams));
+				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 ",
-						 assign_expr_param(yylval.rec->dno,
-										   params, &nparams));
+				snprintf(buf, sizeof(buf), " $%d ", yylval.rec->dno + 1);
 				appendStringInfoString(&ds, buf);
+				paramnos = bms_add_member(paramnos, yylval.rec->dno);
 				break;
 
 			default:
@@ -2240,13 +2192,11 @@ make_execsql_stmt(const char *sqlstart, int lineno)
 		}
 	}
 
-	expr = palloc(sizeof(PLpgSQL_expr) + sizeof(int) * nparams - sizeof(int));
+	expr = palloc0(sizeof(PLpgSQL_expr));
 	expr->dtype			= PLPGSQL_DTYPE_EXPR;
 	expr->query			= pstrdup(ds.data);
 	expr->plan			= NULL;
-	expr->nparams		= nparams;
-	while(nparams-- > 0)
-		expr->params[nparams] = params[nparams];
+	expr->paramnos		= paramnos;
 	pfree(ds.data);
 
 	check_sql_expr(expr->query);
@@ -2600,9 +2550,6 @@ check_assignable(PLpgSQL_datum *datum)
 		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;
@@ -3095,24 +3042,10 @@ make_case(int lineno, PLpgSQL_expr *t_expr,
 		{
 			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;
+			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,17 +3053,14 @@ make_case(int lineno, PLpgSQL_expr *t_expr,
 			/* And do the string hacking */
 			initStringInfo(&ds);
 
-			appendStringInfo(&ds, "SELECT $%d IN(%s)",
-								nparams + 1,
-								expr->query + 7);
+			appendStringInfo(&ds, "SELECT $%d IN (%s)",
+							 t_varno + 1,
+							 expr->query + 7);
 
-			new_expr->query = pstrdup(ds.data);
-
-			pfree(ds.data);
 			pfree(expr->query);
-			pfree(expr);
+			expr->query = pstrdup(ds.data);
 
-			cwt->expr = new_expr;
+			pfree(ds.data);
 		}
 	}
 
diff --git a/src/pl/plpgsql/src/pl_comp.c b/src/pl/plpgsql/src/pl_comp.c
index 604b7dbf0865479afd8e8301bf5ede35e1064f66..ef0dcb0f8df9052189a63527981ee0b7ec73b928 100644
--- a/src/pl/plpgsql/src/pl_comp.c
+++ b/src/pl/plpgsql/src/pl_comp.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_comp.c,v 1.139 2009/09/22 23:43:42 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_comp.c,v 1.140 2009/11/04 22:26:07 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -624,20 +624,24 @@ do_compile(FunctionCallInfo fcinfo,
 										 true);
 			function->tg_table_name_varno = var->dno;
 
-
-			/* add variable tg_table_schema */
+			/* 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:
@@ -931,34 +935,6 @@ plpgsql_parse_word(const char *word)
 	/* Do case conversion and word separation */
 	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
 	 */
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index a3a39e8e2d86e1b14486de2c287748cb4e7e91b5..f7920206382fd3bdc5c9557a7c557ded406eaa75 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.248 2009/08/06 20:44:31 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.249 2009/11/04 22:26:07 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -26,6 +26,7 @@
 #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,10 +155,11 @@ static void exec_assign_value(PLpgSQL_execstate *estate,
 				  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 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,8 +174,11 @@ static int exec_run_select(PLpgSQL_execstate *estate,
 				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 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,12 +519,20 @@ plpgsql_exec_trigger(PLpgSQL_function *func,
 
 	/*
 	 * 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
+	 * in functions that are used for multiple trigger types.  For example,
+	 * we might have a test like "if (TG_OP = 'INSERT' and NEW.foo = 'xyz')",
+	 * which should parse regardless of the current trigger type.
 	 */
 	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,30 +541,22 @@ plpgsql_exec_trigger(PLpgSQL_function *func,
 		 * 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");
@@ -631,19 +636,36 @@ plpgsql_exec_trigger(PLpgSQL_function *func,
 	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;
+	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
 	{
-		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]);
+		var->value = (Datum) 0;
+		var->isnull = true;
+		var->freeval = false;
 	}
 
 	estate.err_text = gettext_noop("during function entry");
@@ -756,10 +778,6 @@ plpgsql_exec_error_callback(void *arg)
 {
 	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;
@@ -784,7 +802,7 @@ plpgsql_exec_error_callback(void *arg)
 			 * local variable initialization"
 			 */
 			errcontext("PL/pgSQL function \"%s\" line %d %s",
-					   estate->err_func->fn_name,
+					   estate->func->fn_name,
 					   estate->err_stmt->lineno,
 					   _(estate->err_text));
 		}
@@ -795,7 +813,7 @@ plpgsql_exec_error_callback(void *arg)
 			 * arguments into local variables"
 			 */
 			errcontext("PL/pgSQL function \"%s\" %s",
-					   estate->err_func->fn_name,
+					   estate->func->fn_name,
 					   _(estate->err_text));
 		}
 	}
@@ -803,13 +821,13 @@ plpgsql_exec_error_callback(void *arg)
 	{
 		/* translator: last %s is a plpgsql statement type name */
 		errcontext("PL/pgSQL function \"%s\" line %d at %s",
-				   estate->err_func->fn_name,
+				   estate->func->fn_name,
 				   estate->err_stmt->lineno,
 				   plpgsql_stmt_typename(estate->err_stmt));
 	}
 	else
 		errcontext("PL/pgSQL function \"%s\"",
-				   estate->err_func->fn_name);
+				   estate->func->fn_name);
 }
 
 
@@ -856,7 +874,6 @@ copy_plpgsql_datum(PLpgSQL_datum *datum)
 		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
@@ -977,10 +994,13 @@ exec_stmt_block(PLpgSQL_execstate *estate, PLpgSQL_stmt_block *block)
 					if (rec->freetup)
 					{
 						heap_freetuple(rec->tup);
-						FreeTupleDesc(rec->tupdesc);
 						rec->freetup = false;
 					}
-
+					if (rec->freetupdesc)
+					{
+						FreeTupleDesc(rec->tupdesc);
+						rec->freetupdesc = false;
+					}
 					rec->tup = NULL;
 					rec->tupdesc = NULL;
 				}
@@ -1885,10 +1905,9 @@ exec_stmt_forc(PLpgSQL_execstate *estate, PLpgSQL_stmt_forc *stmt)
 	PLpgSQL_var *curvar;
 	char	   *curname = NULL;
 	PLpgSQL_expr *query;
+	ParamListInfo paramLI;
 	Portal		portal;
 	int			rc;
-	Datum	   *values;
-	char	   *nulls;
 
 	/* ----------
 	 * Get the cursor variable and if it has an assigned name, check
@@ -1954,19 +1973,25 @@ exec_stmt_forc(PLpgSQL_execstate *estate, PLpgSQL_stmt_forc *stmt)
 		exec_prepare_plan(estate, query, curvar->cursor_options);
 
 	/*
-	 * Now build up the values and nulls arguments for SPI_execute_plan()
+	 * Set up ParamListInfo (note this is only carrying a hook function,
+	 * not any actual data values, at this point)
 	 */
-	eval_expr_params(estate, query, &values, &nulls);
+	paramLI = setup_param_list(estate, query);
 
 	/*
-	 * Open the cursor
+	 * Open the cursor (the paramlist will get copied into the portal)
 	 */
-	portal = SPI_cursor_open(curname, query->plan, values, nulls,
-							 estate->readonly_func);
+	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,8 +2017,6 @@ exec_stmt_forc(PLpgSQL_execstate *estate, PLpgSQL_stmt_forc *stmt)
 		curvar->isnull = true;
 	}
 
-	pfree(values);
-	pfree(nulls);
 	if (curname)
 		pfree(curname);
 
@@ -2599,6 +2622,11 @@ plpgsql_estate_setup(PLpgSQL_execstate *estate,
 					 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,9 +2644,6 @@ plpgsql_estate_setup(PLpgSQL_execstate *estate,
 	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);
@@ -2627,11 +2652,14 @@ plpgsql_estate_setup(PLpgSQL_execstate *estate,
 	estate->eval_tuptable = NULL;
 	estate->eval_processed = 0;
 	estate->eval_lastoid = InvalidOid;
+	estate->eval_econtext = NULL;
+	estate->cur_expr = NULL;
 
-	estate->err_func = func;
 	estate->err_stmt = NULL;
 	estate->err_text = NULL;
 
+	estate->plugin_info = NULL;
+
 	/*
 	 * Create an EState and ExprContext for evaluation of simple expressions.
 	 */
@@ -2682,30 +2710,20 @@ static void
 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.)
+	 * The grammar can't conveniently set expr->func while building the
+	 * parse tree, so make sure it's set before parser hooks need 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);
-	}
+	expr->func = estate->func;
 
 	/*
 	 * Generate and save the plan
 	 */
-	plan = SPI_prepare_cursor(expr->query, expr->nparams, argtypes,
+	plan = SPI_prepare_params(expr->query,
+							  (ParserSetupHook) plpgsql_parser_setup,
+							  (void *) expr,
 							  cursorOptions);
 	if (plan == NULL)
 	{
@@ -2722,17 +2740,13 @@ exec_prepare_plan(PLpgSQL_execstate *estate,
 						 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",
+				elog(ERROR, "SPI_prepare_params 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);
 }
 
 
@@ -2744,8 +2758,7 @@ static int
 exec_stmt_execsql(PLpgSQL_execstate *estate,
 				  PLpgSQL_stmt_execsql *stmt)
 {
-	Datum	   *values;
-	char	   *nulls;
+	ParamListInfo paramLI;
 	long		tcount;
 	int			rc;
 	PLpgSQL_expr *expr = stmt->sqlstmt;
@@ -2782,9 +2795,10 @@ exec_stmt_execsql(PLpgSQL_execstate *estate,
 	}
 
 	/*
-	 * Now build up the values and nulls arguments for SPI_execute_plan()
+	 * Set up ParamListInfo (note this is only carrying a hook function,
+	 * not any actual data values, at this point)
 	 */
-	eval_expr_params(estate, expr, &values, &nulls);
+	paramLI = setup_param_list(estate, expr);
 
 	/*
 	 * If we have INTO, then we only need one row back ... but if we have INTO
@@ -2810,8 +2824,8 @@ exec_stmt_execsql(PLpgSQL_execstate *estate,
 	/*
 	 * Execute the plan
 	 */
-	rc = SPI_execute_plan(expr->plan, values, nulls,
-						  estate->readonly_func, tcount);
+	rc = SPI_execute_plan_with_paramlist(expr->plan, paramLI,
+										 estate->readonly_func, tcount);
 
 	/*
 	 * Check for error, and set FOUND if appropriate (for historical reasons
@@ -2852,7 +2866,7 @@ exec_stmt_execsql(PLpgSQL_execstate *estate,
 			break;
 
 		default:
-			elog(ERROR, "SPI_execute_plan failed executing query \"%s\": %s",
+			elog(ERROR, "SPI_execute_plan_with_paramlist failed executing query \"%s\": %s",
 				 expr->query, SPI_result_code_string(rc));
 	}
 
@@ -2919,8 +2933,8 @@ exec_stmt_execsql(PLpgSQL_execstate *estate,
 					 (rc == SPI_OK_SELECT) ? errhint("If you want to discard the results of a SELECT, use PERFORM instead.") : 0));
 	}
 
-	pfree(values);
-	pfree(nulls);
+	if (paramLI)
+		pfree(paramLI);
 
 	return PLPGSQL_RC_OK;
 }
@@ -3142,8 +3156,7 @@ exec_stmt_open(PLpgSQL_execstate *estate, PLpgSQL_stmt_open *stmt)
 	char	   *curname = NULL;
 	PLpgSQL_expr *query;
 	Portal		portal;
-	Datum	   *values;
-	char	   *nulls;
+	ParamListInfo paramLI;
 	bool		isnull;
 
 	/* ----------
@@ -3280,15 +3293,17 @@ exec_stmt_open(PLpgSQL_execstate *estate, PLpgSQL_stmt_open *stmt)
 	}
 
 	/*
-	 * Now build up the values and nulls arguments for SPI_execute_plan()
+	 * Set up ParamListInfo (note this is only carrying a hook function,
+	 * not any actual data values, at this point)
 	 */
-	eval_expr_params(estate, query, &values, &nulls);
+	paramLI = setup_param_list(estate, query);
 
 	/*
 	 * Open the cursor
 	 */
-	portal = SPI_cursor_open(curname, query->plan, values, nulls,
-							 estate->readonly_func);
+	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,10 +3314,10 @@ exec_stmt_open(PLpgSQL_execstate *estate, PLpgSQL_stmt_open *stmt)
 	if (curname == NULL)
 		assign_text_var(curvar, portal->name);
 
-	pfree(values);
-	pfree(nulls);
 	if (curname)
 		pfree(curname);
+	if (paramLI)
+		pfree(paramLI);
 
 	return PLPGSQL_RC_OK;
 }
@@ -3755,7 +3770,7 @@ exec_assign_value(PLpgSQL_execstate *estate,
 				} while (target->dtype == PLPGSQL_DTYPE_ARRAYELEM);
 
 				/* Fetch current value of array datum */
-				exec_eval_datum(estate, target, InvalidOid,
+				exec_eval_datum(estate, target,
 							  &arraytypeid, &oldarraydatum, &oldarrayisnull);
 
 				arrayelemtypeid = get_element_type(arraytypeid);
@@ -3860,8 +3875,6 @@ exec_assign_value(PLpgSQL_execstate *estate,
  *
  * 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,7 +3885,6 @@ exec_assign_value(PLpgSQL_execstate *estate,
 static void
 exec_eval_datum(PLpgSQL_execstate *estate,
 				PLpgSQL_datum *datum,
-				Oid expectedtypeid,
 				Oid *typeid,
 				Datum *value,
 				bool *isnull)
@@ -3888,11 +3900,6 @@ exec_eval_datum(PLpgSQL_execstate *estate,
 				*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;
 			}
 
@@ -3913,11 +3920,6 @@ exec_eval_datum(PLpgSQL_execstate *estate,
 				*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;
 			}
 
@@ -3950,11 +3952,6 @@ exec_eval_datum(PLpgSQL_execstate *estate,
 				*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;
 			}
 
@@ -3979,42 +3976,96 @@ exec_eval_datum(PLpgSQL_execstate *estate,
 									rec->refname, recfield->fieldname)));
 				*typeid = SPI_gettypeid(rec->tupdesc, fno);
 				*value = SPI_getbinval(rec->tup, rec->tupdesc, fno, isnull);
-				if (expectedtypeid != InvalidOid && expectedtypeid != *typeid)
+				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; specifically, we may have
+ * a tupdesc but no row value for a record variable.  (This currently can
+ * happen only for a trigger's NEW/OLD records.)
+ */
+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_DATATYPE_MISMATCH),
-							 errmsg("type of \"%s.%s\" does not match that when preparing the plan",
-									rec->refname, recfield->fieldname)));
+						  (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_TRIGARG:
+		case PLPGSQL_DTYPE_RECFIELD:
 			{
-				PLpgSQL_trigarg *trigarg = (PLpgSQL_trigarg *) datum;
-				int			tgargno;
+				PLpgSQL_recfield *recfield = (PLpgSQL_recfield *) datum;
+				PLpgSQL_rec *rec;
+				int			fno;
 
-				*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)
+				rec = (PLpgSQL_rec *) (estate->datums[recfield->recparentno]);
+				if (rec->tupdesc == NULL)
 					ereport(ERROR,
-							(errcode(ERRCODE_DATATYPE_MISMATCH),
-							 errmsg("type of tg_argv[%d] does not match that when preparing the plan",
-									tgargno)));
+						  (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,8 +4196,7 @@ static int
 exec_run_select(PLpgSQL_execstate *estate,
 				PLpgSQL_expr *expr, long maxtuples, Portal *portalP)
 {
-	Datum	   *values;
-	char	   *nulls;
+	ParamListInfo paramLI;
 	int			rc;
 
 	/*
@@ -4156,30 +4206,32 @@ exec_run_select(PLpgSQL_execstate *estate,
 		exec_prepare_plan(estate, expr, 0);
 
 	/*
-	 * Now build up the values and nulls arguments for SPI_execute_plan()
+	 * Set up ParamListInfo (note this is only carrying a hook function,
+	 * not any actual data values, at this point)
 	 */
-	eval_expr_params(estate, expr, &values, &nulls);
+	paramLI = setup_param_list(estate, expr);
 
 	/*
 	 * 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);
+		*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));
-		pfree(values);
-		pfree(nulls);
+		if (paramLI)
+			pfree(paramLI);
 		return SPI_OK_CURSOR;
 	}
 
 	/*
 	 * Execute the query
 	 */
-	rc = SPI_execute_plan(expr->plan, values, nulls,
-						  estate->readonly_func, maxtuples);
+	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,8 +4243,8 @@ exec_run_select(PLpgSQL_execstate *estate,
 	estate->eval_processed = SPI_processed;
 	estate->eval_lastoid = SPI_lastoid;
 
-	pfree(values);
-	pfree(nulls);
+	if (paramLI)
+		pfree(paramLI);
 
 	return rc;
 }
@@ -4378,7 +4430,7 @@ exec_eval_simple_expr(PLpgSQL_execstate *estate,
 	CachedPlanSource *plansource;
 	CachedPlan *cplan;
 	ParamListInfo paramLI;
-	int			i;
+	PLpgSQL_expr *save_cur_expr;
 	MemoryContext oldcontext;
 
 	/*
@@ -4425,42 +4477,6 @@ exec_eval_simple_expr(PLpgSQL_execstate *estate,
 		expr->expr_simple_lxid = curlxid;
 	}
 
-	/*
-	 * 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.
@@ -4476,6 +4492,22 @@ exec_eval_simple_expr(PLpgSQL_execstate *estate,
 		PushActiveSnapshot(GetTransactionSnapshot());
 	}
 
+	/*
+	 * 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
 	 */
@@ -4483,11 +4515,15 @@ exec_eval_simple_expr(PLpgSQL_execstate *estate,
 						   econtext,
 						   isNull,
 						   NULL);
-	MemoryContextSwitchTo(oldcontext);
+
+	/* Assorted cleanup */
+	estate->cur_expr = save_cur_expr;
 
 	if (!estate->readonly_func)
 		PopActiveSnapshot();
 
+	MemoryContextSwitchTo(oldcontext);
+
 	SPI_pop();
 
 	/*
@@ -4503,32 +4539,136 @@ exec_eval_simple_expr(PLpgSQL_execstate *estate,
 
 
 /*
- * Build up the values and nulls arguments for SPI_execute_plan()
+ * 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 void
-eval_expr_params(PLpgSQL_execstate *estate,
-				 PLpgSQL_expr *expr, Datum **p_values, char **p_nulls)
+static ParamListInfo
+setup_param_list(PLpgSQL_execstate *estate, PLpgSQL_expr *expr)
 {
-	Datum	   *values;
-	char	   *nulls;
-	int			i;
-
-	*p_values = values = (Datum *) palloc(expr->nparams * sizeof(Datum));
-	*p_nulls = nulls = (char *) palloc(expr->nparams * sizeof(char));
+	ParamListInfo paramLI;
 
-	for (i = 0; i < expr->nparams; i++)
+	/*
+	 * 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)
 	{
-		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] = ' ';
+		/* 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;
+		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;
+
+		/*
+		 * Also make sure this is set before parser hooks need it.  There
+		 * is no need to save and restore, since the value is always correct
+		 * once set.
+		 */
+		expr->func = estate->func;
 	}
+	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,7 +4850,7 @@ make_tuple_from_row(PLpgSQL_execstate *estate,
 			elog(ERROR, "dropped rowtype entry for non-dropped column");
 
 		exec_eval_datum(estate, estate->datums[row->varnos[i]],
-						InvalidOid, &fieldtypeid, &dvalues[i], &nulls[i]);
+						&fieldtypeid, &dvalues[i], &nulls[i]);
 		if (fieldtypeid != tupdesc->attrs[i]->atttypid)
 			return NULL;
 	}
diff --git a/src/pl/plpgsql/src/pl_funcs.c b/src/pl/plpgsql/src/pl_funcs.c
index d814e8f4f3cf700803799aac266a5d350edce31a..274d02711416794c0113d6abf13c889e7156e00d 100644
--- a/src/pl/plpgsql/src/pl_funcs.c
+++ b/src/pl/plpgsql/src/pl_funcs.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_funcs.c,v 1.81 2009/09/29 20:05:29 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_funcs.c,v 1.82 2009/11/04 22:26:07 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1148,21 +1148,7 @@ dump_getdiag(PLpgSQL_stmt_getdiag *stmt)
 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("'");
+	printf("'%s'", expr->query);
 }
 
 void
@@ -1240,11 +1226,6 @@ plpgsql_dumptree(PLpgSQL_function *func)
 				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);
 		}
diff --git a/src/pl/plpgsql/src/pl_handler.c b/src/pl/plpgsql/src/pl_handler.c
index 4f506eb97ca29e3b718dc60b563695a137c43843..7741308f28748730328bf120a0b96f5da690d843 100644
--- a/src/pl/plpgsql/src/pl_handler.c
+++ b/src/pl/plpgsql/src/pl_handler.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_handler.c,v 1.46 2009/09/22 23:43:42 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_handler.c,v 1.47 2009/11/04 22:26:07 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -68,6 +68,7 @@ Datum
 plpgsql_call_handler(PG_FUNCTION_ARGS)
 {
 	PLpgSQL_function *func;
+	PLpgSQL_execstate *save_cur_estate;
 	Datum		retval;
 	int			rc;
 
@@ -80,6 +81,9 @@ plpgsql_call_handler(PG_FUNCTION_ARGS)
 	/* 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,14 +101,17 @@ plpgsql_call_handler(PG_FUNCTION_ARGS)
 	}
 	PG_CATCH();
 	{
-		/* Decrement use-count and propagate error */
+		/* 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
 	 */
diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h
index 55ddf54ab2bbecfdde1fc3439725d1d2ec55a4e4..3d0f155a884142d477b8a4fbe55ece44478ad84f 100644
--- a/src/pl/plpgsql/src/plpgsql.h
+++ b/src/pl/plpgsql/src/plpgsql.h
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.117 2009/09/29 20:05:29 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.118 2009/11/04 22:26:07 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -22,6 +22,7 @@
 #include "fmgr.h"
 #include "commands/trigger.h"
 #include "executor/spi.h"
+#include "nodes/bitmapset.h"
 #include "utils/tuplestore.h"
 
 /**********************************************************************
@@ -58,8 +59,7 @@ enum
 	PLPGSQL_DTYPE_REC,
 	PLPGSQL_DTYPE_RECFIELD,
 	PLPGSQL_DTYPE_ARRAYELEM,
-	PLPGSQL_DTYPE_EXPR,
-	PLPGSQL_DTYPE_TRIGARG
+	PLPGSQL_DTYPE_EXPR
 };
 
 /* ----------
@@ -162,8 +162,7 @@ typedef struct
 
 /*
  * PLpgSQL_datum is the common supertype for PLpgSQL_expr, PLpgSQL_var,
- * PLpgSQL_row, PLpgSQL_rec, PLpgSQL_recfield, PLpgSQL_arrayelem, and
- * PLpgSQL_trigarg
+ * PLpgSQL_row, PLpgSQL_rec, PLpgSQL_recfield, and PLpgSQL_arrayelem
  */
 typedef struct
 {								/* Generic datum array item		*/
@@ -189,7 +188,11 @@ typedef struct PLpgSQL_expr
 	int			dno;
 	char	   *query;
 	SPIPlanPtr	plan;
-	Oid		   *plan_argtypes;
+	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,10 +205,6 @@ typedef struct PLpgSQL_expr
 	 */
 	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;
 
 
@@ -284,14 +283,6 @@ typedef struct
 } PLpgSQL_arrayelem;
 
 
-typedef struct
-{								/* Positional argument to trigger	*/
-	int			dtype;
-	int			dno;
-	PLpgSQL_expr *argnum;
-} PLpgSQL_trigarg;
-
-
 typedef struct
 {								/* Item in the compilers namestack	*/
 	int			itemtype;
@@ -670,17 +661,22 @@ typedef struct PLpgSQL_function
 	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
+typedef struct PLpgSQL_execstate
 {								/* Runtime execution data	*/
+	PLpgSQL_function *func;		/* function being executed */
+
 	Datum		retval;
 	bool		retisnull;
 	Oid			rettype;		/* type of current retval */
@@ -699,9 +695,6 @@ typedef struct
 	MemoryContext tuple_store_cxt;
 	ReturnSetInfo *rsi;
 
-	int			trig_nargs;
-	Datum	   *trig_argv;
-
 	int			found_varno;
 	int			ndatums;
 	PLpgSQL_datum **datums;
@@ -711,11 +704,12 @@ typedef struct
 	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_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;
 
diff --git a/src/test/regress/expected/plpgsql.out b/src/test/regress/expected/plpgsql.out
index a362fb537057a422dffd652df1ded66827e2a222..e3cacf589eca3c49a9763db5accc2fd319676786 100644
--- a/src/test/regress/expected/plpgsql.out
+++ b/src/test/regress/expected/plpgsql.out
@@ -285,11 +285,9 @@ begin
     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;
+    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);
diff --git a/src/test/regress/sql/plpgsql.sql b/src/test/regress/sql/plpgsql.sql
index 80de8eb72f8e43a989d40012fa4a008b87738503..3e6b6de539e7bb94d675f8d9695d5b3ab2a28e73 100644
--- a/src/test/regress/sql/plpgsql.sql
+++ b/src/test/regress/sql/plpgsql.sql
@@ -347,11 +347,9 @@ begin
     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;
+    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);