diff --git a/doc/src/sgml/plperl.sgml b/doc/src/sgml/plperl.sgml
index 7893d263775466945fcf638b4dd1a94316a03d78..7642f50ca45b5c12b76c8dafff0aa7ef70dca82f 100644
--- a/doc/src/sgml/plperl.sgml
+++ b/doc/src/sgml/plperl.sgml
@@ -1,5 +1,5 @@
 <!--
-$PostgreSQL: pgsql/doc/src/sgml/plperl.sgml,v 2.31 2004/11/19 23:22:54 tgl Exp $
+$PostgreSQL: pgsql/doc/src/sgml/plperl.sgml,v 2.32 2004/11/21 21:17:01 tgl Exp $
 -->
 
  <chapter id="plperl">
@@ -219,9 +219,13 @@ $nrows = $rv-&gt;{processed};
        Emit a log or error message. Possible levels are
        <literal>DEBUG</>, <literal>LOG</>, <literal>INFO</>,
        <literal>NOTICE</>, <literal>WARNING</>, and <literal>ERROR</>.
-       <literal>ERROR</> raises an error condition: further execution
-       of the function is abandoned, and the current transaction is
-       aborted.
+       <literal>ERROR</>
+        raises an error condition; if this is not trapped by the surrounding
+        Perl code, the error propagates out to the calling query, causing
+        the current transaction or subtransaction to be aborted.  This
+        is effectively the same as the Perl <literal>die</> command.
+        The other levels simply report the message to the system log
+        and/or client.
       </para>
      </listitem>
     </varlistentry>
diff --git a/doc/src/sgml/pltcl.sgml b/doc/src/sgml/pltcl.sgml
index 09f8f82eaa4d2a261d5602c67362dd198f2a78c0..b454c6a45f8afd21a23c67efc03234d7e85ce541 100644
--- a/doc/src/sgml/pltcl.sgml
+++ b/doc/src/sgml/pltcl.sgml
@@ -1,5 +1,5 @@
 <!--
-$PostgreSQL: pgsql/doc/src/sgml/pltcl.sgml,v 2.31 2004/09/20 22:48:25 tgl Exp $
+$PostgreSQL: pgsql/doc/src/sgml/pltcl.sgml,v 2.32 2004/11/21 21:17:02 tgl Exp $
 -->
 
  <chapter id="pltcl">
@@ -449,17 +449,19 @@ SELECT 'doesn''t' AS ret
       <term><function>elog</> <replaceable>level</replaceable> <replaceable>msg</replaceable></term>
       <listitem>
        <para>
-	Emits a log or error message. Possible levels are
-	<literal>DEBUG</>, <literal>LOG</>, <literal>INFO</>,
-	<literal>NOTICE</>, <literal>WARNING</>, <literal>ERROR</>, and
-	<literal>FATAL</>. Most simply emit the given message just like
-	the <literal>elog</> C function. <literal>ERROR</>
-	raises an error condition: further execution of the function is
-	abandoned, and the current transaction is aborted.
-	<literal>FATAL</> aborts the transaction and causes the current
-	session to shut down.  (There is probably no good reason to use
-	this error level in PL/Tcl functions, but it's provided for
-	completeness.)
+        Emits a log or error message. Possible levels are
+        <literal>DEBUG</>, <literal>LOG</>, <literal>INFO</>,
+        <literal>NOTICE</>, <literal>WARNING</>, <literal>ERROR</>, and
+        <literal>FATAL</>. Most simply emit the given message just like
+        the <literal>elog</> C function. <literal>ERROR</>
+        raises an error condition; if this is not trapped by the surrounding
+        Tcl code, the error propagates out to the calling query, causing
+        the current transaction or subtransaction to be aborted.  This
+        is effectively the same as the Tcl <literal>error</> command.
+        <literal>FATAL</> aborts the transaction and causes the current
+        session to shut down.  (There is probably no good reason to use
+        this error level in PL/Tcl functions, but it's provided for
+        completeness.)
        </para>
       </listitem>
      </varlistentry>
diff --git a/doc/src/sgml/release.sgml b/doc/src/sgml/release.sgml
index e0d58a0ee0a602dbdf543625c454327a3689590c..39f6f763c9bdecfec00970377c1e04c88f18b2b3 100644
--- a/doc/src/sgml/release.sgml
+++ b/doc/src/sgml/release.sgml
@@ -1,5 +1,5 @@
 <!--
-$PostgreSQL: pgsql/doc/src/sgml/release.sgml,v 1.309 2004/11/20 21:44:24 tgl Exp $
+$PostgreSQL: pgsql/doc/src/sgml/release.sgml,v 1.310 2004/11/21 21:17:02 tgl Exp $
 -->
 
 <appendix id="release">
@@ -1686,6 +1686,15 @@ $PostgreSQL: pgsql/doc/src/sgml/release.sgml,v 1.309 2004/11/20 21:44:24 tgl Exp
      </para>
     </listitem>
 
+    <listitem>
+     <para>
+      In PL/Tcl, SPI commands are now run in subtransactions.  If an error
+      occurs, the subtransaction is cleaned up and the error is reported
+      as an ordinary Tcl error, which can be trapped with <literal>catch</>.
+      Formerly, it was not possible to catch such errors.
+     </para>
+    </listitem>
+
    </itemizedlist>
   </sect3>
 
