pl/python custom exceptions for SPI

Started by Jan Urbańskiabout 15 years ago18 messages
#1Jan Urbański
wulczer@wulczer.org
1 attachment(s)

Here's a patch implementing custom Python exceptions for SPI errors
mentioned in
http://archives.postgresql.org/pgsql-hackers/2010-12/msg01991.php. It's
an incremental patch on top of the explicit-subxacts patch sent eariler.

Git branch for this patch:
https://github.com/wulczer/postgres/tree/custom-spi-exceptions.

What the patch does is provide a Python exception per each error defined
in utils/errcodes.h, and then raise the corresponding exception when a
SPI call fails. The parsing of errcodes.h in the Makefile is a little
grotty and would probably have to be ported to the Windows build system,
which I have no idea about.

With this patch you can do:

from plpy import spiexceptions

try:
plpy.execute("insert into t values (4)")
catch spiexceptions.UniqueViolation:
plpy.notice("unique constraint violation")
catch spiexceptions.NotNullViolation:
plpy.notice("not null constraint violation")

All exceptions inherint from plpy.SPIError, so code thta just catches a
blanket SPIErorr will continue to work.

The patch lacks user-facing docs, if it gets accepted I'll write some.
Not sure if we should provide a table similar to
http://www.postgresql.org/docs/current/static/errcodes-appendix.html, or
just refer to that table and state that the rule is changing underscores
to camel case...

Also, I just realised that this patch does not really depend on the
explicit-subxacts change, but rather only on the spi-in-subxacts, so if
need be I can generate it as an incremental changeset ofer the latter
and not the former.

Cheers,
Jan

Attachments:

plpython-custom-spi-exceptions.difftext/x-patch; name=plpython-custom-spi-exceptions.diffDownload
diff --git a/src/pl/plpython/Makefile b/src/pl/plpython/Makefile
index 33dddc6..dd5b445 100644
*** a/src/pl/plpython/Makefile
--- b/src/pl/plpython/Makefile
*************** rpathdir = $(python_libdir)
*** 38,44 ****
  
  NAME = plpython$(python_majorversion)
  OBJS = plpython.o
! 
  
  # Python on win32 ships with import libraries only for Microsoft Visual C++,
  # which are not compatible with mingw gcc. Therefore we need to build a
--- 38,44 ----
  
  NAME = plpython$(python_majorversion)
  OBJS = plpython.o
! SPIEXCEPTIONS = spiexceptions.h
  
  # Python on win32 ships with import libraries only for Microsoft Visual C++,
  # which are not compatible with mingw gcc. Therefore we need to build a
*************** PSQLDIR = $(bindir)
*** 86,93 ****
  
  include $(top_srcdir)/src/Makefile.shlib
  
  
! all: all-lib
  
  install: all installdirs install-lib
  ifeq ($(python_majorversion),2)
--- 86,113 ----
  
  include $(top_srcdir)/src/Makefile.shlib
  
+ # A quite horrendous sed, but does the job. The steps are, in order:
+ # 1. Remove everything up to the line with Class 03. We only generate
+ #    exceptions for errors, not for warnings or notices
+ # 2. Remove lines that don't define an error code
+ # 3. Change ERRCODE_XXX into { "spiexceptions.ERRCODE_XY_Z, "XY_Z", ERRCODE_XY_Z },
+ # 4. Leave an uppercase letter after a dot or a quote, change the rest
+ #    into lowercase thus giving us
+ #    { "spiexceptions.Errcode_xy_z, "Xy_z", ERRCODE_XY_Z },
+ # 5. change lowercase letters after an underscore into uppercase, giving us
+ #    { "spiexceptions.ErrcodeXyZ, "XyZ", ERRCODE_XY_Z },
+ gen-spiexceptions:
+ 	echo "/* autogenerated from utils/errcodes.h, do not edit */" > $(SPIEXCEPTIONS)
+ 	sed -e '1,/Class 03/ d' \
+             -e '/^#define ERRCODE_.*MAKE_SQLSTATE/! d' \
+             -e 's|#define ERRCODE_\([^\t ]*\).*|{ "spiexceptions.\1", "\1", ERRCODE_\1 },|' \
+             -e 's|\(["\.]\)\([A-Z]\)\([^"]*\)|\1\2\L\3|g' \
+             -e 's|_\([a-z]\)|\u\1|g' \
+ 	$(top_srcdir)/src/include/utils/errcodes.h >> $(SPIEXCEPTIONS)
  
! .PHONY: gen-spiexceptions
! 
! all: gen-spiexceptions all-lib
  
  install: all installdirs install-lib
  ifeq ($(python_majorversion),2)
*************** clean distclean maintainer-clean: clean-
*** 138,143 ****
--- 158,164 ----
  	rm -f $(OBJS)
  	rm -rf results
  	rm -f regression.diffs regression.out
+ 	rm -f $(SPIEXCEPTIONS)
  ifeq ($(PORTNAME), win32)
  	rm -f python${pytverstr}.def
  endif
diff --git a/src/pl/plpython/expected/plpython_error.out b/src/pl/plpython/expected/plpython_error.out
index 7fc8337..718ebce 100644
*** a/src/pl/plpython/expected/plpython_error.out
--- b/src/pl/plpython/expected/plpython_error.out
*************** CREATE FUNCTION sql_syntax_error() RETUR
*** 8,14 ****
  'plpy.execute("syntax error")'
          LANGUAGE plpythonu;
  SELECT 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,14 ----
  'plpy.execute("syntax error")'
          LANGUAGE plpythonu;
  SELECT sql_syntax_error();
! ERROR:  spiexceptions.SyntaxError: syntax error at or near "syntax"
  CONTEXT:  PL/Python function "sql_syntax_error"
  /* check the handling of uncaught python exceptions
   */
*************** CREATE FUNCTION exception_index_invalid_
*** 27,33 ****
  return rv[0]'
  	LANGUAGE plpythonu;
  SELECT exception_index_invalid_nested();
! ERROR:  plpy.SPIError: function test5(unknown) does not exist
  CONTEXT:  PL/Python function "exception_index_invalid_nested"
  /* a typo
   */
--- 27,33 ----
  return rv[0]'
  	LANGUAGE plpythonu;
  SELECT exception_index_invalid_nested();
! ERROR:  spiexceptions.UndefinedFunction: function test5(unknown) does not exist
  CONTEXT:  PL/Python function "exception_index_invalid_nested"
  /* a typo
   */
*************** return None
*** 43,49 ****
  '
  	LANGUAGE plpythonu;
  SELECT invalid_type_uncaught('rick');
! 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
   * the typo, and return None
--- 43,49 ----
  '
  	LANGUAGE plpythonu;
  SELECT invalid_type_uncaught('rick');
! ERROR:  spiexceptions.UndefinedObject: type "test" does not exist
  CONTEXT:  PL/Python function "invalid_type_uncaught"
  /* for what it's worth catch the exception generated by
   * the typo, and return None
*************** SELECT valid_type('rick');
*** 109,111 ****
--- 109,149 ----
   
  (1 row)
  
+ /* Check catching specific types of exceptions
+  */
+ CREATE TABLE specific (
+     i integer PRIMARY KEY
+ );
+ NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index "specific_pkey" for table "specific"
+ CREATE FUNCTION specific_exception(i integer) RETURNS void AS
+ $$
+ from plpy import spiexceptions
+ try:
+     plpy.execute("insert into specific values (%s)" % (i or "NULL"));
+ except spiexceptions.NotNullViolation, e:
+     plpy.notice("Violated the NOT NULL constraint, sqlstate %s" % e.sqlstate)
+ except spiexceptions.UniqueViolation, e:
+     plpy.notice("Violated the UNIQUE constraint, sqlstate %s" % e.sqlstate)
+ $$ LANGUAGE plpythonu;
+ SELECT specific_exception(2);
+  specific_exception 
+ --------------------
+  
+ (1 row)
+ 
+ SELECT specific_exception(NULL);
+ NOTICE:  Violated the NOT NULL constraint, sqlstate 23502
+ CONTEXT:  PL/Python function "specific_exception"
+  specific_exception 
+ --------------------
+  
+ (1 row)
+ 
+ SELECT specific_exception(2);
+ NOTICE:  Violated the UNIQUE constraint, sqlstate 23505
+ CONTEXT:  PL/Python function "specific_exception"
+  specific_exception 
+ --------------------
+  
+ (1 row)
+ 
diff --git a/src/pl/plpython/expected/plpython_subxact.out b/src/pl/plpython/expected/plpython_subxact.out
index 25a5a4b..fd7e9d9 100644
*** a/src/pl/plpython/expected/plpython_subxact.out
--- b/src/pl/plpython/expected/plpython_subxact.out
*************** SELECT * FROM subxact_tbl;
*** 43,49 ****
  
  TRUNCATE subxact_tbl;
  SELECT subxact_test('SPI');
! ERROR:  plpy.SPIError: invalid input syntax for integer: "oops"
  CONTEXT:  PL/Python function "subxact_test"
  SELECT * FROM subxact_tbl;
   i 
--- 43,49 ----
  
  TRUNCATE subxact_tbl;
  SELECT subxact_test('SPI');
! ERROR:  spiexceptions.InvalidTextRepresentation: invalid input syntax for integer: "oops"
  CONTEXT:  PL/Python function "subxact_test"
  SELECT * FROM subxact_tbl;
   i 
*************** SELECT * FROM subxact_tbl;
*** 87,93 ****
  
  TRUNCATE subxact_tbl;
  SELECT subxact_ctx_test('SPI');
! ERROR:  plpy.SPIError: invalid input syntax for integer: "oops"
  CONTEXT:  PL/Python function "subxact_ctx_test"
  SELECT * FROM subxact_tbl;
   i 
--- 87,93 ----
  
  TRUNCATE subxact_tbl;
  SELECT subxact_ctx_test('SPI');
! ERROR:  spiexceptions.InvalidTextRepresentation: invalid input syntax for integer: "oops"
  CONTEXT:  PL/Python function "subxact_ctx_test"
  SELECT * FROM subxact_tbl;
   i 
*************** with plpy.subxact():
*** 122,128 ****
  return "ok"
  $$ LANGUAGE plpythonu;
  SELECT subxact_nested_test();
! ERROR:  plpy.SPIError: syntax error at or near "error"
  CONTEXT:  PL/Python function "subxact_nested_test"
  SELECT * FROM subxact_tbl;
   i 
--- 122,128 ----
  return "ok"
  $$ LANGUAGE plpythonu;
  SELECT subxact_nested_test();
! ERROR:  spiexceptions.SyntaxError: syntax error at or near "error"
  CONTEXT:  PL/Python function "subxact_nested_test"
  SELECT * FROM subxact_tbl;
   i 
*************** SELECT * FROM subxact_tbl;
*** 131,137 ****
  
  TRUNCATE subxact_tbl;
  SELECT subxact_nested_test('t');
! NOTICE:  Swallowed SPIError('syntax error at or near "error"',)
  CONTEXT:  PL/Python function "subxact_nested_test"
   subxact_nested_test 
  ---------------------
--- 131,137 ----
  
  TRUNCATE subxact_tbl;
  SELECT subxact_nested_test('t');
! NOTICE:  Swallowed SyntaxError('syntax error at or near "error"',)
  CONTEXT:  PL/Python function "subxact_nested_test"
   subxact_nested_test 
  ---------------------
*************** with plpy.subxact():
*** 157,163 ****
  return "ok"
  $$ LANGUAGE plpythonu;
  SELECT subxact_deeply_nested_test();
! NOTICE:  Swallowed SPIError('syntax error at or near "error"',)
  CONTEXT:  PL/Python function "subxact_nested_test"
  SQL statement "select subxact_nested_test('t')"
  PL/Python function "subxact_nested_test"
--- 157,163 ----
  return "ok"
  $$ LANGUAGE plpythonu;
  SELECT subxact_deeply_nested_test();
! NOTICE:  Swallowed SyntaxError('syntax error at or near "error"',)
  CONTEXT:  PL/Python function "subxact_nested_test"
  SQL statement "select subxact_nested_test('t')"
  PL/Python function "subxact_nested_test"
diff --git a/src/pl/plpython/expected/plpython_test.out b/src/pl/plpython/expected/plpython_test.out
index 674c739..ef2b284 100644
*** a/src/pl/plpython/expected/plpython_test.out
--- b/src/pl/plpython/expected/plpython_test.out
*************** contents.sort()
*** 43,51 ****
  return ", ".join(contents)
  $$ LANGUAGE plpythonu;
  select module_contents();
!                                           module_contents                                           
! ----------------------------------------------------------------------------------------------------
!  Error, Fatal, SPIError, debug, error, execute, fatal, info, log, notice, prepare, subxact, warning
  (1 row)
  
  CREATE FUNCTION elog_test() RETURNS void
--- 43,51 ----
  return ", ".join(contents)
  $$ LANGUAGE plpythonu;
  select module_contents();
!                                                   module_contents                                                  
! -------------------------------------------------------------------------------------------------------------------
!  Error, Fatal, SPIError, debug, error, execute, fatal, info, log, notice, prepare, spiexceptions, subxact, warning
  (1 row)
  
  CREATE FUNCTION elog_test() RETURNS void
diff --git a/src/pl/plpython/plpython.c b/src/pl/plpython/plpython.c
index bd5dc65..69d8d2c 100644
*** a/src/pl/plpython/plpython.c
--- b/src/pl/plpython/plpython.c
*************** typedef struct PLySubxactObject
*** 258,263 ****
--- 258,285 ----
  	bool	exited;
  } PLySubxactObject;
  
+ /* A list of all known exceptions, generated from utils/errcodes.h */
+ typedef struct ExceptionMap
+ {
+ 	char		*name;
+ 	char		*classname;
+ 	int			sqlstate;
+ } ExceptionMap;
+ 
+ static const ExceptionMap exception_map[] = {
+ #include "spiexceptions.h"
+ 	{NULL, NULL, 0}
+ };
+ 
+ /* A hashtable mapping sqlstates to exceptions, for speedy lookup */
+ static HTAB *PLy_spi_exceptions;
+ 
+ typedef struct PLyExceptionEntry
+ {
+ 	int			sqlstate;	/* hash key, must be first */
+ 	PyObject	*exc;		/* corresponding exception */
+ } PLyExceptionEntry;
+ 
  /* function declarations */
  
  #if PY_MAJOR_VERSION >= 3
*************** static PyMethodDef PLy_methods[] = {
*** 2765,2770 ****
--- 2787,2796 ----
  	{NULL, NULL, 0, NULL}
  };
  
+ static PyMethodDef PLy_exc_methods[] = {
+ 	{NULL, NULL, 0, NULL}
+ };
+ 
  #if PY_MAJOR_VERSION >= 3
  static PyModuleDef PLy_module = {
  	PyModuleDef_HEAD_INIT,		/* m_base */
*************** static PyModuleDef PLy_module = {
*** 2777,2782 ****
--- 2803,2820 ----
  	NULL,                       /* m_clear */
  	NULL                        /* m_free */
  };
+ 
+ static PyModuleDef PLy_exc_module = {
+ 	PyModuleDef_HEAD_INIT,		/* m_base */
+ 	"spiexceptions",			/* m_name */
+ 	NULL,						/* m_doc */
+ 	-1,							/* m_size */
+ 	PLy_exc_methods,			/* m_methods */
+ 	NULL,						/* m_reload */
+ 	NULL,						/* m_traverse */
+ 	NULL,						/* m_clear */
+ 	NULL						/* m_free */
+ };
  #endif
  
  /* plan object methods */
*************** PLy_spi_prepare(PyObject *self, PyObject
*** 3080,3086 ****
  	}
  	PG_CATCH();
  	{
! 		ErrorData	*edata;
  
  		Py_DECREF(plan);
  		Py_XDECREF(optr);
--- 3118,3126 ----
  	}
  	PG_CATCH();
  	{
! 		ErrorData				*edata;
! 		PLyExceptionEntry		*entry;
! 		PyObject				*exc;
  
  		Py_DECREF(plan);
  		Py_XDECREF(optr);
*************** PLy_spi_prepare(PyObject *self, PyObject
*** 3105,3112 ****
  			SPI_restore_connection();
  		}
  
  		/* Make Python raise the exception */
! 		PLy_exception_set(PLy_exc_spi_error, edata->message);
  		return NULL;
  	}
  	PG_END_TRY();
--- 3145,3158 ----
  			SPI_restore_connection();
  		}
  
+ 		/* Look up the correct exception */
+ 		entry = hash_search(PLy_spi_exceptions, &(edata->sqlerrcode),
+ 							HASH_FIND, NULL);
+ 		/* We really should find it, but just in case have a fallback */
+ 		Assert(entry != NULL);
+ 		exc = entry ? entry->exc : PLy_exc_spi_error;
  		/* Make Python raise the exception */
! 		PLy_exception_set(exc, edata->message);
  		return NULL;
  	}
  	PG_END_TRY();
*************** PLy_spi_execute_plan(PyObject *ob, PyObj
*** 3257,3263 ****
  	}
  	PG_CATCH();
  	{
! 		ErrorData	*edata;
  
  		/* Save error info */
  		MemoryContextSwitchTo(oldcontext);
--- 3303,3311 ----
  	}
  	PG_CATCH();
  	{
! 		ErrorData				*edata;
! 		PLyExceptionEntry		*entry;
! 		PyObject				*exc;
  
  		/* Save error info */
  		MemoryContextSwitchTo(oldcontext);
*************** PLy_spi_execute_plan(PyObject *ob, PyObj
*** 3292,3299 ****
  			SPI_restore_connection();
  		}
  
  		/* Make Python raise the exception */
! 		PLy_exception_set(PLy_exc_spi_error, edata->message);
  		return NULL;
  	}
  	PG_END_TRY();
--- 3340,3353 ----
  			SPI_restore_connection();
  		}
  
+ 		/* Look up the correct exception */
+ 		entry = hash_search(PLy_spi_exceptions, &(edata->sqlerrcode),
+ 							HASH_FIND, NULL);
+ 		/* We really should find it, but just in case have a fallback */
+ 		Assert(entry != NULL);
+ 		exc = entry ? entry->exc : PLy_exc_spi_error;
  		/* Make Python raise the exception */
! 		PLy_exception_set(exc, edata->message);
  		return NULL;
  	}
  	PG_END_TRY();
*************** PLy_spi_execute_query(char *query, long
*** 3352,3358 ****
  	}
  	PG_CATCH();
  	{
! 		ErrorData	*edata;
  
  		/* Save error info */
  		MemoryContextSwitchTo(oldcontext);
--- 3406,3414 ----
  	}
  	PG_CATCH();
  	{
! 		ErrorData				*edata;
! 		PLyExceptionEntry		*entry;
! 		PyObject				*exc;
  
  		/* Save error info */
  		MemoryContextSwitchTo(oldcontext);
*************** PLy_spi_execute_query(char *query, long
*** 3374,3381 ****
  			SPI_restore_connection();
  		}
  
  		/* Make Python raise the exception */
! 		PLy_exception_set(PLy_exc_spi_error, edata->message);
  		return NULL;
  	}
  	PG_END_TRY();
--- 3430,3443 ----
  			SPI_restore_connection();
  		}
  
+ 		/* Look up the correct exception */
+ 		entry = hash_search(PLy_spi_exceptions, &edata->sqlerrcode,
+ 							HASH_FIND, NULL);
+ 		/* We really should find it, but just in case have a fallback */
+ 		Assert(entry != NULL);
+ 		exc = entry ? entry->exc : PLy_exc_spi_error;
  		/* Make Python raise the exception */
! 		PLy_exception_set(exc, edata->message);
  		return NULL;
  	}
  	PG_END_TRY();
*************** static void PLy_add_exception(PyObject *
*** 3606,3625 ****
--- 3668,3724 ----
  #endif
  }
  
+ /* Add all the autogenerated exceptions as subclasses of SPIError */
+ static void
+ PLy_generate_spi_exceptions(PyObject *mod, PyObject *base)
+ {
+ 	int	i;
+ 
+ 	for (i = 0; exception_map[i].name != NULL; i++)
+ 	{
+ 		bool					 found;
+ 		PyObject				*exc;
+ 		PLyExceptionEntry		*entry;
+ 		PyObject				*sqlstate;
+ 		PyObject				*dict = PyDict_New();
+ 
+ 		sqlstate = PyString_FromString(unpack_sql_state(
+ 										   exception_map[i].sqlstate));
+ 		PyDict_SetItemString(dict, "sqlstate", sqlstate);
+ 		Py_DECREF(sqlstate);
+ 		exc = PyErr_NewException(exception_map[i].name, base, dict);
+ 		PLy_add_exception(mod, exception_map[i].classname, exc);
+ 		entry = hash_search(PLy_spi_exceptions, &exception_map[i].sqlstate,
+ 							HASH_ENTER, &found);
+ 		entry->exc = exc;
+ 		Assert(!found);
+ 	}
+ }
+ 
  /* Add exceptions to the plpy module */
  static void
  PLy_initialize_exceptions(PyObject *plpy)
  {
  	PyObject	*mod;
+ 	PyObject	*exc;
+ 	PyObject	*excmod;
+ 	HASHCTL		 hash_ctl;
  
  #if PY_MAJOR_VERSION < 3
  	/* For Python <3 we add the exceptions to the module dictionary */
  	mod = PyModule_GetDict(plpy);
+ 	excmod = Py_InitModule("spiexceptions", PLy_exc_methods);
+ 	exc = PyModule_GetDict(excmod);
  #else
  	/* In Python 3 you add them directly into the module */
  	mod = plpy;
+ 	excmod = PyModule_Create(&PLy_exc_module);
+ 	exc = excmod;
  #endif
  
+ 	if (PyModule_AddObject(plpy, "spiexceptions", excmod) < 0)
+ 		PLy_elog(ERROR, "Failed to add the spiexceptions module");
+ 
  	PLy_exc_error = PyErr_NewException("plpy.Error", NULL, NULL);
  	PLy_exc_fatal = PyErr_NewException("plpy.Fatal", NULL, NULL);
  	PLy_exc_spi_error = PyErr_NewException("plpy.SPIError", NULL, NULL);
*************** PLy_initialize_exceptions(PyObject *plpy
*** 3627,3632 ****
--- 3726,3740 ----
  	PLy_add_exception(mod, "Error", PLy_exc_error);
  	PLy_add_exception(mod, "Fatal", PLy_exc_fatal);
  	PLy_add_exception(mod, "SPIError", PLy_exc_spi_error);
+ 
+ 	memset(&hash_ctl, 0, sizeof(hash_ctl));
+ 	hash_ctl.keysize = sizeof(int);
+ 	hash_ctl.entrysize = sizeof(PLyExceptionEntry);
+ 	hash_ctl.hash = tag_hash;
+ 	PLy_spi_exceptions = hash_create("SPI exceptions", 256,
+ 									 &hash_ctl, HASH_ELEM | HASH_FUNCTION);
+ 
+ 	PLy_generate_spi_exceptions(exc, PLy_exc_spi_error);
  }
  
  /*
diff --git a/src/pl/plpython/sql/plpython_error.sql b/src/pl/plpython/sql/plpython_error.sql
index 5ca6849..2306c1d 100644
*** a/src/pl/plpython/sql/plpython_error.sql
--- b/src/pl/plpython/sql/plpython_error.sql
*************** return None
*** 107,109 ****
--- 107,130 ----
  	LANGUAGE plpythonu;
  
  SELECT valid_type('rick');
+ 
+ /* Check catching specific types of exceptions
+  */
+ CREATE TABLE specific (
+     i integer PRIMARY KEY
+ );
+ 
+ CREATE FUNCTION specific_exception(i integer) RETURNS void AS
+ $$
+ from plpy import spiexceptions
+ try:
+     plpy.execute("insert into specific values (%s)" % (i or "NULL"));
+ except spiexceptions.NotNullViolation, e:
+     plpy.notice("Violated the NOT NULL constraint, sqlstate %s" % e.sqlstate)
+ except spiexceptions.UniqueViolation, e:
+     plpy.notice("Violated the UNIQUE constraint, sqlstate %s" % e.sqlstate)
+ $$ LANGUAGE plpythonu;
+ 
+ SELECT specific_exception(2);
+ SELECT specific_exception(NULL);
+ SELECT specific_exception(2);
#2Jan Urbański
wulczer@wulczer.org
In reply to: Jan Urbański (#1)
1 attachment(s)
Re: pl/python custom exceptions for SPI

On 23/12/10 15:40, Jan Urbański wrote:

Here's a patch implementing custom Python exceptions for SPI errors
mentioned in
http://archives.postgresql.org/pgsql-hackers/2010-12/msg01991.php. It's
an incremental patch on top of the explicit-subxacts patch sent eariler.

I changed that patch to use Perl instead of sed to generate the
exceptions, which should be a more portable. It's still not nice, and I
think the way forward is to have a common format for SQLSTATE
conditions, as proposed in
http://archives.postgresql.org/message-id/4D19C93C.5000703@wulczer.org.

I failed to follow on with that patch because I couldn't figure out how
to persuade the buildsystem to generate errcodes.h early enough for the
rest of the system to compile, not to mention doing it for the MSVC
build system.

Cheers,
Jan

Attachments:

plpython-custom-spi-exceptions.difftext/x-patch; name=plpython-custom-spi-exceptions.diffDownload
diff --git a/src/pl/plpython/Makefile b/src/pl/plpython/Makefile
index 33dddc6..0d7ddee 100644
*** a/src/pl/plpython/Makefile
--- b/src/pl/plpython/Makefile
*************** rpathdir = $(python_libdir)
*** 38,44 ****
  
  NAME = plpython$(python_majorversion)
  OBJS = plpython.o
! 
  
  # Python on win32 ships with import libraries only for Microsoft Visual C++,
  # which are not compatible with mingw gcc. Therefore we need to build a
--- 38,44 ----
  
  NAME = plpython$(python_majorversion)
  OBJS = plpython.o
! SPIEXCEPTIONS = spiexceptions.h
  
  # Python on win32 ships with import libraries only for Microsoft Visual C++,
  # which are not compatible with mingw gcc. Therefore we need to build a
*************** PSQLDIR = $(bindir)
*** 86,93 ****
  
  include $(top_srcdir)/src/Makefile.shlib
  
  
! all: all-lib
  
  install: all installdirs install-lib
  ifeq ($(python_majorversion),2)
--- 86,102 ----
  
  include $(top_srcdir)/src/Makefile.shlib
  
+ .PHONY: gen-spiexceptions
  
! # Generate spiexceptions.h from utils/errcodes.h
! spiexceptions.h: $(top_srcdir)/src/include/utils/errcodes.h
! 	$(PERL) $(srcdir)/generate-spiexceptions.pl $^ > $(SPIEXCEPTIONS)
! 
! gen-spiexceptions: $(SPIEXCEPTIONS)
! 
! all: gen-spiexceptions all-lib
! 
! distprep: gen-spiexceptions
  
  install: all installdirs install-lib
  ifeq ($(python_majorversion),2)
*************** clean distclean maintainer-clean: clean-
*** 138,143 ****
--- 147,153 ----
  	rm -f $(OBJS)
  	rm -rf results
  	rm -f regression.diffs regression.out
+ 	rm -f $(SPIEXCEPTIONS)
  ifeq ($(PORTNAME), win32)
  	rm -f python${pytverstr}.def
  endif
diff --git a/src/pl/plpython/expected/plpython_error.out b/src/pl/plpython/expected/plpython_error.out
index 7fc8337..718ebce 100644
*** a/src/pl/plpython/expected/plpython_error.out
--- b/src/pl/plpython/expected/plpython_error.out
*************** CREATE FUNCTION sql_syntax_error() RETUR
*** 8,14 ****
  'plpy.execute("syntax error")'
          LANGUAGE plpythonu;
  SELECT 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,14 ----
  'plpy.execute("syntax error")'
          LANGUAGE plpythonu;
  SELECT sql_syntax_error();
! ERROR:  spiexceptions.SyntaxError: syntax error at or near "syntax"
  CONTEXT:  PL/Python function "sql_syntax_error"
  /* check the handling of uncaught python exceptions
   */
*************** CREATE FUNCTION exception_index_invalid_
*** 27,33 ****
  return rv[0]'
  	LANGUAGE plpythonu;
  SELECT exception_index_invalid_nested();
! ERROR:  plpy.SPIError: function test5(unknown) does not exist
  CONTEXT:  PL/Python function "exception_index_invalid_nested"
  /* a typo
   */
--- 27,33 ----
  return rv[0]'
  	LANGUAGE plpythonu;
  SELECT exception_index_invalid_nested();
! ERROR:  spiexceptions.UndefinedFunction: function test5(unknown) does not exist
  CONTEXT:  PL/Python function "exception_index_invalid_nested"
  /* a typo
   */
*************** return None
*** 43,49 ****
  '
  	LANGUAGE plpythonu;
  SELECT invalid_type_uncaught('rick');
! 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
   * the typo, and return None
--- 43,49 ----
  '
  	LANGUAGE plpythonu;
  SELECT invalid_type_uncaught('rick');
! ERROR:  spiexceptions.UndefinedObject: type "test" does not exist
  CONTEXT:  PL/Python function "invalid_type_uncaught"
  /* for what it's worth catch the exception generated by
   * the typo, and return None
*************** SELECT valid_type('rick');
*** 109,111 ****
--- 109,149 ----
   
  (1 row)
  
+ /* Check catching specific types of exceptions
+  */
+ CREATE TABLE specific (
+     i integer PRIMARY KEY
+ );
+ NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index "specific_pkey" for table "specific"
+ CREATE FUNCTION specific_exception(i integer) RETURNS void AS
+ $$
+ from plpy import spiexceptions
+ try:
+     plpy.execute("insert into specific values (%s)" % (i or "NULL"));
+ except spiexceptions.NotNullViolation, e:
+     plpy.notice("Violated the NOT NULL constraint, sqlstate %s" % e.sqlstate)
+ except spiexceptions.UniqueViolation, e:
+     plpy.notice("Violated the UNIQUE constraint, sqlstate %s" % e.sqlstate)
+ $$ LANGUAGE plpythonu;
+ SELECT specific_exception(2);
+  specific_exception 
+ --------------------
+  
+ (1 row)
+ 
+ SELECT specific_exception(NULL);
+ NOTICE:  Violated the NOT NULL constraint, sqlstate 23502
+ CONTEXT:  PL/Python function "specific_exception"
+  specific_exception 
+ --------------------
+  
+ (1 row)
+ 
+ SELECT specific_exception(2);
+ NOTICE:  Violated the UNIQUE constraint, sqlstate 23505
+ CONTEXT:  PL/Python function "specific_exception"
+  specific_exception 
+ --------------------
+  
+ (1 row)
+ 
diff --git a/src/pl/plpython/expected/plpython_subxact.out b/src/pl/plpython/expected/plpython_subxact.out
index 25a5a4b..fd7e9d9 100644
*** a/src/pl/plpython/expected/plpython_subxact.out
--- b/src/pl/plpython/expected/plpython_subxact.out
*************** SELECT * FROM subxact_tbl;
*** 43,49 ****
  
  TRUNCATE subxact_tbl;
  SELECT subxact_test('SPI');
! ERROR:  plpy.SPIError: invalid input syntax for integer: "oops"
  CONTEXT:  PL/Python function "subxact_test"
  SELECT * FROM subxact_tbl;
   i 
--- 43,49 ----
  
  TRUNCATE subxact_tbl;
  SELECT subxact_test('SPI');
! ERROR:  spiexceptions.InvalidTextRepresentation: invalid input syntax for integer: "oops"
  CONTEXT:  PL/Python function "subxact_test"
  SELECT * FROM subxact_tbl;
   i 
*************** SELECT * FROM subxact_tbl;
*** 87,93 ****
  
  TRUNCATE subxact_tbl;
  SELECT subxact_ctx_test('SPI');
! ERROR:  plpy.SPIError: invalid input syntax for integer: "oops"
  CONTEXT:  PL/Python function "subxact_ctx_test"
  SELECT * FROM subxact_tbl;
   i 
--- 87,93 ----
  
  TRUNCATE subxact_tbl;
  SELECT subxact_ctx_test('SPI');
! ERROR:  spiexceptions.InvalidTextRepresentation: invalid input syntax for integer: "oops"
  CONTEXT:  PL/Python function "subxact_ctx_test"
  SELECT * FROM subxact_tbl;
   i 
*************** with plpy.subxact():
*** 122,128 ****
  return "ok"
  $$ LANGUAGE plpythonu;
  SELECT subxact_nested_test();
! ERROR:  plpy.SPIError: syntax error at or near "error"
  CONTEXT:  PL/Python function "subxact_nested_test"
  SELECT * FROM subxact_tbl;
   i 
--- 122,128 ----
  return "ok"
  $$ LANGUAGE plpythonu;
  SELECT subxact_nested_test();
! ERROR:  spiexceptions.SyntaxError: syntax error at or near "error"
  CONTEXT:  PL/Python function "subxact_nested_test"
  SELECT * FROM subxact_tbl;
   i 
*************** SELECT * FROM subxact_tbl;
*** 131,137 ****
  
  TRUNCATE subxact_tbl;
  SELECT subxact_nested_test('t');
! NOTICE:  Swallowed SPIError('syntax error at or near "error"',)
  CONTEXT:  PL/Python function "subxact_nested_test"
   subxact_nested_test 
  ---------------------
--- 131,137 ----
  
  TRUNCATE subxact_tbl;
  SELECT subxact_nested_test('t');
! NOTICE:  Swallowed SyntaxError('syntax error at or near "error"',)
  CONTEXT:  PL/Python function "subxact_nested_test"
   subxact_nested_test 
  ---------------------
*************** with plpy.subxact():
*** 157,163 ****
  return "ok"
  $$ LANGUAGE plpythonu;
  SELECT subxact_deeply_nested_test();
! NOTICE:  Swallowed SPIError('syntax error at or near "error"',)
  CONTEXT:  PL/Python function "subxact_nested_test"
  SQL statement "select subxact_nested_test('t')"
  PL/Python function "subxact_nested_test"
--- 157,163 ----
  return "ok"
  $$ LANGUAGE plpythonu;
  SELECT subxact_deeply_nested_test();
! NOTICE:  Swallowed SyntaxError('syntax error at or near "error"',)
  CONTEXT:  PL/Python function "subxact_nested_test"
  SQL statement "select subxact_nested_test('t')"
  PL/Python function "subxact_nested_test"
diff --git a/src/pl/plpython/expected/plpython_test.out b/src/pl/plpython/expected/plpython_test.out
index 674c739..ef2b284 100644
*** a/src/pl/plpython/expected/plpython_test.out
--- b/src/pl/plpython/expected/plpython_test.out
*************** contents.sort()
*** 43,51 ****
  return ", ".join(contents)
  $$ LANGUAGE plpythonu;
  select module_contents();
!                                           module_contents                                           
! ----------------------------------------------------------------------------------------------------
!  Error, Fatal, SPIError, debug, error, execute, fatal, info, log, notice, prepare, subxact, warning
  (1 row)
  
  CREATE FUNCTION elog_test() RETURNS void
--- 43,51 ----
  return ", ".join(contents)
  $$ LANGUAGE plpythonu;
  select module_contents();
!                                                   module_contents                                                  
! -------------------------------------------------------------------------------------------------------------------
!  Error, Fatal, SPIError, debug, error, execute, fatal, info, log, notice, prepare, spiexceptions, subxact, warning
  (1 row)
  
  CREATE FUNCTION elog_test() RETURNS void
diff --git a/src/pl/plpython/generate-spiexceptions.pl b/src/pl/plpython/generate-spiexceptions.pl
index ...d70f439 .
*** a/src/pl/plpython/generate-spiexceptions.pl
--- b/src/pl/plpython/generate-spiexceptions.pl
***************
*** 0 ****
--- 1,37 ----
+ #!/usr/bin/perl
+ #
+ # Generate the spiexceptions.h file from src/include/utils/errcodes.h
+ # The generated header file is used by PL/Python to create a Python
+ # exception object per SPI error code
+ 
+ use warnings;
+ use strict;
+ 
+ print "/* autogenerated from utils/errcodes.h, do not edit */\n";
+ 
+ # Are we processing errors, or still looking at success and warning codes
+ my $in_errors = 0;
+ 
+ open my $errcodes, $ARGV[0] or die;
+ 
+ while (<$errcodes>) {
+     chomp;
+ 
+     # Class 03 is the first one that includes errors
+     /Class 03/ && ($in_errors = 1);
+ 
+     next unless $in_errors;
+     next unless /^#define ERRCODE_.*MAKE_SQLSTATE/;
+ 
+     # Get the exception name
+     (my $exception) = /^#define ERRCODE_(\S*)/;
+ 
+     # Change SOME_ERROR_CONDITION to SomeErrorCondition
+     my $pyexception = $exception;
+     $pyexception =~ s/([A-Z])([^_]*)(?:_|$)/$1\L$2/g;
+ 
+     print "{ \"spiexceptions.$pyexception\", " .
+ 	"\"$pyexception\", ERRCODE_$exception },\n";
+ }
+ 
+ close $errcodes;
diff --git a/src/pl/plpython/plpython.c b/src/pl/plpython/plpython.c
index 7506b54..468aedd 100644
*** a/src/pl/plpython/plpython.c
--- b/src/pl/plpython/plpython.c
*************** typedef struct PLySubxactObject
*** 258,263 ****
--- 258,285 ----
  	bool	exited;
  } PLySubxactObject;
  
+ /* A list of all known exceptions, generated from utils/errcodes.h */
+ typedef struct ExceptionMap
+ {
+ 	char		*name;
+ 	char		*classname;
+ 	int			sqlstate;
+ } ExceptionMap;
+ 
+ static const ExceptionMap exception_map[] = {
+ #include "spiexceptions.h"
+ 	{NULL, NULL, 0}
+ };
+ 
+ /* A hashtable mapping sqlstates to exceptions, for speedy lookup */
+ static HTAB *PLy_spi_exceptions;
+ 
+ typedef struct PLyExceptionEntry
+ {
+ 	int			sqlstate;	/* hash key, must be first */
+ 	PyObject	*exc;		/* corresponding exception */
+ } PLyExceptionEntry;
+ 
  /* function declarations */
  
  #if PY_MAJOR_VERSION >= 3
*************** static PyMethodDef PLy_methods[] = {
*** 2765,2770 ****
--- 2787,2796 ----
  	{NULL, NULL, 0, NULL}
  };
  
+ static PyMethodDef PLy_exc_methods[] = {
+ 	{NULL, NULL, 0, NULL}
+ };
+ 
  #if PY_MAJOR_VERSION >= 3
  static PyModuleDef PLy_module = {
  	PyModuleDef_HEAD_INIT,		/* m_base */
*************** static PyModuleDef PLy_module = {
*** 2777,2782 ****
--- 2803,2820 ----
  	NULL,                       /* m_clear */
  	NULL                        /* m_free */
  };
+ 
+ static PyModuleDef PLy_exc_module = {
+ 	PyModuleDef_HEAD_INIT,		/* m_base */
+ 	"spiexceptions",			/* m_name */
+ 	NULL,						/* m_doc */
+ 	-1,							/* m_size */
+ 	PLy_exc_methods,			/* m_methods */
+ 	NULL,						/* m_reload */
+ 	NULL,						/* m_traverse */
+ 	NULL,						/* m_clear */
+ 	NULL						/* m_free */
+ };
  #endif
  
  /* plan object methods */
*************** PLy_spi_prepare(PyObject *self, PyObject
*** 3080,3086 ****
  	}
  	PG_CATCH();
  	{
! 		ErrorData	*edata;
  
  		Py_DECREF(plan);
  		Py_XDECREF(optr);
--- 3118,3126 ----
  	}
  	PG_CATCH();
  	{
! 		ErrorData				*edata;
! 		PLyExceptionEntry		*entry;
! 		PyObject				*exc;
  
  		Py_DECREF(plan);
  		Py_XDECREF(optr);
*************** PLy_spi_prepare(PyObject *self, PyObject
*** 3105,3112 ****
  			SPI_restore_connection();
  		}
  
  		/* Make Python raise the exception */
! 		PLy_exception_set(PLy_exc_spi_error, edata->message);
  		return NULL;
  	}
  	PG_END_TRY();
--- 3145,3158 ----
  			SPI_restore_connection();
  		}
  
+ 		/* Look up the correct exception */
+ 		entry = hash_search(PLy_spi_exceptions, &(edata->sqlerrcode),
+ 							HASH_FIND, NULL);
+ 		/* We really should find it, but just in case have a fallback */
+ 		Assert(entry != NULL);
+ 		exc = entry ? entry->exc : PLy_exc_spi_error;
  		/* Make Python raise the exception */
! 		PLy_exception_set(exc, edata->message);
  		return NULL;
  	}
  	PG_END_TRY();
*************** PLy_spi_execute_plan(PyObject *ob, PyObj
*** 3257,3263 ****
  	}
  	PG_CATCH();
  	{
! 		ErrorData	*edata;
  
  		/* Save error info */
  		MemoryContextSwitchTo(oldcontext);
--- 3303,3311 ----
  	}
  	PG_CATCH();
  	{
! 		ErrorData				*edata;
! 		PLyExceptionEntry		*entry;
! 		PyObject				*exc;
  
  		/* Save error info */
  		MemoryContextSwitchTo(oldcontext);
*************** PLy_spi_execute_plan(PyObject *ob, PyObj
*** 3292,3299 ****
  			SPI_restore_connection();
  		}
  
  		/* Make Python raise the exception */
! 		PLy_exception_set(PLy_exc_spi_error, edata->message);
  		return NULL;
  	}
  	PG_END_TRY();
--- 3340,3353 ----
  			SPI_restore_connection();
  		}
  
+ 		/* Look up the correct exception */
+ 		entry = hash_search(PLy_spi_exceptions, &(edata->sqlerrcode),
+ 							HASH_FIND, NULL);
+ 		/* We really should find it, but just in case have a fallback */
+ 		Assert(entry != NULL);
+ 		exc = entry ? entry->exc : PLy_exc_spi_error;
  		/* Make Python raise the exception */
! 		PLy_exception_set(exc, edata->message);
  		return NULL;
  	}
  	PG_END_TRY();
*************** PLy_spi_execute_query(char *query, long
*** 3352,3358 ****
  	}
  	PG_CATCH();
  	{
! 		ErrorData	*edata;
  
  		/* Save error info */
  		MemoryContextSwitchTo(oldcontext);
--- 3406,3414 ----
  	}
  	PG_CATCH();
  	{
! 		ErrorData				*edata;
! 		PLyExceptionEntry		*entry;
! 		PyObject				*exc;
  
  		/* Save error info */
  		MemoryContextSwitchTo(oldcontext);
*************** PLy_spi_execute_query(char *query, long
*** 3374,3381 ****
  			SPI_restore_connection();
  		}
  
  		/* Make Python raise the exception */
! 		PLy_exception_set(PLy_exc_spi_error, edata->message);
  		return NULL;
  	}
  	PG_END_TRY();
--- 3430,3443 ----
  			SPI_restore_connection();
  		}
  
+ 		/* Look up the correct exception */
+ 		entry = hash_search(PLy_spi_exceptions, &edata->sqlerrcode,
+ 							HASH_FIND, NULL);
+ 		/* We really should find it, but just in case have a fallback */
+ 		Assert(entry != NULL);
+ 		exc = entry ? entry->exc : PLy_exc_spi_error;
  		/* Make Python raise the exception */
! 		PLy_exception_set(exc, edata->message);
  		return NULL;
  	}
  	PG_END_TRY();
*************** static void PLy_add_exception(PyObject *
*** 3606,3625 ****
--- 3668,3724 ----
  #endif
  }
  
