2015-10-16 8:12 GMT+02:00 Craig Ringer <cr...@2ndquadrant.com>:

> On 16 October 2015 at 02:47, Pavel Stehule <pavel.steh...@gmail.com>
> wrote:
>
> >  postgres=# do $$
> > x = plpy.SPIError('Nazdarek');
> > x.spidata = (100, "Some detail", "some hint", None, None);
> > raise x;
> > $$ language plpythonu;
>
> Shouldn't that look more like
>
> raise plpy.SPIError(msg="Message", sqlstate="0P001", hint="Turn it on
> and off again") ?
>
> Keyword args are very much the norm for this sort of thing. I recall
> them being pretty reasonable to deal with in the CPython API too, but
> otherwise a trivial Python wrapper in the module can easily adapt the
> interface.
>

I wrote a constructor for SPIError with keyword parameters support - see
attached patch

The code is working

 postgres=# do $$
raise plpy.SPIError("pokus",hint = "some info");
$$ language plpythonu;
ERROR:  plpy.SPIError: pokus
HINT:  some info
CONTEXT:  Traceback (most recent call last):
  PL/Python anonymous code block, line 2, in <module>
    raise plpy.SPIError("pokus",hint = "some info");
PL/Python anonymous code block

but the implementation is pretty ugly :( - I didn't write C extensions for
Python before, and the extending exception class with some methods isn't
well supported and well documented.

Any help is welcome

Regards

Pavel


>
>
> --
>  Craig Ringer                   http://www.2ndQuadrant.com/
>  PostgreSQL Development, 24x7 Support, Training & Services
>
diff --git a/src/pl/plpython/expected/plpython_error.out b/src/pl/plpython/expected/plpython_error.out
new file mode 100644
index 1f52af7..c9b5e69
*** a/src/pl/plpython/expected/plpython_error.out
--- b/src/pl/plpython/expected/plpython_error.out
*************** EXCEPTION WHEN division_by_zero THEN
*** 408,413 ****
--- 408,420 ----
  	-- NOOP
  END
  $$ LANGUAGE plpgsql;
+ ERROR:  spiexceptions.DivisionByZero: None
+ CONTEXT:  Traceback (most recent call last):
+   PL/Python function "plpy_raise_spiexception", line 2, in <module>
+     raise plpy.spiexceptions.DivisionByZero()
+ PL/Python function "plpy_raise_spiexception"
+ SQL statement "SELECT plpy_raise_spiexception()"
+ PL/pgSQL function inline_code_block line 3 at SQL statement
  /* setting a custom sqlstate should be handled
   */
  CREATE FUNCTION plpy_raise_spiexception_override() RETURNS void AS $$
*************** EXCEPTION WHEN SQLSTATE 'SILLY' THEN
*** 422,424 ****
--- 429,487 ----
  	-- NOOP
  END
  $$ LANGUAGE plpgsql;
+ ERROR:  spiexceptions.DivisionByZero: None
+ CONTEXT:  Traceback (most recent call last):
+   PL/Python function "plpy_raise_spiexception_override", line 4, in <module>
+     raise exc
+ PL/Python function "plpy_raise_spiexception_override"
+ SQL statement "SELECT plpy_raise_spiexception_override()"
+ PL/pgSQL function inline_code_block line 3 at SQL statement
+ CREATE FUNCTION nested_error_ereport() RETURNS text
+ 	AS
+ 'def fun1():
+ 	raise plpy.SPIError("HiHi");
+ 
+ def fun2():
+ 	fun1()
+ 
+ def fun3():
+ 	fun2()
+ 
+ fun3()
+ return "not reached"
+ '
+ 	LANGUAGE plpythonu;
+ SELECT nested_error_ereport();
+ ERROR:  plpy.SPIError: HiHi
+ CONTEXT:  Traceback (most recent call last):
+   PL/Python function "nested_error_ereport", line 10, in <module>
+     fun3()
+   PL/Python function "nested_error_ereport", line 8, in fun3
+     fun2()
+   PL/Python function "nested_error_ereport", line 5, in fun2
+     fun1()
+   PL/Python function "nested_error_ereport", line 2, in fun1
+     raise plpy.SPIError("HiHi");
+ PL/Python function "nested_error_ereport"
+ \set VERBOSITY verbose
+ do $$
+ x = plpy.SPIError("pokus",hint = "some info", sqlstate='AA234'); raise x;
+ $$ language plpythonu;
+ ERROR:  AA234: plpy.SPIError: pokus
+ HINT:  some info
+ CONTEXT:  Traceback (most recent call last):
+   PL/Python anonymous code block, line 2, in <module>
+     x = plpy.SPIError("pokus",hint = "some info", sqlstate='AA234'); raise x;
+ PL/Python anonymous code block
+ LOCATION:  PLy_elog, plpy_elog.c:119
+ do $$
+ raise plpy.SPIError("pokus",hint = "some info", sqlstate='AA234');
+ $$ language plpythonu;
+ ERROR:  AA234: plpy.SPIError: pokus
+ HINT:  some info
+ CONTEXT:  Traceback (most recent call last):
+   PL/Python anonymous code block, line 2, in <module>
+     raise plpy.SPIError("pokus",hint = "some info", sqlstate='AA234');
+ PL/Python anonymous code block
+ LOCATION:  PLy_elog, plpy_elog.c:119
+ \set VERBOSITY default
diff --git a/src/pl/plpython/plpy_elog.c b/src/pl/plpython/plpy_elog.c
new file mode 100644
index 15406d6..6825732
*** a/src/pl/plpython/plpy_elog.c
--- b/src/pl/plpython/plpy_elog.c
*************** PyObject   *PLy_exc_fatal = NULL;
*** 21,29 ****
  PyObject   *PLy_exc_spi_error = NULL;
  
  
- static void PLy_traceback(char **xmsg, char **tbmsg, int *tb_depth);
  static void PLy_get_spi_error_data(PyObject *exc, int *sqlerrcode, char **detail,
! 					   char **hint, char **query, int *position);
  static char *get_source_line(const char *src, int lineno);
  
  
--- 21,30 ----
  PyObject   *PLy_exc_spi_error = NULL;
  
  
  static void PLy_get_spi_error_data(PyObject *exc, int *sqlerrcode, char **detail,
! 					   char **hint, char **query, int *position,
! 					   char **schema_name, char **table_name, char **column_name,
! 					   char **datatype_name, char **constraint_name);
  static char *get_source_line(const char *src, int lineno);
  
  
*************** PLy_elog(int elevel, const char *fmt,...
*** 51,62 ****
  	char	   *hint = NULL;
  	char	   *query = NULL;
  	int			position = 0;
  
  	PyErr_Fetch(&exc, &val, &tb);
  	if (exc != NULL)
  	{
  		if (PyErr_GivenExceptionMatches(val, PLy_exc_spi_error))
! 			PLy_get_spi_error_data(val, &sqlerrcode, &detail, &hint, &query, &position);
  		else if (PyErr_GivenExceptionMatches(val, PLy_exc_fatal))
  			elevel = FATAL;
  	}
--- 52,70 ----
  	char	   *hint = NULL;
  	char	   *query = NULL;
  	int			position = 0;
+ 	char	   *schema_name = NULL;
+ 	char	   *table_name = NULL;
+ 	char	   *column_name = NULL;
+ 	char	   *datatype_name = NULL;
+ 	char	   *constraint_name = NULL;
  
  	PyErr_Fetch(&exc, &val, &tb);
  	if (exc != NULL)
  	{
  		if (PyErr_GivenExceptionMatches(val, PLy_exc_spi_error))
! 			PLy_get_spi_error_data(val, &sqlerrcode, &detail, &hint, &query, &position,
! 						&schema_name, &table_name, &column_name,
! 						&datatype_name, &constraint_name);
  		else if (PyErr_GivenExceptionMatches(val, PLy_exc_fatal))
  			elevel = FATAL;
  	}
*************** PLy_elog(int elevel, const char *fmt,...
*** 103,109 ****
  				 (tb_depth > 0 && tbmsg) ? errcontext("%s", tbmsg) : 0,
  				 (hint) ? errhint("%s", hint) : 0,
  				 (query) ? internalerrquery(query) : 0,
! 				 (position) ? internalerrposition(position) : 0));
  	}
  	PG_CATCH();
  	{
--- 111,122 ----
  				 (tb_depth > 0 && tbmsg) ? errcontext("%s", tbmsg) : 0,
  				 (hint) ? errhint("%s", hint) : 0,
  				 (query) ? internalerrquery(query) : 0,
! 				 (position) ? internalerrposition(position) : 0,
! 				 (schema_name) ? err_generic_string(PG_DIAG_SCHEMA_NAME, schema_name) : 0,
! 				 (table_name) ? err_generic_string(PG_DIAG_TABLE_NAME, table_name) : 0,
! 				 (column_name) ? err_generic_string(PG_DIAG_COLUMN_NAME, column_name) : 0,
! 				 (datatype_name) ? err_generic_string(PG_DIAG_DATATYPE_NAME, datatype_name) : 0,
! 				 (constraint_name) ? err_generic_string(PG_DIAG_CONSTRAINT_NAME, constraint_name) : 0));
  	}
  	PG_CATCH();
  	{
*************** PLy_elog(int elevel, const char *fmt,...
*** 132,138 ****
   * tbmsg (both as palloc'd strings) and the traceback depth in
   * tb_depth.
   */
! static void
  PLy_traceback(char **xmsg, char **tbmsg, int *tb_depth)
  {
  	PyObject   *e,
--- 145,151 ----
   * tbmsg (both as palloc'd strings) and the traceback depth in
   * tb_depth.
   */
! void
  PLy_traceback(char **xmsg, char **tbmsg, int *tb_depth)
  {
  	PyObject   *e,
*************** PLy_get_spi_sqlerrcode(PyObject *exc, in
*** 365,371 ****
   * Extract the error data from a SPIError
   */
  static void
! PLy_get_spi_error_data(PyObject *exc, int *sqlerrcode, char **detail, char **hint, char **query, int *position)
  {
  	PyObject   *spidata = NULL;
  
--- 378,386 ----
   * Extract the error data from a SPIError
   */
  static void
! PLy_get_spi_error_data(PyObject *exc, int *sqlerrcode, char **detail, char **hint, char **query, int *position,
! 			char **schema_name, char **table_name, char **column_name,
! 			char **datatype_name, char **constraint_name)
  {
  	PyObject   *spidata = NULL;
  
*************** PLy_get_spi_error_data(PyObject *exc, in
*** 373,379 ****
  
  	if (spidata != NULL)
  	{
! 		PyArg_ParseTuple(spidata, "izzzi", sqlerrcode, detail, hint, query, position);
  	}
  	else
  	{
--- 388,396 ----
  
  	if (spidata != NULL)
  	{
! 		PyArg_ParseTuple(spidata, "izzzizzzzz", sqlerrcode, detail, hint, query, position,
! 						    schema_name, table_name, column_name,
! 						    datatype_name, constraint_name);
  	}
  	else
  	{
diff --git a/src/pl/plpython/plpy_elog.h b/src/pl/plpython/plpy_elog.h
new file mode 100644
index 94725c2..29a0d9b
*** a/src/pl/plpython/plpy_elog.h
--- b/src/pl/plpython/plpy_elog.h
*************** extern void PLy_exception_set(PyObject *
*** 17,20 ****
--- 17,22 ----
  extern void PLy_exception_set_plural(PyObject *exc, const char *fmt_singular, const char *fmt_plural,
  	unsigned long n,...) pg_attribute_printf(2, 5) pg_attribute_printf(3, 5);
  
+ extern void PLy_traceback(char **xmsg, char **tbmsg, int *tb_depth);
+ 
  #endif   /* PLPY_ELOG_H */
diff --git a/src/pl/plpython/plpy_plpymodule.c b/src/pl/plpython/plpy_plpymodule.c
new file mode 100644
index a44b7fb..e8bf582
*** a/src/pl/plpython/plpy_plpymodule.c
--- b/src/pl/plpython/plpy_plpymodule.c
***************
*** 23,29 ****
  
  HTAB	   *PLy_spi_exceptions = NULL;
  
- 
  static void PLy_add_exceptions(PyObject *plpy);
  static void PLy_generate_spi_exceptions(PyObject *mod, PyObject *base);
  
--- 23,28 ----
*************** static PyObject *PLy_quote_literal(PyObj
*** 39,44 ****
--- 38,45 ----
  static PyObject *PLy_quote_nullable(PyObject *self, PyObject *args);
  static PyObject *PLy_quote_ident(PyObject *self, PyObject *args);
  
+ /* class methods */
+ static PyObject *PLy_spi_error_init(PyObject *self, PyObject *args, PyObject *kw);
  
  /* A list of all known exceptions, generated from backend/utils/errcodes.txt */
  typedef struct ExceptionMap
*************** static PyMethodDef PLy_exc_methods[] = {
*** 99,104 ****
--- 100,110 ----
  	{NULL, NULL, 0, NULL}
  };
  
+ static PyMethodDef PLy_exc_spi_error_methods[] = {
+ 	{"__init__", (PyCFunction) PLy_spi_error_init, METH_KEYWORDS, NULL},
+ 	{NULL, NULL, 0, NULL}
+ };
+ 
  #if PY_MAJOR_VERSION >= 3
  static PyModuleDef PLy_module = {
  	PyModuleDef_HEAD_INIT,		/* m_base */
*************** PLy_add_exceptions(PyObject *plpy)
*** 187,192 ****
--- 193,201 ----
  	PyObject   *excmod;
  	HASHCTL		hash_ctl;
  
+ 	PyObject   *spi_error_dict;
+ 	PyMethodDef	*method;
+ 
  #if PY_MAJOR_VERSION < 3
  	excmod = Py_InitModule("spiexceptions", PLy_exc_methods);
  #else
*************** PLy_add_exceptions(PyObject *plpy)
*** 207,215 ****
  	 */
  	Py_INCREF(excmod);
  
  	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);
  
  	if (PLy_exc_error == NULL ||
  		PLy_exc_fatal == NULL ||
--- 216,256 ----
  	 */
  	Py_INCREF(excmod);
  
+ 	spi_error_dict = PyDict_New();
+ 
  	PLy_exc_error = PyErr_NewException("plpy.Error", NULL, NULL);
  	PLy_exc_fatal = PyErr_NewException("plpy.Fatal", NULL, NULL);
! 
! 	/* generate constructor for plpy.SPIError class */
! 	for (method = PLy_exc_spi_error_methods; method->ml_name != NULL; method++)
! 	{
! 		PyObject   *func;
! 		PyObject   *meth;
! 
! 		func = PyCFunction_New(method, NULL);
! 		if (func == NULL)
! 			PLy_elog(ERROR, "could not import function \"%s\"", method->ml_name);
! 
! /*
!  * It is not probably correct, last parameter class is null, should be
!  * PLy_exc_spi_error, but I have to create class after dictionary
!  * setting :(.
!  */
! 		meth = PyMethod_New(func, NULL, NULL);
! 		if (meth == NULL)
! 			PLy_elog(ERROR, "could not import method \"%s\"", method->ml_name);
! 
! 		if (PyDict_SetItemString(spi_error_dict, method->ml_name, meth))
! 			PLy_elog(ERROR, "could public method \"%s\" in dictionary", method->ml_name);
! 
! 		Py_DECREF(meth);
! 		Py_DECREF(func);
! 	}
! 
! /*
!  * It doesn't work with empty spi_error_dic, so dictionary must be set first.
!  */
! 	PLy_exc_spi_error = PyErr_NewException("plpy.SPIError", NULL, spi_error_dict);
  
  	if (PLy_exc_error == NULL ||
  		PLy_exc_fatal == NULL ||
*************** PLy_add_exceptions(PyObject *plpy)
*** 223,228 ****
--- 264,271 ----
  	Py_INCREF(PLy_exc_spi_error);
  	PyModule_AddObject(plpy, "SPIError", PLy_exc_spi_error);
  
+ 	Py_DECREF(spi_error_dict);
+ 
  	memset(&hash_ctl, 0, sizeof(hash_ctl));
  	hash_ctl.keysize = sizeof(int);
  	hash_ctl.entrysize = sizeof(PLyExceptionEntry);
*************** PLy_fatal(PyObject *self, PyObject *args
*** 316,321 ****
--- 359,446 ----
  }
  
  static PyObject *
+ PLy_spi_error_init(PyObject *self, PyObject *args, PyObject *kw)
+ {
+ 	int sqlstate = 0;
+ 
+ 	const char *sqlstatestr = NULL;
+ 	const char *message = NULL;
+ 	const char *detail = NULL;
+ 	const char *hint = NULL;
+ 	const char *column = NULL;
+ 	const char *constraint = NULL;
+ 	const char *datatype = NULL;
+ 	const char *table = NULL;
+ 	const char *schema = NULL;
+ 	PyObject   *exc_args = NULL;
+ 	PyObject   *spierror = NULL;
+ 	PyObject   *spidata = NULL;
+ 
+ 	PyObject   *_self;
+ 
+ 	static char *kwlist[] = { "self", "message", "detail", "hint",
+ 				  "sqlstate",
+ 				  "schema","table", "column",
+ 				  "datatype", "constraint",
+ 				  NULL };
+ 
+ 	if (!PyArg_ParseTupleAndKeywords(args, kw, "O|ssssssssssss", kwlist,
+ 			 &_self,
+ 			 &message, &detail, &hint,
+ 			 &sqlstatestr,
+ 			 &schema, &table, &column,
+ 			 &datatype, &constraint))
+ 		return NULL;
+ 
+ 	exc_args = Py_BuildValue("(s)", message);
+ 	if (!exc_args)
+ 		goto failure;
+ 
+ 	/* create a new SPI exception with the error message as the parameter */
+ 	if (PyObject_SetAttrString(_self, "args", exc_args) == -1)
+ 		goto failure;
+ 
+ 	if (sqlstatestr != NULL)
+ 	{
+ 		if (strlen(sqlstatestr) != 5)
+ 			PLy_elog(ERROR, "invalid SQLSTATE code");
+ 
+ 		if (strspn(sqlstatestr, "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ") != 5)
+ 			PLy_elog(ERROR, "invalid SQLSTATE code");
+ 
+ 		sqlstate = MAKE_SQLSTATE(sqlstatestr[0],
+ 							  sqlstatestr[1],
+ 							  sqlstatestr[2],
+ 							  sqlstatestr[3],
+ 							  sqlstatestr[4]);
+ 	}
+ 
+ 	spidata = Py_BuildValue("(izzzizzzzz)", sqlstate, detail, hint,
+ 					NULL, -1,
+ 					schema, table, column,
+ 					datatype, constraint);
+ 	if (!spidata)
+ 		goto failure;
+ 
+ 	if (PyObject_SetAttrString(_self, "spidata", spidata) == -1)
+ 		goto failure;
+ 
+ 	Py_DECREF(exc_args);
+ 	Py_DECREF(spidata);
+ 
+ 	Py_INCREF(Py_None);
+ 	return Py_None;
+ 
+ failure:
+ 	Py_XDECREF(args);
+ 	Py_XDECREF(spidata);
+ 
+ 	PLy_elog(ERROR, "could not create SPIError object");
+ 
+ 	return NULL;
+ }
+ 
+ static PyObject *
  PLy_quote_literal(PyObject *self, PyObject *args)
  {
  	const char *str;
diff --git a/src/pl/plpython/plpy_spi.c b/src/pl/plpython/plpy_spi.c
new file mode 100644
index d0e255f..4f143d6
*** a/src/pl/plpython/plpy_spi.c
--- b/src/pl/plpython/plpy_spi.c
***************
*** 30,36 ****
  static PyObject *PLy_spi_execute_query(char *query, long limit);
  static PyObject *PLy_spi_execute_plan(PyObject *ob, PyObject *list, long limit);
  static PyObject *PLy_spi_execute_fetch_result(SPITupleTable *tuptable, int rows, int status);
- static void PLy_spi_exception_set(PyObject *excclass, ErrorData *edata);
  
  
  /* prepare(query="select * from foo")
--- 30,35 ----
*************** PLy_spi_subtransaction_abort(MemoryConte
*** 532,538 ****
   * Raise a SPIError, passing in it more error details, like the
   * internal query and error position.
   */
! static void
  PLy_spi_exception_set(PyObject *excclass, ErrorData *edata)
  {
  	PyObject   *args = NULL;
--- 531,537 ----
   * Raise a SPIError, passing in it more error details, like the
   * internal query and error position.
   */
! void
  PLy_spi_exception_set(PyObject *excclass, ErrorData *edata)
  {
  	PyObject   *args = NULL;
*************** PLy_spi_exception_set(PyObject *excclass
*** 548,555 ****
  	if (!spierror)
  		goto failure;
  
! 	spidata = Py_BuildValue("(izzzi)", edata->sqlerrcode, edata->detail, edata->hint,
! 							edata->internalquery, edata->internalpos);
  	if (!spidata)
  		goto failure;
  
--- 547,556 ----
  	if (!spierror)
  		goto failure;
  
! 	spidata = Py_BuildValue("(izzzizzzzz)", edata->sqlerrcode, edata->detail, edata->hint,
! 							edata->internalquery, edata->internalpos,
! 							edata->schema_name, edata->table_name, edata->column_name,
! 							edata->datatype_name, edata->constraint_name);
  	if (!spidata)
  		goto failure;
  
diff --git a/src/pl/plpython/plpy_spi.h b/src/pl/plpython/plpy_spi.h
new file mode 100644
index b042794..e9a5991
*** a/src/pl/plpython/plpy_spi.h
--- b/src/pl/plpython/plpy_spi.h
*************** extern void PLy_spi_subtransaction_begin
*** 22,25 ****
--- 22,28 ----
  extern void PLy_spi_subtransaction_commit(MemoryContext oldcontext, ResourceOwner oldowner);
  extern void PLy_spi_subtransaction_abort(MemoryContext oldcontext, ResourceOwner oldowner);
  
+ /* set spi exception */
+ extern void PLy_spi_exception_set(PyObject *excclass, ErrorData *edata);
+ 
  #endif   /* PLPY_SPI_H */
diff --git a/src/pl/plpython/sql/plpython_error.sql b/src/pl/plpython/sql/plpython_error.sql
new file mode 100644
index d0df7e6..90da808
*** a/src/pl/plpython/sql/plpython_error.sql
--- b/src/pl/plpython/sql/plpython_error.sql
*************** EXCEPTION WHEN SQLSTATE 'SILLY' THEN
*** 328,330 ****
--- 328,362 ----
  	-- NOOP
  END
  $$ LANGUAGE plpgsql;
+ 
+ 
+ CREATE FUNCTION nested_error_ereport() RETURNS text
+ 	AS
+ 'def fun1():
+ 	raise plpy.SPIError("HiHi");
+ 
+ def fun2():
+ 	fun1()
+ 
+ def fun3():
+ 	fun2()
+ 
+ fun3()
+ return "not reached"
+ '
+ 	LANGUAGE plpythonu;
+ 
+ SELECT nested_error_ereport();
+ 
+ \set VERBOSITY verbose
+ 
+ do $$
+ x = plpy.SPIError("pokus",hint = "some info", sqlstate='AA234'); raise x;
+ $$ language plpythonu;
+ 
+ do $$
+ raise plpy.SPIError("pokus",hint = "some info", sqlstate='AA234');
+ $$ language plpythonu;
+ 
+ \set VERBOSITY default
+ 
-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Reply via email to