diff --git a/doc/src/sgml/plpython.sgml b/doc/src/sgml/plpython.sgml
index befd6afb15e..e05c2937b1f 100644
--- a/doc/src/sgml/plpython.sgml
+++ b/doc/src/sgml/plpython.sgml
@@ -858,6 +858,9 @@ $$ LANGUAGE plpythonu;
plpy.foo.
+
+ Database Access Functions
+
The plpy module provides two
functions called execute and
@@ -937,6 +940,33 @@ CREATE FUNCTION usesavedplan() RETURNS trigger AS $$
$$ LANGUAGE plpythonu;
+
+
+
+
+ Trapping Errors
+
+
+ Functions accessing the database might encounter errors, which
+ will cause them to abort and raise an exception. Both
+ plpy.execute and
+ plpy.prepare can raise an instance of
+ plpy.SPIError, which by default will terminate
+ the function. This error can be handled just like any other
+ Python exception, by using the try/except
+ construct. For example:
+
+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;
+
+
+
diff --git a/src/pl/plpython/expected/plpython_error.out b/src/pl/plpython/expected/plpython_error.out
index 2b6141c376e..7597ca73f12 100644
--- a/src/pl/plpython/expected/plpython_error.out
+++ b/src/pl/plpython/expected/plpython_error.out
@@ -32,8 +32,6 @@ CREATE FUNCTION sql_syntax_error() RETURNS text
'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
^
@@ -56,8 +54,6 @@ CREATE FUNCTION exception_index_invalid_nested() RETURNS text
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')
^
@@ -78,8 +74,6 @@ return None
'
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
@@ -101,8 +95,6 @@ return None
'
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
@@ -128,8 +120,6 @@ return None
'
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
@@ -150,3 +140,25 @@ SELECT valid_type('rick');
(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"
diff --git a/src/pl/plpython/expected/plpython_error_0.out b/src/pl/plpython/expected/plpython_error_0.out
index 3842d8fb4a1..42e41196306 100644
--- a/src/pl/plpython/expected/plpython_error_0.out
+++ b/src/pl/plpython/expected/plpython_error_0.out
@@ -32,8 +32,6 @@ CREATE FUNCTION sql_syntax_error() RETURNS text
'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
^
@@ -56,8 +54,6 @@ CREATE FUNCTION exception_index_invalid_nested() RETURNS text
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')
^
@@ -78,8 +74,6 @@ return None
'
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
@@ -101,8 +95,6 @@ return None
'
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
@@ -128,8 +120,6 @@ return None
'
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
@@ -150,3 +140,25 @@ SELECT valid_type('rick');
(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"
diff --git a/src/pl/plpython/plpython.c b/src/pl/plpython/plpython.c
index fbfeb2c9f16..fff7de76743 100644
--- a/src/pl/plpython/plpython.c
+++ b/src/pl/plpython/plpython.c
@@ -101,6 +101,7 @@ typedef int Py_ssize_t;
#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"
@@ -2856,6 +2857,7 @@ PLy_spi_prepare(PyObject *self, PyObject *args)
char *query;
void *tmpplan;
volatile MemoryContext oldcontext;
+ volatile ResourceOwner oldowner;
int nargs;
if (!PyArg_ParseTuple(args, "s|O", &query, &list))
@@ -2879,6 +2881,11 @@ PLy_spi_prepare(PyObject *self, PyObject *args)
plan->args = nargs ? PLy_malloc(sizeof(PLyTypeInfo) * nargs) : NULL;
oldcontext = CurrentMemoryContext;
+ oldowner = CurrentResourceOwner;
+
+ BeginInternalSubTransaction(NULL);
+ MemoryContextSwitchTo(oldcontext);
+
PG_TRY();
{
int i;
@@ -2958,20 +2965,42 @@ PLy_spi_prepare(PyObject *self, PyObject *args)
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;
}
@@ -3013,6 +3042,7 @@ PLy_spi_execute_plan(PyObject *ob, PyObject *list, long limit)
rv;
PLyPlanObject *plan;
volatile MemoryContext oldcontext;
+ volatile ResourceOwner oldowner;
PyObject *ret;
if (list != NULL)
@@ -3048,6 +3078,12 @@ PLy_spi_execute_plan(PyObject *ob, PyObject *list, long limit)
}
oldcontext = CurrentMemoryContext;
+ oldowner = CurrentResourceOwner;
+
+ BeginInternalSubTransaction(NULL);
+ /* Want to run inside function's memory context */
+ MemoryContextSwitchTo(oldcontext);
+
PG_TRY();
{
char *nulls;
@@ -3100,12 +3136,24 @@ PLy_spi_execute_plan(PyObject *ob, PyObject *list, long limit)
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();
@@ -3123,10 +3171,19 @@ PLy_spi_execute_plan(PyObject *ob, PyObject *list, long limit)
}
}
- 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;
}
@@ -3142,6 +3199,14 @@ PLy_spi_execute_plan(PyObject *ob, PyObject *list, long limit)
}
}
+ if (rv < 0)
+ {
+ PLy_exception_set(PLy_exc_spi_error,
+ "SPI_execute_plan failed: %s",
+ SPI_result_code_string(rv));
+ return NULL;
+ }
+
return ret;
}
@@ -3150,26 +3215,55 @@ PLy_spi_execute_query(char *query, long limit)
{
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;
}
diff --git a/src/pl/plpython/sql/plpython_error.sql b/src/pl/plpython/sql/plpython_error.sql
index 6509257b24d..7861cd61a2f 100644
--- a/src/pl/plpython/sql/plpython_error.sql
+++ b/src/pl/plpython/sql/plpython_error.sql
@@ -130,3 +130,25 @@ return None
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();