<literal>plpy.<replaceable>foo</replaceable></literal>.
   </para>
 
+  <sect2>
+    <title>Database Access Functions</title>
+
   <para>
    The <literal>plpy</literal> module provides two
    functions called <function>execute</function> and
 $$ LANGUAGE plpythonu;
 </programlisting>
   </para>
+
+  </sect2>
+
+  <sect2>
+   <title>Trapping Errors</title>
+
+   <para>
+    Functions accessing the database might encounter errors, which
+    will cause them to abort and raise an exception.  Both
+    <function>plpy.execute</function> and
+    <function>plpy.prepare</function> can raise an instance of
+    <literal>plpy.SPIError</literal>, which by default will terminate
+    the function.  This error can be handled just like any other
+    Python exception, by using the <literal>try/except</literal>
+    construct.  For example:
+<programlisting>
+CREATE FUNCTION try_adding_joe() RETURNS text AS $$
+    try:
+        plpy.execute("INSERT INTO users(username) VALUES ('joe')")
+    except plpy.SPIError:
+        return "something went wrong"
+    else:
+        return "Joe added"
+$$ LANGUAGE plpythonu;
+</programlisting>
+   </para>
+  </sect2>
  </sect1>
 
  <sect1 id="plpython-util">
 
 'plpy.execute("syntax error")'
         LANGUAGE plpythonu;
 SELECT sql_syntax_error();
-WARNING:  plpy.SPIError: unrecognized error in PLy_spi_execute_query
-CONTEXT:  PL/Python function "sql_syntax_error"
 ERROR:  plpy.SPIError: syntax error at or near "syntax"
 LINE 1: syntax error
         ^
 return rv[0]'
    LANGUAGE plpythonu;
 SELECT exception_index_invalid_nested();
-WARNING:  plpy.SPIError: unrecognized error in PLy_spi_execute_query
-CONTEXT:  PL/Python function "exception_index_invalid_nested"
 ERROR:  plpy.SPIError: function test5(unknown) does not exist
 LINE 1: SELECT test5('foo')
                ^
 '
    LANGUAGE plpythonu;
 SELECT invalid_type_uncaught('rick');
-WARNING:  plpy.SPIError: unrecognized error in PLy_spi_prepare
-CONTEXT:  PL/Python function "invalid_type_uncaught"
 ERROR:  plpy.SPIError: type "test" does not exist
 CONTEXT:  PL/Python function "invalid_type_uncaught"
 /* for what it's worth catch the exception generated by
 '
    LANGUAGE plpythonu;
 SELECT invalid_type_caught('rick');
-WARNING:  plpy.SPIError: unrecognized error in PLy_spi_prepare
-CONTEXT:  PL/Python function "invalid_type_caught"
 NOTICE:  type "test" does not exist
 CONTEXT:  PL/Python function "invalid_type_caught"
  invalid_type_caught 
 '
    LANGUAGE plpythonu;
 SELECT invalid_type_reraised('rick');
-WARNING:  plpy.SPIError: unrecognized error in PLy_spi_prepare
-CONTEXT:  PL/Python function "invalid_type_reraised"
 ERROR:  plpy.Error: type "test" does not exist
 CONTEXT:  PL/Python function "invalid_type_reraised"
 /* no typo no messing about
  
 (1 row)
 
+/* manually starting subtransactions - a bad idea
+ */
+CREATE FUNCTION manual_subxact() RETURNS void AS $$
+plpy.execute("savepoint save")
+plpy.execute("create table foo(x integer)")
+plpy.execute("rollback to save")
+$$ LANGUAGE plpythonu;
+SELECT manual_subxact();
+ERROR:  plpy.SPIError: SPI_execute failed: SPI_ERROR_TRANSACTION
+CONTEXT:  PL/Python function "manual_subxact"
+/* same for prepared plans
+ */
+CREATE FUNCTION manual_subxact_prepared() RETURNS void AS $$
+save = plpy.prepare("savepoint save")
+rollback = plpy.prepare("rollback to save")
+plpy.execute(save)
+plpy.execute("create table foo(x integer)")
+plpy.execute(rollback)
+$$ LANGUAGE plpythonu;
+SELECT manual_subxact_prepared();
+ERROR:  plpy.SPIError: SPI_execute_plan failed: SPI_ERROR_TRANSACTION
+CONTEXT:  PL/Python function "manual_subxact_prepared"
 
 'plpy.execute("syntax error")'
         LANGUAGE plpythonu;
 SELECT sql_syntax_error();