+ /* Add all the autogenerated exceptions as subclasses of SPIError */
+ static void
+ PLy_generate_spi_exceptions(PyObject *mod, PyObject *base)
+ {
+ 	int	i;
+ 
+ 	for (i = 0; exception_map[i].name != NULL; i++)
+ 	{
+ 		bool					 found;
+ 		PyObject				*exc;
+ 		PLyExceptionEntry		*entry;
+ 		PyObject				*sqlstate;
+ 		PyObject				*dict = PyDict_New();
+ 
+ 		sqlstate = PyString_FromString(unpack_sql_state(
+ 										   exception_map[i].sqlstate));
+ 		PyDict_SetItemString(dict, "sqlstate", sqlstate);
+ 		Py_DECREF(sqlstate);
+ 		exc = PyErr_NewException(exception_map[i].name, base, dict);
+ 		PLy_add_exception(mod, exception_map[i].classname, exc);
+ 		entry = hash_search(PLy_spi_exceptions, &exception_map[i].sqlstate,
+ 							HASH_ENTER, &found);
+ 		entry->exc = exc;
+ 		Assert(!found);
+ 	}
+ }
+ 
  /* Add exceptions to the plpy module */
  static void
  PLy_initialize_exceptions(PyObject *plpy)
  {
  	PyObject	*mod;
+ 	PyObject	*exc;
+ 	PyObject	*excmod;
+ 	HASHCTL		 hash_ctl;
  
  #if PY_MAJOR_VERSION < 3
  	/* For Python <3 we add the exceptions to the module dictionary */
  	mod = PyModule_GetDict(plpy);
+ 	excmod = Py_InitModule("spiexceptions", PLy_exc_methods);
+ 	exc = PyModule_GetDict(excmod);
  #else
  	/* In Python 3 you add them directly into the module */
  	mod = plpy;
+ 	excmod = PyModule_Create(&PLy_exc_module);
+ 	exc = excmod;
  #endif
  
+ 	if (PyModule_AddObject(plpy, "spiexceptions", excmod) < 0)
+ 		PLy_elog(ERROR, "Failed to add the spiexceptions module");
+ 
  	PLy_exc_error = PyErr_NewException("plpy.Error", NULL, NULL);
  	PLy_exc_fatal = PyErr_NewException("plpy.Fatal", NULL, NULL);
  	PLy_exc_spi_error = PyErr_NewException("plpy.SPIError", NULL, NULL);
*************** PLy_initialize_exceptions(PyObject *plpy
*** 3627,3632 ****
--- 3726,3740 ----
  	PLy_add_exception(mod, "Error", PLy_exc_error);
  	PLy_add_exception(mod, "Fatal", PLy_exc_fatal);
  	PLy_add_exception(mod, "SPIError", PLy_exc_spi_error);
+ 
+ 	memset(&hash_ctl, 0, sizeof(hash_ctl));
+ 	hash_ctl.keysize = sizeof(int);
+ 	hash_ctl.entrysize = sizeof(PLyExceptionEntry);
+ 	hash_ctl.hash = tag_hash;
+ 	PLy_spi_exceptions = hash_create("SPI exceptions", 256,
+ 									 &hash_ctl, HASH_ELEM | HASH_FUNCTION);
+ 
+ 	PLy_generate_spi_exceptions(exc, PLy_exc_spi_error);
  }
  
  /*
diff --git a/src/pl/plpython/sql/plpython_error.sql b/src/pl/plpython/sql/plpython_error.sql
index 5ca6849..2306c1d 100644
*** a/src/pl/plpython/sql/plpython_error.sql
--- b/src/pl/plpython/sql/plpython_error.sql
*************** return None
*** 107,109 ****
--- 107,130 ----
  	LANGUAGE plpythonu;
  
  SELECT valid_type('rick');
+ 
+ /* Check catching specific types of exceptions
+  */
+ CREATE TABLE specific (
+     i integer PRIMARY KEY
+ );
+ 
+ CREATE FUNCTION specific_exception(i integer) RETURNS void AS
+ $$
+ from plpy import spiexceptions
+ try:
+     plpy.execute("insert into specific values (%s)" % (i or "NULL"));
+ except spiexceptions.NotNullViolation, e:
+     plpy.notice("Violated the NOT NULL constraint, sqlstate %s" % e.sqlstate)
+ except spiexceptions.UniqueViolation, e:
+     plpy.notice("Violated the UNIQUE constraint, sqlstate %s" % e.sqlstate)
+ $$ LANGUAGE plpythonu;
+ 
+ SELECT specific_exception(2);
+ SELECT specific_exception(NULL);
+ SELECT specific_exception(2);
#3Hannu Krosing
hannu@2ndquadrant.com
In reply to: Jan Urbański (#2)
Re: pl/python custom exceptions for SPI

On 10.1.2011 17:20, Jan Urbański wrote:

On 23/12/10 15:40, Jan Urbański wrote:

Here's a patch implementing custom Python exceptions for SPI errors
mentioned in
http://archives.postgresql.org/pgsql-hackers/2010-12/msg01991.php. It's
an incremental patch on top of the explicit-subxacts patch sent eariler.

I changed that patch to use Perl instead of sed to generate the
exceptions, which should be a more portable.

Why not python ?

It's still not nice, and I
think the way forward is to have a common format for SQLSTATE
conditions, as proposed in
http://archives.postgresql.org/message-id/4D19C93C.5000703@wulczer.org.

I failed to follow on with that patch because I couldn't figure out how
to persuade the buildsystem to generate errcodes.h early enough for the
rest of the system to compile, not to mention doing it for the MSVC
build system.

Cheers,
Jan

--
--------------------------------------------
Hannu Krosing
Senior Consultant,
Infinite Scalability& Performance
http://www.2ndQuadrant.com/books/

#4Tom Lane
tgl@sss.pgh.pa.us
In reply to: Hannu Krosing (#3)
Re: pl/python custom exceptions for SPI

Hannu Krosing <hannu@2ndquadrant.com> writes:

On 10.1.2011 17:20, Jan Urbański wrote:

I changed that patch to use Perl instead of sed to generate the
exceptions, which should be a more portable.

Why not python ?

Because we're not adding even more different tool requirements to the
build process. Perl is what we've chosen to depend on, and there is no
reason to use a different tool here.

regards, tom lane

#5Jan Urbański
wulczer@wulczer.org
In reply to: Tom Lane (#4)
1 attachment(s)
Re: pl/python custom exceptions for SPI

On 11/01/11 01:27, Tom Lane wrote:

Hannu Krosing <hannu@2ndquadrant.com> writes:

On 10.1.2011 17:20, Jan Urbański wrote:

I changed that patch to use Perl instead of sed to generate the
exceptions, which should be a more portable.

Why not python ?

Because we're not adding even more different tool requirements to the
build process. Perl is what we've chosen to depend on, and there is no
reason to use a different tool here.

Yep, exactly.

While looking at it again I fixed a problem with spiexceptions.h not
being distributed (and IIRC we don't require Perl for tarball builds)
and added generating it to the MSVC build system.

Updated patch attached.

Cheers,
Jan

Attachments:

plpython-custom-spi-exceptions.difftext/x-patch; name=plpython-custom-spi-exceptions.diffDownload
diff --git a/src/pl/plpython/Makefile b/src/pl/plpython/Makefile
index 33dddc6..1fe386b 100644
*** a/src/pl/plpython/Makefile
--- b/src/pl/plpython/Makefile
*************** rpathdir = $(python_libdir)
*** 38,44 ****
  
  NAME = plpython$(python_majorversion)
  OBJS = plpython.o
! 
  
  # Python on win32 ships with import libraries only for Microsoft Visual C++,
  # which are not compatible with mingw gcc. Therefore we need to build a
--- 38,44 ----
  
  NAME = plpython$(python_majorversion)
  OBJS = plpython.o
! SPIEXCEPTIONS = spiexceptions.h
  
  # Python on win32 ships with import libraries only for Microsoft Visual C++,
  # which are not compatible with mingw gcc. Therefore we need to build a
*************** PSQLDIR = $(bindir)
*** 86,93 ****
  
  include $(top_srcdir)/src/Makefile.shlib
  
  
! all: all-lib
  
  install: all installdirs install-lib
  ifeq ($(python_majorversion),2)
--- 86,102 ----
  
  include $(top_srcdir)/src/Makefile.shlib
  
+ .PHONY: gen-spiexceptions
  
! # Generate spiexceptions.h from utils/errcodes.h
! spiexceptions.h: $(top_srcdir)/src/include/utils/errcodes.h
! 	$(PERL) $(srcdir)/generate-spiexceptions.pl $^ > $(SPIEXCEPTIONS)
! 
! gen-spiexceptions: $(SPIEXCEPTIONS)
! 
! all: gen-spiexceptions all-lib
! 
! distprep: gen-spiexceptions
  
  install: all installdirs install-lib
  ifeq ($(python_majorversion),2)
*************** endif
*** 134,143 ****
  submake:
  	$(MAKE) -C $(top_builddir)/src/test/regress pg_regress$(X)
  
! clean distclean maintainer-clean: clean-lib
  	rm -f $(OBJS)
  	rm -rf results
  	rm -f regression.diffs regression.out
  ifeq ($(PORTNAME), win32)
  	rm -f python${pytverstr}.def
  endif
--- 143,157 ----
  submake:
  	$(MAKE) -C $(top_builddir)/src/test/regress pg_regress$(X)
  
! clean distclean: clean-lib
  	rm -f $(OBJS)
  	rm -rf results
  	rm -f regression.diffs regression.out
+ 
+ # since we distribute spiexceptions.h, only remove it in maintainer-clean
+ maintainer-clean: clean distclean
+ 	rm -f $(SPIEXCEPTIONS)
+ 
  ifeq ($(PORTNAME), win32)
  	rm -f python${pytverstr}.def
  endif
diff --git a/src/pl/plpython/expected/plpython_error.out b/src/pl/plpython/expected/plpython_error.out
index 7fc8337..718ebce 100644
*** a/src/pl/plpython/expected/plpython_error.out
--- b/src/pl/plpython/expected/plpython_error.out
*************** CREATE FUNCTION sql_syntax_error() RETUR
*** 8,14 ****
  'plpy.execute("syntax error")'
          LANGUAGE plpythonu;
  SELECT 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,14 ----
  'plpy.execute("syntax error")'
          LANGUAGE plpythonu;
  SELECT sql_syntax_error();
! ERROR:  spiexceptions.SyntaxError: syntax error at or near "syntax"
  CONTEXT:  PL/Python function "sql_syntax_error"
  /* check the handling of uncaught python exceptions
   */
*************** CREATE FUNCTION exception_index_invalid_
*** 27,33 ****
  return rv[0]'
  	LANGUAGE plpythonu;
  SELECT exception_index_invalid_nested();
! ERROR:  plpy.SPIError: function test5(unknown) does not exist
  CONTEXT:  PL/Python function "exception_index_invalid_nested"
  /* a typo
   */
--- 27,33 ----
  return rv[0]'
  	LANGUAGE plpythonu;
  SELECT exception_index_invalid_nested();
! ERROR:  spiexceptions.UndefinedFunction: function test5(unknown) does not exist
  CONTEXT:  PL/Python function "exception_index_invalid_nested"
  /* a typo
   */
*************** return None
*** 43,49 ****
  '
  	LANGUAGE plpythonu;
  SELECT invalid_type_uncaught('rick');
! 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
   * the typo, and return None
--- 43,49 ----
  '
  	LANGUAGE plpythonu;
  SELECT invalid_type_uncaught('rick');
! ERROR:  spiexceptions.UndefinedObject: type "test" does not exist
  CONTEXT:  PL/Python function "invalid_type_uncaught"
  /* for what it's worth catch the exception generated by
   * the typo, and return None
*************** SELECT valid_type('rick');
*** 109,111 ****
--- 109,149 ----
   
  (1 row)
  
+ /* Check catching specific types of exceptions
+  */
+ CREATE TABLE specific (
+     i integer PRIMARY KEY
+ );
+ NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index "specific_pkey" for table "specific"
+ CREATE FUNCTION specific_exception(i integer) RETURNS void AS
+ $$
+ from plpy import spiexceptions
+ try:
+     plpy.execute("insert into specific values (%s)" % (i or "NULL"));
+ except spiexceptions.NotNullViolation, e:
+     plpy.notice("Violated the NOT NULL constraint, sqlstate %s" % e.sqlstate)
+ except spiexceptions.UniqueViolation, e:
+     plpy.notice("Violated the UNIQUE constraint, sqlstate %s" % e.sqlstate)
+ $$ LANGUAGE plpythonu;
+ SELECT specific_exception(2);
+  specific_exception 
+ --------------------
+  
+ (1 row)
+ 
+ SELECT specific_exception(NULL);
+ NOTICE:  Violated the NOT NULL constraint, sqlstate 23502
+ CONTEXT:  PL/Python function "specific_exception"
+  specific_exception 
+ --------------------
+  
+ (1 row)
+ 
+ SELECT specific_exception(2);
+ NOTICE:  Violated the UNIQUE constraint, sqlstate 23505
+ CONTEXT:  PL/Python function "specific_exception"
+  specific_exception 
+ --------------------
+  
+ (1 row)
+ 
diff --git a/src/pl/plpython/expected/plpython_subxact.out b/src/pl/plpython/expected/plpython_subxact.out
index 25a5a4b..fd7e9d9 100644
*** a/src/pl/plpython/expected/plpython_subxact.out
--- b/src/pl/plpython/expected/plpython_subxact.out
*************** SELECT * FROM subxact_tbl;
*** 43,49 ****
  
  TRUNCATE subxact_tbl;
  SELECT subxact_test('SPI');
! ERROR:  plpy.SPIError: invalid input syntax for integer: "oops"
  CONTEXT:  PL/Python function "subxact_test"
  SELECT * FROM subxact_tbl;
   i 
--- 43,49 ----
  
  TRUNCATE subxact_tbl;
  SELECT subxact_test('SPI');
! ERROR:  spiexceptions.InvalidTextRepresentation: invalid input syntax for integer: "oops"
  CONTEXT:  PL/Python function "subxact_test"
  SELECT * FROM subxact_tbl;
   i 
*************** SELECT * FROM subxact_tbl;
*** 87,93 ****
  
  TRUNCATE subxact_tbl;
  SELECT subxact_ctx_test('SPI');
! ERROR:  plpy.SPIError: invalid input syntax for integer: "oops"
  CONTEXT:  PL/Python function "subxact_ctx_test"
  SELECT * FROM subxact_tbl;
   i 
--- 87,93 ----
  
  TRUNCATE subxact_tbl;
  SELECT subxact_ctx_test('SPI');
! ERROR:  spiexceptions.InvalidTextRepresentation: invalid input syntax for integer: "oops"
  CONTEXT:  PL/Python function "subxact_ctx_test"
  SELECT * FROM subxact_tbl;
   i 
*************** with plpy.subxact():
*** 122,128 ****
  return "ok"
  $$ LANGUAGE plpythonu;
  SELECT subxact_nested_test();
! ERROR:  plpy.SPIError: syntax error at or near "error"
  CONTEXT:  PL/Python function "subxact_nested_test"
  SELECT * FROM subxact_tbl;
   i 
--- 122,128 ----
  return "ok"
  $$ LANGUAGE plpythonu;
  SELECT subxact_nested_test();
! ERROR:  spiexceptions.SyntaxError: syntax error at or near "error"
  CONTEXT:  PL/Python function "subxact_nested_test"
  SELECT * FROM subxact_tbl;
   i 
*************** SELECT * FROM subxact_tbl;
*** 131,137 ****
  
  TRUNCATE subxact_tbl;
  SELECT subxact_nested_test('t');
! NOTICE:  Swallowed SPIError('syntax error at or near "error"',)
  CONTEXT:  PL/Python function "subxact_nested_test"
   subxact_nested_test 
  ---------------------
--- 131,137 ----
  
  TRUNCATE subxact_tbl;
  SELECT subxact_nested_test('t');
! NOTICE:  Swallowed SyntaxError('syntax error at or near "error"',)
  CONTEXT:  PL/Python function "subxact_nested_test"
   subxact_nested_test 
  ---------------------
*************** with plpy.subxact():
*** 157,163 ****
  return "ok"
  $$ LANGUAGE plpythonu;
  SELECT subxact_deeply_nested_test();
! NOTICE:  Swallowed SPIError('syntax error at or near "error"',)
  CONTEXT:  PL/Python function "subxact_nested_test"
  SQL statement "select subxact_nested_test('t')"
  PL/Python function "subxact_nested_test"
--- 157,163 ----
  return "ok"
  $$ LANGUAGE plpythonu;
  SELECT subxact_deeply_nested_test();
! NOTICE:  Swallowed SyntaxError('syntax error at or near "error"',)
  CONTEXT:  PL/Python function "subxact_nested_test"
  SQL statement "select subxact_nested_test('t')"
  PL/Python function "subxact_nested_test"
diff --git a/src/pl/plpython/expected/plpython_test.out b/src/pl/plpython/expected/plpython_test.out
index 674c739..ef2b284 100644
*** a/src/pl/plpython/expected/plpython_test.out
--- b/src/pl/plpython/expected/plpython_test.out
*************** contents.sort()
*** 43,51 ****
  return ", ".join(contents)
  $$ LANGUAGE plpythonu;
  select module_contents();
!                                           module_contents                                           
! ----------------------------------------------------------------------------------------------------
!  Error, Fatal, SPIError, debug, error, execute, fatal, info, log, notice, prepare, subxact, warning
  (1 row)
  
  CREATE FUNCTION elog_test() RETURNS void
--- 43,51 ----
  return ", ".join(contents)
  $$ LANGUAGE plpythonu;
  select module_contents();
!                                                   module_contents                                                  
! -------------------------------------------------------------------------------------------------------------------
!  Error, Fatal, SPIError, debug, error, execute, fatal, info, log, notice, prepare, spiexceptions, subxact, warning
  (1 row)
  
  CREATE FUNCTION elog_test() RETURNS void
diff --git a/src/pl/plpython/generate-spiexceptions.pl b/src/pl/plpython/generate-spiexceptions.pl
index ...d70f439 .
*** a/src/pl/plpython/generate-spiexceptions.pl
--- b/src/pl/plpython/generate-spiexceptions.pl
***************
*** 0 ****
--- 1,37 ----
+ #!/usr/bin/perl
+ #
+ # Generate the spiexceptions.h file from src/include/utils/errcodes.h
+ # The generated header file is used by PL/Python to create a Python
+ # exception object per SPI error code
+ 
+ use warnings;
+ use strict;
+ 
+ print "/* autogenerated from utils/errcodes.h, do not edit */\n";
+ 
+ # Are we processing errors, or still looking at success and warning codes
+ my $in_errors = 0;
+ 
+ open my $errcodes, $ARGV[0] or die;
+ 
+ while (<$errcodes>) {
+     chomp;
+ 
+     # Class 03 is the first one that includes errors
+     /Class 03/ && ($in_errors = 1);
+ 
+     next unless $in_errors;
+     next unless /^#define ERRCODE_.*MAKE_SQLSTATE/;
+ 
+     # Get the exception name
+     (my $exception) = /^#define ERRCODE_(\S*)/;
+ 
+     # Change SOME_ERROR_CONDITION to SomeErrorCondition
+     my $pyexception = $exception;
+     $pyexception =~ s/([A-Z])([^_]*)(?:_|$)/$1\L$2/g;
+ 
+     print "{ \"spiexceptions.$pyexception\", " .
+ 	"\"$pyexception\", ERRCODE_$exception },\n";
+ }
+ 
+ close $errcodes;
diff --git a/src/pl/plpython/plpython.c b/src/pl/plpython/plpython.c
index 7506b54..468aedd 100644
*** a/src/pl/plpython/plpython.c
--- b/src/pl/plpython/plpython.c
*************** typedef struct PLySubxactObject
*** 258,263 ****
--- 258,285 ----
  	bool	exited;
  } PLySubxactObject;
  
+ /* A list of all known exceptions, generated from utils/errcodes.h */
+ typedef struct ExceptionMap
+ {
+ 	char		*name;
+ 	char		*classname;
+ 	int			sqlstate;
+ } ExceptionMap;
+ 
+ static const ExceptionMap exception_map[] = {
+ #include "spiexceptions.h"
+ 	{NULL, NULL, 0}
+ };
+ 
+ /* A hashtable mapping sqlstates to exceptions, for speedy lookup */
+ static HTAB *PLy_spi_exceptions;
+ 
+ typedef struct PLyExceptionEntry
+ {
+ 	int			sqlstate;	/* hash key, must be first */
+ 	PyObject	*exc;		/* corresponding exception */
+ } PLyExceptionEntry;
+ 
  /* function declarations */
  
  #if PY_MAJOR_VERSION >= 3
*************** static PyMethodDef PLy_methods[] = {
*** 2765,2770 ****
--- 2787,2796 ----
  	{NULL, NULL, 0, NULL}
  };
  
+ static PyMethodDef PLy_exc_methods[] = {
+ 	{NULL, NULL, 0, NULL}
+ };
+ 
  #if PY_MAJOR_VERSION >= 3
  static PyModuleDef PLy_module = {
  	PyModuleDef_HEAD_INIT,		/* m_base */
*************** static PyModuleDef PLy_module = {
*** 2777,2782 ****
--- 2803,2820 ----
  	NULL,                       /* m_clear */
  	NULL                        /* m_free */
  };
+ 
+ static PyModuleDef PLy_exc_module = {
+ 	PyModuleDef_HEAD_INIT,		/* m_base */
+ 	"spiexceptions",			/* m_name */
+ 	NULL,						/* m_doc */
+ 	-1,							/* m_size */
+ 	PLy_exc_methods,			/* m_methods */
+ 	NULL,						/* m_reload */
+ 	NULL,						/* m_traverse */
+ 	NULL,						/* m_clear */
+ 	NULL						/* m_free */
+ };
  #endif
  
  /* plan object methods */
*************** PLy_spi_prepare(PyObject *self, PyObject
*** 3080,3086 ****
  	}
  	PG_CATCH();
  	{
! 		ErrorData	*edata;
  
  		Py_DECREF(plan);
  		Py_XDECREF(optr);
--- 3118,3126 ----
  	}
  	PG_CATCH();
  	{
! 		ErrorData				*edata;
! 		PLyExceptionEntry		*entry;
! 		PyObject				*exc;
  
  		Py_DECREF(plan);
  		Py_XDECREF(optr);
*************** PLy_spi_prepare(PyObject *self, PyObject
*** 3105,3112 ****
  			SPI_restore_connection();
  		}
  
  		/* Make Python raise the exception */
! 		PLy_exception_set(PLy_exc_spi_error, edata->message);
  		return NULL;
  	}
  	PG_END_TRY();
--- 3145,3158 ----
  			SPI_restore_connection();
  		}
  
+ 		/* Look up the correct exception */
+ 		entry = hash_search(PLy_spi_exceptions, &(edata->sqlerrcode),
+ 							HASH_FIND, NULL);
+ 		/* We really should find it, but just in case have a fallback */
+ 		Assert(entry != NULL);
+ 		exc = entry ? entry->exc : PLy_exc_spi_error;
  		/* Make Python raise the exception */
! 		PLy_exception_set(exc, edata->message);
  		return NULL;
  	}
  	PG_END_TRY();
*************** PLy_spi_execute_plan(PyObject *ob, PyObj
*** 3257,3263 ****
  	}
  	PG_CATCH();
  	{
! 		ErrorData	*edata;
  
  		/* Save error info */
  		MemoryContextSwitchTo(oldcontext);
--- 3303,3311 ----
  	}
  	PG_CATCH();
  	{
! 		ErrorData				*edata;
! 		PLyExceptionEntry		*entry;
! 		PyObject				*exc;
  
  		/* Save error info */
  		MemoryContextSwitchTo(oldcontext);
*************** PLy_spi_execute_plan(PyObject *ob, PyObj
*** 3292,3299 ****
  			SPI_restore_connection();
  		}
  
  		/* Make Python raise the exception */
! 		PLy_exception_set(PLy_exc_spi_error, edata->message);
  		return NULL;
  	}
  	PG_END_TRY();
--- 3340,3353 ----
  			SPI_restore_connection();
  		}
  
+ 		/* Look up the correct exception */
+ 		entry = hash_search(PLy_spi_exceptions, &(edata->sqlerrcode),
+ 							HASH_FIND, NULL);
+ 		/* We really should find it, but just in case have a fallback */
+ 		Assert(entry != NULL);
+ 		exc = entry ? entry->exc : PLy_exc_spi_error;
  		/* Make Python raise the exception */
! 		PLy_exception_set(exc, edata->message);
  		return NULL;
  	}
  	PG_END_TRY();
*************** PLy_spi_execute_query(char *query, long
*** 3352,3358 ****
  	}
  	PG_CATCH();
  	{
! 		ErrorData	*edata;
  
  		/* Save error info */
  		MemoryContextSwitchTo(oldcontext);
--- 3406,3414 ----
  	}
  	PG_CATCH();
  	{
! 		ErrorData				*edata;
! 		PLyExceptionEntry		*entry;
! 		PyObject				*exc;
  
  		/* Save error info */
  		MemoryContextSwitchTo(oldcontext);
*************** PLy_spi_execute_query(char *query, long
*** 3374,3381 ****
  			SPI_restore_connection();
  		}
  
  		/* Make Python raise the exception */
! 		PLy_exception_set(PLy_exc_spi_error, edata->message);
  		return NULL;
  	}
  	PG_END_TRY();
--- 3430,3443 ----
  			SPI_restore_connection();
  		}
  