diff --git a/src/pl/plperl/plperl.c b/src/pl/plperl/plperl.c
index d2746641852e39fd20cd148980a5f4ac8bdc9cfd..36665cff271f1697605d0ef0419c4c553e513f61 100644
--- a/src/pl/plperl/plperl.c
+++ b/src/pl/plperl/plperl.c
@@ -33,7 +33,7 @@
  *	  ENHANCEMENTS, OR MODIFICATIONS.
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/pl/plperl/plperl.c,v 1.59 2004/11/20 19:07:40 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/pl/plperl/plperl.c,v 1.60 2004/11/21 21:17:03 tgl Exp $
  *
  **********************************************************************/
 
@@ -1593,20 +1593,79 @@ plperl_build_tuple_argument(HeapTuple tuple, TupleDesc tupdesc)
 }
 
 
+/*
+ * Implementation of spi_exec_query() Perl function
+ */
 HV *
 plperl_spi_exec(char *query, int limit)
 {
 	HV		   *ret_hv;
-	int			spi_rv;
 
-	spi_rv = SPI_execute(query, plperl_current_prodesc->fn_readonly, limit);
-	ret_hv = plperl_spi_execute_fetch_result(SPI_tuptable, SPI_processed, spi_rv);
+	/*
+	 * Execute the query inside a sub-transaction, so we can cope with
+	 * errors sanely
+	 */
+	MemoryContext oldcontext = CurrentMemoryContext;
+	ResourceOwner oldowner = CurrentResourceOwner;
+
+	BeginInternalSubTransaction(NULL);
+	/* Want to run inside function's memory context */
+	MemoryContextSwitchTo(oldcontext);
+
+	PG_TRY();
+	{
+		int			spi_rv;
+
+		spi_rv = SPI_execute(query, plperl_current_prodesc->fn_readonly,
+							 limit);
+		ret_hv = plperl_spi_execute_fetch_result(SPI_tuptable, SPI_processed,
+												 spi_rv);
+
+		/* Commit the inner transaction, return to outer xact context */
+		ReleaseCurrentSubTransaction();
+		MemoryContextSwitchTo(oldcontext);
+		CurrentResourceOwner = oldowner;
+		/*
+		 * AtEOSubXact_SPI() should not have popped any SPI context,
+		 * but just in case it did, make sure we remain connected.
+		 */
+		SPI_restore_connection();
+	}
+	PG_CATCH();
+	{
+		ErrorData  *edata;
+
+		/* Save error info */
+		MemoryContextSwitchTo(oldcontext);
+		edata = CopyErrorData();
+		FlushErrorState();
+
+		/* Abort the inner transaction */
+		RollbackAndReleaseCurrentSubTransaction();
+		MemoryContextSwitchTo(oldcontext);
+		CurrentResourceOwner = oldowner;
+
+		/*
+		 * If AtEOSubXact_SPI() popped any SPI context of the subxact,
+		 * it will have left us in a disconnected state.  We need this
+		 * hack to return to connected state.
+		 */
+		SPI_restore_connection();
+
+		/* Punt the error to Perl */
+		croak("%s", edata->message);
+
+		/* Can't get here, but keep compiler quiet */
+		return NULL;
+	}
+	PG_END_TRY();
 
 	return ret_hv;
 }
 
 static HV  *