-WARNING:  plpy.SPIError: unrecognized error in PLy_spi_execute_query
-CONTEXT:  PL/Python function "sql_syntax_error"
 ERROR:  plpy.SPIError: syntax error at or near "syntax"
 LINE 1: syntax error
         ^
 return rv[0]'
    LANGUAGE plpythonu;
 SELECT exception_index_invalid_nested();
-WARNING:  plpy.SPIError: unrecognized error in PLy_spi_execute_query
-CONTEXT:  PL/Python function "exception_index_invalid_nested"
 ERROR:  plpy.SPIError: function test5(unknown) does not exist
 LINE 1: SELECT test5('foo')
                ^
 '
    LANGUAGE plpythonu;
 SELECT invalid_type_uncaught('rick');
-WARNING:  plpy.SPIError: unrecognized error in PLy_spi_prepare
-CONTEXT:  PL/Python function "invalid_type_uncaught"
 ERROR:  plpy.SPIError: type "test" does not exist
 CONTEXT:  PL/Python function "invalid_type_uncaught"
 /* for what it's worth catch the exception generated by
 '
    LANGUAGE plpythonu;
 SELECT invalid_type_caught('rick');
-WARNING:  plpy.SPIError: unrecognized error in PLy_spi_prepare
-CONTEXT:  PL/Python function "invalid_type_caught"
 NOTICE:  type "test" does not exist
 CONTEXT:  PL/Python function "invalid_type_caught"
  invalid_type_caught 
 '
    LANGUAGE plpythonu;
 SELECT invalid_type_reraised('rick');
-WARNING:  plpy.SPIError: unrecognized error in PLy_spi_prepare
-CONTEXT:  PL/Python function "invalid_type_reraised"
 ERROR:  plpy.Error: type "test" does not exist
 CONTEXT:  PL/Python function "invalid_type_reraised"
 /* no typo no messing about
  
 (1 row)
 
+/* manually starting subtransactions - a bad idea
+ */
+CREATE FUNCTION manual_subxact() RETURNS void AS $$
+plpy.execute("savepoint save")
+plpy.execute("create table foo(x integer)")
+plpy.execute("rollback to save")
+$$ LANGUAGE plpythonu;
+SELECT manual_subxact();
+ERROR:  plpy.SPIError: SPI_execute failed: SPI_ERROR_TRANSACTION
+CONTEXT:  PL/Python function "manual_subxact"
+/* same for prepared plans
+ */
+CREATE FUNCTION manual_subxact_prepared() RETURNS void AS $$
+save = plpy.prepare("savepoint save")
+rollback = plpy.prepare("rollback to save")
+plpy.execute(save)
+plpy.execute("create table foo(x integer)")
+plpy.execute(rollback)
+$$ LANGUAGE plpythonu;
+SELECT manual_subxact_prepared();
+ERROR:  plpy.SPIError: SPI_execute_plan failed: SPI_ERROR_TRANSACTION
+CONTEXT:  PL/Python function "manual_subxact_prepared"
 
 #include "nodes/makefuncs.h"
 #include "parser/parse_type.h"
 #include "tcop/tcopprot.h"
+#include "access/xact.h"
 #include "utils/builtins.h"
 #include "utils/hsearch.h"
 #include "utils/lsyscache.h"
    char       *query;
    void       *tmpplan;
    volatile MemoryContext oldcontext;
+   volatile ResourceOwner oldowner;
    int         nargs;
 
    if (!PyArg_ParseTuple(args, "s|O", &query, &list))
    plan->args = nargs ? PLy_malloc(sizeof(PLyTypeInfo) * nargs) : NULL;
 
    oldcontext = CurrentMemoryContext;