+ 		/* Look up the correct exception */
+ 		entry = hash_search(PLy_spi_exceptions, &edata->sqlerrcode,
+ 							HASH_FIND, NULL);
+ 		/* We really should find it, but just in case have a fallback */
+ 		Assert(entry != NULL);
+ 		exc = entry ? entry->exc : PLy_exc_spi_error;
  		/* Make Python raise the exception */
! 		PLy_exception_set(exc, edata->message);
  		return NULL;
  	}
  	PG_END_TRY();
*************** static void PLy_add_exception(PyObject *
*** 3606,3625 ****
--- 3668,3724 ----
  #endif
  }
  
+ /* Add all the autogenerated exceptions as subclasses of SPIError */
+ static void
+ PLy_generate_spi_exceptions(PyObject *mod, PyObject *base)
+ {
+ 	int	i;
+ 
+ 	for (i = 0; exception_map[i].name != NULL; i++)
+ 	{
+ 		bool					 found;
+ 		PyObject				*exc;
+ 		PLyExceptionEntry		*entry;
+ 		PyObject				*sqlstate;
+ 		PyObject				*dict = PyDict_New();
+ 
+ 		sqlstate = PyString_FromString(unpack_sql_state(
+ 										   exception_map[i].sqlstate));
+ 		PyDict_SetItemString(dict, "sqlstate", sqlstate);
+ 		Py_DECREF(sqlstate);
+ 		exc = PyErr_NewException(exception_map[i].name, base, dict);
+ 		PLy_add_exception(mod, exception_map[i].classname, exc);
+ 		entry = hash_search(PLy_spi_exceptions, &exception_map[i].sqlstate,
+ 							HASH_ENTER, &found);
+ 		entry->exc = exc;
+ 		Assert(!found);
+ 	}
+ }
+ 
  /* Add exceptions to the plpy module */
  static void
  PLy_initialize_exceptions(PyObject *plpy)
  {
  	PyObject	*mod;
+ 	PyObject	*exc;
+ 	PyObject	*excmod;
+ 	HASHCTL		 hash_ctl;
  
  #if PY_MAJOR_VERSION < 3
  	/* For Python <3 we add the exceptions to the module dictionary */
  	mod = PyModule_GetDict(plpy);
+ 	excmod = Py_InitModule("spiexceptions", PLy_exc_methods);
+ 	exc = PyModule_GetDict(excmod);
  #else
  	/* In Python 3 you add them directly into the module */
  	mod = plpy;
+ 	excmod = PyModule_Create(&PLy_exc_module);
+ 	exc = excmod;
  #endif
  
+ 	if (PyModule_AddObject(plpy, "spiexceptions", excmod) < 0)
+ 		PLy_elog(ERROR, "Failed to add the spiexceptions module");
+ 
  	PLy_exc_error = PyErr_NewException("plpy.Error", NULL, NULL);
  	PLy_exc_fatal = PyErr_NewException("plpy.Fatal", NULL, NULL);
  	PLy_exc_spi_error = PyErr_NewException("plpy.SPIError", NULL, NULL);
*************** PLy_initialize_exceptions(PyObject *plpy
*** 3627,3632 ****
--- 3726,3740 ----
  	PLy_add_exception(mod, "Error", PLy_exc_error);
  	PLy_add_exception(mod, "Fatal", PLy_exc_fatal);
  	PLy_add_exception(mod, "SPIError", PLy_exc_spi_error);
+ 
+ 	memset(&hash_ctl, 0, sizeof(hash_ctl));
+ 	hash_ctl.keysize = sizeof(int);
+ 	hash_ctl.entrysize = sizeof(PLyExceptionEntry);
+ 	hash_ctl.hash = tag_hash;
+ 	PLy_spi_exceptions = hash_create("SPI exceptions", 256,
+ 									 &hash_ctl, HASH_ELEM | HASH_FUNCTION);
+ 
+ 	PLy_generate_spi_exceptions(exc, PLy_exc_spi_error);
  }
  
  /*
diff --git a/src/pl/plpython/sql/plpython_error.sql b/src/pl/plpython/sql/plpython_error.sql
index 5ca6849..2306c1d 100644
*** a/src/pl/plpython/sql/plpython_error.sql
--- b/src/pl/plpython/sql/plpython_error.sql
*************** return None
*** 107,109 ****
--- 107,130 ----
  	LANGUAGE plpythonu;
  
  SELECT valid_type('rick');
+ 
+ /* Check catching specific types of exceptions
+  */
+ CREATE TABLE specific (
+     i integer PRIMARY KEY
+ );
+ 
+ CREATE FUNCTION specific_exception(i integer) RETURNS void AS
+ $$
+ from plpy import spiexceptions
+ try:
+     plpy.execute("insert into specific values (%s)" % (i or "NULL"));
+ except spiexceptions.NotNullViolation, e:
+     plpy.notice("Violated the NOT NULL constraint, sqlstate %s" % e.sqlstate)
+ except spiexceptions.UniqueViolation, e:
+     plpy.notice("Violated the UNIQUE constraint, sqlstate %s" % e.sqlstate)
+ $$ LANGUAGE plpythonu;
+ 
+ SELECT specific_exception(2);
+ SELECT specific_exception(NULL);
+ SELECT specific_exception(2);
diff --git a/src/tools/msvc/Solution.pm b/src/tools/msvc/Solution.pm
index b737e1e..5ad643e 100644
*** a/src/tools/msvc/Solution.pm
--- b/src/tools/msvc/Solution.pm
*************** s{PG_VERSION_STR "[^"]+"}{__STRINGIFY(x)
*** 273,278 ****
--- 273,284 ----
          );
      }
  
+     if ($self->{options}->{python} && IsNewer('src\pl\plpython\spiexceptions.h','src\include\utils\errcodes.h'))
+     {
+         print "Generating spiexceptions.h...\n";
+         system("perl src\pl\plpython\generate-spiexceptions.pl src\include\utils\errcodes.h > src\pl\plpython\spiexceptions.h");
+     }
+ 
      if (IsNewer('src\interfaces\libpq\libpq.rc','src\interfaces\libpq\libpq.rc.in'))
      {
          print "Generating libpq.rc...\n";
#6Jan Urbański
wulczer@wulczer.org
In reply to: Jan Urbański (#5)
1 attachment(s)
Re: pl/python custom exceptions for SPI

On 11/01/11 12:20, Jan Urbański wrote:

On 11/01/11 01:27, Tom Lane wrote:

Hannu Krosing <hannu@2ndquadrant.com> writes:

On 10.1.2011 17:20, Jan Urbański wrote:

I changed that patch to use Perl instead of sed to generate the
exceptions, which should be a more portable.

Updated as an incremental patch on to of the recently sent version of
explicit-subxacts.

Attachments:

plpython-custom-spi-exceptions.difftext/x-patch; name=plpython-custom-spi-exceptions.diffDownload
diff --git a/src/pl/plpython/Makefile b/src/pl/plpython/Makefile
index 33dddc6..1fe386b 100644
*** a/src/pl/plpython/Makefile
--- b/src/pl/plpython/Makefile
*************** rpathdir = $(python_libdir)
*** 38,44 ****
  
  NAME = plpython$(python_majorversion)
  OBJS = plpython.o
! 
  
  # Python on win32 ships with import libraries only for Microsoft Visual C++,
  # which are not compatible with mingw gcc. Therefore we need to build a
--- 38,44 ----
  
  NAME = plpython$(python_majorversion)
  OBJS = plpython.o
! SPIEXCEPTIONS = spiexceptions.h
  
  # Python on win32 ships with import libraries only for Microsoft Visual C++,
  # which are not compatible with mingw gcc. Therefore we need to build a
*************** PSQLDIR = $(bindir)
*** 86,93 ****
  
  include $(top_srcdir)/src/Makefile.shlib
  
  
! all: all-lib
  
  install: all installdirs install-lib
  ifeq ($(python_majorversion),2)
--- 86,102 ----
  
  include $(top_srcdir)/src/Makefile.shlib
  
+ .PHONY: gen-spiexceptions
  
! # Generate spiexceptions.h from utils/errcodes.h
! spiexceptions.h: $(top_srcdir)/src/include/utils/errcodes.h
! 	$(PERL) $(srcdir)/generate-spiexceptions.pl $^ > $(SPIEXCEPTIONS)
! 
! gen-spiexceptions: $(SPIEXCEPTIONS)
! 
! all: gen-spiexceptions all-lib
! 
! distprep: gen-spiexceptions
  
  install: all installdirs install-lib
  ifeq ($(python_majorversion),2)
*************** endif
*** 134,143 ****
  submake:
  	$(MAKE) -C $(top_builddir)/src/test/regress pg_regress$(X)
  
! clean distclean maintainer-clean: clean-lib
  	rm -f $(OBJS)
  	rm -rf results
  	rm -f regression.diffs regression.out
  ifeq ($(PORTNAME), win32)
  	rm -f python${pytverstr}.def
  endif
--- 143,157 ----
  submake:
  	$(MAKE) -C $(top_builddir)/src/test/regress pg_regress$(X)
  
! clean distclean: clean-lib
  	rm -f $(OBJS)
  	rm -rf results
  	rm -f regression.diffs regression.out
+ 
+ # since we distribute spiexceptions.h, only remove it in maintainer-clean
+ maintainer-clean: clean distclean
+ 	rm -f $(SPIEXCEPTIONS)
+ 
  ifeq ($(PORTNAME), win32)
  	rm -f python${pytverstr}.def
  endif
diff --git a/src/pl/plpython/expected/plpython_error.out b/src/pl/plpython/expected/plpython_error.out
index 4eeda6f..45ce136 100644
*** a/src/pl/plpython/expected/plpython_error.out
--- b/src/pl/plpython/expected/plpython_error.out
*************** CREATE FUNCTION sql_syntax_error() RETUR
*** 8,14 ****
  'plpy.execute("syntax error")'
          LANGUAGE plpythonu;
  SELECT sql_syntax_error();
! ERROR:  plpy.SPIError: syntax error at or near "syntax"
  LINE 1: syntax error
          ^
  QUERY:  syntax error
--- 8,14 ----
  'plpy.execute("syntax error")'
          LANGUAGE plpythonu;
  SELECT sql_syntax_error();
! ERROR:  spiexceptions.SyntaxError: syntax error at or near "syntax"
  LINE 1: syntax error
          ^
  QUERY:  syntax error
*************** CREATE FUNCTION exception_index_invalid_
*** 30,36 ****
  return rv[0]'
  	LANGUAGE plpythonu;
  SELECT exception_index_invalid_nested();
! ERROR:  plpy.SPIError: function test5(unknown) does not exist
  LINE 1: SELECT test5('foo')
                 ^
  HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
--- 30,36 ----
  return rv[0]'
  	LANGUAGE plpythonu;
  SELECT exception_index_invalid_nested();
! ERROR:  spiexceptions.UndefinedFunction: function test5(unknown) does not exist
  LINE 1: SELECT test5('foo')
                 ^
  HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
*************** return None
*** 50,56 ****
  '
  	LANGUAGE plpythonu;
  SELECT invalid_type_uncaught('rick');
! 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
   * the typo, and return None
--- 50,56 ----
  '
  	LANGUAGE plpythonu;
  SELECT invalid_type_uncaught('rick');
! ERROR:  spiexceptions.UndefinedObject: type "test" does not exist
  CONTEXT:  PL/Python function "invalid_type_uncaught"
  /* for what it's worth catch the exception generated by
   * the typo, and return None
*************** SELECT valid_type('rick');
*** 116,121 ****
--- 116,159 ----
   
  (1 row)
  
+ /* Check catching specific types of exceptions
+  */
+ CREATE TABLE specific (
+     i integer PRIMARY KEY
+ );
+ NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index "specific_pkey" for table "specific"
+ CREATE FUNCTION specific_exception(i integer) RETURNS void AS
+ $$
+ from plpy import spiexceptions
+ try:
+     plpy.execute("insert into specific values (%s)" % (i or "NULL"));
+ except spiexceptions.NotNullViolation, e:
+     plpy.notice("Violated the NOT NULL constraint, sqlstate %s" % e.sqlstate)
+ except spiexceptions.UniqueViolation, e:
+     plpy.notice("Violated the UNIQUE constraint, sqlstate %s" % e.sqlstate)
+ $$ LANGUAGE plpythonu;
+ SELECT specific_exception(2);
+  specific_exception 
+ --------------------
+  
+ (1 row)
+ 
+ SELECT specific_exception(NULL);
+ NOTICE:  Violated the NOT NULL constraint, sqlstate 23502
+ CONTEXT:  PL/Python function "specific_exception"
+  specific_exception 
+ --------------------
+  
+ (1 row)
+ 
+ SELECT specific_exception(2);
+ NOTICE:  Violated the UNIQUE constraint, sqlstate 23505
+ CONTEXT:  PL/Python function "specific_exception"
+  specific_exception 
+ --------------------
+  
+ (1 row)
+ 
  /* manually starting subtransactions - a bad idea
   */
  CREATE FUNCTION manual_subxact() RETURNS void AS $$
diff --git a/src/pl/plpython/expected/plpython_subxact.out b/src/pl/plpython/expected/plpython_subxact.out
index 9888a20..f330783 100644
*** a/src/pl/plpython/expected/plpython_subxact.out
--- b/src/pl/plpython/expected/plpython_subxact.out
*************** SELECT * FROM subxact_tbl;
*** 43,49 ****
  
  TRUNCATE subxact_tbl;
  SELECT subxact_test('SPI');
! ERROR:  plpy.SPIError: invalid input syntax for integer: "oops"
  LINE 1: insert into subxact_tbl values('oops')
                                         ^
  QUERY:  insert into subxact_tbl values('oops')
--- 43,49 ----
  
  TRUNCATE subxact_tbl;
  SELECT subxact_test('SPI');
! ERROR:  spiexceptions.InvalidTextRepresentation: invalid input syntax for integer: "oops"
  LINE 1: insert into subxact_tbl values('oops')
                                         ^
  QUERY:  insert into subxact_tbl values('oops')
*************** SELECT * FROM subxact_tbl;
*** 90,96 ****
  
  TRUNCATE subxact_tbl;
  SELECT subxact_ctx_test('SPI');
! ERROR:  plpy.SPIError: invalid input syntax for integer: "oops"
  LINE 1: insert into subxact_tbl values('oops')
                                         ^
  QUERY:  insert into subxact_tbl values('oops')
--- 90,96 ----
  
  TRUNCATE subxact_tbl;
  SELECT subxact_ctx_test('SPI');
! ERROR:  spiexceptions.InvalidTextRepresentation: invalid input syntax for integer: "oops"
  LINE 1: insert into subxact_tbl values('oops')
                                         ^
  QUERY:  insert into subxact_tbl values('oops')
*************** with plpy.subxact():
*** 128,134 ****
  return "ok"
  $$ LANGUAGE plpythonu;
  SELECT subxact_nested_test();
! ERROR:  plpy.SPIError: syntax error at or near "error"
  LINE 1: error
          ^
  QUERY:  error
--- 128,134 ----
  return "ok"
  $$ LANGUAGE plpythonu;
  SELECT subxact_nested_test();
! ERROR:  spiexceptions.SyntaxError: syntax error at or near "error"
  LINE 1: error
          ^
  QUERY:  error
*************** SELECT * FROM subxact_tbl;
*** 140,146 ****
  
  TRUNCATE subxact_tbl;
  SELECT subxact_nested_test('t');
! NOTICE:  Swallowed SPIError('syntax error at or near "error"',)
  CONTEXT:  PL/Python function "subxact_nested_test"
   subxact_nested_test 
  ---------------------
--- 140,146 ----
  
  TRUNCATE subxact_tbl;
  SELECT subxact_nested_test('t');
! NOTICE:  Swallowed SyntaxError('syntax error at or near "error"',)
  CONTEXT:  PL/Python function "subxact_nested_test"
   subxact_nested_test 
  ---------------------
*************** with plpy.subxact():
*** 166,172 ****
  return "ok"
  $$ LANGUAGE plpythonu;
  SELECT subxact_deeply_nested_test();
! NOTICE:  Swallowed SPIError('syntax error at or near "error"',)
  CONTEXT:  PL/Python function "subxact_nested_test"
  SQL statement "select subxact_nested_test('t')"
  PL/Python function "subxact_nested_test"
--- 166,172 ----
  return "ok"
  $$ LANGUAGE plpythonu;
  SELECT subxact_deeply_nested_test();
! NOTICE:  Swallowed SyntaxError('syntax error at or near "error"',)
  CONTEXT:  PL/Python function "subxact_nested_test"
  SQL statement "select subxact_nested_test('t')"
  PL/Python function "subxact_nested_test"
diff --git a/src/pl/plpython/expected/plpython_test.out b/src/pl/plpython/expected/plpython_test.out
index 674c739..ef2b284 100644
*** a/src/pl/plpython/expected/plpython_test.out
--- b/src/pl/plpython/expected/plpython_test.out
*************** contents.sort()
*** 43,51 ****
  return ", ".join(contents)
  $$ LANGUAGE plpythonu;
  select module_contents();
!                                           module_contents                                           
! ----------------------------------------------------------------------------------------------------
!  Error, Fatal, SPIError, debug, error, execute, fatal, info, log, notice, prepare, subxact, warning
  (1 row)
  
  CREATE FUNCTION elog_test() RETURNS void
--- 43,51 ----
  return ", ".join(contents)
  $$ LANGUAGE plpythonu;
  select module_contents();
!                                                   module_contents                                                  
! -------------------------------------------------------------------------------------------------------------------
!  Error, Fatal, SPIError, debug, error, execute, fatal, info, log, notice, prepare, spiexceptions, subxact, warning
  (1 row)
  
  CREATE FUNCTION elog_test() RETURNS void
diff --git a/src/pl/plpython/generate-spiexceptions.pl b/src/pl/plpython/generate-spiexceptions.pl
index ...d70f439 .
*** a/src/pl/plpython/generate-spiexceptions.pl
--- b/src/pl/plpython/generate-spiexceptions.pl
***************
*** 0 ****
--- 1,37 ----
+ #!/usr/bin/perl
+ #
+ # Generate the spiexceptions.h file from src/include/utils/errcodes.h
+ # The generated header file is used by PL/Python to create a Python
+ # exception object per SPI error code
+ 
+ use warnings;
+ use strict;
+ 
+ print "/* autogenerated from utils/errcodes.h, do not edit */\n";
+ 
+ # Are we processing errors, or still looking at success and warning codes
+ my $in_errors = 0;
+ 
+ open my $errcodes, $ARGV[0] or die;
+ 
+ while (<$errcodes>) {
+     chomp;
+ 
+     # Class 03 is the first one that includes errors
+     /Class 03/ && ($in_errors = 1);
+ 
+     next unless $in_errors;
+     next unless /^#define ERRCODE_.*MAKE_SQLSTATE/;
+ 
+     # Get the exception name
+     (my $exception) = /^#define ERRCODE_(\S*)/;
+ 
+     # Change SOME_ERROR_CONDITION to SomeErrorCondition
+     my $pyexception = $exception;
+     $pyexception =~ s/([A-Z])([^_]*)(?:_|$)/$1\L$2/g;
+ 
+     print "{ \"spiexceptions.$pyexception\", " .
+ 	"\"$pyexception\", ERRCODE_$exception },\n";
+ }
+ 
+ close $errcodes;
diff --git a/src/pl/plpython/plpython.c b/src/pl/plpython/plpython.c
index 0068eac..af4b254 100644
*** a/src/pl/plpython/plpython.c
--- b/src/pl/plpython/plpython.c
*************** typedef struct PLySubxactObject
*** 261,266 ****
--- 261,288 ----
  	bool	exited;
  } PLySubxactObject;
  
+ /* A list of all known exceptions, generated from utils/errcodes.h */
+ typedef struct ExceptionMap
+ {
+ 	char		*name;
+ 	char		*classname;
+ 	int			sqlstate;
+ } ExceptionMap;
+ 
+ static const ExceptionMap exception_map[] = {
+ #include "spiexceptions.h"
+ 	{NULL, NULL, 0}
+ };
+ 
+ /* A hashtable mapping sqlstates to exceptions, for speedy lookup */
+ static HTAB *PLy_spi_exceptions;
+ 
+ typedef struct PLyExceptionEntry
+ {
+ 	int			sqlstate;	/* hash key, must be first */
+ 	PyObject	*exc;		/* corresponding exception */
+ } PLyExceptionEntry;
+ 
  /* function declarations */
  
  #if PY_MAJOR_VERSION >= 3
*************** __attribute__((format(printf, 2, 5)))
*** 299,305 ****
  __attribute__((format(printf, 3, 5)));
  
  /* like PLy_exception_set, but conserve more fields from ErrorData */
! static void PLy_spi_exception_set(ErrorData *edata);
  
  /* Get the innermost python procedure called from the backend */
  static char *PLy_procedure_name(PLyProcedure *);
--- 321,327 ----
  __attribute__((format(printf, 3, 5)));
  
  /* like PLy_exception_set, but conserve more fields from ErrorData */
! static void PLy_spi_exception_set(PyObject *excclass, ErrorData *edata);
  
  /* Get the innermost python procedure called from the backend */
  static char *PLy_procedure_name(PLyProcedure *);
*************** static PyMethodDef PLy_methods[] = {
*** 2766,2771 ****
--- 2788,2797 ----
  	{NULL, NULL, 0, NULL}
  };
  
+ static PyMethodDef PLy_exc_methods[] = {
+ 	{NULL, NULL, 0, NULL}
+ };
+ 
  #if PY_MAJOR_VERSION >= 3
  static PyModuleDef PLy_module = {
  	PyModuleDef_HEAD_INIT,		/* m_base */
*************** static PyModuleDef PLy_module = {
*** 2774,2779 ****
--- 2800,2817 ----
  	-1,							/* m_size */
  	PLy_methods,				/* m_methods */
  };
+ 
+ static PyModuleDef PLy_exc_module = {
+ 	PyModuleDef_HEAD_INIT,		/* m_base */
+ 	"spiexceptions",			/* m_name */
+ 	NULL,						/* m_doc */
+ 	-1,							/* m_size */
+ 	PLy_exc_methods,			/* m_methods */
+ 	NULL,						/* m_reload */
+ 	NULL,						/* m_traverse */
+ 	NULL,						/* m_clear */
+ 	NULL						/* m_free */
+ };
  #endif
  
  /* plan object methods */
*************** PLy_spi_prepare(PyObject *self, PyObject
*** 3077,3083 ****
  	}
  	PG_CATCH();
  	{
! 		ErrorData	*edata;
  
  		/* Save error info */
  		MemoryContextSwitchTo(oldcontext);
--- 3115,3123 ----
  	}
  	PG_CATCH();
  	{
! 		ErrorData				*edata;
! 		PLyExceptionEntry		*entry;
! 		PyObject				*exc;
  
  		/* Save error info */
  		MemoryContextSwitchTo(oldcontext);
*************** PLy_spi_prepare(PyObject *self, PyObject
*** 3101,3108 ****
  			SPI_restore_connection();
  		}
  
  		/* Make Python raise the exception */
! 		PLy_spi_exception_set(edata);
  		return NULL;
  	}
  	PG_END_TRY();
--- 3141,3154 ----
  			SPI_restore_connection();
  		}
  
+ 		/* Look up the correct exception */
+ 		entry = hash_search(PLy_spi_exceptions, &(edata->sqlerrcode),
+ 							HASH_FIND, NULL);
+ 		/* We really should find it, but just in case have a fallback */
+ 		Assert(entry != NULL);
+ 		exc = entry ? entry->exc : PLy_exc_spi_error;
  		/* Make Python raise the exception */
! 		PLy_spi_exception_set(exc, edata);
  		return NULL;
  	}
  	PG_END_TRY();
