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

Reply via email to