+   oldowner = CurrentResourceOwner;
+
+   BeginInternalSubTransaction(NULL);
+   MemoryContextSwitchTo(oldcontext);
+
    PG_TRY();
    {
        int i;
        if (plan->plan == NULL)
            elog(ERROR, "SPI_saveplan failed: %s",
                 SPI_result_code_string(SPI_result));
+
+       /* 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();
        Py_DECREF(plan);
        Py_XDECREF(optr);
-       if (!PyErr_Occurred())
-           PLy_exception_set(PLy_exc_spi_error,
-                             "unrecognized error in PLy_spi_prepare");
-       PLy_elog(WARNING, NULL);
+
+       /* 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();
+
+       /* Make Python raise the exception */
        PLy_spi_exception_set(edata);
        return NULL;
    }
                rv;
    PLyPlanObject *plan;
    volatile MemoryContext oldcontext;
+   volatile ResourceOwner oldowner;
    PyObject   *ret;
 
    if (list != NULL)
    }
 
    oldcontext = CurrentMemoryContext;
+   oldowner = CurrentResourceOwner;
+
+   BeginInternalSubTransaction(NULL);
+   /* Want to run inside function's memory context */
+   MemoryContextSwitchTo(oldcontext);
+
    PG_TRY();
    {
        char       *nulls;
 
        if (nargs > 0)
            pfree(nulls);
+
+       /* 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();
    {
        int         k;
        ErrorData   *edata;
 
+       /* Save error info */
        MemoryContextSwitchTo(oldcontext);
        edata = CopyErrorData();
        FlushErrorState();
            }
        }
 
-       if (!PyErr_Occurred())
-           PLy_exception_set(PLy_exc_spi_error,
-                             "unrecognized error in PLy_spi_execute_plan");
-       PLy_elog(WARNING, NULL);
+       /* 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();
+
+       /* Make Python raise the exception */
        PLy_spi_exception_set(edata);
        return NULL;
    }
        }
    }
 
+   if (rv < 0)
+   {
+       PLy_exception_set(PLy_exc_spi_error,
+                         "SPI_execute_plan failed: %s",
+                         SPI_result_code_string(rv));
+       return NULL;
+   }
+
    return ret;
 }
 
 {
    int         rv;
    volatile MemoryContext oldcontext;
+   volatile ResourceOwner oldowner;
    PyObject   *ret;
 
    oldcontext = CurrentMemoryContext;
+   oldowner = CurrentResourceOwner;
+
+   BeginInternalSubTransaction(NULL);
+   /* Want to run inside function's memory context */
+   MemoryContextSwitchTo(oldcontext);
+
    PG_TRY();
    {
        pg_verifymbstr(query, strlen(query), false);
        rv = SPI_execute(query, PLy_curr_procedure->fn_readonly, limit);
        ret = PLy_spi_execute_fetch_result(SPI_tuptable, SPI_processed, 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();
-       if (!PyErr_Occurred())
-           PLy_exception_set(PLy_exc_spi_error,
-                             "unrecognized error in PLy_spi_execute_query");
-       PLy_elog(WARNING, NULL);
+
+       /* 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();
+
+       /* Make Python raise the exception */
        PLy_spi_exception_set(edata);
        return NULL;
    }
 
    LANGUAGE plpythonu;
 
 SELECT valid_type('rick');
+
+/* manually starting subtransactions - a bad idea
+ */
+CREATE FUNCTION manual_subxact() RETURNS void AS $$
+plpy.execute("savepoint save")
+plpy.execute("create table foo(x integer)")
+plpy.execute("rollback to save")
+$$ LANGUAGE plpythonu;
+
+SELECT manual_subxact();
+
+/* same for prepared plans
+ */
+CREATE FUNCTION manual_subxact_prepared() RETURNS void AS $$
+save = plpy.prepare("savepoint save")
+rollback = plpy.prepare("rollback to save")
+plpy.execute(save)
+plpy.execute("create table foo(x integer)")
+plpy.execute(rollback)
+$$ LANGUAGE plpythonu;
+
+SELECT manual_subxact_prepared();