*************** PLy_spi_execute_plan(PyObject *ob, PyObj
*** 3257,3264 ****
  	}
  	PG_CATCH();
  	{
  		int			k;
- 		ErrorData	*edata;
  
  		/* Save error info */
  		MemoryContextSwitchTo(oldcontext);
--- 3303,3312 ----
  	}
  	PG_CATCH();
  	{
+ 		ErrorData				*edata;
+ 		PLyExceptionEntry		*entry;
+ 		PyObject				*exc;
  		int			k;
  
  		/* Save error info */
  		MemoryContextSwitchTo(oldcontext);
*************** PLy_spi_execute_plan(PyObject *ob, PyObj
*** 3293,3300 ****
  			SPI_restore_connection();
  		}
  
  		/* Make Python raise the exception */
! 		PLy_spi_exception_set(edata);
  		return NULL;
  	}
  	PG_END_TRY();
--- 3341,3354 ----
  			SPI_restore_connection();
  		}
  
+ 		/* Look up the correct exception */
+ 		entry = hash_search(PLy_spi_exceptions, &(edata->sqlerrcode),
+ 							HASH_FIND, NULL);
+ 		/* We really should find it, but just in case have a fallback */
+ 		Assert(entry != NULL);
+ 		exc = entry ? entry->exc : PLy_exc_spi_error;
  		/* Make Python raise the exception */
! 		PLy_spi_exception_set(exc, edata);
  		return NULL;
  	}
  	PG_END_TRY();
*************** PLy_spi_execute_query(char *query, long
*** 3356,3362 ****
  	}
  	PG_CATCH();
  	{
! 		ErrorData	*edata;
  
  		/* Save error info */
  		MemoryContextSwitchTo(oldcontext);
--- 3410,3418 ----
  	}
  	PG_CATCH();
  	{
! 		ErrorData				*edata;
! 		PLyExceptionEntry		*entry;
! 		PyObject				*exc;
  
  		/* Save error info */
  		MemoryContextSwitchTo(oldcontext);
*************** PLy_spi_execute_query(char *query, long
*** 3378,3385 ****
  			SPI_restore_connection();
  		}
  
  		/* Make Python raise the exception */
! 		PLy_spi_exception_set(edata);
  		return NULL;
  	}
  	PG_END_TRY();
--- 3434,3447 ----
  			SPI_restore_connection();
  		}
  
+ 		/* Look up the correct exception */
+ 		entry = hash_search(PLy_spi_exceptions, &edata->sqlerrcode,
+ 							HASH_FIND, NULL);
+ 		/* We really should find it, but just in case have a fallback */
+ 		Assert(entry != NULL);
+ 		exc = entry ? entry->exc : PLy_exc_spi_error;
  		/* Make Python raise the exception */
! 		PLy_spi_exception_set(exc, edata);
  		return NULL;
  	}
  	PG_END_TRY();
*************** PLy_subxact_exit(PyObject *self, PyObjec
*** 3608,3616 ****
--- 3670,3715 ----
  /*
   * Add exceptions to the plpy module
   */
+ /* Add all the autogenerated exceptions as subclasses of SPIError */
+ static void
+ PLy_generate_spi_exceptions(PyObject *mod, PyObject *base)
+ {
+ 	int	i;
+ 
+ 	for (i = 0; exception_map[i].name != NULL; i++)
+ 	{
+ 		bool					 found;
+ 		PyObject				*exc;
+ 		PLyExceptionEntry		*entry;
+ 		PyObject				*sqlstate;
+ 		PyObject				*dict = PyDict_New();
+ 
+ 		sqlstate = PyString_FromString(unpack_sql_state(
+ 										   exception_map[i].sqlstate));
+ 		PyDict_SetItemString(dict, "sqlstate", sqlstate);
+ 		Py_DECREF(sqlstate);
+ 		exc = PyErr_NewException(exception_map[i].name, base, dict);
+ 		PyModule_AddObject(mod, exception_map[i].classname, exc);
+ 		entry = hash_search(PLy_spi_exceptions, &exception_map[i].sqlstate,
+ 							HASH_ENTER, &found);
+ 		entry->exc = exc;
+ 		Assert(!found);
+ 	}
+ }
+ 
  static void
  PLy_add_exceptions(PyObject *plpy)
  {
+ 	PyObject	*excmod;
+ 	HASHCTL		 hash_ctl;
+ #if PY_MAJOR_VERSION < 3
+ 	excmod = Py_InitModule("spiexceptions", PLy_exc_methods);
+ #else
+ 	excmod = PyModule_Create(&PLy_exc_module);
+ #endif
+ 	if (PyModule_AddObject(plpy, "spiexceptions", excmod) < 0)
+ 		PLy_elog(ERROR, "Failed to add the spiexceptions module");
+ 
  	PLy_exc_error = PyErr_NewException("plpy.Error", NULL, NULL);
  	PLy_exc_fatal = PyErr_NewException("plpy.Fatal", NULL, NULL);
  	PLy_exc_spi_error = PyErr_NewException("plpy.SPIError", NULL, NULL);
*************** PLy_add_exceptions(PyObject *plpy)
*** 3621,3626 ****
--- 3720,3734 ----
  	PyModule_AddObject(plpy, "Fatal", PLy_exc_fatal);
  	Py_INCREF(PLy_exc_spi_error);
  	PyModule_AddObject(plpy, "SPIError", PLy_exc_spi_error);
+ 
+ 	memset(&hash_ctl, 0, sizeof(hash_ctl));
+ 	hash_ctl.keysize = sizeof(int);
+ 	hash_ctl.entrysize = sizeof(PLyExceptionEntry);
+ 	hash_ctl.hash = tag_hash;
+ 	PLy_spi_exceptions = hash_create("SPI exceptions", 256,
+ 									 &hash_ctl, HASH_ELEM | HASH_FUNCTION);
+ 
+ 	PLy_generate_spi_exceptions(excmod, PLy_exc_spi_error);
  }
  
  #if PY_MAJOR_VERSION >= 3
*************** PLy_exception_set_plural(PyObject *exc,
*** 3927,3933 ****
   * internal query and error position.
   */
  static void
! PLy_spi_exception_set(ErrorData *edata)
  {
  	PyObject	*args = NULL;
  	PyObject	*spierror = NULL;
--- 4035,4041 ----
   * internal query and error position.
   */
  static void
! PLy_spi_exception_set(PyObject *excclass, ErrorData *edata)
  {
  	PyObject	*args = NULL;
  	PyObject	*spierror = NULL;
*************** PLy_spi_exception_set(ErrorData *edata)
*** 3937,3944 ****
  	if (!args)
  		goto failure;
  
! 	/* create a new SPIError with the error message as the parameter */
! 	spierror = PyObject_CallObject(PLy_exc_spi_error, args);
  	if (!spierror)
  		goto failure;
  
--- 4045,4052 ----
  	if (!args)
  		goto failure;
  
! 	/* create a new SPI exception with the error message as the parameter */
! 	spierror = PyObject_CallObject(excclass, args);
  	if (!spierror)
  		goto failure;
  
*************** PLy_spi_exception_set(ErrorData *edata)
*** 3950,3956 ****
  	if (PyObject_SetAttrString(spierror, "spidata", spidata) == -1)
  		goto failure;
  
! 	PyErr_SetObject(PLy_exc_spi_error, spierror);
  
  	Py_DECREF(args);
  	Py_DECREF(spierror);
--- 4058,4064 ----
  	if (PyObject_SetAttrString(spierror, "spidata", spidata) == -1)
  		goto failure;
  
! 	PyErr_SetObject(excclass, spierror);
  
  	Py_DECREF(args);
  	Py_DECREF(spierror);
diff --git a/src/pl/plpython/sql/plpython_error.sql b/src/pl/plpython/sql/plpython_error.sql
index 876a8d6..798de83 100644
*** a/src/pl/plpython/sql/plpython_error.sql
--- b/src/pl/plpython/sql/plpython_error.sql
*************** return None
*** 108,113 ****
--- 108,134 ----
  
  SELECT valid_type('rick');
  
+ /* Check catching specific types of exceptions
+  */
+ CREATE TABLE specific (
+     i integer PRIMARY KEY
+ );
+ 
+ CREATE FUNCTION specific_exception(i integer) RETURNS void AS
+ $$
+ from plpy import spiexceptions
+ try:
+     plpy.execute("insert into specific values (%s)" % (i or "NULL"));
+ except spiexceptions.NotNullViolation, e:
+     plpy.notice("Violated the NOT NULL constraint, sqlstate %s" % e.sqlstate)
+ except spiexceptions.UniqueViolation, e:
+     plpy.notice("Violated the UNIQUE constraint, sqlstate %s" % e.sqlstate)
+ $$ LANGUAGE plpythonu;
+ 
+ SELECT specific_exception(2);
+ SELECT specific_exception(NULL);
+ SELECT specific_exception(2);
+ 
  /* manually starting subtransactions - a bad idea
   */
  CREATE FUNCTION manual_subxact() RETURNS void AS $$
diff --git a/src/tools/msvc/Solution.pm b/src/tools/msvc/Solution.pm
index b737e1e..5ad643e 100644
*** a/src/tools/msvc/Solution.pm
--- b/src/tools/msvc/Solution.pm
*************** s{PG_VERSION_STR "[^"]+"}{__STRINGIFY(x)
*** 273,278 ****
--- 273,284 ----
          );
      }
  
+     if ($self->{options}->{python} && IsNewer('src\pl\plpython\spiexceptions.h','src\include\utils\errcodes.h'))
+     {
+         print "Generating spiexceptions.h...\n";
+         system("perl src\pl\plpython\generate-spiexceptions.pl src\include\utils\errcodes.h > src\pl\plpython\spiexceptions.h");
+     }
+ 
      if (IsNewer('src\interfaces\libpq\libpq.rc','src\interfaces\libpq\libpq.rc.in'))
      {
          print "Generating libpq.rc...\n";
#7Jan Urbański
wulczer@wulczer.org
In reply to: Jan Urbański (#6)
1 attachment(s)
Re: pl/python custom exceptions for SPI

On 27/01/11 23:24, Jan Urbański wrote:

On 11/01/11 12:20, Jan Urbański wrote:

On 11/01/11 01:27, Tom Lane wrote:

Hannu Krosing <hannu@2ndquadrant.com> writes:

On 10.1.2011 17:20, Jan Urbański wrote:

I changed that patch to use Perl instead of sed to generate the
exceptions, which should be a more portable.

Updated as an incremental patch on to of the recently sent version of
explicit-subxacts.

Updated again.

Attachments:

plpython-custom-spi-exceptions.patchtext/x-patch; name=plpython-custom-spi-exceptions.patchDownload
diff --git a/src/pl/plpython/Makefile b/src/pl/plpython/Makefile
index 33dddc6..b3f0d0e 100644
*** a/src/pl/plpython/Makefile
--- b/src/pl/plpython/Makefile
*************** PSQLDIR = $(bindir)
*** 86,94 ****
--- 86,102 ----
  
  include $(top_srcdir)/src/Makefile.shlib
  
+ # Force this dependency to be known (see src/pl/plpgsql/src/Makefile)
+ plpython.o: spiexceptions.h
+ 
+ # Generate spiexceptions.h from utils/errcodes.h
+ spiexceptions.h: $(top_srcdir)/src/backend/utils/errcodes.txt generate-spiexceptions.pl
+ 	$(PERL) $(srcdir)/generate-spiexceptions.pl $< > $@
  
  all: all-lib
  
+ distprep: spiexceptions.h
+ 
  install: all installdirs install-lib
  ifeq ($(python_majorversion),2)
  	cd '$(DESTDIR)$(pkglibdir)' && rm -f plpython$(DLSUFFIX) && $(LN_S) $(shlib) plpython$(DLSUFFIX)
*************** endif
*** 134,143 ****
  submake:
  	$(MAKE) -C $(top_builddir)/src/test/regress pg_regress$(X)
  
! clean distclean maintainer-clean: clean-lib
  	rm -f $(OBJS)
  	rm -rf results
  	rm -f regression.diffs regression.out
  ifeq ($(PORTNAME), win32)
  	rm -f python${pytverstr}.def
  endif
--- 142,156 ----
  submake:
  	$(MAKE) -C $(top_builddir)/src/test/regress pg_regress$(X)
  
! clean distclean: clean-lib
  	rm -f $(OBJS)
  	rm -rf results
  	rm -f regression.diffs regression.out
+ 
+ # since we distribute spiexceptions.h, only remove it in maintainer-clean
+ maintainer-clean: clean distclean
+ 	rm -f spiexceptions.h
+ 
  ifeq ($(PORTNAME), win32)
  	rm -f python${pytverstr}.def
  endif
diff --git a/src/pl/plpython/expected/plpython_error.out b/src/pl/plpython/expected/plpython_error.out
index 7597ca7..afbc6db 100644
*** a/src/pl/plpython/expected/plpython_error.out
--- b/src/pl/plpython/expected/plpython_error.out
*************** CREATE FUNCTION sql_syntax_error() RETUR
*** 32,38 ****
  'plpy.execute("syntax error")'
          LANGUAGE plpythonu;
  SELECT sql_syntax_error();
! ERROR:  plpy.SPIError: syntax error at or near "syntax"
  LINE 1: syntax error
          ^
  QUERY:  syntax error
--- 32,38 ----
  'plpy.execute("syntax error")'
          LANGUAGE plpythonu;
  SELECT sql_syntax_error();
! ERROR:  spiexceptions.SyntaxError: syntax error at or near "syntax"
  LINE 1: syntax error
          ^
  QUERY:  syntax error
*************** CREATE FUNCTION exception_index_invalid_
*** 54,60 ****
  return rv[0]'
  	LANGUAGE plpythonu;
  SELECT exception_index_invalid_nested();
! ERROR:  plpy.SPIError: function test5(unknown) does not exist
  LINE 1: SELECT test5('foo')
                 ^
  HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
--- 54,60 ----
  return rv[0]'
  	LANGUAGE plpythonu;
  SELECT exception_index_invalid_nested();
! ERROR:  spiexceptions.UndefinedFunction: function test5(unknown) does not exist
  LINE 1: SELECT test5('foo')
                 ^
  HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
*************** return None
*** 74,80 ****
  '
  	LANGUAGE plpythonu;
  SELECT invalid_type_uncaught('rick');
! 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
   * the typo, and return None
--- 74,80 ----
  '
  	LANGUAGE plpythonu;
  SELECT invalid_type_uncaught('rick');
! ERROR:  spiexceptions.UndefinedObject: type "test" does not exist
  CONTEXT:  PL/Python function "invalid_type_uncaught"
  /* for what it's worth catch the exception generated by
   * the typo, and return None
*************** SELECT valid_type('rick');
*** 140,145 ****
--- 140,183 ----
   
  (1 row)
  
+ /* Check catching specific types of exceptions
+  */
+ CREATE TABLE specific (
+     i integer PRIMARY KEY
+ );
+ NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index "specific_pkey" for table "specific"
+ CREATE FUNCTION specific_exception(i integer) RETURNS void AS
+ $$
+ from plpy import spiexceptions
+ try:
+     plpy.execute("insert into specific values (%s)" % (i or "NULL"));
+ except spiexceptions.NotNullViolation, e:
+     plpy.notice("Violated the NOT NULL constraint, sqlstate %s" % e.sqlstate)
+ except spiexceptions.UniqueViolation, e:
+     plpy.notice("Violated the UNIQUE constraint, sqlstate %s" % e.sqlstate)
+ $$ LANGUAGE plpythonu;
+ SELECT specific_exception(2);
+  specific_exception 
+ --------------------
+  
+ (1 row)
+ 
+ SELECT specific_exception(NULL);
+ NOTICE:  Violated the NOT NULL constraint, sqlstate 23502
+ CONTEXT:  PL/Python function "specific_exception"
+  specific_exception 
+ --------------------
+  
+ (1 row)
+ 
+ SELECT specific_exception(2);
+ NOTICE:  Violated the UNIQUE constraint, sqlstate 23505
+ CONTEXT:  PL/Python function "specific_exception"
+  specific_exception 
+ --------------------
+  
+ (1 row)
+ 
  /* manually starting subtransactions - a bad idea
   */
  CREATE FUNCTION manual_subxact() RETURNS void AS $$
diff --git a/src/pl/plpython/expected/plpython_subxact.out b/src/pl/plpython/expected/plpython_subxact.out
index 7508883..d7808f2 100644
*** a/src/pl/plpython/expected/plpython_subxact.out
--- b/src/pl/plpython/expected/plpython_subxact.out
*************** SELECT * FROM subxact_tbl;
*** 43,49 ****
  
  TRUNCATE subxact_tbl;
  SELECT subxact_test('SPI');
! ERROR:  plpy.SPIError: invalid input syntax for integer: "oops"
  LINE 1: insert into subxact_tbl values('oops')
                                         ^
  QUERY:  insert into subxact_tbl values('oops')
--- 43,49 ----
  
  TRUNCATE subxact_tbl;
  SELECT subxact_test('SPI');
! ERROR:  spiexceptions.InvalidTextRepresentation: invalid input syntax for integer: "oops"
  LINE 1: insert into subxact_tbl values('oops')
                                         ^
  QUERY:  insert into subxact_tbl values('oops')
*************** SELECT * FROM subxact_tbl;
*** 90,96 ****
  
  TRUNCATE subxact_tbl;
  SELECT subxact_ctx_test('SPI');
! ERROR:  plpy.SPIError: invalid input syntax for integer: "oops"
  LINE 1: insert into subxact_tbl values('oops')
                                         ^
  QUERY:  insert into subxact_tbl values('oops')
--- 90,96 ----
  
  TRUNCATE subxact_tbl;
  SELECT subxact_ctx_test('SPI');
! ERROR:  spiexceptions.InvalidTextRepresentation: invalid input syntax for integer: "oops"
  LINE 1: insert into subxact_tbl values('oops')
                                         ^
  QUERY:  insert into subxact_tbl values('oops')
*************** with plpy.subtransaction():
*** 128,134 ****
  return "ok"
  $$ LANGUAGE plpythonu;
  SELECT subxact_nested_test();
! ERROR:  plpy.SPIError: syntax error at or near "error"
  LINE 1: error
          ^
  QUERY:  error
--- 128,134 ----
  return "ok"
  $$ LANGUAGE plpythonu;
  SELECT subxact_nested_test();
! ERROR:  spiexceptions.SyntaxError: syntax error at or near "error"
  LINE 1: error
          ^
  QUERY:  error
*************** SELECT * FROM subxact_tbl;
*** 140,146 ****
  
  TRUNCATE subxact_tbl;
  SELECT subxact_nested_test('t');
! NOTICE:  Swallowed SPIError('syntax error at or near "error"',)
  CONTEXT:  PL/Python function "subxact_nested_test"
   subxact_nested_test 
  ---------------------
--- 140,146 ----
  
  TRUNCATE subxact_tbl;
  SELECT subxact_nested_test('t');
! NOTICE:  Swallowed SyntaxError('syntax error at or near "error"',)
  CONTEXT:  PL/Python function "subxact_nested_test"
   subxact_nested_test 
  ---------------------
*************** with plpy.subtransaction():
*** 166,172 ****
  return "ok"
  $$ LANGUAGE plpythonu;
  SELECT subxact_deeply_nested_test();
! NOTICE:  Swallowed SPIError('syntax error at or near "error"',)
  CONTEXT:  PL/Python function "subxact_nested_test"
  SQL statement "select subxact_nested_test('t')"
  PL/Python function "subxact_nested_test"
--- 166,172 ----
  return "ok"
  $$ LANGUAGE plpythonu;
  SELECT subxact_deeply_nested_test();
! NOTICE:  Swallowed SyntaxError('syntax error at or near "error"',)
  CONTEXT:  PL/Python function "subxact_nested_test"
  SQL statement "select subxact_nested_test('t')"
  PL/Python function "subxact_nested_test"
diff --git a/src/pl/plpython/expected/plpython_test.out b/src/pl/plpython/expected/plpython_test.out
index c7d875e..2cb2ef6 100644
*** a/src/pl/plpython/expected/plpython_test.out
--- b/src/pl/plpython/expected/plpython_test.out
*************** contents.sort()
*** 43,51 ****
  return ", ".join(contents)
  $$ LANGUAGE plpythonu;
  select module_contents();
!                                               module_contents                                              
! -----------------------------------------------------------------------------------------------------------
!  Error, Fatal, SPIError, debug, error, execute, fatal, info, log, notice, prepare, subtransaction, warning
  (1 row)
  
  CREATE FUNCTION elog_test() RETURNS void
--- 43,51 ----
  return ", ".join(contents)
  $$ LANGUAGE plpythonu;
  select module_contents();
!                                                      module_contents                                                      
! --------------------------------------------------------------------------------------------------------------------------
!  Error, Fatal, SPIError, debug, error, execute, fatal, info, log, notice, prepare, spiexceptions, subtransaction, warning
  (1 row)
  
  CREATE FUNCTION elog_test() RETURNS void
diff --git a/src/pl/plpython/generate-spiexceptions.pl b/src/pl/plpython/generate-spiexceptions.pl
index ...cf050d1 .
*** a/src/pl/plpython/generate-spiexceptions.pl
--- b/src/pl/plpython/generate-spiexceptions.pl
***************
*** 0 ****
--- 1,44 ----
+ #!/usr/bin/perl
+ #
+ # Generate the spiexceptions.h header from errcodes.txt
+ # Copyright (c) 2000-2011, PostgreSQL Global Development Group
+ 
+ use warnings;
+ use strict;
+ 
+ print "/* autogenerated from src/backend/utils/errcodes.txt, do not edit */\n";
+ print "/* there is deliberately not an #ifndef SPIEXCEPTIONS_H here */\n";
+ 
+ open my $errcodes, $ARGV[0] or die;
+ 
+ while (<$errcodes>) {
+     chomp;
+ 
+     # Skip comments
+     next if /^#/;
+     next if /^\s*$/;
+ 
+     # Skip section headers
+     next if /^Section:/;
+ 
+     die unless /^([^\s]{5})\s+([EWS])\s+([^\s]+)(?:\s+)?([^\s]+)?/;
+ 
+     (my $sqlstate,
+      my $type,
+      my $errcode_macro,
+      my $condition_name) = ($1, $2, $3, $4);
+ 
+     # Skip non-errors
+     next unless $type eq 'E';
+ 
+     # Skip lines without PL/pgSQL condition names
+     next unless defined($condition_name);
+ 
+     # Change some_error_condition to SomeErrorCondition
+     $condition_name =~ s/([a-z])([^_]*)(?:_|$)/\u$1$2/g;
+ 
+     print "{ \"spiexceptions.$condition_name\", " .
+ 	"\"$condition_name\", $errcode_macro },\n";
+ }
+ 
+ close $errcodes;
diff --git a/src/pl/plpython/plpython.c b/src/pl/plpython/plpython.c
index 7692c0b..7637116 100644
*** a/src/pl/plpython/plpython.c
--- b/src/pl/plpython/plpython.c
*************** typedef struct PLySubxactObject
*** 261,266 ****
--- 261,288 ----
  	bool	exited;
  } PLySubxactObject;
  
+ /* A list of all known exceptions, generated from backend/utils/errcodes.txt */
+ typedef struct ExceptionMap
+ {
+ 	char		*name;
+ 	char		*classname;
+ 	int			sqlstate;
+ } ExceptionMap;
+ 
+ static const ExceptionMap exception_map[] = {
+ #include "spiexceptions.h"
+ 	{NULL, NULL, 0}
+ };
+ 
+ /* A hashtable mapping sqlstates to exceptions, for speedy lookup */
+ static HTAB *PLy_spi_exceptions;
+ 
+ typedef struct PLyExceptionEntry
+ {
+ 	int			sqlstate;	/* hash key, must be first */
+ 	PyObject	*exc;		/* corresponding exception */
+ } PLyExceptionEntry;
+ 
  /* function declarations */
  
  #if PY_MAJOR_VERSION >= 3
*************** __attribute__((format(printf, 2, 5)))
*** 302,308 ****
  __attribute__((format(printf, 3, 5)));
  
  /* like PLy_exception_set, but conserve more fields from ErrorData */
! static void PLy_spi_exception_set(ErrorData *edata);
  
  /* Get the innermost python procedure called from the backend */
  static char *PLy_procedure_name(PLyProcedure *);
--- 324,330 ----
  __attribute__((format(printf, 3, 5)));
  
  /* like PLy_exception_set, but conserve more fields from ErrorData */
! static void PLy_spi_exception_set(PyObject *excclass, ErrorData *edata);
  
  /* Get the innermost python procedure called from the backend */
  static char *PLy_procedure_name(PLyProcedure *);
*************** static PyMethodDef PLy_methods[] = {
*** 2805,2810 ****
--- 2827,2836 ----
  	{NULL, NULL, 0, NULL}
  };
  
+ static PyMethodDef PLy_exc_methods[] = {
+ 	{NULL, NULL, 0, NULL}
+ };
+ 
  #if PY_MAJOR_VERSION >= 3
  static PyModuleDef PLy_module = {
  	PyModuleDef_HEAD_INIT,		/* m_base */
*************** static PyModuleDef PLy_module = {
*** 2813,2818 ****
--- 2839,2856 ----
  	-1,							/* m_size */
  	PLy_methods,				/* m_methods */
  };
+ 
+ static PyModuleDef PLy_exc_module = {
+ 	PyModuleDef_HEAD_INIT,		/* m_base */
+ 	"spiexceptions",			/* m_name */
+ 	NULL,						/* m_doc */
+ 	-1,							/* m_size */
+ 	PLy_exc_methods,			/* m_methods */
+ 	NULL,						/* m_reload */
+ 	NULL,						/* m_traverse */
+ 	NULL,						/* m_clear */
+ 	NULL						/* m_free */
+ };
  #endif
  
  /* plan object methods */
*************** PLy_spi_prepare(PyObject *self, PyObject
*** 3116,3122 ****
  	}
  	PG_CATCH();
  	{
! 		ErrorData	*edata;
  
  		/* Save error info */
  		MemoryContextSwitchTo(oldcontext);
--- 3154,3162 ----
  	}
  	PG_CATCH();
  	{
! 		ErrorData				*edata;
! 		PLyExceptionEntry		*entry;
! 		PyObject				*exc;
  
  		/* Save error info */
  		MemoryContextSwitchTo(oldcontext);
*************** PLy_spi_prepare(PyObject *self, PyObject
*** 3140,3147 ****
  			SPI_restore_connection();
  		}
  
  		/* Make Python raise the exception */
! 		PLy_spi_exception_set(edata);
  		return NULL;
  	}
  	PG_END_TRY();
--- 3180,3193 ----
  			SPI_restore_connection();
  		}
  
+ 		/* Look up the correct exception */
+ 		entry = hash_search(PLy_spi_exceptions, &(edata->sqlerrcode),
+ 							HASH_FIND, NULL);
+ 		/* We really should find it, but just in case have a fallback */
+ 		Assert(entry != NULL);
+ 		exc = entry ? entry->exc : PLy_exc_spi_error;
  		/* Make Python raise the exception */
! 		PLy_spi_exception_set(exc, edata);
  		return NULL;
  	}
  	PG_END_TRY();
*************** PLy_spi_execute_plan(PyObject *ob, PyObj
*** 3296,3303 ****
  	}
  	PG_CATCH();
  	{
  		int			k;
- 		ErrorData	*edata;
  
  		/* Save error info */
  		MemoryContextSwitchTo(oldcontext);
--- 3342,3351 ----
  	}
  	PG_CATCH();
  	{
+ 		ErrorData				*edata;
+ 		PLyExceptionEntry		*entry;
+ 		PyObject				*exc;
  		int			k;
  
  		/* Save error info */
  		MemoryContextSwitchTo(oldcontext);
*************** PLy_spi_execute_plan(PyObject *ob, PyObj
*** 3332,3339 ****
  			SPI_restore_connection();
  		}
  
  		/* Make Python raise the exception */
! 		PLy_spi_exception_set(edata);
  		return NULL;
  	}
  	PG_END_TRY();
--- 3380,3393 ----
  			SPI_restore_connection();
  		}
  
+ 		/* Look up the correct exception */
+ 		entry = hash_search(PLy_spi_exceptions, &(edata->sqlerrcode),
+ 							HASH_FIND, NULL);
+ 		/* We really should find it, but just in case have a fallback */
+ 		Assert(entry != NULL);
+ 		exc = entry ? entry->exc : PLy_exc_spi_error;
  		/* Make Python raise the exception */
! 		PLy_spi_exception_set(exc, edata);
  		return NULL;
  	}
  	PG_END_TRY();
