Here's a patch implementing a executing SPI in an subtransaction mentioned in http://archives.postgresql.org/pgsql-hackers/2010-12/msg01991.php. It's an incremental patch on top of the plpython-refactor patch sent eariler.
Git branch for this patch: https://github.com/wulczer/postgres/tree/spi-in-subxacts. Without it the error handling in PL/Python is really broken, as we jump between from a saught longjmp back into Python without any cleanup. As an additional bonus you no longer get all the ugly "unrecognized error in PLy_spi_execute_query" errors. Cheers, Jan
diff --git a/src/pl/plpython/expected/plpython_error.out b/src/pl/plpython/expected/plpython_error.out index 70890a8..7fc8337 100644 *** a/src/pl/plpython/expected/plpython_error.out --- b/src/pl/plpython/expected/plpython_error.out *************** CREATE FUNCTION sql_syntax_error() RETUR *** 8,15 **** '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" CONTEXT: PL/Python function "sql_syntax_error" /* check the handling of uncaught python exceptions --- 8,13 ---- *************** CREATE FUNCTION exception_index_invalid_ *** 29,36 **** 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 CONTEXT: PL/Python function "exception_index_invalid_nested" /* a typo --- 27,32 ---- *************** return None *** 47,54 **** ' 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 --- 43,48 ---- *************** return None *** 70,77 **** ' 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 --- 64,69 ---- *************** return None *** 97,104 **** ' 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 --- 89,94 ---- diff --git a/src/pl/plpython/plpython.c b/src/pl/plpython/plpython.c index 67eb0f3..5b216b2 100644 *** a/src/pl/plpython/plpython.c --- b/src/pl/plpython/plpython.c *************** typedef int Py_ssize_t; *** 101,106 **** --- 101,107 ---- #include "nodes/makefuncs.h" #include "parser/parse_type.h" #include "tcop/tcopprot.h" + #include "access/xact.h" #include "utils/builtins.h" #include "utils/lsyscache.h" #include "utils/memutils.h" *************** PLy_spi_prepare(PyObject *self, PyObject *** 2829,2834 **** --- 2830,2836 ---- int nargs; MemoryContext volatile oldcontext = CurrentMemoryContext; + ResourceOwner volatile oldowner = CurrentResourceOwner; if (!PyArg_ParseTuple(args, "s|O", &query, &list)) { *************** PLy_spi_prepare(PyObject *self, PyObject *** 2852,2857 **** --- 2854,2862 ---- plan->values = PLy_malloc(sizeof(Datum) * nargs); plan->args = PLy_malloc(sizeof(PLyTypeInfo) * nargs); + BeginInternalSubTransaction(NULL); + MemoryContextSwitchTo(oldcontext); + PG_TRY(); { int i; *************** PLy_spi_prepare(PyObject *self, PyObject *** 2931,2949 **** if (plan->plan == NULL) elog(ERROR, "SPI_saveplan failed: %s", SPI_result_code_string(SPI_result)); } PG_CATCH(); { ErrorData *edata; - MemoryContextSwitchTo(oldcontext); - edata = CopyErrorData(); 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); PLy_exception_set(PLy_exc_spi_error, edata->message); return NULL; } --- 2936,2978 ---- 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; Py_DECREF(plan); Py_XDECREF(optr); ! ! /* 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(); ! ! /* Make Python raise the exception */ PLy_exception_set(PLy_exc_spi_error, edata->message); return NULL; } *************** PLy_spi_execute_plan(PyObject *ob, PyObj *** 2985,2990 **** --- 3014,3020 ---- PLyPlanObject *plan; PyObject *ret; MemoryContext volatile oldcontext = CurrentMemoryContext; + ResourceOwner volatile oldowner = CurrentResourceOwner; if (list != NULL) { *************** PLy_spi_execute_plan(PyObject *ob, PyObj *** 3018,3023 **** --- 3048,3057 ---- return NULL; } + BeginInternalSubTransaction(NULL); + /* Want to run inside function's memory context */ + MemoryContextSwitchTo(oldcontext); + PG_TRY(); { char *nulls; *************** PLy_spi_execute_plan(PyObject *ob, PyObj *** 3071,3082 **** --- 3105,3126 ---- 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(); { ErrorData *edata; + /* Save error info */ MemoryContextSwitchTo(oldcontext); edata = CopyErrorData(); FlushErrorState(); *************** PLy_spi_execute_plan(PyObject *ob, PyObj *** 3094,3103 **** } } ! if (!PyErr_Occurred()) ! PLy_exception_set(PLy_exc_spi_error, ! "unrecognized error in PLy_spi_execute_plan"); ! PLy_elog(WARNING, NULL); PLy_exception_set(PLy_exc_spi_error, edata->message); return NULL; } --- 3138,3156 ---- } } ! /* 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_exception_set(PLy_exc_spi_error, edata->message); return NULL; } *************** PLy_spi_execute_query(char *query, long *** 3122,3158 **** int rv; PyObject *ret; MemoryContext volatile oldcontext = CurrentMemoryContext; 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); } PG_CATCH(); { ErrorData *edata; 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); PLy_exception_set(PLy_exc_spi_error, edata->message); return NULL; } PG_END_TRY(); - if (rv < 0) - { - PLy_exception_set(PLy_exc_spi_error, - "SPI_execute failed: %s", - SPI_result_code_string(rv)); - return NULL; - } - return ret; } --- 3175,3234 ---- int rv; PyObject *ret; MemoryContext volatile oldcontext = CurrentMemoryContext; + ResourceOwner volatile oldowner = CurrentResourceOwner; + + /* + * Execute the query inside a sub-transaction, so we can cope with errors + * sanely + */ + 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(); ! ! /* 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_exception_set(PLy_exc_spi_error, edata->message); return NULL; } PG_END_TRY(); return ret; }
-- Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers