From 95f6d2d20921b7c2dbec29bf2706fd9448208aa6 Mon Sep 17 00:00:00 2001
From: Tom Lane <tgl@sss.pgh.pa.us>
Date: Thu, 15 Mar 2007 23:12:07 +0000
Subject: [PATCH] Make use of plancache module for SPI plans.  In particular,
 since plpgsql uses SPI plans, this finally fixes the ancient gotcha that you
 can't drop and recreate a temp table used by a plpgsql function.

Along the way, clean up SPI's API a little bit by declaring SPI plan
pointers as "SPIPlanPtr" instead of "void *".  This is cosmetic but
helps to forestall simple programming mistakes.  (I have changed some
but not all of the callers to match; there are still some "void *"'s
in contrib and the PL's.  This is intentional so that we can see if
anyone's compiler complains about it.)
---
 contrib/spi/refint.c                    |  10 +-
 contrib/spi/timetravel.c                |   4 +-
 doc/src/sgml/spi.sgml                   | 100 +++---
 src/backend/executor/spi.c              | 402 +++++++++++++++++-------
 src/backend/utils/adt/ri_triggers.c     |  58 ++--
 src/backend/utils/adt/ruleutils.c       |  10 +-
 src/backend/utils/adt/xml.c             |   6 +-
 src/backend/utils/cache/plancache.c     |  24 +-
 src/include/executor/spi.h              |  34 +-
 src/include/executor/spi_priv.h         |  59 ++--
 src/include/utils/plancache.h           |   3 +-
 src/pl/plpgsql/src/pl_exec.c            | 119 +++++--
 src/pl/plpgsql/src/plpgsql.h            |   7 +-
 src/test/regress/expected/plancache.out |  61 ++++
 src/test/regress/regress.c              |   6 +-
 src/test/regress/sql/plancache.sql      |  40 +++
 16 files changed, 656 insertions(+), 287 deletions(-)

diff --git a/contrib/spi/refint.c b/contrib/spi/refint.c
index c0c55eb080e..d88975d5e9c 100644
--- a/contrib/spi/refint.c
+++ b/contrib/spi/refint.c
@@ -19,7 +19,7 @@ typedef struct
 {
 	char	   *ident;
 	int			nplans;
-	void	  **splan;
+	SPIPlanPtr *splan;
 }	EPlan;
 
 static EPlan *FPlans = NULL;
@@ -163,7 +163,7 @@ check_primary_key(PG_FUNCTION_ARGS)
 	 */
 	if (plan->nplans <= 0)
 	{
-		void	   *pplan;
+		SPIPlanPtr	pplan;
 		char		sql[8192];
 
 		/*
@@ -191,7 +191,7 @@ check_primary_key(PG_FUNCTION_ARGS)
 		if (pplan == NULL)
 			/* internal error */
 			elog(ERROR, "check_primary_key: SPI_saveplan returned %d", SPI_result);
-		plan->splan = (void **) malloc(sizeof(void *));
+		plan->splan = (SPIPlanPtr *) malloc(sizeof(SPIPlanPtr));
 		*(plan->splan) = pplan;
 		plan->nplans = 1;
 	}
@@ -413,11 +413,11 @@ check_foreign_key(PG_FUNCTION_ARGS)
 	 */
 	if (plan->nplans <= 0)
 	{
-		void	   *pplan;
+		SPIPlanPtr	pplan;
 		char		sql[8192];
 		char	  **args2 = args;
 
-		plan->splan = (void **) malloc(nrefs * sizeof(void *));
+		plan->splan = (SPIPlanPtr *) malloc(nrefs * sizeof(SPIPlanPtr));
 
 		for (r = 0; r < nrefs; r++)
 		{
diff --git a/contrib/spi/timetravel.c b/contrib/spi/timetravel.c
index a1f69a10fbb..4b238fa425f 100644
--- a/contrib/spi/timetravel.c
+++ b/contrib/spi/timetravel.c
@@ -24,7 +24,7 @@ Datum		get_timetravel(PG_FUNCTION_ARGS);
 typedef struct
 {
 	char	   *ident;
-	void	   *splan;
+	SPIPlanPtr	splan;
 }	EPlan;
 
 static EPlan *Plans = NULL;		/* for UPDATE/DELETE */
@@ -308,7 +308,7 @@ timetravel(PG_FUNCTION_ARGS)
 	/* if there is no plan ... */
 	if (plan->splan == NULL)
 	{
-		void	   *pplan;
+		SPIPlanPtr	pplan;
 		Oid		   *ctypes;
 		char		sql[8192];
 		char		separ = ' ';
diff --git a/doc/src/sgml/spi.sgml b/doc/src/sgml/spi.sgml
index 90a68a5d01b..c7c4304b3a7 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.53 2007/02/18 01:47:40 momjian Exp $ -->
+<!-- $PostgreSQL: pgsql/doc/src/sgml/spi.sgml,v 1.54 2007/03/15 23:12:06 tgl Exp $ -->
 
 <chapter id="spi">
  <title>Server Programming Interface</title>
@@ -14,7 +14,7 @@
   <acronym>SQL</acronym> commands inside their functions.
   <acronym>SPI</acronym> is a set of
   interface functions to simplify access to the parser, planner,
-  optimizer, and executor. <acronym>SPI</acronym> also does some
+  and executor. <acronym>SPI</acronym> also does some
   memory management.
  </para>
 
@@ -322,7 +322,8 @@ SPI_execute("INSERT INTO foo SELECT * FROM bar", false, 5);
 
   <para>
    You can pass multiple commands in one string, but later commands cannot
-   depend on the creation of objects earlier in the string.
+   depend on the creation of objects earlier in the string, because the
+   whole string will be parsed and planned before execution begins.
    <function>SPI_execute</function> returns the
    result for the command executed last.  The <parameter>count</parameter>
    limit applies to each command separately, but it is not applied to
@@ -699,7 +700,7 @@ int SPI_exec(const char * <parameter>command</parameter>, long <parameter>count<
 
  <refsynopsisdiv>
 <synopsis>
-void * SPI_prepare(const char * <parameter>command</parameter>, int <parameter>nargs</parameter>, Oid * <parameter>argtypes</parameter>)
+SPIPlanPtr SPI_prepare(const char * <parameter>command</parameter>, int <parameter>nargs</parameter>, Oid * <parameter>argtypes</parameter>)
 </synopsis>
  </refsynopsisdiv>
 
@@ -790,6 +791,13 @@ void * SPI_prepare(const char * <parameter>command</parameter>, int <parameter>n
  <refsect1>
   <title>Notes</title>
 
+  <para>
+   <type>SPIPlanPtr</> is declared as a pointer to an opaque struct type in
+   <filename>spi.h</>.  It is unwise to try to access its contents
+   directly, as that makes your code much more likely to break in
+   future revisions of <productname>PostgreSQL</productname>.
+  </para>
+
   <para>
    There is a disadvantage to using parameters: since the planner does
    not know the values that will be supplied for the parameters, it
@@ -816,7 +824,7 @@ void * SPI_prepare(const char * <parameter>command</parameter>, int <parameter>n
 
  <refsynopsisdiv>
 <synopsis>
-int SPI_getargcount(void * <parameter>plan</parameter>)
+int SPI_getargcount(SPIPlanPtr <parameter>plan</parameter>)
 </synopsis>
  </refsynopsisdiv>
 
@@ -834,7 +842,7 @@ int SPI_getargcount(void * <parameter>plan</parameter>)
 
   <variablelist>
    <varlistentry>
-    <term><literal>void * <parameter>plan</parameter></literal></term>
+    <term><literal>SPIPlanPtr <parameter>plan</parameter></literal></term>
     <listitem>
      <para>
       execution plan (returned by <function>SPI_prepare</function>)
@@ -847,9 +855,10 @@ int SPI_getargcount(void * <parameter>plan</parameter>)
  <refsect1>
   <title>Return Value</title>
   <para>
-    The expected argument count for the <parameter>plan</parameter>, or
-    <symbol>SPI_ERROR_ARGUMENT</symbol> if the <parameter>plan
-    </parameter> is <symbol>NULL</symbol>
+    The count of expected arguments for the <parameter>plan</parameter>.
+    If the <parameter>plan</parameter> is <symbol>NULL</symbol> or invalid,
+    <varname>SPI_result</varname> is set to <symbol>SPI_ERROR_ARGUMENT</symbol>
+    and <literal>-1</literal> is returned.
   </para>
  </refsect1>
 </refentry>
@@ -871,7 +880,7 @@ int SPI_getargcount(void * <parameter>plan</parameter>)
 
  <refsynopsisdiv>
 <synopsis>
-Oid SPI_getargtypeid(void * <parameter>plan</parameter>, int <parameter>argIndex</parameter>)
+Oid SPI_getargtypeid(SPIPlanPtr <parameter>plan</parameter>, int <parameter>argIndex</parameter>)
 </synopsis>
  </refsynopsisdiv>
 
@@ -890,7 +899,7 @@ Oid SPI_getargtypeid(void * <parameter>plan</parameter>, int <parameter>argIndex
 
   <variablelist>
    <varlistentry>
-    <term><literal>void * <parameter>plan</parameter></literal></term>
+    <term><literal>SPIPlanPtr <parameter>plan</parameter></literal></term>
     <listitem>
      <para>
       execution plan (returned by <function>SPI_prepare</function>)
@@ -912,11 +921,13 @@ Oid SPI_getargtypeid(void * <parameter>plan</parameter>, int <parameter>argIndex
  <refsect1>
   <title>Return Value</title>
   <para>
-    The type id of the argument at the given index, or
-    <symbol>SPI_ERROR_ARGUMENT</symbol> if the <parameter>plan</parameter> is
-    <symbol>NULL</symbol> or <parameter>argIndex</parameter> is less than 0 or
+    The type id of the argument at the given index.
+    If the <parameter>plan</parameter> is <symbol>NULL</symbol> or invalid,
+    or <parameter>argIndex</parameter> is less than 0 or
     not less than the number of arguments declared for the
-    <parameter>plan</parameter>
+    <parameter>plan</parameter>,
+    <varname>SPI_result</varname> is set to <symbol>SPI_ERROR_ARGUMENT</symbol>
+    and <symbol>InvalidOid</symbol> is returned.
   </para>
  </refsect1>
 </refentry>
@@ -939,7 +950,7 @@ Oid SPI_getargtypeid(void * <parameter>plan</parameter>, int <parameter>argIndex
 
  <refsynopsisdiv>
 <synopsis>
-bool SPI_is_cursor_plan(void * <parameter>plan</parameter>)
+bool SPI_is_cursor_plan(SPIPlanPtr <parameter>plan</parameter>)
 </synopsis>
  </refsynopsisdiv>
 
@@ -964,7 +975,7 @@ bool SPI_is_cursor_plan(void * <parameter>plan</parameter>)
 
   <variablelist>
    <varlistentry>
-    <term><literal>void * <parameter>plan</parameter></literal></term>
+    <term><literal>SPIPlanPtr <parameter>plan</parameter></literal></term>
     <listitem>
      <para>
       execution plan (returned by <function>SPI_prepare</function>)
@@ -978,9 +989,10 @@ bool SPI_is_cursor_plan(void * <parameter>plan</parameter>)
   <title>Return Value</title>
   <para>
     <symbol>true</symbol> or <symbol>false</symbol> to indicate if the
-    <parameter>plan</parameter> can produce a cursor or not, or
-    <symbol>SPI_ERROR_ARGUMENT</symbol> if the <parameter>plan</parameter>
-    is <symbol>NULL</symbol>
+    <parameter>plan</parameter> can produce a cursor or not.
+    If the <parameter>plan</parameter> is <symbol>NULL</symbol> or invalid,
+    <varname>SPI_result</varname> is set to <symbol>SPI_ERROR_ARGUMENT</symbol>
+    and <symbol>false</symbol> is returned.
   </para>
  </refsect1>
 </refentry>
@@ -1001,7 +1013,7 @@ bool SPI_is_cursor_plan(void * <parameter>plan</parameter>)
 
  <refsynopsisdiv>
 <synopsis>
-int SPI_execute_plan(void * <parameter>plan</parameter>, Datum * <parameter>values</parameter>, const char * <parameter>nulls</parameter>,
+int SPI_execute_plan(SPIPlanPtr <parameter>plan</parameter>, Datum * <parameter>values</parameter>, const char * <parameter>nulls</parameter>,
                      bool <parameter>read_only</parameter>, long <parameter>count</parameter>)
 </synopsis>
  </refsynopsisdiv>
@@ -1022,7 +1034,7 @@ int SPI_execute_plan(void * <parameter>plan</parameter>, Datum * <parameter>valu
 
   <variablelist>
    <varlistentry>
-    <term><literal>void * <parameter>plan</parameter></literal></term>
+    <term><literal>SPIPlanPtr <parameter>plan</parameter></literal></term>
     <listitem>
      <para>
       execution plan (returned by <function>SPI_prepare</function>)
@@ -1091,8 +1103,8 @@ int SPI_execute_plan(void * <parameter>plan</parameter>, Datum * <parameter>valu
      <term><symbol>SPI_ERROR_ARGUMENT</symbol></term>
      <listitem>
       <para>
-       if <parameter>plan</parameter> is <symbol>NULL</symbol> or
-       <parameter>count</parameter> is less than 0
+       if <parameter>plan</parameter> is <symbol>NULL</symbol> or invalid,
+       or <parameter>count</parameter> is less than 0
       </para>
      </listitem>
     </varlistentry>
@@ -1143,7 +1155,7 @@ int SPI_execute_plan(void * <parameter>plan</parameter>, Datum * <parameter>valu
 
  <refsynopsisdiv>
 <synopsis>
-int SPI_execp(void * <parameter>plan</parameter>, Datum * <parameter>values</parameter>, const char * <parameter>nulls</parameter>, long <parameter>count</parameter>)
+int SPI_execp(SPIPlanPtr <parameter>plan</parameter>, Datum * <parameter>values</parameter>, const char * <parameter>nulls</parameter>, long <parameter>count</parameter>)
 </synopsis>
  </refsynopsisdiv>
 
@@ -1163,7 +1175,7 @@ int SPI_execp(void * <parameter>plan</parameter>, Datum * <parameter>values</par
 
   <variablelist>
    <varlistentry>
-    <term><literal>void * <parameter>plan</parameter></literal></term>
+    <term><literal>SPIPlanPtr <parameter>plan</parameter></literal></term>
     <listitem>
      <para>
       execution plan (returned by <function>SPI_prepare</function>)
@@ -1242,7 +1254,7 @@ int SPI_execp(void * <parameter>plan</parameter>, Datum * <parameter>values</par
 
  <refsynopsisdiv>
 <synopsis>
-Portal SPI_cursor_open(const char * <parameter>name</parameter>, void * <parameter>plan</parameter>,
+Portal SPI_cursor_open(const char * <parameter>name</parameter>, SPIPlanPtr <parameter>plan</parameter>,
                        Datum * <parameter>values</parameter>, const char * <parameter>nulls</parameter>,
                        bool <parameter>read_only</parameter>)
 </synopsis>
@@ -1268,6 +1280,11 @@ Portal SPI_cursor_open(const char * <parameter>name</parameter>, void * <paramet
    to the procedure's caller provides a way of returning a row set as
    result.
   </para>
+
+  <para>
+   The passed-in data will be copied into the cursor's portal, so it
+   can be freed while the cursor still exists.
+  </para>
  </refsect1>
 
  <refsect1>
@@ -1285,7 +1302,7 @@ Portal SPI_cursor_open(const char * <parameter>name</parameter>, void * <paramet
    </varlistentry>
 
    <varlistentry>
-    <term><literal>void * <parameter>plan</parameter></literal></term>
+    <term><literal>SPIPlanPtr <parameter>plan</parameter></literal></term>
     <listitem>
      <para>
       execution plan (returned by <function>SPI_prepare</function>)
@@ -1602,7 +1619,7 @@ void SPI_cursor_close(Portal <parameter>portal</parameter>)
 
  <refsynopsisdiv>
 <synopsis>
-void * SPI_saveplan(void * <parameter>plan</parameter>)
+SPIPlanPtr SPI_saveplan(SPIPlanPtr <parameter>plan</parameter>)
 </synopsis>
  </refsynopsisdiv>
 
@@ -1611,8 +1628,8 @@ void * SPI_saveplan(void * <parameter>plan</parameter>)
 
   <para>
    <function>SPI_saveplan</function> saves a passed plan (prepared by
-   <function>SPI_prepare</function>) in memory protected from freeing
-   by <function>SPI_finish</function> and by the transaction manager
+   <function>SPI_prepare</function>) in memory that will not be freed
+   by <function>SPI_finish</function> nor by the transaction manager,
    and returns a pointer to the saved plan.  This gives you the
    ability to reuse prepared plans in the subsequent invocations of
    your procedure in the current session.
@@ -1624,7 +1641,7 @@ void * SPI_saveplan(void * <parameter>plan</parameter>)
 
   <variablelist>
    <varlistentry>
-    <term><literal>void * <parameter>plan</parameter></literal></term>
+    <term><literal>SPIPlanPtr <parameter>plan</parameter></literal></term>
     <listitem>
      <para>
       the plan to be saved
@@ -1646,7 +1663,7 @@ void * SPI_saveplan(void * <parameter>plan</parameter>)
      <term><symbol>SPI_ERROR_ARGUMENT</symbol></term>
      <listitem>
       <para>
-       if <parameter>plan</parameter> is <symbol>NULL</symbol>
+       if <parameter>plan</parameter> is <symbol>NULL</symbol> or invalid
       </para>
      </listitem>
     </varlistentry>
@@ -1666,10 +1683,17 @@ void * SPI_saveplan(void * <parameter>plan</parameter>)
  <refsect1>
   <title>Notes</title>
 
+  <para>
+   The passed-in plan is not freed, so you might wish to do
+   <function>SPI_freeplan</function> on it to avoid leaking memory
+   until <function>SPI_finish</>.
+  </para>
+
   <para>
    If one of the objects (a table, function, etc.) referenced by the
-   prepared plan is dropped during the session then the results of
-   <function>SPI_execute_plan</function> for this plan will be unpredictable.
+   prepared plan is dropped or redefined, then future executions of
+   <function>SPI_execute_plan</function> may fail or return different
+   results than the plan initially indicates.
   </para>
  </refsect1>
 </refentry>
@@ -2879,7 +2903,7 @@ void SPI_freetuptable(SPITupleTable * <parameter>tuptable</parameter>)
 
  <refsynopsisdiv>
 <synopsis>
-int SPI_freeplan(void *<parameter>plan</parameter>)
+int SPI_freeplan(SPIPlanPtr <parameter>plan</parameter>)
 </synopsis>
  </refsynopsisdiv>
 
@@ -2898,7 +2922,7 @@ int SPI_freeplan(void *<parameter>plan</parameter>)
 
   <variablelist>
    <varlistentry>
-    <term><literal>void * <parameter>plan</parameter></literal></term>
+    <term><literal>SPIPlanPtr <parameter>plan</parameter></literal></term>
     <listitem>
      <para>
       pointer to plan to free
@@ -2913,7 +2937,7 @@ int SPI_freeplan(void *<parameter>plan</parameter>)
 
   <para>
    <symbol>SPI_ERROR_ARGUMENT</symbol> if <parameter>plan</parameter>
-   is <symbol>NULL</symbol>.
+   is <symbol>NULL</symbol> or invalid
   </para>
  </refsect1>
 </refentry>
diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c
index f538f508ba3..7a34add7105 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.171 2007/03/13 00:33:40 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/executor/spi.c,v 1.172 2007/03/15 23:12:06 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -34,9 +34,9 @@ static int	_SPI_stack_depth = 0;		/* allocated size of _SPI_stack */
 static int	_SPI_connected = -1;
 static int	_SPI_curid = -1;
 
-static void _SPI_prepare_plan(const char *src, _SPI_plan *plan);
+static void _SPI_prepare_plan(const char *src, SPIPlanPtr plan);
 
-static int _SPI_execute_plan(_SPI_plan *plan,
+static int _SPI_execute_plan(SPIPlanPtr plan,
 				  Datum *Values, const char *Nulls,
 				  Snapshot snapshot, Snapshot crosscheck_snapshot,
 				  bool read_only, long tcount);
@@ -48,7 +48,8 @@ static void _SPI_error_callback(void *arg);
 static void _SPI_cursor_operation(Portal portal, bool forward, long count,
 					  DestReceiver *dest);
 
-static _SPI_plan *_SPI_copy_plan(_SPI_plan *plan, int location);
+static SPIPlanPtr _SPI_copy_plan(SPIPlanPtr plan, MemoryContext parentcxt);
+static SPIPlanPtr _SPI_save_plan(SPIPlanPtr plan);
 
 static int	_SPI_begin_call(bool execmem);
 static int	_SPI_end_call(bool procmem);
@@ -306,10 +307,8 @@ SPI_execute(const char *src, bool read_only, long tcount)
 	if (res < 0)
 		return res;
 
-	plan.plancxt = NULL;		/* doesn't have own context */
-	plan.query = src;
-	plan.nargs = 0;
-	plan.argtypes = NULL;
+	memset(&plan, 0, sizeof(_SPI_plan));
+	plan.magic = _SPI_PLAN_MAGIC;
 
 	_SPI_prepare_plan(src, &plan);
 
@@ -330,22 +329,22 @@ SPI_exec(const char *src, long tcount)
 
 /* Execute a previously prepared plan */
 int
-SPI_execute_plan(void *plan, Datum *Values, const char *Nulls,
+SPI_execute_plan(SPIPlanPtr plan, Datum *Values, const char *Nulls,
 				 bool read_only, long tcount)
 {
 	int			res;
 
-	if (plan == NULL || tcount < 0)
+	if (plan == NULL || plan->magic != _SPI_PLAN_MAGIC || tcount < 0)
 		return SPI_ERROR_ARGUMENT;
 
-	if (((_SPI_plan *) plan)->nargs > 0 && Values == NULL)
+	if (plan->nargs > 0 && Values == NULL)
 		return SPI_ERROR_PARAM;
 
 	res = _SPI_begin_call(true);
 	if (res < 0)
 		return res;
 
-	res = _SPI_execute_plan((_SPI_plan *) plan,
+	res = _SPI_execute_plan(plan,
 							Values, Nulls,
 							InvalidSnapshot, InvalidSnapshot,
 							read_only, tcount);
@@ -356,7 +355,7 @@ SPI_execute_plan(void *plan, Datum *Values, const char *Nulls,
 
 /* Obsolete version of SPI_execute_plan */
 int
-SPI_execp(void *plan, Datum *Values, const char *Nulls, long tcount)
+SPI_execp(SPIPlanPtr plan, Datum *Values, const char *Nulls, long tcount)
 {
 	return SPI_execute_plan(plan, Values, Nulls, false, tcount);
 }
@@ -371,24 +370,24 @@ SPI_execp(void *plan, Datum *Values, const char *Nulls, long tcount)
  * fetching a new snapshot for each query.
  */
 int
-SPI_execute_snapshot(void *plan,
+SPI_execute_snapshot(SPIPlanPtr plan,
 					 Datum *Values, const char *Nulls,
 					 Snapshot snapshot, Snapshot crosscheck_snapshot,
 					 bool read_only, long tcount)
 {
 	int			res;
 
-	if (plan == NULL || tcount < 0)
+	if (plan == NULL || plan->magic != _SPI_PLAN_MAGIC || tcount < 0)
 		return SPI_ERROR_ARGUMENT;
 
-	if (((_SPI_plan *) plan)->nargs > 0 && Values == NULL)
+	if (plan->nargs > 0 && Values == NULL)
 		return SPI_ERROR_PARAM;
 
 	res = _SPI_begin_call(true);
 	if (res < 0)
 		return res;
 
-	res = _SPI_execute_plan((_SPI_plan *) plan,
+	res = _SPI_execute_plan(plan,
 							Values, Nulls,
 							snapshot, crosscheck_snapshot,
 							read_only, tcount);
@@ -397,11 +396,11 @@ SPI_execute_snapshot(void *plan,
 	return res;
 }
 
-void *
+SPIPlanPtr
 SPI_prepare(const char *src, int nargs, Oid *argtypes)
 {
 	_SPI_plan	plan;
-	_SPI_plan  *result;
+	SPIPlanPtr	result;
 
 	if (src == NULL || nargs < 0 || (nargs > 0 && argtypes == NULL))
 	{
@@ -413,27 +412,28 @@ SPI_prepare(const char *src, int nargs, Oid *argtypes)
 	if (SPI_result < 0)
 		return NULL;
 
-	plan.plancxt = NULL;		/* doesn't have own context */
-	plan.query = src;
+	memset(&plan, 0, sizeof(_SPI_plan));
+	plan.magic = _SPI_PLAN_MAGIC;
 	plan.nargs = nargs;
 	plan.argtypes = argtypes;
 
 	_SPI_prepare_plan(src, &plan);
 
 	/* copy plan to procedure context */
-	result = _SPI_copy_plan(&plan, _SPI_CPLAN_PROCXT);
+	result = _SPI_copy_plan(&plan, _SPI_current->procCxt);
 
 	_SPI_end_call(true);
 
-	return (void *) result;
+	return result;
 }
 
-void *
-SPI_saveplan(void *plan)
+SPIPlanPtr
+SPI_saveplan(SPIPlanPtr plan)
 {
-	_SPI_plan  *newplan;
+	SPIPlanPtr	newplan;
 
-	if (plan == NULL)
+	/* We don't currently support copying an already-saved plan */
+	if (plan == NULL || plan->magic != _SPI_PLAN_MAGIC || plan->saved)
 	{
 		SPI_result = SPI_ERROR_ARGUMENT;
 		return NULL;
@@ -443,23 +443,36 @@ SPI_saveplan(void *plan)
 	if (SPI_result < 0)
 		return NULL;
 
-	newplan = _SPI_copy_plan((_SPI_plan *) plan, _SPI_CPLAN_TOPCXT);
+	newplan = _SPI_save_plan(plan);
 
 	_SPI_curid--;
 	SPI_result = 0;
 
-	return (void *) newplan;
+	return newplan;
 }
 
 int
-SPI_freeplan(void *plan)
+SPI_freeplan(SPIPlanPtr plan)
 {
-	_SPI_plan  *spiplan = (_SPI_plan *) plan;
-
-	if (plan == NULL)
+	if (plan == NULL || plan->magic != _SPI_PLAN_MAGIC)
 		return SPI_ERROR_ARGUMENT;
 
-	MemoryContextDelete(spiplan->plancxt);
+	/* If plancache.c owns the plancache entries, we must release them */
+	if (plan->saved)
+	{
+		ListCell   *lc;
+
+		foreach(lc, plan->plancache_list)
+		{
+			CachedPlanSource *plansource = (CachedPlanSource *) lfirst(lc);
+
+			DropCachedPlan(plansource);
+		}
+	}
+
+	/* Now get rid of the _SPI_plan and subsidiary data in its plancxt */
+	MemoryContextDelete(plan->plancxt);
+
 	return 0;
 }
 
@@ -819,18 +832,18 @@ SPI_freetuptable(SPITupleTable *tuptable)
 }
 
 
-
 /*
  * SPI_cursor_open()
  *
  *	Open a prepared SPI plan as a portal
  */
 Portal
-SPI_cursor_open(const char *name, void *plan,
+SPI_cursor_open(const char *name, SPIPlanPtr plan,
 				Datum *Values, const char *Nulls,
 				bool read_only)
 {
-	_SPI_plan  *spiplan = (_SPI_plan *) plan;
+	CachedPlanSource *plansource;
+	CachedPlan *cplan;
 	List	   *stmt_list;
 	ParamListInfo paramLI;
 	Snapshot	snapshot;
@@ -842,25 +855,23 @@ SPI_cursor_open(const char *name, void *plan,
 	 * Check that the plan is something the Portal code will special-case as
 	 * returning one tupleset.
 	 */
-	if (!SPI_is_cursor_plan(spiplan))
+	if (!SPI_is_cursor_plan(plan))
 	{
 		/* try to give a good error message */
-		Node	   *stmt;
-
-		if (list_length(spiplan->stmt_list_list) != 1)
+		if (list_length(plan->plancache_list) != 1)
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_CURSOR_DEFINITION),
 					 errmsg("cannot open multi-query plan as cursor")));
-		stmt = PortalListGetPrimaryStmt((List *) linitial(spiplan->stmt_list_list));
+		plansource = (CachedPlanSource *) linitial(plan->plancache_list);
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_CURSOR_DEFINITION),
 		/* translator: %s is name of a SQL command, eg INSERT */
 				 errmsg("cannot open %s query as cursor",
-						CreateCommandTag(stmt))));
+						plansource->commandTag)));
 	}
 
-	Assert(list_length(spiplan->stmt_list_list) == 1);
-	stmt_list = (List *) linitial(spiplan->stmt_list_list);
+	Assert(list_length(plan->plancache_list) == 1);
+	plansource = (CachedPlanSource *) linitial(plan->plancache_list);
 
 	/* Reset SPI result (note we deliberately don't touch lastoid) */
 	SPI_processed = 0;
@@ -880,23 +891,20 @@ SPI_cursor_open(const char *name, void *plan,
 		portal = CreatePortal(name, false, false);
 	}
 
-	/* Switch to portal's memory and copy the plans to there */
-	oldcontext = MemoryContextSwitchTo(PortalGetHeapMemory(portal));
-	stmt_list = copyObject(stmt_list);
-
-	/* If the plan has parameters, set them up */
-	if (spiplan->nargs > 0)
+	/* If the plan has parameters, copy them into the portal */
+	if (plan->nargs > 0)
 	{
+		oldcontext = MemoryContextSwitchTo(PortalGetHeapMemory(portal));
 		/* sizeof(ParamListInfoData) includes the first array element */
 		paramLI = (ParamListInfo) palloc(sizeof(ParamListInfoData) +
-							  (spiplan->nargs - 1) *sizeof(ParamExternData));
-		paramLI->numParams = spiplan->nargs;
+							  (plan->nargs - 1) *sizeof(ParamExternData));
+		paramLI->numParams = plan->nargs;
 
-		for (k = 0; k < spiplan->nargs; k++)
+		for (k = 0; k < plan->nargs; k++)
 		{
 			ParamExternData *prm = &paramLI->params[k];
 
-			prm->ptype = spiplan->argtypes[k];
+			prm->ptype = plan->argtypes[k];
 			prm->pflags = 0;
 			prm->isnull = (Nulls && Nulls[k] == 'n');
 			if (prm->isnull)
@@ -915,21 +923,35 @@ SPI_cursor_open(const char *name, void *plan,
 									   paramTypByVal, paramTypLen);
 			}
 		}
+		MemoryContextSwitchTo(oldcontext);
 	}
 	else
 		paramLI = NULL;
 
+	if (plan->saved)
+	{
+		/* Replan if needed, and increment plan refcount for portal */
+		cplan = RevalidateCachedPlan(plansource, false);
+		stmt_list = cplan->stmt_list;
+	}
+	else
+	{
+		/* No replan, but copy the plan into the portal's context */
+		oldcontext = MemoryContextSwitchTo(PortalGetHeapMemory(portal));
+		stmt_list = copyObject(plansource->plan->stmt_list);
+		MemoryContextSwitchTo(oldcontext);
+		cplan = NULL;			/* portal shouldn't depend on cplan */
+	}
+
 	/*
 	 * Set up the portal.
 	 */
 	PortalDefineQuery(portal,
 					  NULL,		/* no statement name */
-					  spiplan->query,
-					  CreateCommandTag(PortalListGetPrimaryStmt(stmt_list)),
+					  plansource->query_string,
+					  plansource->commandTag,
 					  stmt_list,
-					  NULL);
-
-	MemoryContextSwitchTo(oldcontext);
+					  cplan);
 
 	/*
 	 * Set up options for portal.
@@ -1023,28 +1045,29 @@ SPI_cursor_close(Portal portal)
  * parameter is at index zero.
  */
 Oid
-SPI_getargtypeid(void *plan, int argIndex)
+SPI_getargtypeid(SPIPlanPtr plan, int argIndex)
 {
-	if (plan == NULL || argIndex < 0 || argIndex >= ((_SPI_plan *) plan)->nargs)
+	if (plan == NULL || plan->magic != _SPI_PLAN_MAGIC ||
+		argIndex < 0 || argIndex >= plan->nargs)
 	{
 		SPI_result = SPI_ERROR_ARGUMENT;
 		return InvalidOid;
 	}
-	return ((_SPI_plan *) plan)->argtypes[argIndex];
+	return plan->argtypes[argIndex];
 }
 
 /*
  * Returns the number of arguments for the prepared plan.
  */
 int
-SPI_getargcount(void *plan)
+SPI_getargcount(SPIPlanPtr plan)
 {
-	if (plan == NULL)
+	if (plan == NULL || plan->magic != _SPI_PLAN_MAGIC)
 	{
 		SPI_result = SPI_ERROR_ARGUMENT;
 		return -1;
 	}
-	return ((_SPI_plan *) plan)->nargs;
+	return plan->nargs;
 }
 
 /*
@@ -1057,31 +1080,32 @@ SPI_getargcount(void *plan)
  *	  plan: A plan previously prepared using SPI_prepare
  */
 bool
-SPI_is_cursor_plan(void *plan)
+SPI_is_cursor_plan(SPIPlanPtr plan)
 {
-	_SPI_plan  *spiplan = (_SPI_plan *) plan;
+	CachedPlanSource *plansource;
+	CachedPlan *cplan;
 
-	if (spiplan == NULL)
+	if (plan == NULL || plan->magic != _SPI_PLAN_MAGIC)
 	{
 		SPI_result = SPI_ERROR_ARGUMENT;
 		return false;
 	}
 
-	if (list_length(spiplan->stmt_list_list) != 1)
+	if (list_length(plan->plancache_list) != 1)
 		return false;			/* not exactly 1 pre-rewrite command */
+	plansource = (CachedPlanSource *) linitial(plan->plancache_list);
 
-	switch (ChoosePortalStrategy((List *) linitial(spiplan->stmt_list_list)))
+	if (plan->saved)
 	{
-		case PORTAL_ONE_SELECT:
-		case PORTAL_ONE_RETURNING:
-		case PORTAL_UTIL_SELECT:
-			/* OK */
-			return true;
-
-		case PORTAL_MULTI_QUERY:
-			/* will not return tuples */
-			break;
+		/* Make sure the plan is up to date */
+		cplan = RevalidateCachedPlan(plansource, true);
+		ReleaseCachedPlan(cplan, true);
 	}
+
+	/* Does it return tuples? */
+	if (plansource->resultDesc)
+		return true;
+
 	return false;
 }
 
@@ -1248,13 +1272,15 @@ spi_printtup(TupleTableSlot *slot, DestReceiver *self)
  *
  * At entry, plan->argtypes and plan->nargs must be valid.
  *
- * Result lists are stored into *plan.
+ * Results are stored into *plan (specifically, plan->plancache_list).
+ * Note however that the result trees are all in CurrentMemoryContext
+ * and need to be copied somewhere to survive.
  */
 static void
-_SPI_prepare_plan(const char *src, _SPI_plan *plan)
+_SPI_prepare_plan(const char *src, SPIPlanPtr plan)
 {
 	List	   *raw_parsetree_list;
-	List	   *stmt_list_list;
+	List	   *plancache_list;
 	ListCell   *list_item;
 	ErrorContextCallback spierrcontext;
 	Oid		   *argtypes = plan->argtypes;
@@ -1282,26 +1308,44 @@ _SPI_prepare_plan(const char *src, _SPI_plan *plan)
 	raw_parsetree_list = pg_parse_query(src);
 
 	/*
-	 * Do parse analysis and rule rewrite for each raw parsetree.
-	 *
-	 * We save the results from each raw parsetree as a separate sublist.
-	 * This allows _SPI_execute_plan() to know where the boundaries between
-	 * original queries fall.
+	 * Do parse analysis and rule rewrite for each raw parsetree, then
+	 * cons up a phony plancache entry for each one.
 	 */
-	stmt_list_list = NIL;
+	plancache_list = NIL;
 
 	foreach(list_item, raw_parsetree_list)
 	{
 		Node	   *parsetree = (Node *) lfirst(list_item);
-		List	   *query_list;
-
-		query_list = pg_analyze_and_rewrite(parsetree, src, argtypes, nargs);
-
-		stmt_list_list = lappend(stmt_list_list,
-								 pg_plan_queries(query_list, NULL, false));
+		List	   *stmt_list;
+		CachedPlanSource *plansource;
+		CachedPlan *cplan;
+
+		/* Need a copyObject here to keep parser from modifying raw tree */
+		stmt_list = pg_analyze_and_rewrite(copyObject(parsetree),
+										   src, argtypes, nargs);
+		stmt_list = pg_plan_queries(stmt_list, NULL, false);
+
+		plansource = (CachedPlanSource *) palloc0(sizeof(CachedPlanSource));
+		cplan = (CachedPlan *) palloc0(sizeof(CachedPlan));
+
+	    plansource->raw_parse_tree = parsetree;
+		/* cast-away-const here is a bit ugly, but there's no reason to copy */
+	    plansource->query_string = (char *) src;
+		plansource->commandTag = CreateCommandTag(parsetree);
+		plansource->param_types = argtypes;
+		plansource->num_params = nargs;
+		plansource->fully_planned = true;
+		plansource->fixed_result = false;
+		plansource->resultDesc = PlanCacheComputeResultDesc(stmt_list);
+		plansource->plan = cplan;
+
+		cplan->stmt_list = stmt_list;
+		cplan->fully_planned = true;
+
+		plancache_list = lappend(plancache_list, plansource);
 	}
 
-	plan->stmt_list_list = stmt_list_list;
+	plan->plancache_list = plancache_list;
 
 	/*
 	 * Pop the error context stack
@@ -1319,7 +1363,7 @@ _SPI_prepare_plan(const char *src, _SPI_plan *plan)
  * tcount: execution tuple-count limit, or 0 for none
  */
 static int
-_SPI_execute_plan(_SPI_plan *plan, Datum *Values, const char *Nulls,
+_SPI_execute_plan(SPIPlanPtr plan, Datum *Values, const char *Nulls,
 				  Snapshot snapshot, Snapshot crosscheck_snapshot,
 				  bool read_only, long tcount)
 {
@@ -1334,10 +1378,11 @@ _SPI_execute_plan(_SPI_plan *plan, Datum *Values, const char *Nulls,
 	saveActiveSnapshot = ActiveSnapshot;
 	PG_TRY();
 	{
-		ListCell   *stmt_list_list_item;
+		ListCell   *lc1;
 		ErrorContextCallback spierrcontext;
 		int			nargs = plan->nargs;
 		ParamListInfo paramLI;
+		CachedPlan *cplan = NULL;
 
 		/* Convert parameters to form wanted by executor */
 		if (nargs > 0)
@@ -1366,18 +1411,34 @@ _SPI_execute_plan(_SPI_plan *plan, Datum *Values, const char *Nulls,
 		 * Setup error traceback support for ereport()
 		 */
 		spierrcontext.callback = _SPI_error_callback;
-		spierrcontext.arg = (void *) plan->query;
+		spierrcontext.arg = NULL;
 		spierrcontext.previous = error_context_stack;
 		error_context_stack = &spierrcontext;
 
-		foreach(stmt_list_list_item, plan->stmt_list_list)
+		foreach(lc1, plan->plancache_list)
 		{
-			List	   *stmt_list = (List *) lfirst(stmt_list_list_item);
-			ListCell   *stmt_list_item;
+			CachedPlanSource *plansource = (CachedPlanSource *) lfirst(lc1);
+			List	   *stmt_list;
+			ListCell   *lc2;
 
-			foreach(stmt_list_item, stmt_list)
+			spierrcontext.arg = (void *) plansource->query_string;
+
+			if (plan->saved)
 			{
-				Node	   *stmt = (Node *) lfirst(stmt_list_item);
+				/* Replan if needed, and increment plan refcount locally */
+				cplan = RevalidateCachedPlan(plansource, true);
+				stmt_list = cplan->stmt_list;
+			}
+			else
+			{
+				/* No replan here */
+				cplan = NULL;
+				stmt_list = plansource->plan->stmt_list;
+			}
+
+			foreach(lc2, stmt_list)
+			{
+				Node	   *stmt = (Node *) lfirst(lc2);
 				bool		canSetTag;
 				QueryDesc  *qdesc;
 				DestReceiver *dest;
@@ -1510,10 +1571,19 @@ _SPI_execute_plan(_SPI_plan *plan, Datum *Values, const char *Nulls,
 					goto fail;
 				}
 			}
+
+			/* Done with this plan, so release refcount */
+			if (cplan)
+				ReleaseCachedPlan(cplan, true);
+			cplan = NULL;
 		}
 
 fail:
 
+		/* We no longer need the cached plan refcount, if any */
+		if (cplan)
+			ReleaseCachedPlan(cplan, true);
+
 		/*
 		 * Pop the error context stack
 		 */
@@ -1772,22 +1842,18 @@ _SPI_checktuples(void)
 	return failed;
 }
 
-static _SPI_plan *
-_SPI_copy_plan(_SPI_plan *plan, int location)
+/*
+ * Make an "unsaved" copy of the given plan, in a child context of parentcxt.
+ */
+static SPIPlanPtr
+_SPI_copy_plan(SPIPlanPtr plan, MemoryContext parentcxt)
 {
-	_SPI_plan  *newplan;
-	MemoryContext oldcxt;
+	SPIPlanPtr	newplan;
 	MemoryContext plancxt;
-	MemoryContext parentcxt;
+	MemoryContext oldcxt;
+	ListCell   *lc;
 
-	/* Determine correct parent for the plan's memory context */
-	if (location == _SPI_CPLAN_PROCXT)
-		parentcxt = _SPI_current->procCxt;
-	else if (location == _SPI_CPLAN_TOPCXT)
-		parentcxt = TopMemoryContext;
-	else
-		/* (this case not currently used) */
-		parentcxt = CurrentMemoryContext;
+	Assert(!plan->saved);		/* not currently supported */
 
 	/*
 	 * Create a memory context for the plan.  We don't expect the plan to be
@@ -1801,10 +1867,11 @@ _SPI_copy_plan(_SPI_plan *plan, int location)
 	oldcxt = MemoryContextSwitchTo(plancxt);
 
 	/* Copy the SPI plan into its own context */
-	newplan = (_SPI_plan *) palloc(sizeof(_SPI_plan));
+	newplan = (SPIPlanPtr) palloc(sizeof(_SPI_plan));
+	newplan->magic = _SPI_PLAN_MAGIC;
+	newplan->saved = false;
+	newplan->plancache_list = NIL;
 	newplan->plancxt = plancxt;
-	newplan->query = pstrdup(plan->query);
-	newplan->stmt_list_list = (List *) copyObject(plan->stmt_list_list);
 	newplan->nargs = plan->nargs;
 	if (plan->nargs > 0)
 	{
@@ -1814,6 +1881,101 @@ _SPI_copy_plan(_SPI_plan *plan, int location)
 	else
 		newplan->argtypes = NULL;
 
+	foreach(lc, plan->plancache_list)
+	{
+		CachedPlanSource *plansource = (CachedPlanSource *) lfirst(lc);
+		CachedPlanSource *newsource;
+		CachedPlan *cplan;
+		CachedPlan *newcplan;
+
+		/* Note: we assume we don't need to revalidate the plan */
+		cplan = plansource->plan;
+
+		newsource = (CachedPlanSource *) palloc0(sizeof(CachedPlanSource));
+		newcplan = (CachedPlan *) palloc0(sizeof(CachedPlan));
+
+	    newsource->raw_parse_tree = copyObject(plansource->raw_parse_tree);
+	    newsource->query_string = pstrdup(plansource->query_string);
+		newsource->commandTag = plansource->commandTag;
+		newsource->param_types = newplan->argtypes;
+		newsource->num_params = newplan->nargs;
+		newsource->fully_planned = plansource->fully_planned;
+		newsource->fixed_result = plansource->fixed_result;
+		if (plansource->resultDesc)
+			newsource->resultDesc = CreateTupleDescCopy(plansource->resultDesc);
+		newsource->plan = newcplan;
+
+		newcplan->stmt_list = copyObject(cplan->stmt_list);
+		newcplan->fully_planned = cplan->fully_planned;
+
+		newplan->plancache_list = lappend(newplan->plancache_list, newsource);
+	}
+
+	MemoryContextSwitchTo(oldcxt);
+
+	return newplan;
+}
+
+/*
+ * Make a "saved" copy of the given plan, entrusting everything to plancache.c
+ */
+static SPIPlanPtr
+_SPI_save_plan(SPIPlanPtr plan)
+{
+	SPIPlanPtr	newplan;
+	MemoryContext plancxt;
+	MemoryContext oldcxt;
+	ListCell   *lc;
+
+	Assert(!plan->saved);		/* not currently supported */
+
+	/*
+	 * Create a memory context for the plan.  We don't expect the plan to be
+	 * very large, so use smaller-than-default alloc parameters.
+	 */
+	plancxt = AllocSetContextCreate(CacheMemoryContext,
+									"SPI Plan",
+									ALLOCSET_SMALL_MINSIZE,
+									ALLOCSET_SMALL_INITSIZE,
+									ALLOCSET_SMALL_MAXSIZE);
+	oldcxt = MemoryContextSwitchTo(plancxt);
+
+	/* Copy the SPI plan into its own context */
+	newplan = (SPIPlanPtr) palloc(sizeof(_SPI_plan));
+	newplan->magic = _SPI_PLAN_MAGIC;
+	newplan->saved = true;
+	newplan->plancache_list = NIL;
+	newplan->plancxt = plancxt;
+	newplan->nargs = plan->nargs;
+	if (plan->nargs > 0)
+	{
+		newplan->argtypes = (Oid *) palloc(plan->nargs * sizeof(Oid));
+		memcpy(newplan->argtypes, plan->argtypes, plan->nargs * sizeof(Oid));
+	}
+	else
+		newplan->argtypes = NULL;
+
+	foreach(lc, plan->plancache_list)
+	{
+		CachedPlanSource *plansource = (CachedPlanSource *) lfirst(lc);
+		CachedPlanSource *newsource;
+		CachedPlan *cplan;
+
+		/* Note: we assume we don't need to revalidate the plan */
+		cplan = plansource->plan;
+
+		newsource = CreateCachedPlan(plansource->raw_parse_tree,
+									 plansource->query_string,
+									 plansource->commandTag,
+									 newplan->argtypes,
+									 newplan->nargs,
+									 cplan->stmt_list,
+									 true,
+									 false);
+
+		newplan->plancache_list = lappend(newplan->plancache_list, newsource);
+	}
+
 	MemoryContextSwitchTo(oldcxt);
 
 	return newplan;
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index 4ba2bb7a98f..72200ced9dc 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -8,16 +8,14 @@
  *	across query and transaction boundaries, in fact they live as long as
  *	the backend does.  This works because the hashtable structures
  *	themselves are allocated by dynahash.c in its permanent DynaHashCxt,
- *	and the parse/plan node trees they point to are copied into
- *	TopMemoryContext using SPI_saveplan().	This is pretty ugly, since there
- *	is no way to free a no-longer-needed plan tree, but then again we don't
- *	yet have any bookkeeping that would allow us to detect that a plan isn't
- *	needed anymore.  Improve it someday.
+ *	and the SPI plans they point to are saved using SPI_saveplan().
+ *	There is not currently any provision for throwing away a no-longer-needed
+ *	plan --- consider improving this someday.
  *
  *
  * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
  *
- * $PostgreSQL: pgsql/src/backend/utils/adt/ri_triggers.c,v 1.91 2007/02/14 01:58:57 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/utils/adt/ri_triggers.c,v 1.92 2007/03/15 23:12:06 tgl Exp $
  *
  * ----------
  */
@@ -35,7 +33,7 @@
 #include "catalog/pg_constraint.h"
 #include "catalog/pg_operator.h"
 #include "commands/trigger.h"
-#include "executor/spi_priv.h"
+#include "executor/spi.h"
 #include "parser/parse_coerce.h"
 #include "parser/parse_relation.h"
 #include "miscadmin.h"
@@ -135,7 +133,7 @@ typedef struct RI_QueryKey
 typedef struct RI_QueryHashEntry
 {
 	RI_QueryKey key;
-	void	   *plan;
+	SPIPlanPtr	plan;
 } RI_QueryHashEntry;
 
 
@@ -206,18 +204,18 @@ static bool ri_Check_Pk_Match(Relation pk_rel, Relation fk_rel,
 				  const RI_ConstraintInfo *riinfo);
 
 static void ri_InitHashTables(void);
-static void *ri_FetchPreparedPlan(RI_QueryKey *key);
-static void ri_HashPreparedPlan(RI_QueryKey *key, void *plan);
+static SPIPlanPtr ri_FetchPreparedPlan(RI_QueryKey *key);
+static void ri_HashPreparedPlan(RI_QueryKey *key, SPIPlanPtr plan);
 static RI_CompareHashEntry *ri_HashCompareOp(Oid eq_opr, Oid typeid);
 
 static void ri_CheckTrigger(FunctionCallInfo fcinfo, const char *funcname,
 				int tgkind);
 static void ri_FetchConstraintInfo(RI_ConstraintInfo *riinfo,
 					   Trigger *trigger, Relation trig_rel, bool rel_is_pk);
-static void *ri_PlanCheck(const char *querystr, int nargs, Oid *argtypes,
+static SPIPlanPtr ri_PlanCheck(const char *querystr, int nargs, Oid *argtypes,
 			 RI_QueryKey *qkey, Relation fk_rel, Relation pk_rel,
 			 bool cache_plan);
-static bool ri_PerformCheck(RI_QueryKey *qkey, void *qplan,
+static bool ri_PerformCheck(RI_QueryKey *qkey, SPIPlanPtr qplan,
 				Relation fk_rel, Relation pk_rel,
 				HeapTuple old_tuple, HeapTuple new_tuple,
 				bool detectNewRows,
@@ -248,7 +246,7 @@ RI_FKey_check(PG_FUNCTION_ARGS)
 	HeapTuple	old_row;
 	Buffer		new_row_buf;
 	RI_QueryKey qkey;
-	void	   *qplan;
+	SPIPlanPtr	qplan;
 	int			i;
 
 	/*
@@ -542,7 +540,7 @@ ri_Check_Pk_Match(Relation pk_rel, Relation fk_rel,
 				  HeapTuple old_row,
 				  const RI_ConstraintInfo *riinfo)
 {
-	void	   *qplan;
+	SPIPlanPtr	qplan;
 	RI_QueryKey qkey;
 	int			i;
 	bool		result;
@@ -678,7 +676,7 @@ RI_FKey_noaction_del(PG_FUNCTION_ARGS)
 	Relation	pk_rel;
 	HeapTuple	old_row;
 	RI_QueryKey qkey;
-	void	   *qplan;
+	SPIPlanPtr	qplan;
 	int			i;
 
 	/*
@@ -855,7 +853,7 @@ RI_FKey_noaction_upd(PG_FUNCTION_ARGS)
 	HeapTuple	new_row;
 	HeapTuple	old_row;
 	RI_QueryKey qkey;
-	void	   *qplan;
+	SPIPlanPtr	qplan;
 	int			i;
 
 	/*
@@ -1040,7 +1038,7 @@ RI_FKey_cascade_del(PG_FUNCTION_ARGS)
 	Relation	pk_rel;
 	HeapTuple	old_row;
 	RI_QueryKey qkey;
-	void	   *qplan;
+	SPIPlanPtr	qplan;
 	int			i;
 
 	/*
@@ -1203,7 +1201,7 @@ RI_FKey_cascade_upd(PG_FUNCTION_ARGS)
 	HeapTuple	new_row;
 	HeapTuple	old_row;
 	RI_QueryKey qkey;
-	void	   *qplan;
+	SPIPlanPtr	qplan;
 	int			i;
 	int			j;
 
@@ -1397,7 +1395,7 @@ RI_FKey_restrict_del(PG_FUNCTION_ARGS)
 	Relation	pk_rel;
 	HeapTuple	old_row;
 	RI_QueryKey qkey;
-	void	   *qplan;
+	SPIPlanPtr	qplan;
 	int			i;
 
 	/*
@@ -1569,7 +1567,7 @@ RI_FKey_restrict_upd(PG_FUNCTION_ARGS)
 	HeapTuple	new_row;
 	HeapTuple	old_row;
 	RI_QueryKey qkey;
-	void	   *qplan;
+	SPIPlanPtr	qplan;
 	int			i;
 
 	/*
@@ -1744,7 +1742,7 @@ RI_FKey_setnull_del(PG_FUNCTION_ARGS)
 	Relation	pk_rel;
 	HeapTuple	old_row;
 	RI_QueryKey qkey;
-	void	   *qplan;
+	SPIPlanPtr	qplan;
 	int			i;
 
 	/*
@@ -1916,7 +1914,7 @@ RI_FKey_setnull_upd(PG_FUNCTION_ARGS)
 	HeapTuple	new_row;
 	HeapTuple	old_row;
 	RI_QueryKey qkey;
-	void	   *qplan;
+	SPIPlanPtr	qplan;
 	int			i;
 	bool		use_cached_query;
 
@@ -2130,7 +2128,7 @@ RI_FKey_setdefault_del(PG_FUNCTION_ARGS)
 	Relation	pk_rel;
 	HeapTuple	old_row;
 	RI_QueryKey qkey;
-	void	   *qplan;
+	SPIPlanPtr	qplan;
 
 	/*
 	 * Check that this is a valid trigger call on the right time and event.
@@ -2313,7 +2311,7 @@ RI_FKey_setdefault_upd(PG_FUNCTION_ARGS)
 	HeapTuple	new_row;
 	HeapTuple	old_row;
 	RI_QueryKey qkey;
-	void	   *qplan;
+	SPIPlanPtr	qplan;
 
 	/*
 	 * Check that this is a valid trigger call on the right time and event.
@@ -2637,7 +2635,7 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	int			old_work_mem;
 	char		workmembuf[32];
 	int			spi_result;
-	void	   *qplan;
+	SPIPlanPtr	qplan;
 
 	/*
 	 * Check to make sure current user has enough permissions to do the test
@@ -3165,12 +3163,12 @@ ri_FetchConstraintInfo(RI_ConstraintInfo *riinfo,
  * If cache_plan is true, the plan is saved into our plan hashtable
  * so that we don't need to plan it again.
  */
-static void *
+static SPIPlanPtr
 ri_PlanCheck(const char *querystr, int nargs, Oid *argtypes,
 			 RI_QueryKey *qkey, Relation fk_rel, Relation pk_rel,
 			 bool cache_plan)
 {
-	void	   *qplan;
+	SPIPlanPtr	qplan;
 	Relation	query_rel;
 	Oid			save_uid;
 
@@ -3212,7 +3210,7 @@ ri_PlanCheck(const char *querystr, int nargs, Oid *argtypes,
  * Perform a query to enforce an RI restriction
  */
 static bool
-ri_PerformCheck(RI_QueryKey *qkey, void *qplan,
+ri_PerformCheck(RI_QueryKey *qkey, SPIPlanPtr qplan,
 				Relation fk_rel, Relation pk_rel,
 				HeapTuple old_tuple, HeapTuple new_tuple,
 				bool detectNewRows,
@@ -3576,7 +3574,7 @@ ri_InitHashTables(void)
  *	and saved SPI execution plans. Return the plan if found or NULL.
  * ----------
  */
-static void *
+static SPIPlanPtr
 ri_FetchPreparedPlan(RI_QueryKey *key)
 {
 	RI_QueryHashEntry *entry;
@@ -3606,7 +3604,7 @@ ri_FetchPreparedPlan(RI_QueryKey *key)
  * ----------
  */
 static void
-ri_HashPreparedPlan(RI_QueryKey *key, void *plan)
+ri_HashPreparedPlan(RI_QueryKey *key, SPIPlanPtr plan)
 {
 	RI_QueryHashEntry *entry;
 	bool		found;
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index f17061738a4..9ec0aa56f0a 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -9,7 +9,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/utils/adt/ruleutils.c,v 1.252 2007/02/27 23:48:08 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/utils/adt/ruleutils.c,v 1.253 2007/03/15 23:12:06 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -105,9 +105,9 @@ typedef struct
  * Global data
  * ----------
  */
-static void *plan_getrulebyoid = NULL;
+static SPIPlanPtr plan_getrulebyoid = NULL;
 static char *query_getrulebyoid = "SELECT * FROM pg_catalog.pg_rewrite WHERE oid = $1";
-static void *plan_getviewrule = NULL;
+static SPIPlanPtr plan_getviewrule = NULL;
 static char *query_getviewrule = "SELECT * FROM pg_catalog.pg_rewrite WHERE ev_class = $1 AND rulename = $2";
 
 
@@ -250,7 +250,7 @@ pg_get_ruledef_worker(Oid ruleoid, int prettyFlags)
 	if (plan_getrulebyoid == NULL)
 	{
 		Oid			argtypes[1];
-		void	   *plan;
+		SPIPlanPtr	plan;
 
 		argtypes[0] = OIDOID;
 		plan = SPI_prepare(query_getrulebyoid, 1, argtypes);
@@ -380,7 +380,7 @@ pg_get_viewdef_worker(Oid viewoid, int prettyFlags)
 	if (plan_getviewrule == NULL)
 	{
 		Oid			argtypes[2];
-		void	   *plan;
+		SPIPlanPtr	plan;
 
 		argtypes[0] = OIDOID;
 		argtypes[1] = NAMEOID;
diff --git a/src/backend/utils/adt/xml.c b/src/backend/utils/adt/xml.c
index 921fe1d9f50..fa82837fd0c 100644
--- a/src/backend/utils/adt/xml.c
+++ b/src/backend/utils/adt/xml.c
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/backend/utils/adt/xml.c,v 1.34 2007/03/03 19:32:55 neilc Exp $
+ * $PostgreSQL: pgsql/src/backend/utils/adt/xml.c,v 1.35 2007/03/15 23:12:06 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1800,7 +1800,7 @@ query_to_xmlschema(PG_FUNCTION_ARGS)
 	const char *targetns = _textout(PG_GETARG_TEXT_P(3));
 
 	const char *result;
-	void	   *plan;
+	SPIPlanPtr	plan;
 	Portal		portal;
 
 	SPI_connect();
@@ -1871,7 +1871,7 @@ query_to_xml_and_xmlschema(PG_FUNCTION_ARGS)
 	const char *targetns = _textout(PG_GETARG_TEXT_P(3));
 
 	const char *xmlschema;
-	void	   *plan;
+	SPIPlanPtr	plan;
 	Portal		portal;
 
 	SPI_connect();
diff --git a/src/backend/utils/cache/plancache.c b/src/backend/utils/cache/plancache.c
index 95ed49818cb..61a576d35d9 100644
--- a/src/backend/utils/cache/plancache.c
+++ b/src/backend/utils/cache/plancache.c
@@ -33,7 +33,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/utils/cache/plancache.c,v 1.1 2007/03/13 00:33:42 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/utils/cache/plancache.c,v 1.2 2007/03/15 23:12:06 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -77,7 +77,6 @@ static void ScanQueryForRelids(Query *parsetree,
 							   void *arg);
 static bool ScanQueryWalker(Node *node, ScanQueryWalkerContext *context);
 static bool rowmark_member(List *rowMarks, int rt_index);
-static TupleDesc ComputeResultDesc(List *stmt_list);
 static void PlanCacheCallback(Datum arg, Oid relid);
 static void InvalRelid(Oid relid, LOCKMODE lockmode,
 					   InvalRelidContext *context);
@@ -153,7 +152,7 @@ CreateCachedPlan(Node *raw_parse_tree,
 	plansource->fully_planned = fully_planned;
 	plansource->fixed_result = fixed_result;
 	plansource->generation = 0;			/* StoreCachedPlan will increment */
-	plansource->resultDesc = ComputeResultDesc(stmt_list);
+	plansource->resultDesc = PlanCacheComputeResultDesc(stmt_list);
 	plansource->plan = NULL;
 	plansource->context = source_context;
 	plansource->orig_plan = NULL;
@@ -225,7 +224,7 @@ FastCreateCachedPlan(Node *raw_parse_tree,
 	plansource->fully_planned = fully_planned;
 	plansource->fixed_result = fixed_result;
 	plansource->generation = 0;			/* StoreCachedPlan will increment */
-	plansource->resultDesc = ComputeResultDesc(stmt_list);
+	plansource->resultDesc = PlanCacheComputeResultDesc(stmt_list);
 	plansource->plan = NULL;
 	plansource->context = context;
 	plansource->orig_plan = NULL;
@@ -271,12 +270,13 @@ StoreCachedPlan(CachedPlanSource *plansource,
 	{
 		/*
 		 * Make a dedicated memory context for the CachedPlan and its
-		 * subsidiary data.
+		 * subsidiary data.  It's probably not going to be large, but
+		 * just in case, use the default maxsize parameter.
 		 */
 		plan_context = AllocSetContextCreate(CacheMemoryContext,
 											 "CachedPlan",
-											 ALLOCSET_DEFAULT_MINSIZE,
-											 ALLOCSET_DEFAULT_INITSIZE,
+											 ALLOCSET_SMALL_MINSIZE,
+											 ALLOCSET_SMALL_INITSIZE,
 											 ALLOCSET_DEFAULT_MAXSIZE);
 
 		/*
@@ -445,7 +445,7 @@ RevalidateCachedPlan(CachedPlanSource *plansource, bool useResOwner)
 		 * Check or update the result tupdesc.  XXX should we use a weaker
 		 * condition than equalTupleDescs() here?
 		 */
-		resultDesc = ComputeResultDesc(slist);
+		resultDesc = PlanCacheComputeResultDesc(slist);
 		if (resultDesc == NULL && plansource->resultDesc == NULL)
 		{
 			/* OK, doesn't return tuples */
@@ -718,14 +718,14 @@ rowmark_member(List *rowMarks, int rt_index)
 }
 
 /*
- * ComputeResultDesc: given a list of either fully-planned statements or
- * Queries, determine the result tupledesc it will produce.  Returns NULL
+ * PlanCacheComputeResultDesc: given a list of either fully-planned statements
+ * or Queries, determine the result tupledesc it will produce.  Returns NULL
  * if the execution will not return tuples.
  *
  * Note: the result is created or copied into current memory context.
  */
-static TupleDesc
-ComputeResultDesc(List *stmt_list)
+TupleDesc
+PlanCacheComputeResultDesc(List *stmt_list)
 {
 	Node	   *node;
 	Query	   *query;
diff --git a/src/include/executor/spi.h b/src/include/executor/spi.h
index 9cbbc035dcc..c041726a45a 100644
--- a/src/include/executor/spi.h
+++ b/src/include/executor/spi.h
@@ -1,8 +1,12 @@
 /*-------------------------------------------------------------------------
  *
  * spi.h
+ *				Server Programming Interface public declarations
  *
- * $PostgreSQL: pgsql/src/include/executor/spi.h,v 1.58 2006/10/04 00:30:08 momjian Exp $
+ * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * $PostgreSQL: pgsql/src/include/executor/spi.h,v 1.59 2007/03/15 23:12:06 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -24,6 +28,7 @@
 #include "catalog/pg_language.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_type.h"
+#include "executor/execdefs.h"
 #include "executor/executor.h"
 #include "nodes/execnodes.h"
 #include "nodes/params.h"
@@ -38,9 +43,9 @@
 #include "utils/datum.h"
 #include "utils/portal.h"
 #include "utils/syscache.h"
-#include "executor/execdefs.h"
 
-typedef struct
+
+typedef struct SPITupleTable
 {
 	MemoryContext tuptabcxt;	/* memory context of result table */
 	uint32		alloced;		/* # of alloced vals */
@@ -49,6 +54,9 @@ typedef struct
 	HeapTuple  *vals;			/* tuples */
 } SPITupleTable;
 
+/* Plans are opaque structs for standard users of SPI */
+typedef struct _SPI_plan *SPIPlanPtr;
+
 #define SPI_ERROR_CONNECT		(-1)
 #define SPI_ERROR_COPY			(-2)
 #define SPI_ERROR_OPUNKNOWN		(-3)
@@ -86,23 +94,23 @@ extern void SPI_push(void);
 extern void SPI_pop(void);
 extern void SPI_restore_connection(void);
 extern int	SPI_execute(const char *src, bool read_only, long tcount);
-extern int SPI_execute_plan(void *plan, Datum *Values, const char *Nulls,
+extern int	SPI_execute_plan(SPIPlanPtr plan, Datum *Values, const char *Nulls,
 				 bool read_only, long tcount);
 extern int	SPI_exec(const char *src, long tcount);
-extern int SPI_execp(void *plan, Datum *Values, const char *Nulls,
+extern int	SPI_execp(SPIPlanPtr plan, Datum *Values, const char *Nulls,
 		  long tcount);
-extern int SPI_execute_snapshot(void *plan,
+extern int	SPI_execute_snapshot(SPIPlanPtr plan,
 					 Datum *Values, const char *Nulls,
 					 Snapshot snapshot,
 					 Snapshot crosscheck_snapshot,
 					 bool read_only, long tcount);
-extern void *SPI_prepare(const char *src, int nargs, Oid *argtypes);
-extern void *SPI_saveplan(void *plan);
-extern int	SPI_freeplan(void *plan);
+extern SPIPlanPtr SPI_prepare(const char *src, int nargs, Oid *argtypes);
+extern SPIPlanPtr SPI_saveplan(SPIPlanPtr plan);
+extern int	SPI_freeplan(SPIPlanPtr plan);
 
-extern Oid	SPI_getargtypeid(void *plan, int argIndex);
-extern int	SPI_getargcount(void *plan);
-extern bool SPI_is_cursor_plan(void *plan);
+extern Oid	SPI_getargtypeid(SPIPlanPtr plan, int argIndex);
+extern int	SPI_getargcount(SPIPlanPtr plan);
+extern bool SPI_is_cursor_plan(SPIPlanPtr plan);
 extern const char *SPI_result_code_string(int code);
 
 extern HeapTuple SPI_copytuple(HeapTuple tuple);
@@ -123,7 +131,7 @@ extern void SPI_pfree(void *pointer);
 extern void SPI_freetuple(HeapTuple pointer);
 extern void SPI_freetuptable(SPITupleTable *tuptable);
 
-extern Portal SPI_cursor_open(const char *name, void *plan,
+extern Portal SPI_cursor_open(const char *name, SPIPlanPtr plan,
 				Datum *Values, const char *Nulls, bool read_only);
 extern Portal SPI_cursor_find(const char *name);
 extern void SPI_cursor_fetch(Portal portal, bool forward, long count);
diff --git a/src/include/executor/spi_priv.h b/src/include/executor/spi_priv.h
index 5e65bd750ae..7ce7d0b0983 100644
--- a/src/include/executor/spi_priv.h
+++ b/src/include/executor/spi_priv.h
@@ -6,7 +6,7 @@
  * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/executor/spi_priv.h,v 1.27 2007/02/20 17:32:17 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/executor/spi_priv.h,v 1.28 2007/03/15 23:12:07 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -16,6 +16,8 @@
 #include "executor/spi.h"
 
 
+#define _SPI_PLAN_MAGIC		569278163
+
 typedef struct
 {
 	/* current results */
@@ -25,29 +27,46 @@ typedef struct
 
 	MemoryContext procCxt;		/* procedure context */
 	MemoryContext execCxt;		/* executor context */
-	MemoryContext savedcxt;
+	MemoryContext savedcxt;		/* context of SPI_connect's caller */
 	SubTransactionId connectSubid;		/* ID of connecting subtransaction */
 } _SPI_connection;
 
-typedef struct
+/*
+ * SPI plans have two states: saved or unsaved.
+ *
+ * For an unsaved plan, the _SPI_plan struct and all its subsidiary data are in
+ * a dedicated memory context identified by plancxt.  An unsaved plan is good
+ * at most for the current transaction, since the locks that protect it from
+ * schema changes will be lost at end of transaction.  Hence the plancxt is
+ * always a transient one.
+ *
+ * For a saved plan, the _SPI_plan struct and the argument type array are in
+ * the plancxt (which can be really small).  All the other subsidiary state
+ * is in plancache entries identified by plancache_list (note: the list cells
+ * themselves are in plancxt).  We rely on plancache.c to keep the cache
+ * entries up-to-date as needed.  The plancxt is a child of CacheMemoryContext
+ * since it should persist until explicitly destroyed.
+ *
+ * To avoid redundant coding, the representation of unsaved plans matches
+ * that of saved plans, ie, plancache_list is a list of CachedPlanSource
+ * structs which in turn point to CachedPlan structs.  However, in an unsaved
+ * plan all these structs are just created by spi.c and are not known to
+ * plancache.c.  We don't try very hard to make all their fields valid,
+ * only the ones spi.c actually uses.
+ *
+ * Note: if the original query string contained only whitespace and comments,
+ * the plancache_list will be NIL and so there is no place to store the
+ * query string.  We don't care about that, but we do care about the
+ * argument type array, which is why it's seemingly-redundantly stored.
+ */
+typedef struct _SPI_plan
 {
-	/* Context containing _SPI_plan itself as well as subsidiary data */
-	MemoryContext plancxt;
-	/* Original query string (used for error reporting) */
-	const char *query;
-	/*
-	 * List of List of PlannedStmts and utility stmts; one sublist per
-	 * original parsetree
-	 */
-	List	   *stmt_list_list;
-	/* Argument types, if a prepared plan */
-	int			nargs;
-	Oid		   *argtypes;
+	int			magic;			/* should equal _SPI_PLAN_MAGIC */
+	bool		saved;			/* saved or unsaved plan? */
+	List	   *plancache_list;	/* one CachedPlanSource per parsetree */
+	MemoryContext plancxt;		/* Context containing _SPI_plan and data */
+	int			nargs;			/* number of plan arguments */
+	Oid		   *argtypes;		/* Argument types (NULL if nargs is 0) */
 } _SPI_plan;
 
-
-#define _SPI_CPLAN_CURCXT	0
-#define _SPI_CPLAN_PROCXT	1
-#define _SPI_CPLAN_TOPCXT	2
-
 #endif   /* SPI_PRIV_H */
diff --git a/src/include/utils/plancache.h b/src/include/utils/plancache.h
index 833ec473b13..4f03cd9e0a2 100644
--- a/src/include/utils/plancache.h
+++ b/src/include/utils/plancache.h
@@ -8,7 +8,7 @@
  * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/utils/plancache.h,v 1.1 2007/03/13 00:33:43 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/utils/plancache.h,v 1.2 2007/03/15 23:12:07 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -101,5 +101,6 @@ extern void DropCachedPlan(CachedPlanSource *plansource);
 extern CachedPlan *RevalidateCachedPlan(CachedPlanSource *plansource,
 										bool useResOwner);
 extern void ReleaseCachedPlan(CachedPlan *plan, bool useResOwner);
+extern TupleDesc PlanCacheComputeResultDesc(List *stmt_list);
 
 #endif   /* PLANCACHE_H */
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index 68a68a6634c..eb29d7333a8 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.189 2007/02/20 17:32:18 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.190 2007/03/15 23:12:07 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -123,8 +123,9 @@ static void exec_prepare_plan(PLpgSQL_execstate *estate,
 				  PLpgSQL_expr *expr);
 static bool exec_simple_check_node(Node *node);
 static void exec_simple_check_plan(PLpgSQL_expr *expr);
-static Datum exec_eval_simple_expr(PLpgSQL_execstate *estate,
+static bool exec_eval_simple_expr(PLpgSQL_execstate *estate,
 					  PLpgSQL_expr *expr,
+					  Datum *result,
 					  bool *isNull,
 					  Oid *rettype);
 
@@ -409,7 +410,7 @@ plpgsql_exec_function(PLpgSQL_function *func, FunctionCallInfo fcinfo)
 				void	   *tmp;
 
 				len = datumGetSize(estate.retval, false, func->fn_rettyplen);
-				tmp = (void *) SPI_palloc(len);
+				tmp = SPI_palloc(len);
 				memcpy(tmp, DatumGetPointer(estate.retval), len);
 				estate.retval = PointerGetDatum(tmp);
 			}
@@ -2294,8 +2295,7 @@ exec_prepare_plan(PLpgSQL_execstate *estate,
 				  PLpgSQL_expr *expr)
 {
 	int			i;
-	_SPI_plan  *spi_plan;
-	void	   *plan;
+	SPIPlanPtr	plan;
 	Oid		   *argtypes;
 
 	/*
@@ -2343,12 +2343,11 @@ exec_prepare_plan(PLpgSQL_execstate *estate,
 		}
 	}
 	expr->plan = SPI_saveplan(plan);
-	spi_plan = (_SPI_plan *) expr->plan;
-	expr->plan_argtypes = spi_plan->argtypes;
-	expr->expr_simple_expr = NULL;
+	SPI_freeplan(plan);
+	plan = expr->plan;
+	expr->plan_argtypes = plan->argtypes;
 	exec_simple_check_plan(expr);
 
-	SPI_freeplan(plan);
 	pfree(argtypes);
 }
 
@@ -2374,17 +2373,16 @@ exec_stmt_execsql(PLpgSQL_execstate *estate,
 	 */
 	if (expr->plan == NULL)
 	{
-		_SPI_plan  *spi_plan;
 		ListCell   *l;
 
 		exec_prepare_plan(estate, expr);
 		stmt->mod_stmt = false;
-		spi_plan = (_SPI_plan *) expr->plan;
-		foreach(l, spi_plan->stmt_list_list)
+		foreach(l, expr->plan->plancache_list)
 		{
+			CachedPlanSource *plansource = (CachedPlanSource *) lfirst(l);
 			ListCell   *l2;
 
-			foreach(l2, (List *) lfirst(l))
+			foreach(l2, plansource->plan->stmt_list)
 			{
 				PlannedStmt *p = (PlannedStmt *) lfirst(l2);
 
@@ -2735,7 +2733,7 @@ exec_stmt_dynfors(PLpgSQL_execstate *estate, PLpgSQL_stmt_dynfors *stmt)
 	PLpgSQL_row *row = NULL;
 	SPITupleTable *tuptab;
 	int			n;
-	void	   *plan;
+	SPIPlanPtr	plan;
 	Portal		portal;
 	bool		found = false;
 
@@ -2959,7 +2957,7 @@ exec_stmt_open(PLpgSQL_execstate *estate, PLpgSQL_stmt_open *stmt)
 		Datum		queryD;
 		Oid			restype;
 		char	   *querystr;
-		void	   *curplan;
+		SPIPlanPtr	curplan;
 
 		/* ----------
 		 * We evaluate the string expression after the
@@ -3860,10 +3858,11 @@ exec_eval_expr(PLpgSQL_execstate *estate,
 			   bool *isNull,
 			   Oid *rettype)
 {
+	Datum		result;
 	int			rc;
 
 	/*
-	 * If not already done create a plan for this expression
+	 * If first time through, create a plan for this expression.
 	 */
 	if (expr->plan == NULL)
 		exec_prepare_plan(estate, expr);
@@ -3872,9 +3871,12 @@ exec_eval_expr(PLpgSQL_execstate *estate,
 	 * If this is a simple expression, bypass SPI and use the executor
 	 * directly
 	 */
-	if (expr->expr_simple_expr != NULL)
-		return exec_eval_simple_expr(estate, expr, isNull, rettype);
+	if (exec_eval_simple_expr(estate, expr, &result, isNull, rettype))
+		return result;
 
+	/*
+	 * Else do it the hard way via exec_run_select
+	 */
 	rc = exec_run_select(estate, expr, 2, NULL);
 	if (rc != SPI_OK_SELECT)
 		ereport(ERROR,
@@ -3994,23 +3996,64 @@ exec_run_select(PLpgSQL_execstate *estate,
  * exec_eval_simple_expr -		Evaluate a simple expression returning
  *								a Datum by directly calling ExecEvalExpr().
  *
+ * If successful, store results into *result, *isNull, *rettype and return
+ * TRUE.  If the expression is not simple (any more), return FALSE.
+ *
+ * It is possible though unlikely for a simple expression to become non-simple
+ * (consider for example redefining a trivial view).  We must handle that for
+ * correctness; fortunately it's normally inexpensive to do
+ * RevalidateCachedPlan on a simple expression.  We do not consider the other
+ * direction (non-simple expression becoming simple) because we'll still give
+ * correct results if that happens, and it's unlikely to be worth the cycles
+ * to check.
+ *
  * Note: if pass-by-reference, the result is in the eval_econtext's
  * temporary memory context.  It will be freed when exec_eval_cleanup
  * is done.
  * ----------
  */
-static Datum
+static bool
 exec_eval_simple_expr(PLpgSQL_execstate *estate,
 					  PLpgSQL_expr *expr,
+					  Datum *result,
 					  bool *isNull,
 					  Oid *rettype)
 {
-	Datum		retval;
 	ExprContext *econtext = estate->eval_econtext;
+	CachedPlanSource *plansource;
+	CachedPlan *cplan;
 	ParamListInfo paramLI;
 	int			i;
 	Snapshot	saveActiveSnapshot;
 
+	/*
+	 * Forget it if expression wasn't simple before.
+	 */
+	if (expr->expr_simple_expr == NULL)
+		return false;
+
+	/*
+	 * Revalidate cached plan, so that we will notice if it became stale.
+	 * (We also need to hold a refcount while using the plan.)  Note that
+	 * even if replanning occurs, the length of plancache_list can't change,
+	 * since it is a property of the raw parsetree generated from the query
+	 * text.
+	 */
+	Assert(list_length(expr->plan->plancache_list) == 1);
+	plansource = (CachedPlanSource *) linitial(expr->plan->plancache_list);
+	cplan = RevalidateCachedPlan(plansource, true);
+	if (cplan->generation != expr->expr_simple_generation)
+	{
+		/* It got replanned ... is it still simple? */
+		exec_simple_check_plan(expr);
+		if (expr->expr_simple_expr == NULL)
+		{
+			/* Ooops, release refcount and fail */
+			ReleaseCachedPlan(cplan, true);
+			return false;
+		}
+	}
+
 	/*
 	 * Pass back previously-determined result type.
 	 */
@@ -4018,7 +4061,8 @@ exec_eval_simple_expr(PLpgSQL_execstate *estate,
 
 	/*
 	 * Prepare the expression for execution, if it's not been done already in
-	 * the current eval_estate.
+	 * the current eval_estate.  (This will be forced to happen if we called
+	 * exec_simple_check_plan above.)
 	 */
 	if (expr->expr_simple_id != estate->eval_estate_simple_id)
 	{
@@ -4086,10 +4130,10 @@ exec_eval_simple_expr(PLpgSQL_execstate *estate,
 		/*
 		 * Finally we can call the executor to evaluate the expression
 		 */
-		retval = ExecEvalExpr(expr->expr_simple_state,
-							  econtext,
-							  isNull,
-							  NULL);
+		*result = ExecEvalExpr(expr->expr_simple_state,
+							   econtext,
+							   isNull,
+							   NULL);
 		MemoryContextSwitchTo(oldcontext);
 	}
 	PG_CATCH();
@@ -4103,10 +4147,15 @@ exec_eval_simple_expr(PLpgSQL_execstate *estate,
 	ActiveSnapshot = saveActiveSnapshot;
 	SPI_pop();
 
+	/*
+	 * Now we can release our refcount on the cached plan.
+	 */
+	ReleaseCachedPlan(cplan, true);
+
 	/*
 	 * That's it.
 	 */
-	return retval;
+	return true;
 }
 
 
@@ -4673,25 +4722,31 @@ exec_simple_check_node(Node *node)
 static void
 exec_simple_check_plan(PLpgSQL_expr *expr)
 {
-	_SPI_plan  *spi_plan = (_SPI_plan *) expr->plan;
-	List	   *sublist;
+	CachedPlanSource *plansource;
 	PlannedStmt *stmt;
 	Plan	   *plan;
 	TargetEntry *tle;
 
+	/*
+	 * Initialize to "not simple", and remember the plan generation number
+	 * we last checked.  (If the query produces more or less than one parsetree
+	 * we just leave expr_simple_generation set to 0.)
+	 */
 	expr->expr_simple_expr = NULL;
+	expr->expr_simple_generation = 0;
 
 	/*
 	 * 1. We can only evaluate queries that resulted in one single execution
 	 * plan
 	 */
-	if (list_length(spi_plan->stmt_list_list) != 1)
+	if (list_length(expr->plan->plancache_list) != 1)
 		return;
-	sublist = (List *) linitial(spi_plan->stmt_list_list);
-	if (list_length(sublist) != 1)
+	plansource = (CachedPlanSource *) linitial(expr->plan->plancache_list);
+	expr->expr_simple_generation = plansource->generation;
+	if (list_length(plansource->plan->stmt_list) != 1)
 		return;
 
-	stmt = (PlannedStmt *) linitial(sublist);
+	stmt = (PlannedStmt *) linitial(plansource->plan->stmt_list);
 
 	/*
 	 * 2. It must be a RESULT plan --> no scan's required
diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h
index 9f29fcd5da5..0b9b90f9170 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.85 2007/02/09 03:35:34 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.86 2007/03/15 23:12:07 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -173,11 +173,12 @@ typedef struct PLpgSQL_expr
 	int			dtype;
 	int			exprno;
 	char	   *query;
-	void	   *plan;
+	SPIPlanPtr	plan;
 	Oid		   *plan_argtypes;
 	/* fields for "simple expression" fast-path execution: */
 	Expr	   *expr_simple_expr;		/* NULL means not a simple expr */
-	Oid			expr_simple_type;
+	int			expr_simple_generation;	/* plancache generation we checked */
+	Oid			expr_simple_type;		/* result type Oid, if simple */
 
 	/*
 	 * if expr is simple AND prepared in current eval_estate,
diff --git a/src/test/regress/expected/plancache.out b/src/test/regress/expected/plancache.out
index 4980a9ab68b..cac9cfd2574 100644
--- a/src/test/regress/expected/plancache.out
+++ b/src/test/regress/expected/plancache.out
@@ -100,3 +100,64 @@ EXECUTE vprep;
  4567890123456789 |  2283945061728394
 (5 rows)
 
+-- Check basic SPI plan invalidation
+create function cache_test(int) returns int as $$
+declare total int;
+begin
+	create temp table t1(f1 int);
+	insert into t1 values($1);
+	insert into t1 values(11);
+	insert into t1 values(12);
+	insert into t1 values(13);
+	select sum(f1) into total from t1;
+	drop table t1;
+	return total;
+end
+$$ language plpgsql;
+select cache_test(1);
+ cache_test 
+------------
+         37
+(1 row)
+
+select cache_test(2);
+ cache_test 
+------------
+         38
+(1 row)
+
+select cache_test(3);
+ cache_test 
+------------
+         39
+(1 row)
+
+-- Check invalidation of plpgsql "simple expression"
+create temp view v1 as
+  select 2+2 as f1;
+create function cache_test_2() returns int as $$
+begin
+	return f1 from v1;
+end$$ language plpgsql;
+select cache_test_2();
+ cache_test_2 
+--------------
+            4
+(1 row)
+
+create or replace temp view v1 as
+  select 2+2+4 as f1;
+select cache_test_2();
+ cache_test_2 
+--------------
+            8
+(1 row)
+
+create or replace temp view v1 as
+  select 2+2+4+(select max(unique1) from tenk1) as f1;
+select cache_test_2();
+ cache_test_2 
+--------------
+        10007
+(1 row)
+
diff --git a/src/test/regress/regress.c b/src/test/regress/regress.c
index 551b157a169..8f96382dfd6 100644
--- a/src/test/regress/regress.c
+++ b/src/test/regress/regress.c
@@ -1,5 +1,5 @@
 /*
- * $PostgreSQL: pgsql/src/test/regress/regress.c,v 1.69 2007/02/01 19:10:30 momjian Exp $
+ * $PostgreSQL: pgsql/src/test/regress/regress.c,v 1.70 2007/03/15 23:12:07 tgl Exp $
  */
 
 #include "postgres.h"
@@ -451,7 +451,7 @@ extern Datum set_ttdummy(PG_FUNCTION_ARGS);
 
 #define TTDUMMY_INFINITY	999999
 
-static void *splan = NULL;
+static SPIPlanPtr splan = NULL;
 static bool ttoff = false;
 
 PG_FUNCTION_INFO_V1(ttdummy);
@@ -599,7 +599,7 @@ ttdummy(PG_FUNCTION_ARGS)
 	/* if there is no plan ... */
 	if (splan == NULL)
 	{
-		void	   *pplan;
+		SPIPlanPtr	pplan;
 		Oid		   *ctypes;
 		char	   *query;
 
diff --git a/src/test/regress/sql/plancache.sql b/src/test/regress/sql/plancache.sql
index b952efe1972..0b34a62c3bd 100644
--- a/src/test/regress/sql/plancache.sql
+++ b/src/test/regress/sql/plancache.sql
@@ -51,3 +51,43 @@ EXECUTE vprep;
 CREATE OR REPLACE TEMP VIEW voo AS SELECT q1, q2/2 AS q2 FROM foo;
 
 EXECUTE vprep;
+
+-- Check basic SPI plan invalidation
+
+create function cache_test(int) returns int as $$
+declare total int;
+begin
+	create temp table t1(f1 int);
+	insert into t1 values($1);
+	insert into t1 values(11);
+	insert into t1 values(12);
+	insert into t1 values(13);
+	select sum(f1) into total from t1;
+	drop table t1;
+	return total;
+end
+$$ language plpgsql;
+
+select cache_test(1);
+select cache_test(2);
+select cache_test(3);
+
+-- Check invalidation of plpgsql "simple expression"
+
+create temp view v1 as
+  select 2+2 as f1;
+
+create function cache_test_2() returns int as $$
+begin
+	return f1 from v1;
+end$$ language plpgsql;
+
+select cache_test_2();
+
+create or replace temp view v1 as
+  select 2+2+4 as f1;
+select cache_test_2();
+
+create or replace temp view v1 as
+  select 2+2+4+(select max(unique1) from tenk1) as f1;
+select cache_test_2();
-- 
GitLab