*************** PLy_spi_execute_query(char *query, long
*** 3403,3409 ****
  	}
  	PG_CATCH();
  	{
! 		ErrorData	*edata;
  
  		/* Save error info */
  		MemoryContextSwitchTo(oldcontext);
--- 3457,3465 ----
  	}
  	PG_CATCH();
  	{
! 		ErrorData				*edata;
! 		PLyExceptionEntry		*entry;
! 		PyObject				*exc;
  
  		/* Save error info */
  		MemoryContextSwitchTo(oldcontext);
*************** PLy_spi_execute_query(char *query, long
*** 3425,3432 ****
  			SPI_restore_connection();
  		}
  
  		/* Make Python raise the exception */
! 		PLy_spi_exception_set(edata);
  		return NULL;
  	}
  	PG_END_TRY();
--- 3481,3494 ----
  			SPI_restore_connection();
  		}
  
+ 		/* Look up the correct exception */
+ 		entry = hash_search(PLy_spi_exceptions, &edata->sqlerrcode,
+ 							HASH_FIND, NULL);
+ 		/* We really should find it, but just in case have a fallback */
+ 		Assert(entry != NULL);
+ 		exc = entry ? entry->exc : PLy_exc_spi_error;
  		/* Make Python raise the exception */
! 		PLy_spi_exception_set(exc, edata);
  		return NULL;
  	}
  	PG_END_TRY();