-plperl_spi_execute_fetch_result(SPITupleTable *tuptable, int processed, int status)
+plperl_spi_execute_fetch_result(SPITupleTable *tuptable, int processed,
+								int status)
 {
 	HV		   *result;
 
@@ -1619,21 +1678,18 @@ plperl_spi_execute_fetch_result(SPITupleTable *tuptable, int processed, int stat
 
 	if (status == SPI_OK_SELECT)
 	{
-		if (processed)
-		{
-			AV		   *rows;
-			HV		   *row;
-			int			i;
+		AV		   *rows;
+		HV		   *row;
+		int			i;
 
-			rows = newAV();
-			for (i = 0; i < processed; i++)
-			{
-				row = plperl_hash_from_tuple(tuptable->vals[i], tuptable->tupdesc);
-				av_push(rows, newRV_noinc((SV *)row));
-			}
-			hv_store(result, "rows", strlen("rows"),
-					 newRV_noinc((SV *) rows), 0);
+		rows = newAV();
+		for (i = 0; i < processed; i++)
+		{
+			row = plperl_hash_from_tuple(tuptable->vals[i], tuptable->tupdesc);
+			av_push(rows, newRV_noinc((SV *)row));
 		}
+		hv_store(result, "rows", strlen("rows"),
+				 newRV_noinc((SV *) rows), 0);
 	}
 
 	SPI_freetuptable(tuptable);
diff --git a/src/pl/tcl/pltcl.c b/src/pl/tcl/pltcl.c
index da1cee09adf6d7fe0af49b2475f52f42c6f9a619..a95344759a313653e974de1de5172a34f5a75e5a 100644
--- a/src/pl/tcl/pltcl.c
+++ b/src/pl/tcl/pltcl.c
@@ -31,7 +31,7 @@
  *	  ENHANCEMENTS, OR MODIFICATIONS.
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/pl/tcl/pltcl.c,v 1.93 2004/09/14 03:21:27 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/pl/tcl/pltcl.c,v 1.94 2004/11/21 21:17:05 tgl Exp $
  *
  **********************************************************************/
 
@@ -147,19 +147,6 @@ static Tcl_HashTable *pltcl_safe_query_hash = NULL;
 static FunctionCallInfo pltcl_current_fcinfo = NULL;
 static pltcl_proc_desc *pltcl_current_prodesc = NULL;
 
-/*
- * When a callback from Tcl into PG incurs an error, we temporarily store
- * the error information here, and return TCL_ERROR to the Tcl interpreter.
- * Any further callback attempts immediately fail, and when the Tcl interpreter
- * returns to the calling function, we re-throw the error (even if Tcl
- * thinks it trapped the error and doesn't return TCL_ERROR).  Eventually
- * this ought to be improved to let Tcl code really truly trap the error,
- * but that's more of a change from the pre-8.0 semantics than I have time
- * for now --- it will only be possible if the callback query is executed
- * inside a subtransaction.
- */
-static ErrorData *pltcl_error_in_progress = NULL;
-
 /**********************************************************************
  * Forward declarations
  **********************************************************************/
@@ -189,6 +176,12 @@ static int pltcl_returnnull(ClientData cdata, Tcl_Interp *interp,
 
 static int pltcl_SPI_execute(ClientData cdata, Tcl_Interp *interp,
 			   int argc, CONST84 char *argv[]);
+static int pltcl_process_SPI_result(Tcl_Interp *interp,
+									CONST84 char *arrayname,
+									CONST84 char *loop_body,
+									int spi_rc,
+									SPITupleTable *tuptable,
+									int ntuples);
 static int pltcl_SPI_prepare(ClientData cdata, Tcl_Interp *interp,
 				  int argc, CONST84 char *argv[]);
 static int pltcl_SPI_execute_plan(ClientData cdata, Tcl_Interp *interp,
@@ -592,28 +585,16 @@ pltcl_func_handler(PG_FUNCTION_ARGS)
 	Tcl_DStringFree(&tcl_cmd);
 
 	/************************************************************
-	 * If there was an error in a PG callback, propagate that
-	 * no matter what Tcl claims about its success.
-	 ************************************************************/
-	if (pltcl_error_in_progress)
-	{
-		ErrorData  *edata = pltcl_error_in_progress;
-
-		pltcl_error_in_progress = NULL;
-		ReThrowError(edata);
-	}
-
-	/************************************************************
-	 * Check for errors reported by Tcl itself.
+	 * Check for errors reported by Tcl.
 	 ************************************************************/
 	if (tcl_rc != TCL_OK)
 	{
 		UTF_BEGIN;
 		ereport(ERROR,
-				(errmsg("pltcl: %s", interp->result),
-				 errdetail("%s",
-						   UTF_U2E(Tcl_GetVar(interp, "errorInfo",
-											  TCL_GLOBAL_ONLY)))));
+				(errmsg("%s", interp->result),
+				 errcontext("%s",
+							UTF_U2E(Tcl_GetVar(interp, "errorInfo",
+											   TCL_GLOBAL_ONLY)))));
 		UTF_END;
 	}
 
@@ -820,28 +801,16 @@ pltcl_trigger_handler(PG_FUNCTION_ARGS)
 	Tcl_DStringFree(&tcl_cmd);
 
 	/************************************************************
-	 * If there was an error in a PG callback, propagate that
-	 * no matter what Tcl claims about its success.
-	 ************************************************************/
-	if (pltcl_error_in_progress)
-	{
-		ErrorData  *edata = pltcl_error_in_progress;
-
-		pltcl_error_in_progress = NULL;
-		ReThrowError(edata);
-	}
-
-	/************************************************************
-	 * Check for errors reported by Tcl itself.
+	 * Check for errors reported by Tcl.
 	 ************************************************************/
 	if (tcl_rc != TCL_OK)
 	{
 		UTF_BEGIN;
 		ereport(ERROR,
-				(errmsg("pltcl: %s", interp->result),
-				 errdetail("%s",
-						   UTF_U2E(Tcl_GetVar(interp, "errorInfo",
-											  TCL_GLOBAL_ONLY)))));
+				(errmsg("%s", interp->result),
+				 errcontext("%s",
+							UTF_U2E(Tcl_GetVar(interp, "errorInfo",
+											   TCL_GLOBAL_ONLY)))));
 		UTF_END;
 	}
 
@@ -1312,15 +1281,6 @@ pltcl_elog(ClientData cdata, Tcl_Interp *interp,
 	volatile int level;
 	MemoryContext oldcontext;
 
-	/************************************************************
-	 * Suppress messages if an error is already declared
-	 ************************************************************/
-	if (pltcl_error_in_progress)
-	{
-		Tcl_SetResult(interp, "Transaction aborted", TCL_VOLATILE);
-		return TCL_ERROR;
-	}
-
 	if (argc != 3)
 	{
 		Tcl_SetResult(interp, "syntax error - 'elog level msg'",
@@ -1350,8 +1310,9 @@ pltcl_elog(ClientData cdata, Tcl_Interp *interp,
 	}
 
 	/************************************************************
-	 * If elog() throws an error, catch and save it, then return
-	 * error indication to Tcl interpreter.
+	 * If elog() throws an error, catch it and return the error to the
+	 * Tcl interpreter.  Note we are assuming that elog() can't have any
+	 * internal failures that are so bad as to require a transaction abort.
 	 ************************************************************/
 	oldcontext = CurrentMemoryContext;
 	PG_TRY();
@@ -1362,9 +1323,17 @@ pltcl_elog(ClientData cdata, Tcl_Interp *interp,
 	}
 	PG_CATCH();
 	{
+		ErrorData  *edata;
+
+		/* Must reset elog.c's state */
 		MemoryContextSwitchTo(oldcontext);
-		pltcl_error_in_progress = CopyErrorData();
+		edata = CopyErrorData();
 		FlushErrorState();
+
+		/* Pass the error message to Tcl */
+		Tcl_SetResult(interp, edata->message, TCL_VOLATILE);
+		FreeErrorData(edata);
+
 		return TCL_ERROR;
 	}
 	PG_END_TRY();
@@ -1522,6 +1491,83 @@ pltcl_returnnull(ClientData cdata, Tcl_Interp *interp,
 }
 
 
+/*----------
+ * Support for running SPI operations inside subtransactions
+ *
+ * Intended usage pattern is:
+ *
+ *	MemoryContext oldcontext = CurrentMemoryContext;
+ *	ResourceOwner oldowner = CurrentResourceOwner;
+ *
+ *	...
+ *	pltcl_subtrans_begin(oldcontext, oldowner);
+ *	PG_TRY();
+ *	{
+ *		do something risky;
+ *		pltcl_subtrans_commit(oldcontext, oldowner);
+ *	}
+ *	PG_CATCH();
+ *	{
+ *		pltcl_subtrans_abort(interp, oldcontext, oldowner);
+ *		return TCL_ERROR;
+ *	}
+ *	PG_END_TRY();
+ *	return TCL_OK;
+ *----------
+ */
+static void
+pltcl_subtrans_begin(MemoryContext oldcontext, ResourceOwner oldowner)
+{
+	BeginInternalSubTransaction(NULL);
+
+	/* Want to run inside function's memory context */
+	MemoryContextSwitchTo(oldcontext);
+}
+
+static void
+pltcl_subtrans_commit(MemoryContext oldcontext, ResourceOwner oldowner)
+{
+	/* Commit the inner transaction, return to outer xact context */
+	ReleaseCurrentSubTransaction();
+	MemoryContextSwitchTo(oldcontext);
+	CurrentResourceOwner = oldowner;
+
+	/*
+	 * AtEOSubXact_SPI() should not have popped any SPI context,
+	 * but just in case it did, make sure we remain connected.
+	 */
+	SPI_restore_connection();
+}
+
+static void
+pltcl_subtrans_abort(Tcl_Interp *interp,
+					 MemoryContext oldcontext, ResourceOwner oldowner)
+{
+	ErrorData  *edata;
+
+	/* Save error info */
+	MemoryContextSwitchTo(oldcontext);
+	edata = CopyErrorData();
+	FlushErrorState();
+
+	/* Abort the inner transaction */
+	RollbackAndReleaseCurrentSubTransaction();
+	MemoryContextSwitchTo(oldcontext);
+	CurrentResourceOwner = oldowner;
+
+	/*
+	 * If AtEOSubXact_SPI() popped any SPI context of the subxact,
+	 * it will have left us in a disconnected state.  We need this
+	 * hack to return to connected state.
+	 */
+	SPI_restore_connection();
+
+	/* Pass the error message to Tcl */
+	Tcl_SetResult(interp, edata->message, TCL_VOLATILE);
+	FreeErrorData(edata);
+}
+
+
 /**********************************************************************
  * pltcl_SPI_execute()		- The builtin SPI_execute command
  *				  for the Tcl interpreter
@@ -1530,35 +1576,22 @@ static int
 pltcl_SPI_execute(ClientData cdata, Tcl_Interp *interp,
 				  int argc, CONST84 char *argv[])
 {
-	volatile int my_rc;
+	int			my_rc;
 	int			spi_rc;
-	char		buf[64];
+	int			query_idx;
+	int			i;
 	int			count = 0;
 	CONST84 char *volatile arrayname = NULL;
-	volatile int query_idx;
-	int			i;
-	int			loop_rc;
-	int			ntuples;
-	HeapTuple  *volatile tuples;
-	volatile TupleDesc tupdesc = NULL;
-	SPITupleTable *tuptable;
-	MemoryContext oldcontext;
+	CONST84 char *volatile loop_body = NULL;
+	MemoryContext oldcontext = CurrentMemoryContext;
+	ResourceOwner oldowner = CurrentResourceOwner;
 
 	char	   *usage = "syntax error - 'SPI_exec "
 	"?-count n? "
 	"?-array name? query ?loop body?";
 
 	/************************************************************
-	 * Don't do anything if we are already in error mode
-	 ************************************************************/
-	if (pltcl_error_in_progress)
-	{
-		Tcl_SetResult(interp, "Transaction aborted", TCL_VOLATILE);
-		return TCL_ERROR;
-	}
-
-	/************************************************************
-	 * Check the call syntax and get the count option
+	 * Check the call syntax and get the options
 	 ************************************************************/
 	if (argc < 2)
 	{
@@ -1596,133 +1629,143 @@ pltcl_SPI_execute(ClientData cdata, Tcl_Interp *interp,
 	}
 
 	query_idx = i;
-	if (query_idx >= argc)
+	if (query_idx >= argc || query_idx + 2 < argc)
 	{
 		Tcl_SetResult(interp, usage, TCL_VOLATILE);
 		return TCL_ERROR;
 	}
+	if (query_idx + 1 < argc)
+		loop_body = argv[query_idx + 1];
 
 	/************************************************************
-	 * Execute the query and handle return codes
+	 * Execute the query inside a sub-transaction, so we can cope with
+	 * errors sanely
 	 ************************************************************/
-	oldcontext = CurrentMemoryContext;
+
+	pltcl_subtrans_begin(oldcontext, oldowner);
+
 	PG_TRY();
 	{
 		UTF_BEGIN;
 		spi_rc = SPI_execute(UTF_U2E(argv[query_idx]),
 							 pltcl_current_prodesc->fn_readonly, count);
 		UTF_END;
+
+		my_rc = pltcl_process_SPI_result(interp,
+										 arrayname,
+										 loop_body,
+										 spi_rc,
+										 SPI_tuptable,
+										 SPI_processed);
+
+		pltcl_subtrans_commit(oldcontext, oldowner);
 	}
 	PG_CATCH();
 	{
-		MemoryContextSwitchTo(oldcontext);
-		pltcl_error_in_progress = CopyErrorData();
-		FlushErrorState();
-		Tcl_SetResult(interp, "Transaction aborted", TCL_VOLATILE);
+		pltcl_subtrans_abort(interp, oldcontext, oldowner);
 		return TCL_ERROR;
 	}
 	PG_END_TRY();
 
+	return my_rc;
+}
+
+/*
+ * Process the result from SPI_execute or SPI_execute_plan
+ *
+ * Shared code between pltcl_SPI_execute and pltcl_SPI_execute_plan
+ */
+static int
+pltcl_process_SPI_result(Tcl_Interp *interp,
+						 CONST84 char *arrayname,
+						 CONST84 char *loop_body,
+						 int spi_rc,
+						 SPITupleTable *tuptable,
+						 int ntuples)
+{
+	int			my_rc = TCL_OK;
+	char		buf[64];
+	int			i;
+	int			loop_rc;
+	HeapTuple  *tuples;
+	TupleDesc	tupdesc;
+
 	switch (spi_rc)
 	{
 		case SPI_OK_UTILITY:
 			Tcl_SetResult(interp, "0", TCL_VOLATILE);
-			SPI_freetuptable(SPI_tuptable);
-			return TCL_OK;
+			break;
 
 		case SPI_OK_SELINTO:
 		case SPI_OK_INSERT:
 		case SPI_OK_DELETE:
 		case SPI_OK_UPDATE:
-			snprintf(buf, sizeof(buf), "%d", SPI_processed);
+			snprintf(buf, sizeof(buf), "%d", ntuples);
 			Tcl_SetResult(interp, buf, TCL_VOLATILE);
-			SPI_freetuptable(SPI_tuptable);
-			return TCL_OK;
-
-		case SPI_OK_SELECT:
 			break;
 
-		default:
-			Tcl_AppendResult(interp, "pltcl: SPI_execute failed: ",
-							 SPI_result_code_string(spi_rc), NULL);
-			SPI_freetuptable(SPI_tuptable);
-			return TCL_ERROR;
-	}
-
-	/************************************************************
-	 * Only SELECT queries fall through to here - process the tuples we got
-	 ************************************************************/
-	ntuples = SPI_processed;
-	tuptable = SPI_tuptable;
-	if (ntuples > 0)
-	{
-		tuples = tuptable->vals;
-		tupdesc = tuptable->tupdesc;
-	}
+		case SPI_OK_SELECT:
+			/*
+			 * Process the tuples we got
+			 */
+			tuples = tuptable->vals;
+			tupdesc = tuptable->tupdesc;
 
-	my_rc = TCL_OK;
-	PG_TRY();
-	{
-		if (argc == query_idx + 1)
-		{
-			/************************************************************
-			 * If there is no loop body given, just set the variables
-			 * from the first tuple (if any)
-			 ************************************************************/
-			if (ntuples > 0)
-				pltcl_set_tuple_values(interp, arrayname, 0,
-									   tuples[0], tupdesc);
-		}
-		else
-		{
-			/************************************************************
-			 * There is a loop body - process all tuples and evaluate
-			 * the body on each
-			 ************************************************************/
-			query_idx++;
-			for (i = 0; i < ntuples; i++)
+			if (loop_body == NULL)
 			{
-				pltcl_set_tuple_values(interp, arrayname, i,
-									   tuples[i], tupdesc);
-
-				loop_rc = Tcl_Eval(interp, argv[query_idx]);
-
-				if (loop_rc == TCL_OK)
-					continue;
-				if (loop_rc == TCL_CONTINUE)
-					continue;
-				if (loop_rc == TCL_RETURN)
+				/*
+				 * If there is no loop body given, just set the variables
+				 * from the first tuple (if any)
+				 */
+				if (ntuples > 0)
+					pltcl_set_tuple_values(interp, arrayname, 0,
+										   tuples[0], tupdesc);
+			}
+			else
+			{
+				/*
+				 * There is a loop body - process all tuples and evaluate
+				 * the body on each
+				 */
+				for (i = 0; i < ntuples; i++)
 				{
-					my_rc = TCL_RETURN;
+					pltcl_set_tuple_values(interp, arrayname, i,
+										   tuples[i], tupdesc);
+
+					loop_rc = Tcl_Eval(interp, loop_body);
+
+					if (loop_rc == TCL_OK)
+						continue;
+					if (loop_rc == TCL_CONTINUE)
+						continue;
+					if (loop_rc == TCL_RETURN)
+					{
+						my_rc = TCL_RETURN;
+						break;
+					}
+					if (loop_rc == TCL_BREAK)
+						break;
+					my_rc = TCL_ERROR;
 					break;
 				}
-				if (loop_rc == TCL_BREAK)
-					break;
-				my_rc = TCL_ERROR;
-				break;
 			}
-		}
 
-		SPI_freetuptable(tuptable);
-	}
-	PG_CATCH();
-	{
-		MemoryContextSwitchTo(oldcontext);
-		pltcl_error_in_progress = CopyErrorData();
-		FlushErrorState();
-		Tcl_SetResult(interp, "Transaction aborted", TCL_VOLATILE);
-		return TCL_ERROR;
-	}
-	PG_END_TRY();
+			if (my_rc == TCL_OK)
+			{
+				snprintf(buf, sizeof(buf), "%d", ntuples);
+				Tcl_SetResult(interp, buf, TCL_VOLATILE);
+			}
+			break;
 
-	/************************************************************
-	 * Finally return the number of tuples
-	 ************************************************************/
-	if (my_rc == TCL_OK)
-	{
-		snprintf(buf, sizeof(buf), "%d", ntuples);
-		Tcl_SetResult(interp, buf, TCL_VOLATILE);
+		default:
+			Tcl_AppendResult(interp, "pltcl: SPI_execute failed: ",
+							 SPI_result_code_string(spi_rc), NULL);
+			my_rc = TCL_ERROR;
+			break;
 	}
+
+	SPI_freetuptable(tuptable);
+
 	return my_rc;
 }
 
@@ -1748,16 +1791,8 @@ pltcl_SPI_prepare(ClientData cdata, Tcl_Interp *interp,
 	Tcl_HashEntry *hashent;
 	int			hashnew;
 	Tcl_HashTable *query_hash;
-	MemoryContext oldcontext;
-
-	/************************************************************
-	 * Don't do anything if we are already in error mode
-	 ************************************************************/
-	if (pltcl_error_in_progress)
-	{
-		Tcl_SetResult(interp, "Transaction aborted", TCL_VOLATILE);
-		return TCL_ERROR;
-	}
+	MemoryContext oldcontext = CurrentMemoryContext;
+	ResourceOwner oldowner = CurrentResourceOwner;
 
 	/************************************************************
 	 * Check the call syntax
@@ -1785,7 +1820,13 @@ pltcl_SPI_prepare(ClientData cdata, Tcl_Interp *interp,
 	qdesc->arginfuncs = (FmgrInfo *) malloc(nargs * sizeof(FmgrInfo));
 	qdesc->argtypioparams = (Oid *) malloc(nargs * sizeof(Oid));
 
-	oldcontext = CurrentMemoryContext;
+	/************************************************************
+	 * Execute the prepare inside a sub-transaction, so we can cope with
+	 * errors sanely
+	 ************************************************************/
+
+	pltcl_subtrans_begin(oldcontext, oldowner);
+
 	PG_TRY();
 	{
 		/************************************************************
@@ -1844,31 +1885,31 @@ pltcl_SPI_prepare(ClientData cdata, Tcl_Interp *interp,
 		/* Release the procCxt copy to avoid within-function memory leak */
 		SPI_freeplan(plan);
 
-		/************************************************************
-		 * Insert a hashtable entry for the plan and return
-		 * the key to the caller
-		 ************************************************************/
-		if (interp == pltcl_norm_interp)
-			query_hash = pltcl_norm_query_hash;
-		else
-			query_hash = pltcl_safe_query_hash;
-
+		pltcl_subtrans_commit(oldcontext, oldowner);
 	}
 	PG_CATCH();
 	{
-		MemoryContextSwitchTo(oldcontext);
-		pltcl_error_in_progress = CopyErrorData();
-		FlushErrorState();
+		pltcl_subtrans_abort(interp, oldcontext, oldowner);
+
 		free(qdesc->argtypes);
 		free(qdesc->arginfuncs);
 		free(qdesc->argtypioparams);
 		free(qdesc);
 		ckfree((char *) args);
-		Tcl_SetResult(interp, "Transaction aborted", TCL_VOLATILE);
+
 		return TCL_ERROR;
 	}
 	PG_END_TRY();
 
+	/************************************************************
+	 * Insert a hashtable entry for the plan and return
+	 * the key to the caller
+	 ************************************************************/
+	if (interp == pltcl_norm_interp)
+		query_hash = pltcl_norm_query_hash;
+	else
+		query_hash = pltcl_safe_query_hash;
+
 	hashent = Tcl_CreateHashEntry(query_hash, qdesc->qname, &hashnew);
 	Tcl_SetHashValue(hashent, (ClientData) qdesc);
 
@@ -1886,41 +1927,27 @@ static int
 pltcl_SPI_execute_plan(ClientData cdata, Tcl_Interp *interp,
 					   int argc, CONST84 char *argv[])
 {
-	volatile int my_rc;
+	int			my_rc;
 	int			spi_rc;
-	char		buf[64];
-	volatile int i;
+	int			i;
 	int			j;
-	int			loop_body;
 	Tcl_HashEntry *hashent;
 	pltcl_query_desc *qdesc;
-	Datum	   *volatile argvalues = NULL;
 	const char *volatile nulls = NULL;
 	CONST84 char *volatile arrayname = NULL;
+	CONST84 char *volatile loop_body = NULL;
 	int			count = 0;
 	int			callnargs;
-	CONST84 char **callargs;
-	int			loop_rc;
-	int			ntuples;
-	HeapTuple  *volatile tuples = NULL;
-	volatile TupleDesc tupdesc = NULL;
-	SPITupleTable *tuptable;
-	volatile MemoryContext oldcontext;
+	CONST84 char **callargs = NULL;
+	Datum	   *argvalues;
+	MemoryContext oldcontext = CurrentMemoryContext;
+	ResourceOwner oldowner = CurrentResourceOwner;
 	Tcl_HashTable *query_hash;
 
 	char	   *usage = "syntax error - 'SPI_execp "
 	"?-nulls string? ?-count n? "
 	"?-array name? query ?args? ?loop body?";
 
-	/************************************************************
-	 * Don't do anything if we are already in error mode
-	 ************************************************************/
-	if (pltcl_error_in_progress)
-	{
-		Tcl_SetResult(interp, "Transaction aborted", TCL_VOLATILE);
-		return TCL_ERROR;
-	}
-
 	/************************************************************
 	 * Get the options and check syntax
 	 ************************************************************/
@@ -1963,7 +1990,7 @@ pltcl_SPI_execute_plan(ClientData cdata, Tcl_Interp *interp,
 	}
 
 	/************************************************************
-	 * Check minimum call arguments
+	 * Get the prepared plan descriptor by its key
 	 ************************************************************/
 	if (i >= argc)
 	{
@@ -1971,21 +1998,19 @@ pltcl_SPI_execute_plan(ClientData cdata, Tcl_Interp *interp,
 		return TCL_ERROR;
 	}
 
-	/************************************************************
-	 * Get the prepared plan descriptor by its key
-	 ************************************************************/
 	if (interp == pltcl_norm_interp)
 		query_hash = pltcl_norm_query_hash;
 	else
 		query_hash = pltcl_safe_query_hash;
 
-	hashent = Tcl_FindHashEntry(query_hash, argv[i++]);
+	hashent = Tcl_FindHashEntry(query_hash, argv[i]);
 	if (hashent == NULL)
 	{
-		Tcl_AppendResult(interp, "invalid queryid '", argv[--i], "'", NULL);
+		Tcl_AppendResult(interp, "invalid queryid '", argv[i], "'", NULL);
 		return TCL_ERROR;
 	}
 	qdesc = (pltcl_query_desc *) Tcl_GetHashValue(hashent);
+	i++;
 
 	/************************************************************
 	 * If a nulls string is given, check for correct length
@@ -2030,178 +2055,86 @@ pltcl_SPI_execute_plan(ClientData cdata, Tcl_Interp *interp,
 			ckfree((char *) callargs);
 			return TCL_ERROR;
 		}
-
-		/************************************************************
-		 * Setup the value array for SPI_execute_plan() using
-		 * the type specific input functions
-		 ************************************************************/
-		oldcontext = CurrentMemoryContext;
-		PG_TRY();
-		{
-			argvalues = (Datum *) palloc(callnargs * sizeof(Datum));
-
-			for (j = 0; j < callnargs; j++)
-			{
-				if (nulls && nulls[j] == 'n')
-				{
-					/* don't try to convert the input for a null */
-					argvalues[j] = (Datum) 0;
-				}
-				else
-				{
-					UTF_BEGIN;
-					argvalues[j] =
-						FunctionCall3(&qdesc->arginfuncs[j],
-								   CStringGetDatum(UTF_U2E(callargs[j])),
-							  ObjectIdGetDatum(qdesc->argtypioparams[j]),
-									  Int32GetDatum(-1));
-					UTF_END;
-				}
-			}
-		}
-		PG_CATCH();
-		{
-			ckfree((char *) callargs);
-			MemoryContextSwitchTo(oldcontext);
-			pltcl_error_in_progress = CopyErrorData();
-			FlushErrorState();
-			Tcl_SetResult(interp, "Transaction aborted", TCL_VOLATILE);
-			return TCL_ERROR;
-		}
-		PG_END_TRY();
-
-		ckfree((char *) callargs);
 	}
 	else
 		callnargs = 0;
 
 	/************************************************************
-	 * Remember the index of the last processed call
-	 * argument - a loop body for SELECT might follow
+	 * Get loop body if present
 	 ************************************************************/
-	loop_body = i;
+	if (i < argc)
+		loop_body = argv[i++];
 
-	/************************************************************
-	 * Execute the plan
-	 ************************************************************/
-	oldcontext = CurrentMemoryContext;
-	PG_TRY();
+	if (i != argc)
 	{
-		spi_rc = SPI_execute_plan(qdesc->plan, argvalues, nulls,
-								  pltcl_current_prodesc->fn_readonly, count);
-	}
-	PG_CATCH();
-	{
-		MemoryContextSwitchTo(oldcontext);
-		pltcl_error_in_progress = CopyErrorData();
-		FlushErrorState();
-		Tcl_SetResult(interp, "Transaction aborted", TCL_VOLATILE);
+		Tcl_SetResult(interp, usage, TCL_VOLATILE);
 		return TCL_ERROR;
 	}
-	PG_END_TRY();
 
 	/************************************************************
-	 * Check the return code from SPI_execute_plan()
+	 * Execute the plan inside a sub-transaction, so we can cope with
+	 * errors sanely
 	 ************************************************************/
-	switch (spi_rc)
-	{
-		case SPI_OK_UTILITY:
-			Tcl_SetResult(interp, "0", TCL_VOLATILE);
-			SPI_freetuptable(SPI_tuptable);
-			return TCL_OK;
-
-		case SPI_OK_SELINTO:
-		case SPI_OK_INSERT:
-		case SPI_OK_DELETE:
-		case SPI_OK_UPDATE:
-			snprintf(buf, sizeof(buf), "%d", SPI_processed);
-			Tcl_SetResult(interp, buf, TCL_VOLATILE);
-			SPI_freetuptable(SPI_tuptable);
-			return TCL_OK;
 
-		case SPI_OK_SELECT:
-			break;
+	pltcl_subtrans_begin(oldcontext, oldowner);
 
-		default:
-			Tcl_AppendResult(interp, "pltcl: SPI_execute_plan failed: ",
-							 SPI_result_code_string(spi_rc), NULL);
-			SPI_freetuptable(SPI_tuptable);
-			return TCL_ERROR;
-	}
-
-	/************************************************************
-	 * Only SELECT queries fall through to here - process the tuples we got
-	 ************************************************************/
-	ntuples = SPI_processed;
-	tuptable = SPI_tuptable;
-	if (ntuples > 0)
-	{
-		tuples = tuptable->vals;
-		tupdesc = tuptable->tupdesc;
-	}
-
-	my_rc = TCL_OK;
 	PG_TRY();
 	{
-		if (loop_body >= argc)
-		{
-			/************************************************************
-			 * If there is no loop body given, just set the variables
-			 * from the first tuple (if any)
-			 ************************************************************/
-			if (ntuples > 0)
-				pltcl_set_tuple_values(interp, arrayname, 0,
-									   tuples[0], tupdesc);
-		}
-		else
+		/************************************************************
+		 * Setup the value array for SPI_execute_plan() using
+		 * the type specific input functions
+		 ************************************************************/
+		argvalues = (Datum *) palloc(callnargs * sizeof(Datum));
+
+		for (j = 0; j < callnargs; j++)
 		{
-			/************************************************************
-			 * There is a loop body - process all tuples and evaluate
-			 * the body on each
-			 ************************************************************/
-			for (i = 0; i < ntuples; i++)
+			if (nulls && nulls[j] == 'n')
 			{
-				pltcl_set_tuple_values(interp, arrayname, i,
-									   tuples[i], tupdesc);
-
-				loop_rc = Tcl_Eval(interp, argv[loop_body]);
-
-				if (loop_rc == TCL_OK)
-					continue;
-				if (loop_rc == TCL_CONTINUE)
-					continue;
-				if (loop_rc == TCL_RETURN)
-				{
-					my_rc = TCL_RETURN;
-					break;
-				}
-				if (loop_rc == TCL_BREAK)
-					break;
-				my_rc = TCL_ERROR;
-				break;
+				/* don't try to convert the input for a null */
+				argvalues[j] = (Datum) 0;
+			}
+			else
+			{
+				UTF_BEGIN;
+				argvalues[j] =
+					FunctionCall3(&qdesc->arginfuncs[j],
+								  CStringGetDatum(UTF_U2E(callargs[j])),
+								  ObjectIdGetDatum(qdesc->argtypioparams[j]),
+								  Int32GetDatum(-1));
+				UTF_END;
 			}
 		}
 
-		SPI_freetuptable(tuptable);
+		if (callargs)
+			ckfree((char *) callargs);
+		callargs = NULL;
+
+		/************************************************************
+		 * Execute the plan
+		 ************************************************************/
+		spi_rc = SPI_execute_plan(qdesc->plan, argvalues, nulls,
+								  pltcl_current_prodesc->fn_readonly, count);
+
+		my_rc = pltcl_process_SPI_result(interp,
+										 arrayname,
+										 loop_body,
+										 spi_rc,
+										 SPI_tuptable,
+										 SPI_processed);
+
+		pltcl_subtrans_commit(oldcontext, oldowner);
 	}
 	PG_CATCH();
 	{
-		MemoryContextSwitchTo(oldcontext);
-		pltcl_error_in_progress = CopyErrorData();
-		FlushErrorState();
-		Tcl_SetResult(interp, "Transaction aborted", TCL_VOLATILE);
+		pltcl_subtrans_abort(interp, oldcontext, oldowner);
+
+		if (callargs)
+			ckfree((char *) callargs);
+
 		return TCL_ERROR;
 	}
 	PG_END_TRY();
 
-	/************************************************************
-	 * Finally return the number of tuples
-	 ************************************************************/
-	if (my_rc == TCL_OK)
-	{
-		snprintf(buf, sizeof(buf), "%d", ntuples);
-		Tcl_SetResult(interp, buf, TCL_VOLATILE);
-	}
 	return my_rc;
 }
 
diff --git a/src/pl/tcl/test/runtest b/src/pl/tcl/test/runtest
index 32c1433b85e163109a1868d17d3674fc77425946..50b2be07751b0c8f5bafd055f7d479376cfe712d 100755
--- a/src/pl/tcl/test/runtest
+++ b/src/pl/tcl/test/runtest
@@ -6,6 +6,8 @@ export DBNAME
 echo "**** Destroy old database $DBNAME ****"
 dropdb $DBNAME
 
+sleep 1
+
 echo "**** Create test database $DBNAME ****"
 createdb $DBNAME
 
diff --git a/src/pl/tcl/test/test_queries.sql b/src/pl/tcl/test/test_queries.sql
index 98bc513b4ce81f26c2fc6f9b2aaca5949f6cd479..9cb059ed15f8a946a48d87fa43b4b23e00218bc2 100644
--- a/src/pl/tcl/test/test_queries.sql
+++ b/src/pl/tcl/test/test_queries.sql
@@ -1,3 +1,5 @@
+-- suppress CONTEXT so that function OIDs aren't in output
+\set VERBOSITY terse
 
 insert into T_pkey1 values (1, 'key1-1', 'test key');
 insert into T_pkey1 values (1, 'key1-2', 'test key');
diff --git a/src/pl/tcl/test/test_setup.sql b/src/pl/tcl/test/test_setup.sql
index 568a2b3aeb0cced0674d6d5eb985c78b88cf5d9e..78ddd867eb4c3f2b9278174287ea9aa55761c08a 100644
--- a/src/pl/tcl/test/test_setup.sql
+++ b/src/pl/tcl/test/test_setup.sql
@@ -1,3 +1,9 @@
+--
+-- checkpoint so that if we have a crash in the tests, replay of the
+-- just-completed CREATE DATABASE won't discard the core dump file
+--
+checkpoint;
+
 --
 -- Create the tables used in the test queries
 --