*************** PLy_subxact_exit(PyObject *self, PyObjec
*** 3654,3662 ****
--- 3716,3761 ----
  /*
   * Add exceptions to the plpy module
   */
+ /* Add all the autogenerated exceptions as subclasses of SPIError */
+ static void
+ PLy_generate_spi_exceptions(PyObject *mod, PyObject *base)
+ {
+ 	int	i;
+ 
+ 	for (i = 0; exception_map[i].name != NULL; i++)
+ 	{
+ 		bool					 found;
+ 		PyObject				*exc;
+ 		PLyExceptionEntry		*entry;
+ 		PyObject				*sqlstate;
+ 		PyObject				*dict = PyDict_New();
+ 
+ 		sqlstate = PyString_FromString(unpack_sql_state(
+ 										   exception_map[i].sqlstate));
+ 		PyDict_SetItemString(dict, "sqlstate", sqlstate);
+ 		Py_DECREF(sqlstate);
+ 		exc = PyErr_NewException(exception_map[i].name, base, dict);
+ 		PyModule_AddObject(mod, exception_map[i].classname, exc);
+ 		entry = hash_search(PLy_spi_exceptions, &exception_map[i].sqlstate,
+ 							HASH_ENTER, &found);
+ 		entry->exc = exc;
+ 		Assert(!found);
+ 	}
+ }
+ 
  static void
  PLy_add_exceptions(PyObject *plpy)
  {
+ 	PyObject	*excmod;
+ 	HASHCTL		 hash_ctl;
+ #if PY_MAJOR_VERSION < 3
+ 	excmod = Py_InitModule("spiexceptions", PLy_exc_methods);
+ #else
+ 	excmod = PyModule_Create(&PLy_exc_module);
+ #endif
+ 	if (PyModule_AddObject(plpy, "spiexceptions", excmod) < 0)
+ 		PLy_elog(ERROR, "Failed to add the spiexceptions module");
+ 
  	PLy_exc_error = PyErr_NewException("plpy.Error", NULL, NULL);
  	PLy_exc_fatal = PyErr_NewException("plpy.Fatal", NULL, NULL);
  	PLy_exc_spi_error = PyErr_NewException("plpy.SPIError", NULL, NULL);
*************** PLy_add_exceptions(PyObject *plpy)
*** 3667,3672 ****
--- 3766,3780 ----
  	PyModule_AddObject(plpy, "Fatal", PLy_exc_fatal);
  	Py_INCREF(PLy_exc_spi_error);
  	PyModule_AddObject(plpy, "SPIError", PLy_exc_spi_error);
+ 
+ 	memset(&hash_ctl, 0, sizeof(hash_ctl));
+ 	hash_ctl.keysize = sizeof(int);
+ 	hash_ctl.entrysize = sizeof(PLyExceptionEntry);
+ 	hash_ctl.hash = tag_hash;
+ 	PLy_spi_exceptions = hash_create("SPI exceptions", 256,
+ 									 &hash_ctl, HASH_ELEM | HASH_FUNCTION);
+ 
+ 	PLy_generate_spi_exceptions(excmod, PLy_exc_spi_error);
  }
  
  #if PY_MAJOR_VERSION >= 3
*************** PLy_exception_set_plural(PyObject *exc,
*** 3973,3979 ****
   * internal query and error position.
   */
  static void
! PLy_spi_exception_set(ErrorData *edata)
  {
  	PyObject	*args = NULL;
  	PyObject	*spierror = NULL;
--- 4081,4087 ----
   * internal query and error position.
   */
  static void
! PLy_spi_exception_set(PyObject *excclass, ErrorData *edata)
  {
  	PyObject	*args = NULL;
  	PyObject	*spierror = NULL;
*************** PLy_spi_exception_set(ErrorData *edata)
*** 3983,3990 ****
  	if (!args)
  		goto failure;
  
! 	/* create a new SPIError with the error message as the parameter */
! 	spierror = PyObject_CallObject(PLy_exc_spi_error, args);
  	if (!spierror)
  		goto failure;
  
--- 4091,4098 ----
  	if (!args)
  		goto failure;
  
! 	/* create a new SPI exception with the error message as the parameter */
! 	spierror = PyObject_CallObject(excclass, args);
  	if (!spierror)
  		goto failure;
  
*************** PLy_spi_exception_set(ErrorData *edata)
*** 3996,4002 ****
  	if (PyObject_SetAttrString(spierror, "spidata", spidata) == -1)
  		goto failure;
  
! 	PyErr_SetObject(PLy_exc_spi_error, spierror);
  
  	Py_DECREF(args);
  	Py_DECREF(spierror);
--- 4104,4110 ----
  	if (PyObject_SetAttrString(spierror, "spidata", spidata) == -1)
  		goto failure;
  
! 	PyErr_SetObject(excclass, spierror);
  
  	Py_DECREF(args);
  	Py_DECREF(spierror);
diff --git a/src/pl/plpython/sql/plpython_error.sql b/src/pl/plpython/sql/plpython_error.sql
index 7861cd6..6d6a163 100644
*** a/src/pl/plpython/sql/plpython_error.sql
--- b/src/pl/plpython/sql/plpython_error.sql
*************** return None
*** 131,136 ****
--- 131,157 ----
  
  SELECT valid_type('rick');
  
+ /* Check catching specific types of exceptions
+  */
+ CREATE TABLE specific (
+     i integer PRIMARY KEY
+ );
+ 
+ CREATE FUNCTION specific_exception(i integer) RETURNS void AS
+ $$
+ from plpy import spiexceptions
+ try:
+     plpy.execute("insert into specific values (%s)" % (i or "NULL"));
+ except spiexceptions.NotNullViolation, e:
+     plpy.notice("Violated the NOT NULL constraint, sqlstate %s" % e.sqlstate)
+ except spiexceptions.UniqueViolation, e:
+     plpy.notice("Violated the UNIQUE constraint, sqlstate %s" % e.sqlstate)
+ $$ LANGUAGE plpythonu;
+ 
+ SELECT specific_exception(2);
+ SELECT specific_exception(NULL);
+ SELECT specific_exception(2);
+ 
  /* manually starting subtransactions - a bad idea
   */
  CREATE FUNCTION manual_subxact() RETURNS void AS $$
diff --git a/src/tools/msvc/Solution.pm b/src/tools/msvc/Solution.pm
index 49504d7..e1fe4e9 100644
*** a/src/tools/msvc/Solution.pm
--- b/src/tools/msvc/Solution.pm
*************** s{PG_VERSION_STR "[^"]+"}{__STRINGIFY(x)
*** 273,278 ****
--- 273,284 ----
          );
      }
  
+     if ($self->{options}->{python} && IsNewer('src\pl\plpython\spiexceptions.h','src\include\backend\errcodes.txt'))
+     {
+         print "Generating spiexceptions.h...\n";
+         system('perl src\pl\plpython\generate-spiexceptions.pl src\backend\utils\errcodes.txt > src\pl\plpython\spiexceptions.h');
+     }
+ 
      if (IsNewer('src\include\utils\errcodes.h','src\backend\utils\errcodes.txt'))
      {
          print "Generating errcodes.h...\n";
#8Peter Eisentraut
peter_e@gmx.net
In reply to: Jan Urbański (#7)
Re: pl/python custom exceptions for SPI

On sön, 2011-02-06 at 20:44 +0100, Jan Urbański wrote:

On 27/01/11 23:24, Jan Urbański wrote:

On 11/01/11 12:20, Jan Urbański wrote:

On 11/01/11 01:27, Tom Lane wrote:

Hannu Krosing <hannu@2ndquadrant.com> writes:

On 10.1.2011 17:20, Jan Urbański wrote:

I changed that patch to use Perl instead of sed to generate the
exceptions, which should be a more portable.

Updated as an incremental patch on to of the recently sent version of
explicit-subxacts.

Updated again.

Why do the error messages print spiexceptions.SyntaxError instead of
plpy.spiexceptions.SyntaxError? Is this intentional or just the way it
comes out of Python?

Please add some documentation. Not a list of all exceptions, but at
least a paragraph that various kinds of specific exceptions may be
generated, what package and module they are in, and how they relate.

#9Jan Urbański
wulczer@wulczer.org
In reply to: Peter Eisentraut (#8)
Re: pl/python custom exceptions for SPI

On 10/02/11 20:24, Peter Eisentraut wrote:

On sön, 2011-02-06 at 20:44 +0100, Jan Urbański wrote:

On 27/01/11 23:24, Jan Urbański wrote:

On 11/01/11 12:20, Jan Urbański wrote:

On 11/01/11 01:27, Tom Lane wrote:

Hannu Krosing <hannu@2ndquadrant.com> writes:

On 10.1.2011 17:20, Jan Urbański wrote:

I changed that patch to use Perl instead of sed to generate the
exceptions, which should be a more portable.

Updated as an incremental patch on to of the recently sent version of
explicit-subxacts.

Updated again.

Why do the error messages print spiexceptions.SyntaxError instead of
plpy.spiexceptions.SyntaxError? Is this intentional or just the way it
comes out of Python?

That's how traceback.format_exception() works IIRC, which is what the
Python interpreter uses and what PL/Python mimicks in PLy_traceback.

Please add some documentation. Not a list of all exceptions, but at
least a paragraph that various kinds of specific exceptions may be
generated, what package and module they are in, and how they relate.

Sure, Steve already asked for docs in another thread, and I'm writing them.

Jan

#10Steve Singer
ssinger_pg@sympatico.ca
In reply to: Jan Urbański (#9)
Re: pl/python custom exceptions for SPI

On 11-02-10 03:13 PM, Jan Urbański wrote:

On 10/02/11 20:24, Peter Eisentraut wrote:

Here is the rest of my review.

Submission Review
-------------------
Patch applies cleanly.
Documentation is still outstanding but Jan has promised it soon.

Usability Review
-------------------
We don't have this for plpython, that we have a similar idea with
plpgsql. I think this feature is useful and worth having.

The CamelCase naming of the exceptions is consistent with how the
built-in python exceptions are named (camel case).

Feature Test
---------------
I did basic testing of the feature (catching a few exception types
thrown from both direct SQL and prepared statements) and the feature
worked as expected.

Performance Impact
--------------------
The impact of mapping error codes to exception types shouldn't come into
play unless an SPI error is returned and with the hash it should still
be minimal.

Code Review
-------------

Ideally char * members of ExceptionMap would be const, but since many
versions of python take a non-const value to PyErr_NewException that
won't work :(

After you search the for an exception in the hash you have:

/* We really should find it, but just in case have a fallback */
Assert(entry != NULL);
exc = entry ? entry->exc : PLy_exc_spi_error;

I'm not sure the assert is needed here. Just falling back to the
exception type seems reasonable and more desirable than an assert if
showhow a new exception gets missed from the list. I don't feel that
strongly on this.

line 3575: PLy_elog(ERROR, "Failed to add the spiexceptions module");
"Failed" should be "failed"

Other than that the patch looks fine to me.

Show quoted text

Updated again.

Why do the error messages print spiexceptions.SyntaxError instead of
plpy.spiexceptions.SyntaxError? Is this intentional or just the way it
comes out of Python?

That's how traceback.format_exception() works IIRC, which is what the
Python interpreter uses and what PL/Python mimicks in PLy_traceback.

Please add some documentation. Not a list of all exceptions, but at
least a paragraph that various kinds of specific exceptions may be
generated, what package and module they are in, and how they relate.

Sure, Steve already asked for docs in another thread, and I'm writing them.

Jan

#11Jan Urbański
wulczer@wulczer.org
In reply to: Steve Singer (#10)
Re: pl/python custom exceptions for SPI

On 10/02/11 22:26, Steve Singer wrote:

On 11-02-10 03:13 PM, Jan Urbański wrote:

On 10/02/11 20:24, Peter Eisentraut wrote:

Here is the rest of my review.

Thanks!

Ideally char * members of ExceptionMap would be const, but since many
versions of python take a non-const value to PyErr_NewException that
won't work :(

Yeah, I got "discards qualifier" warnings when I tried to declare it as
const :(

After you search the for an exception in the hash you have:

/* We really should find it, but just in case have a fallback */
Assert(entry != NULL);
exc = entry ? entry->exc : PLy_exc_spi_error;

I'm not sure the assert is needed here. Just falling back to the
exception type seems reasonable and more desirable than an assert if
showhow a new exception gets missed from the list. I don't feel that
strongly on this.

Maybe the comment doesn't explain this enough. My intention was that in
regular builds you have a fallback and if you're missing an entry in the
exceptions hash, you just get SPIError. But in assert-enabled builds you
get an error, so you can detect it and fix the exceptions hash.

line 3575: PLy_elog(ERROR, "Failed to add the spiexceptions module");
"Failed" should be "failed"

Gah, I'll never learn. Will fix.

Other than that the patch looks fine to me.

Thanks, I'll have the docs ready by today (and I should've have them by
yesterday :/).

Jan

#12Jan Urbański
wulczer@wulczer.org
In reply to: Jan Urbański (#11)
1 attachment(s)
Re: pl/python custom exceptions for SPI

On 11/02/11 10:53, Jan Urbański wrote:

On 10/02/11 22:26, Steve Singer wrote:

Here's an updated patch with documentation. It's an incremental patch on
top of the latest explicit-subxacts version.

Cheers,
Jan

Attachments:

plpython-custom-spi-exceptions.patchtext/x-patch; name=plpython-custom-spi-exceptions.patchDownload
diff --git a/doc/src/sgml/plpython.sgml b/doc/src/sgml/plpython.sgml
index 87be8c2..aee54bf 100644
*** a/doc/src/sgml/plpython.sgml
--- b/doc/src/sgml/plpython.sgml
*************** $$ LANGUAGE plpythonu;
*** 950,960 ****
      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:
--- 950,959 ----
      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 a subclass 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:
*************** CREATE FUNCTION try_adding_joe() RETURNS
*** 966,971 ****
--- 965,1007 ----
  $$ LANGUAGE plpythonu;
  </programlisting>
     </para>
+    <para>
+     The actual class of the exception being raised corresponds to exact
+     condition that caused the error (refer to <xref linkend="errcodes-table">
+     for a list of possible conditions).  The
+     <literal>plpy.spiexceptions</literal> module defines an exception class for
+     each <productname>PostgreSQL</productname> condition, deriving their names
+     from the condition name.  For instance, <literal>division_by_zero</literal>
+     becomes <literal>DivisionByZero</literal>, <literal>unique_violation</literal>
+     becomes <literal>UniqueViolation</literal>, <literal>fdw_error</literal>
+     becomes <literal>FdwError</literal> and so on.  Each of these exception
+     classes inherits from <literal>SPIError</literal>.  This separation makes
+     it easier to handle specific errors, for instance:
+ <programlisting>
+ CREATE FUNCTION insert_fraction(numerator int, denominator int) RETURNS text AS $$
+     from plpy import spiexceptions
+     try:
+         plpy.execute("INSERT INTO fractions(frac) VALUES (%d / %d)" %
+                     (numerator, denominator))
+     except spiexceptions.DivisionByZero:
+         return "denominator cannot equal zero"
+     except spiexceptions.UniqueViolation:
+         return "already have that fraction"
+     except plpy.SPIError, e:
+         return "other error, SQLSTATE %s" % e.sqlstate
+     else:
+         return "fraction inserted"
+ $$ LANGUAGE plpythonu;
+ </programlisting>
+    </para>
+    <para>
+     Note that because all exceptions from
+     the <literal>plpy.spiexceptions</literal> module inherit
+     from <literal>SPIError</literal>, an <literal>except</literal> clause
+     handling it will catch any database access error. You can differentiate
+     inside the <literal>except</literal> block by looking at
+     the <literal>sqlstate</literal> string attribute.
+    </para>
    </sect2>
   </sect1>
  
diff --git a/src/pl/plpython/Makefile b/src/pl/plpython/Makefile
index 33dddc6..b3f0d0e 100644
*** a/src/pl/plpython/Makefile
--- b/src/pl/plpython/Makefile
*************** PSQLDIR = $(bindir)
*** 86,94 ****
--- 86,102 ----
  
  include $(top_srcdir)/src/Makefile.shlib
  
+ # Force this dependency to be known (see src/pl/plpgsql/src/Makefile)
+ plpython.o: spiexceptions.h
+ 
+ # Generate spiexceptions.h from utils/errcodes.h
+ spiexceptions.h: $(top_srcdir)/src/backend/utils/errcodes.txt generate-spiexceptions.pl
+ 	$(PERL) $(srcdir)/generate-spiexceptions.pl $< > $@
  
  all: all-lib
  
+ distprep: spiexceptions.h
+ 
  install: all installdirs install-lib
  ifeq ($(python_majorversion),2)
  	cd '$(DESTDIR)$(pkglibdir)' && rm -f plpython$(DLSUFFIX) && $(LN_S) $(shlib) plpython$(DLSUFFIX)
*************** endif
*** 134,143 ****
  submake:
  	$(MAKE) -C $(top_builddir)/src/test/regress pg_regress$(X)
  
! clean distclean maintainer-clean: clean-lib
  	rm -f $(OBJS)
  	rm -rf results
  	rm -f regression.diffs regression.out
  ifeq ($(PORTNAME), win32)
  	rm -f python${pytverstr}.def
  endif
--- 142,156 ----
  submake:
  	$(MAKE) -C $(top_builddir)/src/test/regress pg_regress$(X)
  
! clean distclean: clean-lib
  	rm -f $(OBJS)
  	rm -rf results
  	rm -f regression.diffs regression.out
+ 
+ # since we distribute spiexceptions.h, only remove it in maintainer-clean
+ maintainer-clean: clean distclean
+ 	rm -f spiexceptions.h
+ 
  ifeq ($(PORTNAME), win32)
  	rm -f python${pytverstr}.def
  endif
diff --git a/src/pl/plpython/expected/plpython_error.out b/src/pl/plpython/expected/plpython_error.out
index 7597ca7..afbc6db 100644
*** a/src/pl/plpython/expected/plpython_error.out
--- b/src/pl/plpython/expected/plpython_error.out
*************** CREATE FUNCTION sql_syntax_error() RETUR
*** 32,38 ****
  'plpy.execute("syntax error")'
          LANGUAGE plpythonu;
  SELECT sql_syntax_error();
! ERROR:  plpy.SPIError: syntax error at or near "syntax"
  LINE 1: syntax error
          ^
  QUERY:  syntax error
--- 32,38 ----
  'plpy.execute("syntax error")'
          LANGUAGE plpythonu;
  SELECT sql_syntax_error();
! ERROR:  spiexceptions.SyntaxError: syntax error at or near "syntax"
  LINE 1: syntax error
          ^
  QUERY:  syntax error
*************** CREATE FUNCTION exception_index_invalid_
*** 54,60 ****
  return rv[0]'
  	LANGUAGE plpythonu;
  SELECT exception_index_invalid_nested();
! ERROR:  plpy.SPIError: function test5(unknown) does not exist
  LINE 1: SELECT test5('foo')
                 ^
  HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
--- 54,60 ----
  return rv[0]'
  	LANGUAGE plpythonu;
  SELECT exception_index_invalid_nested();
! ERROR:  spiexceptions.UndefinedFunction: function test5(unknown) does not exist
  LINE 1: SELECT test5('foo')
                 ^
  HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
*************** return None
*** 74,80 ****
  '
  	LANGUAGE plpythonu;
  SELECT invalid_type_uncaught('rick');
! 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
   * the typo, and return None
--- 74,80 ----
  '
  	LANGUAGE plpythonu;
  SELECT invalid_type_uncaught('rick');
! ERROR:  spiexceptions.UndefinedObject: type "test" does not exist
  CONTEXT:  PL/Python function "invalid_type_uncaught"
  /* for what it's worth catch the exception generated by
   * the typo, and return None
*************** SELECT valid_type('rick');
*** 140,145 ****
--- 140,183 ----
   
  (1 row)
  
+ /* Check catching specific types of exceptions
+  */
+ CREATE TABLE specific (
+     i integer PRIMARY KEY
+ );
+ NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index "specific_pkey" for table "specific"
+ CREATE FUNCTION specific_exception(i integer) RETURNS void AS
+ $$
+ from plpy import spiexceptions
+ try:
+     plpy.execute("insert into specific values (%s)" % (i or "NULL"));
+ except spiexceptions.NotNullViolation, e:
+     plpy.notice("Violated the NOT NULL constraint, sqlstate %s" % e.sqlstate)
+ except spiexceptions.UniqueViolation, e:
+     plpy.notice("Violated the UNIQUE constraint, sqlstate %s" % e.sqlstate)
+ $$ LANGUAGE plpythonu;
+ SELECT specific_exception(2);
+  specific_exception 
+ --------------------
+  
+ (1 row)
+ 
+ SELECT specific_exception(NULL);
+ NOTICE:  Violated the NOT NULL constraint, sqlstate 23502
+ CONTEXT:  PL/Python function "specific_exception"
+  specific_exception 
+ --------------------
+  
+ (1 row)
+ 
+ SELECT specific_exception(2);
+ NOTICE:  Violated the UNIQUE constraint, sqlstate 23505
+ CONTEXT:  PL/Python function "specific_exception"
+  specific_exception 
+ --------------------
+  
+ (1 row)
+ 
  /* manually starting subtransactions - a bad idea
   */
  CREATE FUNCTION manual_subxact() RETURNS void AS $$
diff --git a/src/pl/plpython/expected/plpython_subxact.out b/src/pl/plpython/expected/plpython_subxact.out
index 7a478fc..c8649fd 100644
*** a/src/pl/plpython/expected/plpython_subxact.out
--- b/src/pl/plpython/expected/plpython_subxact.out
*************** SELECT * FROM subxact_tbl;
*** 43,49 ****
  
  TRUNCATE subxact_tbl;
  SELECT subxact_test('SPI');
! ERROR:  plpy.SPIError: invalid input syntax for integer: "oops"
  LINE 1: insert into subxact_tbl values('oops')
                                         ^
  QUERY:  insert into subxact_tbl values('oops')
--- 43,49 ----
  
  TRUNCATE subxact_tbl;
  SELECT subxact_test('SPI');
! ERROR:  spiexceptions.InvalidTextRepresentation: invalid input syntax for integer: "oops"
  LINE 1: insert into subxact_tbl values('oops')
                                         ^
  QUERY:  insert into subxact_tbl values('oops')
*************** SELECT * FROM subxact_tbl;
*** 90,96 ****
  
  TRUNCATE subxact_tbl;
  SELECT subxact_ctx_test('SPI');
! ERROR:  plpy.SPIError: invalid input syntax for integer: "oops"
  LINE 1: insert into subxact_tbl values('oops')
                                         ^
  QUERY:  insert into subxact_tbl values('oops')
--- 90,96 ----
  
  TRUNCATE subxact_tbl;
  SELECT subxact_ctx_test('SPI');
! ERROR:  spiexceptions.InvalidTextRepresentation: invalid input syntax for integer: "oops"
  LINE 1: insert into subxact_tbl values('oops')
                                         ^
  QUERY:  insert into subxact_tbl values('oops')
*************** with plpy.subtransaction():
*** 128,134 ****
  return "ok"
  $$ LANGUAGE plpythonu;
  SELECT subxact_nested_test();
! ERROR:  plpy.SPIError: syntax error at or near "error"
  LINE 1: error
          ^
  QUERY:  error
--- 128,134 ----
  return "ok"
  $$ LANGUAGE plpythonu;
  SELECT subxact_nested_test();
! ERROR:  spiexceptions.SyntaxError: syntax error at or near "error"
  LINE 1: error
          ^
  QUERY:  error
*************** SELECT * FROM subxact_tbl;
*** 140,146 ****
  
  TRUNCATE subxact_tbl;
  SELECT subxact_nested_test('t');
! NOTICE:  Swallowed SPIError('syntax error at or near "error"',)
  CONTEXT:  PL/Python function "subxact_nested_test"
   subxact_nested_test 
  ---------------------
--- 140,146 ----
  
  TRUNCATE subxact_tbl;
  SELECT subxact_nested_test('t');
! NOTICE:  Swallowed SyntaxError('syntax error at or near "error"',)
  CONTEXT:  PL/Python function "subxact_nested_test"
   subxact_nested_test 
  ---------------------
*************** with plpy.subtransaction():
*** 166,172 ****
  return "ok"
  $$ LANGUAGE plpythonu;
  SELECT subxact_deeply_nested_test();
! NOTICE:  Swallowed SPIError('syntax error at or near "error"',)
  CONTEXT:  PL/Python function "subxact_nested_test"
  SQL statement "select subxact_nested_test('t')"
  PL/Python function "subxact_nested_test"
--- 166,172 ----
  return "ok"
  $$ LANGUAGE plpythonu;
  SELECT subxact_deeply_nested_test();
! NOTICE:  Swallowed SyntaxError('syntax error at or near "error"',)
  CONTEXT:  PL/Python function "subxact_nested_test"
  SQL statement "select subxact_nested_test('t')"
  PL/Python function "subxact_nested_test"
diff --git a/src/pl/plpython/expected/plpython_test.out b/src/pl/plpython/expected/plpython_test.out
index c7d875e..2cb2ef6 100644
*** a/src/pl/plpython/expected/plpython_test.out
--- b/src/pl/plpython/expected/plpython_test.out
*************** contents.sort()
*** 43,51 ****
  return ", ".join(contents)
  $$ LANGUAGE plpythonu;
  select module_contents();
!                                               module_contents                                              
! -----------------------------------------------------------------------------------------------------------
!  Error, Fatal, SPIError, debug, error, execute, fatal, info, log, notice, prepare, subtransaction, warning
  (1 row)
  
  CREATE FUNCTION elog_test() RETURNS void
--- 43,51 ----
  return ", ".join(contents)
  $$ LANGUAGE plpythonu;
  select module_contents();
!                                                      module_contents                                                      
! --------------------------------------------------------------------------------------------------------------------------
!  Error, Fatal, SPIError, debug, error, execute, fatal, info, log, notice, prepare, spiexceptions, subtransaction, warning
  (1 row)
  
  CREATE FUNCTION elog_test() RETURNS void
diff --git a/src/pl/plpython/generate-spiexceptions.pl b/src/pl/plpython/generate-spiexceptions.pl
index ...cf050d1 .
*** a/src/pl/plpython/generate-spiexceptions.pl
--- b/src/pl/plpython/generate-spiexceptions.pl
***************
*** 0 ****
--- 1,44 ----
+ #!/usr/bin/perl
+ #
+ # Generate the spiexceptions.h header from errcodes.txt
+ # Copyright (c) 2000-2011, PostgreSQL Global Development Group
+ 
+ use warnings;
+ use strict;
+ 
+ print "/* autogenerated from src/backend/utils/errcodes.txt, do not edit */\n";
+ print "/* there is deliberately not an #ifndef SPIEXCEPTIONS_H here */\n";
+ 
+ open my $errcodes, $ARGV[0] or die;
+ 
+ while (<$errcodes>) {
+     chomp;
+ 
+     # Skip comments
+     next if /^#/;
+     next if /^\s*$/;
+ 
+     # Skip section headers
+     next if /^Section:/;
+ 
+     die unless /^([^\s]{5})\s+([EWS])\s+([^\s]+)(?:\s+)?([^\s]+)?/;
+ 
+     (my $sqlstate,
+      my $type,
+      my $errcode_macro,
+      my $condition_name) = ($1, $2, $3, $4);
+ 
+     # Skip non-errors
+     next unless $type eq 'E';
+ 
+     # Skip lines without PL/pgSQL condition names
+     next unless defined($condition_name);
+ 
+     # Change some_error_condition to SomeErrorCondition
+     $condition_name =~ s/([a-z])([^_]*)(?:_|$)/\u$1$2/g;
+ 
+     print "{ \"spiexceptions.$condition_name\", " .
+ 	"\"$condition_name\", $errcode_macro },\n";
+ }
+ 
+ close $errcodes;
diff --git a/src/pl/plpython/plpython.c b/src/pl/plpython/plpython.c
index 381e91c..0e5388e 100644
*** a/src/pl/plpython/plpython.c
--- b/src/pl/plpython/plpython.c
*************** typedef struct PLySubxactObject
*** 261,266 ****
--- 261,288 ----
  	bool	exited;
  } PLySubxactObject;
  
+ /* A list of all known exceptions, generated from backend/utils/errcodes.txt */
+ typedef struct ExceptionMap
+ {
+ 	char		*name;
+ 	char		*classname;
+ 	int			sqlstate;
+ } ExceptionMap;
+ 
+ static const ExceptionMap exception_map[] = {
+ #include "spiexceptions.h"
+ 	{NULL, NULL, 0}
+ };
+ 
+ /* A hashtable mapping sqlstates to exceptions, for speedy lookup */
+ static HTAB *PLy_spi_exceptions;
+ 
+ typedef struct PLyExceptionEntry
+ {
+ 	int			sqlstate;	/* hash key, must be first */
+ 	PyObject	*exc;		/* corresponding exception */
+ } PLyExceptionEntry;
+ 
  /* function declarations */
  
  #if PY_MAJOR_VERSION >= 3
*************** __attribute__((format(printf, 2, 5)))
*** 302,308 ****
  __attribute__((format(printf, 3, 5)));
  
  /* like PLy_exception_set, but conserve more fields from ErrorData */
! static void PLy_spi_exception_set(ErrorData *edata);
  
  /* Get the innermost python procedure called from the backend */
  static char *PLy_procedure_name(PLyProcedure *);
--- 324,330 ----
  __attribute__((format(printf, 3, 5)));
  
  /* like PLy_exception_set, but conserve more fields from ErrorData */
! static void PLy_spi_exception_set(PyObject *excclass, ErrorData *edata);
  
  /* Get the innermost python procedure called from the backend */
  static char *PLy_procedure_name(PLyProcedure *);
*************** static PyMethodDef PLy_methods[] = {
*** 2805,2810 ****
--- 2827,2836 ----
  	{NULL, NULL, 0, NULL}
  };
  
+ static PyMethodDef PLy_exc_methods[] = {
+ 	{NULL, NULL, 0, NULL}
+ };
+ 
  #if PY_MAJOR_VERSION >= 3
  static PyModuleDef PLy_module = {
  	PyModuleDef_HEAD_INIT,		/* m_base */
*************** static PyModuleDef PLy_module = {
*** 2813,2818 ****
--- 2839,2856 ----
  	-1,							/* m_size */
  	PLy_methods,				/* m_methods */
  };
+ 
+ static PyModuleDef PLy_exc_module = {
+ 	PyModuleDef_HEAD_INIT,		/* m_base */
+ 	"spiexceptions",			/* m_name */
+ 	NULL,						/* m_doc */
+ 	-1,							/* m_size */
+ 	PLy_exc_methods,			/* m_methods */
+ 	NULL,						/* m_reload */
+ 	NULL,						/* m_traverse */
+ 	NULL,						/* m_clear */
+ 	NULL						/* m_free */
+ };
  #endif
  
  /* plan object methods */
*************** PLy_spi_prepare(PyObject *self, PyObject
*** 3109,3115 ****
  	}
  	PG_CATCH();
  	{
! 		ErrorData	*edata;
  
  		/* Save error info */
  		MemoryContextSwitchTo(oldcontext);
--- 3147,3155 ----
  	}
  	PG_CATCH();
  	{
! 		ErrorData				*edata;
! 		PLyExceptionEntry		*entry;
! 		PyObject				*exc;
  
  		/* Save error info */
  		MemoryContextSwitchTo(oldcontext);
*************** PLy_spi_prepare(PyObject *self, PyObject
*** 3130,3137 ****
  		 */
  		SPI_restore_connection();
  
  		/* Make Python raise the exception */
! 		PLy_spi_exception_set(edata);
  		return NULL;
  	}
  	PG_END_TRY();
--- 3170,3183 ----
  		 */
  		SPI_restore_connection();
  
+ 		/* Look up the correct exception */
+ 		entry = hash_search(PLy_spi_exceptions, &(edata->sqlerrcode),
+ 							HASH_FIND, NULL);
+ 		/* We really should find it, but just in case have a fallback */
+ 		Assert(entry != NULL);
+ 		exc = entry ? entry->exc : PLy_exc_spi_error;
  		/* Make Python raise the exception */
! 		PLy_spi_exception_set(exc, edata);
  		return NULL;
  	}
  	PG_END_TRY();
*************** PLy_spi_execute_plan(PyObject *ob, PyObj
*** 3280,3287 ****
  	}
  	PG_CATCH();
  	{
  		int			k;
- 		ErrorData	*edata;
  
  		/* Save error info */
  		MemoryContextSwitchTo(oldcontext);
--- 3326,3335 ----
  	}
  	PG_CATCH();
  	{
+ 		ErrorData				*edata;
+ 		PLyExceptionEntry		*entry;
+ 		PyObject				*exc;
  		int			k;
  
  		/* Save error info */
  		MemoryContextSwitchTo(oldcontext);
*************** PLy_spi_execute_plan(PyObject *ob, PyObj
*** 3313,3320 ****
  		 */
  		SPI_restore_connection();
  
  		/* Make Python raise the exception */
! 		PLy_spi_exception_set(edata);
  		return NULL;
  	}
  	PG_END_TRY();
--- 3361,3374 ----
  		 */
  		SPI_restore_connection();
  
+ 		/* Look up the correct exception */
+ 		entry = hash_search(PLy_spi_exceptions, &(edata->sqlerrcode),
+ 							HASH_FIND, NULL);
+ 		/* We really should find it, but just in case have a fallback */
+ 		Assert(entry != NULL);
+ 		exc = entry ? entry->exc : PLy_exc_spi_error;
  		/* Make Python raise the exception */
! 		PLy_spi_exception_set(exc, edata);
  		return NULL;
  	}
  	PG_END_TRY();
*************** PLy_spi_execute_query(char *query, long
*** 3374,3380 ****
  	}
  	PG_CATCH();
  	{
! 		ErrorData	*edata;
  
  		/* Save error info */
  		MemoryContextSwitchTo(oldcontext);
--- 3428,3436 ----
  	}
  	PG_CATCH();
  	{
! 		ErrorData				*edata;
! 		PLyExceptionEntry		*entry;
! 		PyObject				*exc;
  
  		/* Save error info */
  		MemoryContextSwitchTo(oldcontext);
*************** PLy_spi_execute_query(char *query, long
*** 3393,3400 ****
  		 */
  		SPI_restore_connection();
  
  		/* Make Python raise the exception */
! 		PLy_spi_exception_set(edata);
  		return NULL;
  	}
  	PG_END_TRY();
--- 3449,3462 ----
  		 */
  		SPI_restore_connection();
  
+ 		/* Look up the correct exception */
+ 		entry = hash_search(PLy_spi_exceptions, &edata->sqlerrcode,
+ 							HASH_FIND, NULL);
+ 		/* We really should find it, but just in case have a fallback */
+ 		Assert(entry != NULL);
+ 		exc = entry ? entry->exc : PLy_exc_spi_error;
  		/* Make Python raise the exception */
! 		PLy_spi_exception_set(exc, edata);
  		return NULL;
  	}
  	PG_END_TRY();
*************** PLy_subxact_exit(PyObject *self, PyObjec
*** 3622,3630 ****
--- 3684,3729 ----
  /*
   * Add exceptions to the plpy module
   */
+ /* Add all the autogenerated exceptions as subclasses of SPIError */
+ static void
+ PLy_generate_spi_exceptions(PyObject *mod, PyObject *base)
+ {
+ 	int	i;
+ 
+ 	for (i = 0; exception_map[i].name != NULL; i++)
+ 	{
+ 		bool					 found;
+ 		PyObject				*exc;
+ 		PLyExceptionEntry		*entry;
+ 		PyObject				*sqlstate;
+ 		PyObject				*dict = PyDict_New();
+ 
+ 		sqlstate = PyString_FromString(unpack_sql_state(
+ 										   exception_map[i].sqlstate));
+ 		PyDict_SetItemString(dict, "sqlstate", sqlstate);
+ 		Py_DECREF(sqlstate);
+ 		exc = PyErr_NewException(exception_map[i].name, base, dict);
+ 		PyModule_AddObject(mod, exception_map[i].classname, exc);
+ 		entry = hash_search(PLy_spi_exceptions, &exception_map[i].sqlstate,
+ 							HASH_ENTER, &found);
+ 		entry->exc = exc;
+ 		Assert(!found);
+ 	}
+ }
+ 
  static void
  PLy_add_exceptions(PyObject *plpy)
  {
+ 	PyObject	*excmod;
+ 	HASHCTL		 hash_ctl;
+ #if PY_MAJOR_VERSION < 3
+ 	excmod = Py_InitModule("spiexceptions", PLy_exc_methods);
+ #else
+ 	excmod = PyModule_Create(&PLy_exc_module);
+ #endif
+ 	if (PyModule_AddObject(plpy, "spiexceptions", excmod) < 0)
+ 		PLy_elog(ERROR, "failed to add the spiexceptions module");
+ 
  	PLy_exc_error = PyErr_NewException("plpy.Error", NULL, NULL);
  	PLy_exc_fatal = PyErr_NewException("plpy.Fatal", NULL, NULL);
  	PLy_exc_spi_error = PyErr_NewException("plpy.SPIError", NULL, NULL);
*************** PLy_add_exceptions(PyObject *plpy)
*** 3635,3640 ****
--- 3734,3748 ----
  	PyModule_AddObject(plpy, "Fatal", PLy_exc_fatal);
  	Py_INCREF(PLy_exc_spi_error);
  	PyModule_AddObject(plpy, "SPIError", PLy_exc_spi_error);
+ 
+ 	memset(&hash_ctl, 0, sizeof(hash_ctl));
+ 	hash_ctl.keysize = sizeof(int);
+ 	hash_ctl.entrysize = sizeof(PLyExceptionEntry);
+ 	hash_ctl.hash = tag_hash;
+ 	PLy_spi_exceptions = hash_create("SPI exceptions", 256,
+ 									 &hash_ctl, HASH_ELEM | HASH_FUNCTION);
+ 
+ 	PLy_generate_spi_exceptions(excmod, PLy_exc_spi_error);
  }
  
  #if PY_MAJOR_VERSION >= 3
*************** PLy_exception_set_plural(PyObject *exc,
*** 3941,3947 ****
   * internal query and error position.
   */
  static void
! PLy_spi_exception_set(ErrorData *edata)
  {
  	PyObject	*args = NULL;
  	PyObject	*spierror = NULL;
--- 4049,4055 ----
   * internal query and error position.
   */
  static void
! PLy_spi_exception_set(PyObject *excclass, ErrorData *edata)
  {
  	PyObject	*args = NULL;
  	PyObject	*spierror = NULL;
*************** PLy_spi_exception_set(ErrorData *edata)
*** 3951,3958 ****
  	if (!args)
  		goto failure;
  
! 	/* create a new SPIError with the error message as the parameter */
! 	spierror = PyObject_CallObject(PLy_exc_spi_error, args);
  	if (!spierror)
  		goto failure;
  
--- 4059,4066 ----
  	if (!args)
  		goto failure;
  
! 	/* create a new SPI exception with the error message as the parameter */
! 	spierror = PyObject_CallObject(excclass, args);
  	if (!spierror)
  		goto failure;
  
*************** PLy_spi_exception_set(ErrorData *edata)
*** 3964,3970 ****
  	if (PyObject_SetAttrString(spierror, "spidata", spidata) == -1)
  		goto failure;
  
! 	PyErr_SetObject(PLy_exc_spi_error, spierror);
  
  	Py_DECREF(args);
  	Py_DECREF(spierror);
--- 4072,4078 ----
  	if (PyObject_SetAttrString(spierror, "spidata", spidata) == -1)
  		goto failure;
  
! 	PyErr_SetObject(excclass, spierror);
  
  	Py_DECREF(args);
  	Py_DECREF(spierror);
diff --git a/src/pl/plpython/sql/plpython_error.sql b/src/pl/plpython/sql/plpython_error.sql
index 7861cd6..6d6a163 100644
*** a/src/pl/plpython/sql/plpython_error.sql
--- b/src/pl/plpython/sql/plpython_error.sql
*************** return None
*** 131,136 ****
--- 131,157 ----
  
  SELECT valid_type('rick');
  
+ /* Check catching specific types of exceptions
+  */
+ CREATE TABLE specific (
+     i integer PRIMARY KEY
+ );
+ 
+ CREATE FUNCTION specific_exception(i integer) RETURNS void AS
+ $$
+ from plpy import spiexceptions
+ try:
+     plpy.execute("insert into specific values (%s)" % (i or "NULL"));
+ except spiexceptions.NotNullViolation, e:
+     plpy.notice("Violated the NOT NULL constraint, sqlstate %s" % e.sqlstate)
+ except spiexceptions.UniqueViolation, e:
+     plpy.notice("Violated the UNIQUE constraint, sqlstate %s" % e.sqlstate)
+ $$ LANGUAGE plpythonu;
+ 
+ SELECT specific_exception(2);
+ SELECT specific_exception(NULL);
+ SELECT specific_exception(2);
+ 
  /* manually starting subtransactions - a bad idea
   */
  CREATE FUNCTION manual_subxact() RETURNS void AS $$
diff --git a/src/tools/msvc/Solution.pm b/src/tools/msvc/Solution.pm
index 49504d7..e1fe4e9 100644
*** a/src/tools/msvc/Solution.pm
--- b/src/tools/msvc/Solution.pm
*************** s{PG_VERSION_STR "[^"]+"}{__STRINGIFY(x)
*** 273,278 ****
--- 273,284 ----
          );
      }
  
+     if ($self->{options}->{python} && IsNewer('src\pl\plpython\spiexceptions.h','src\include\backend\errcodes.txt'))
+     {
+         print "Generating spiexceptions.h...\n";
+         system('perl src\pl\plpython\generate-spiexceptions.pl src\backend\utils\errcodes.txt > src\pl\plpython\spiexceptions.h');
+     }
+ 
      if (IsNewer('src\include\utils\errcodes.h','src\backend\utils\errcodes.txt'))
      {
          print "Generating errcodes.h...\n";
#13Steve Singer
ssinger_pg@sympatico.ca
In reply to: Jan Urbański (#12)
1 attachment(s)
Re: pl/python custom exceptions for SPI

On 11-02-12 05:58 AM, Jan Urbański wrote:

On 11/02/11 10:53, Jan Urbański wrote:

On 10/02/11 22:26, Steve Singer wrote:

Here's an updated patch with documentation. It's an incremental patch on
top of the latest explicit-subxacts version.

This looks fine. I've attached a one word documentation change to go o
top of the patch.

I'll let Peter decide if he likes those assert's or not. I don't have a
good enough sense of if we often use asserts in that fashion elsewhere.

Show quoted text

Cheers,
Jan

Attachments:

plpython_custom_exceptions_docpatch.difftext/x-patch; name=plpython_custom_exceptions_docpatch.diffDownload
diff --git a/doc/src/sgml/plpython.sgml b/doc/src/sgml/plpython.sgml
index aee54bf..4a90430 100644
*** a/doc/src/sgml/plpython.sgml
--- b/doc/src/sgml/plpython.sgml
*************** $$ LANGUAGE plpythonu;
*** 966,972 ****
  </programlisting>
     </para>
     <para>
!     The actual class of the exception being raised corresponds to exact
      condition that caused the error (refer to <xref linkend="errcodes-table">
      for a list of possible conditions).  The
      <literal>plpy.spiexceptions</literal> module defines an exception class for
--- 966,972 ----
  </programlisting>
     </para>
     <para>
!     The actual class of the exception being raised corresponds to the exact
      condition that caused the error (refer to <xref linkend="errcodes-table">
      for a list of possible conditions).  The
      <literal>plpy.spiexceptions</literal> module defines an exception class for
#14Peter Eisentraut
peter_e@gmx.net
In reply to: Jan Urbański (#12)
Re: pl/python custom exceptions for SPI

On lör, 2011-02-12 at 11:58 +0100, Jan Urbański wrote:

On 11/02/11 10:53, Jan Urbański wrote:

On 10/02/11 22:26, Steve Singer wrote:

Here's an updated patch with documentation. It's an incremental patch on
top of the latest explicit-subxacts version.

Committed.

#15Tom Lane
tgl@sss.pgh.pa.us
In reply to: Peter Eisentraut (#14)
Re: pl/python custom exceptions for SPI

Peter Eisentraut <peter_e@gmx.net> writes:

On lör, 2011-02-12 at 11:58 +0100, Jan Urbański wrote:

Here's an updated patch with documentation. It's an incremental patch on
top of the latest explicit-subxacts version.

Committed.

I'm seeing a core dump as well as multiple inconsistencies in error
message spelling in the plpython regression tests on a Fedora 13 box
(python 2.6.4). Several buildfarm critters don't look too happy either.

regards, tom lane

#16Peter Eisentraut
peter_e@gmx.net
In reply to: Tom Lane (#15)
Re: pl/python custom exceptions for SPI

On mån, 2011-02-28 at 12:08 -0500, Tom Lane wrote:

Peter Eisentraut <peter_e@gmx.net> writes:

On lör, 2011-02-12 at 11:58 +0100, Jan Urbański wrote:

Here's an updated patch with documentation. It's an incremental patch on
top of the latest explicit-subxacts version.

Committed.

I'm seeing a core dump as well as multiple inconsistencies in error
message spelling in the plpython regression tests on a Fedora 13 box
(python 2.6.4). Several buildfarm critters don't look too happy either.

Fixed. (Well, some of it. We'll see ...)

#17Tom Lane
tgl@sss.pgh.pa.us
In reply to: Peter Eisentraut (#16)
Re: pl/python custom exceptions for SPI

Peter Eisentraut <peter_e@gmx.net> writes:

On mån, 2011-02-28 at 12:08 -0500, Tom Lane wrote:

I'm seeing a core dump as well as multiple inconsistencies in error
message spelling in the plpython regression tests on a Fedora 13 box
(python 2.6.4). Several buildfarm critters don't look too happy either.

Fixed. (Well, some of it. We'll see ...)

Core dump is still there. It appears to be a python assertion failure.
I installed python's debuginfo and got this backtrace:

Program terminated with signal 6, Aborted.
#0 0x00000032a36328f5 in raise (sig=6) at ../nptl/sysdeps/unix/sysv/linux/raise.c:64
64 return INLINE_SYSCALL (tgkill, 3, pid, selftid, sig);
Missing separate debuginfos, use: debuginfo-install keyutils-libs-1.2-6.fc12.x86_64 krb5-libs-1.7.1-17.fc13.x86_64 libcom_err-1.41.10-7.fc13.x86_64 libselinux-2.0.94-2.fc13.x86_64 openssl-1.0.0c-1.fc13.x86_64 zlib-1.2.3-23.fc12.x86_64
(gdb) bt
#0 0x00000032a36328f5 in raise (sig=6) at ../nptl/sysdeps/unix/sysv/linux/raise.c:64
#1 0x00000032a36340d5 in abort () at abort.c:92
#2 0x00000032a362b8b5 in __assert_fail (assertion=0x32a5b46391 "gc->gc.gc_refs != 0", file=<value optimized out>, line=277, function=<value optimized out>)
at assert.c:81
#3 0x00000032a5b0853e in visit_decref (op=<module at remote 0x7f11c3666d38>, data=<value optimized out>) at Modules/gcmodule.c:277
#4 0x00000032a5a7cbd9 in dict_traverse (op=
{'info': <built-in function info>, 'notice': <built-in function notice>, 'Fatal': <type at remote 0x1bba7e0>, 'log': <built-in function log>, 'prepare': <built-in function prepare>, 'spiexceptions': <module at remote 0x7f11c3666d38>, 'SPIError': <type at remote 0x1bbacc0>, 'Error': <type at remote 0x1bba300>, 'execute': <built-in function execute>, '__package__': None, 'quote_ident': <built-in function quote_ident>, 'warning': <built-in function warning>, 'subtransaction': <built-in function subtransaction>, 'quote_literal': <built-in function quote_literal>, 'quote_nullable': <built-in function quote_nullable>, 'error': <built-in function error>, 'debug': <built-in function debug>, '__name__': 'plpy', 'fatal': <built-in function fatal>, '__doc__': None}, visit=0x32a5b084c0 <visit_decref>, arg=0x0)
at Objects/dictobject.c:2003
#5 0x00000032a5b08c9f in subtract_refs (generation=1) at Modules/gcmodule.c:296
#6 collect (generation=1) at Modules/gcmodule.c:817
#7 0x00000032a5b096fa in collect_generations (basicsize=<value optimized out>) at Modules/gcmodule.c:924
#8 _PyObject_GC_Malloc (basicsize=<value optimized out>) at Modules/gcmodule.c:1363
#9 0x00000032a5b0972e in _PyObject_GC_NewVar (tp=0x32a5d899a0, nitems=1) at Modules/gcmodule.c:1383
#10 0x00000032a5a9703f in PyTuple_New (size=1) at Objects/tupleobject.c:69
#11 0x00000032a5af3697 in r_object (p=0x7fffe1f5f330) at Python/marshal.c:788
#12 0x00000032a5af389c in r_object (p=0x7fffe1f5f330) at Python/marshal.c:927
#13 0x00000032a5af36dd in r_object (p=0x7fffe1f5f330) at Python/marshal.c:794
#14 0x00000032a5af389c in r_object (p=0x7fffe1f5f330) at Python/marshal.c:927
#15 0x00000032a5af36dd in r_object (p=0x7fffe1f5f330) at Python/marshal.c:794
#16 0x00000032a5af389c in r_object (p=0x7fffe1f5f330) at Python/marshal.c:927
#17 0x00000032a5af3fd8 in PyMarshal_ReadObjectFromString (str=<value optimized out>, len=<value optimized out>) at Python/marshal.c:1107
#18 0x00000032a5af5952 in PyMarshal_ReadLastObjectFromFile (fp=0x1c66e50) at Python/marshal.c:1066
#19 0x00000032a5aedb39 in read_compiled_module (cpathname=0x7fffe1f63540 "/usr/lib64/python2.6/string.pyc", fp=<value optimized out>) at Python/import.c:767
#20 0x00000032a5aef69d in load_source_module (name=0x7fffe1f656a0 "string", pathname=0x7fffe1f645c0 "/usr/lib64/python2.6/string.py", fp=0x1c29b30)
at Python/import.c:991
#21 0x00000032a5af0cb5 in import_submodule (mod=None, subname=0x7fffe1f656a0 "string", fullname=0x7fffe1f656a0 "string") at Python/import.c:2589
#22 0x00000032a5af0f34 in load_next (mod=None, altmod=None, p_name=<value optimized out>, buf=0x7fffe1f656a0 "string", p_buflen=0x7fffe1f65698)
at Python/import.c:2409
#23 0x00000032a5af1582 in import_module_level (name=0x0, globals=<value optimized out>, locals=<value optimized out>, fromlist=None, level=<value optimized out>)
at Python/import.c:2131
#24 0x00000032a5af22c4 in PyImport_ImportModuleLevel (name=0x7f11c40c2084 "string", globals=
{'plpy': <module at remote 0x7f11c3666ad0>, 'GD': {}, 'args': [], '__builtins__': <module at remote 0x7f11c4158830>, '__name__': '__main__', 'SD': {}, '__doc__': None, '__plpython_procedure_import_succeed_41194': <function at remote 0x7f11c3652aa0>, '__package__': None}, locals=None, fromlist=None,
level=<value optimized out>) at Python/import.c:2182
#25 0x00000032a5ad762f in builtin___import__ (self=<value optimized out>, args=<value optimized out>, kwds=<value optimized out>) at Python/bltinmodule.c:48
#26 0x00000032a5a43db3 in PyObject_Call (func=<built-in function __import__>, arg=<value optimized out>, kw=<value optimized out>) at Objects/abstract.c:2492
#27 0x00000032a5ad7ba3 in PyEval_CallObjectWithKeywords (func=<built-in function __import__>, arg=
('string', {'plpy': <module at remote 0x7f11c3666ad0>, 'GD': {}, 'args': [], '__builtins__': <module at remote 0x7f11c4158830>, '__name__': '__main__', 'SD': {}, '__doc__': None, '__plpython_procedure_import_succeed_41194': <function at remote 0x7f11c3652aa0>, '__package__': None}, None, None), kw=<value optimized out>)
at Python/ceval.c:3633
#28 0x00000032a5ada844 in PyEval_EvalFrameEx (f=<value optimized out>, throwflag=<value optimized out>) at Python/ceval.c:2192
#29 0x00000032a5ade312 in fast_function (f=<value optimized out>, throwflag=<value optimized out>) at Python/ceval.c:3850
#30 call_function (f=<value optimized out>, throwflag=<value optimized out>) at Python/ceval.c:3785
#31 PyEval_EvalFrameEx (f=<value optimized out>, throwflag=<value optimized out>) at Python/ceval.c:2445
#32 0x00000032a5adf4e9 in PyEval_EvalCodeEx (co=0x7f11c36526c0, globals=<value optimized out>, locals=<value optimized out>, args=<value optimized out>,
argcount=0, kws=0x0, kwcount=0, defs=0x0, defcount=0, closure=0x0) at Python/ceval.c:3026
#33 0x00000032a5adf5b2 in PyEval_EvalCode (co=<value optimized out>, globals=<value optimized out>, locals=<value optimized out>) at Python/ceval.c:526
#34 0x00007f11c418cab2 in PLy_procedure_call (proc=0x1c2c9d8, kargs=<value optimized out>, vargs=<value optimized out>) at plpython.c:1317
#35 0x00007f11c418cdfe in PLy_function_handler (fcinfo=0x1c3fd30, proc=0x1c2c9d8) at plpython.c:1091
#36 0x00007f11c418fb77 in plpython_call_handler (fcinfo=0x1c3fd30) at plpython.c:569
#37 0x0000000000588100 in ExecMakeFunctionResult (fcache=0x1c3fcc0, econtext=0x1c3fa98, isNull=0x1c40670 "\177~\177\177\177\177\177\177\270\004\253\001",
isDone=<value optimized out>) at execQual.c:1826
#38 0x00000000005831b6 in ExecTargetList (projInfo=<value optimized out>, isDone=0x7fffe1f6703c) at execQual.c:5123
#39 ExecProject (projInfo=<value optimized out>, isDone=0x7fffe1f6703c) at execQual.c:5338
#40 0x000000000059b65b in ExecResult (node=0x1c3f980) at nodeResult.c:155
#41 0x0000000000582738 in ExecProcNode (node=0x1c3f980) at execProcnode.c:367
#42 0x0000000000581496 in ExecutePlan (queryDesc=0x19e4df8, direction=<value optimized out>, count=0) at execMain.c:1385
#43 standard_ExecutorRun (queryDesc=0x19e4df8, direction=<value optimized out>, count=0) at execMain.c:311
#44 0x000000000065d5d7 in PortalRunSelect (portal=0x19e2de8, forward=<value optimized out>, count=0, dest=0x1c3de28) at pquery.c:950
#45 0x000000000065e9f0 in PortalRun (portal=0x19e2de8, count=9223372036854775807, isTopLevel=1 '\001', dest=0x1c3de28, altdest=0x1c3de28, completionTag=
0x7fffe1f672a0 "") at pquery.c:794
#46 0x000000000065ae39 in exec_simple_query (query_string=0x1a70ac8 "SELECT import_succeed();") at postgres.c:1059
#47 0x000000000065bdf0 in PostgresMain (argc=<value optimized out>, argv=<value optimized out>, username=<value optimized out>) at postgres.c:3943
#48 0x0000000000615081 in BackendRun () at postmaster.c:3590
#49 BackendStartup () at postmaster.c:3275
#50 ServerLoop () at postmaster.c:1449
#51 0x000000000061795f in PostmasterMain (argc=<value optimized out>, argv=<value optimized out>) at postmaster.c:1110
#52 0x00000000005b2e70 in main (argc=3, argv=0x19c3910) at main.c:199

Don't know python enough to do anything useful with this, but the
reference to "gc_refs" sure makes it look like something is dropping the
ball on when to do INCREF/DECREF.

regards, tom lane

#18Jan Urbański
wulczer@wulczer.org
In reply to: Tom Lane (#17)
Re: pl/python custom exceptions for SPI

On 28/02/11 19:38, Tom Lane wrote:

Peter Eisentraut <peter_e@gmx.net> writes:

On mån, 2011-02-28 at 12:08 -0500, Tom Lane wrote:

I'm seeing a core dump as well as multiple inconsistencies in error
message spelling in the plpython regression tests on a Fedora 13 box
(python 2.6.4). Several buildfarm critters don't look too happy either.

Fixed. (Well, some of it. We'll see ...)

Core dump is still there. It appears to be a python assertion failure.
I installed python's debuginfo and got this backtrace:

Program terminated with signal 6, Aborted.
#0 0x00000032a36328f5 in raise (sig=6) at ../nptl/sysdeps/unix/sysv/linux/raise.c:64
64 return INLINE_SYSCALL (tgkill, 3, pid, selftid, sig);
Missing separate debuginfos, use: debuginfo-install keyutils-libs-1.2-6.fc12.x86_64 krb5-libs-1.7.1-17.fc13.x86_64 libcom_err-1.41.10-7.fc13.x86_64 libselinux-2.0.94-2.fc13.x86_64 openssl-1.0.0c-1.fc13.x86_64 zlib-1.2.3-23.fc12.x86_64
(gdb) bt
#0 0x00000032a36328f5 in raise (sig=6) at ../nptl/sysdeps/unix/sysv/linux/raise.c:64
#1 0x00000032a36340d5 in abort () at abort.c:92
#2 0x00000032a362b8b5 in __assert_fail (assertion=0x32a5b46391 "gc->gc.gc_refs != 0", file=<value optimized out>, line=277, function=<value optimized out>)
at assert.c:81
#3 0x00000032a5b0853e in visit_decref (op=<module at remote 0x7f11c3666d38>, data=<value optimized out>) at Modules/gcmodule.c:277
#4 0x00000032a5a7cbd9 in dict_traverse (op=
{'info': <built-in function info>, 'notice': <built-in function notice>, 'Fatal': <type at remote 0x1bba7e0>, 'log': <built-in function log>, 'prepare': <built-in function prepare>, 'spiexceptions': <module at remote 0x7f11c3666d38>, 'SPIError': <type at remote 0x1bbacc0>, 'Error': <type at remote 0x1bba300>, 'execute': <built-in function execute>, '__package__': None, 'quote_ident': <built-in function quote_ident>, 'warning': <built-in function warning>, 'subtransaction': <built-in function subtransaction>, 'quote_literal': <built-in function quote_literal>, 'quote_nullable': <built-in function quote_nullable>, 'error': <built-in function error>, 'debug': <built-in function debug>, '__name__': 'plpy', 'fatal': <built-in function fatal>, '__doc__': None}, visit=0x32a5b084c0 <visit_decref>, arg=0x0)
at Objects/dictobject.c:2003
[...]
#24 0x00000032a5af22c4 in PyImport_ImportModuleLevel (name=0x7f11c40c2084 "string", globals=

Don't know python enough to do anything useful with this, but the
reference to "gc_refs" sure makes it look like something is dropping the
ball on when to do INCREF/DECREF.

That's strange, the error occurs while trying to import the "string"
module. But the error itself seems to be caused by trying to unref the
spiexceptions module (showing up here as <module at remote
0x7f11c3666d38>). Apparently adding spiexceptions as an object to the
plpy module is not done exactly right.

I'll try to reproduce it in my environment.

Cheers,
Jan