Hi

here is new version.

Now I use a common ancestor "plpy.BaseError" for plpy builtin classes. So
plpy.SPIError isn't descendant of plpy.Error and then there are not
possible incompatibility issues.

Instead modification builtin function plpy.debug, plpy.info, ... and
introduction incompatibility I wrote new set of functions with keyword
parameters (used mainly  for elevel < ERROR):

plpy.raise_debug, plpy.raise_info, plpy.raise_notice, plpy.raise_warning,
plpy.raise_error and plpy.raise_fatal.

With this patch we can write:

plpy.raise_warning('some is wrong', hint = 'bla bla')
raise plpy.Error(some is wrong', sqlcode = 'XX543')

Regards

Pavel
diff --git a/doc/src/sgml/plpython.sgml b/doc/src/sgml/plpython.sgml
new file mode 100644
index 015bbad..089b143
*** a/doc/src/sgml/plpython.sgml
--- b/doc/src/sgml/plpython.sgml
*************** $$ LANGUAGE plpythonu;
*** 1205,1210 ****
--- 1205,1228 ----
      approximately the same functionality
     </para>
    </sect2>
+ 
+   <sect2 id="plpython-raising">
+    <title>Raising Errors</title>
+ 
+    <para>
+     A plpy.Error can be raised from PL/Python, the constructor accepts
+     keyword parameters:
+     <literal><function>plpy.Error</function>([ <replaceable>message</replaceable> [, <replaceable>detail</replaceable> [, <replaceable>hint</replaceable> [, <replaceable>sqlstate</replaceable>  [, <replaceable>schema</replaceable>  [, <replaceable>table</replaceable>  [, <replaceable>column</replaceable>  [, <replaceable>datatype</replaceable>  [, <replaceable>constraint</replaceable> ]]]]]]]]])</literal>.
+    </para>
+    <para>
+     An example of raising custom exception could be written as:
+ <programlisting>
+ DO $$
+   raise plpy.Error('custom message', hint = 'It is test only');
+ $$ LANGUAGE plpythonu;
+ </programlisting>
+    </para>
+   </sect2>
   </sect1>
  
   <sect1 id="plpython-subtransaction">
*************** $$ LANGUAGE plpythonu;
*** 1367,1372 ****
--- 1385,1421 ----
    </para>
  
    <para>
+    The <literal>plpy</literal> module also provides the functions
+    <literal>plpy.raise_debug(<replaceable>args</>)</literal>,
+    <literal>plpy.raise_log(<replaceable>args</>)</literal>,
+    <literal>plpy.raise_info(<replaceable>args</>)</literal>,
+    <literal>plpy.raise_notice(<replaceable>args</>)</literal>,
+    <literal>plpy.raise_warning(<replaceable>args</>)</literal>,
+    <literal>plpy.raise_error(<replaceable>args</>)</literal>, and
+    <literal>plpy.raise_fatal(<replaceable>args</>)</literal>.<indexterm><primary>elog</><secondary>in PL/Python</></indexterm>
+    <function>plpy.raise_error</function> and
+    <function>plpy.raise_fatal</function> actually raise a Python exception
+    which, if uncaught, propagates out to the calling query, causing
+    the current transaction or subtransaction to be aborted.
+    <literal>raise plpy.Error(<replaceable>msg</>)</literal> and
+    <literal>raise plpy.Fatal(<replaceable>msg</>)</literal> are
+    equivalent to calling
+    <function>plpy.raise_error</function> and
+    <function>plpy.raise_fatal</function>, respectively.
+    The other functions only generate messages of different
+    priority levels.
+    Whether messages of a particular priority are reported to the client,
+    written to the server log, or both is controlled by the
+    <xref linkend="guc-log-min-messages"> and
+    <xref linkend="guc-client-min-messages"> configuration
+    variables. See <xref linkend="runtime-config"> for more information.
+    These functions allows to using keyword parameters:
+    <literal>[ <replaceable>message</replaceable> [, <replaceable>detail</replaceable> [, <replaceable>hint</replaceable> [, <replaceable>sqlstate</replaceable>  [, <replaceable>schema</replaceable>  [, <replaceable>table</replaceable>  [, <replaceable>column</replaceable>  [, <replaceable>datatype</replaceable>  [, <replaceable>constraint</replaceable> ]]]]]]]]]</literal>.
+ 
+   </para>
+ 
+ 
+   <para>
     Another set of utility functions are
     <literal>plpy.quote_literal(<replaceable>string</>)</literal>,
     <literal>plpy.quote_nullable(<replaceable>string</>)</literal>, and
diff --git a/src/pl/plpython/expected/plpython_error.out b/src/pl/plpython/expected/plpython_error.out
new file mode 100644
index 1f52af7..cb792eb
*** a/src/pl/plpython/expected/plpython_error.out
--- b/src/pl/plpython/expected/plpython_error.out
*************** EXCEPTION WHEN SQLSTATE 'SILLY' THEN
*** 422,424 ****
--- 422,486 ----
  	-- NOOP
  END
  $$ LANGUAGE plpgsql;
+ /* the possibility to set fields of custom exception
+  */
+ DO $$
+ raise plpy.Error('This is message text.',
+                     detail = 'This is detail text',
+                     hint = 'This is hint text.')
+ $$ LANGUAGE plpythonu;
+ ERROR:  plpy.Error: This is message text.
+ DETAIL:  This is detail text
+ HINT:  This is hint text.
+ CONTEXT:  Traceback (most recent call last):
+   PL/Python anonymous code block, line 4, in <module>
+     hint = 'This is hint text.')
+ PL/Python anonymous code block
+ \set VERBOSITY verbose
+ DO $$
+ raise plpy.Error('This is message text.',
+                     detail = 'This is detail text',
+                     hint = 'This is hint text.',
+                     sqlstate = 'SILLY',
+                     schema = 'any info about schema',
+                     table = 'any info about table',
+                     column = 'any info about column',
+                     datatype = 'any info about datatype',
+                     constraint = 'any info about constraint')
+ $$ LANGUAGE plpythonu;
+ ERROR:  SILLY: plpy.Error: This is message text.
+ DETAIL:  This is detail text
+ HINT:  This is hint text.
+ CONTEXT:  Traceback (most recent call last):
+   PL/Python anonymous code block, line 10, in <module>
+     constraint = 'any info about constraint')
+ PL/Python anonymous code block
+ SCHEMA NAME:  any info about schema
+ TABLE NAME:  any info about table
+ COLUMN NAME:  any info about column
+ DATATYPE NAME:  any info about datatype
+ CONSTRAINT NAME:  any info about constraint
+ LOCATION:  PLy_elog, plpy_elog.c:122
+ \set VERBOSITY default
+ DO $$
+ raise plpy.Error(detail = 'This is detail text')
+ $$ LANGUAGE plpythonu;
+ ERROR:  plpy.Error: 
+ DETAIL:  This is detail text
+ CONTEXT:  Traceback (most recent call last):
+   PL/Python anonymous code block, line 2, in <module>
+     raise plpy.Error(detail = 'This is detail text')
+ PL/Python anonymous code block
+ DO $$
+ raise plpy.Error();
+ $$ LANGUAGE plpythonu;
+ ERROR:  plpy.Error: 
+ CONTEXT:  Traceback (most recent call last):
+   PL/Python anonymous code block, line 2, in <module>
+     raise plpy.Error();
+ PL/Python anonymous code block
+ DO $$
+ raise plpy.Error(sqlstate = 'wrong sql state');
+ $$ LANGUAGE plpythonu;
+ ERROR:  could not create Error object (invalid SQLSTATE code)
+ CONTEXT:  PL/Python anonymous code block
diff --git a/src/pl/plpython/expected/plpython_error_0.out b/src/pl/plpython/expected/plpython_error_0.out
new file mode 100644
index 5323906..3d83a53
*** a/src/pl/plpython/expected/plpython_error_0.out
--- b/src/pl/plpython/expected/plpython_error_0.out
*************** EXCEPTION WHEN SQLSTATE 'SILLY' THEN
*** 422,424 ****
--- 422,486 ----
  	-- NOOP
  END
  $$ LANGUAGE plpgsql;
+ /* the possibility to set fields of custom exception
+  */
+ DO $$
+ raise plpy.Error('This is message text.',
+                     detail = 'This is detail text',
+                     hint = 'This is hint text.')
+ $$ LANGUAGE plpythonu;
+ ERROR:  plpy.Error: This is message text.
+ DETAIL:  This is detail text
+ HINT:  This is hint text.
+ CONTEXT:  Traceback (most recent call last):
+   PL/Python anonymous code block, line 4, in <module>
+     hint = 'This is hint text.')
+ PL/Python anonymous code block
+ \set VERBOSITY verbose
+ DO $$
+ raise plpy.Error('This is message text.',
+                     detail = 'This is detail text',
+                     hint = 'This is hint text.',
+                     sqlstate = 'SILLY',
+                     schema = 'any info about schema',
+                     table = 'any info about table',
+                     column = 'any info about column',
+                     datatype = 'any info about datatype',
+                     constraint = 'any info about constraint')
+ $$ LANGUAGE plpythonu;
+ ERROR:  SILLY: plpy.Error: This is message text.
+ DETAIL:  This is detail text
+ HINT:  This is hint text.
+ CONTEXT:  Traceback (most recent call last):
+   PL/Python anonymous code block, line 10, in <module>
+     constraint = 'any info about constraint')
+ PL/Python anonymous code block
+ SCHEMA NAME:  any info about schema
+ TABLE NAME:  any info about table
+ COLUMN NAME:  any info about column
+ DATATYPE NAME:  any info about datatype
+ CONSTRAINT NAME:  any info about constraint
+ LOCATION:  PLy_elog, plpy_elog.c:122
+ \set VERBOSITY default
+ DO $$
+ raise plpy.Error(detail = 'This is detail text')
+ $$ LANGUAGE plpythonu;
+ ERROR:  plpy.Error: unknown
+ DETAIL:  This is detail text
+ CONTEXT:  Traceback (most recent call last):
+   PL/Python anonymous code block, line 2, in <module>
+     raise plpy.Error(detail = 'This is detail text')
+ PL/Python anonymous code block
+ DO $$
+ raise plpy.Error();
+ $$ LANGUAGE plpythonu;
+ ERROR:  plpy.Error: unknown
+ CONTEXT:  Traceback (most recent call last):
+   PL/Python anonymous code block, line 2, in <module>
+     raise plpy.Error();
+ PL/Python anonymous code block
+ DO $$
+ raise plpy.Error(sqlstate = 'wrong sql state');
+ $$ LANGUAGE plpythonu;
+ ERROR:  could not create Error object (invalid SQLSTATE code)
+ CONTEXT:  PL/Python anonymous code block
diff --git a/src/pl/plpython/expected/plpython_error_5.out b/src/pl/plpython/expected/plpython_error_5.out
new file mode 100644
index 5ff46ca..098e506
*** a/src/pl/plpython/expected/plpython_error_5.out
--- b/src/pl/plpython/expected/plpython_error_5.out
*************** EXCEPTION WHEN SQLSTATE 'SILLY' THEN
*** 422,424 ****
--- 422,486 ----
  	-- NOOP
  END
  $$ LANGUAGE plpgsql;
+ /* the possibility to set fields of custom exception
+  */
+ DO $$
+ raise plpy.Error('This is message text.',
+                     detail = 'This is detail text',
+                     hint = 'This is hint text.')
+ $$ LANGUAGE plpython3u;
+ ERROR:  plpy.Error: This is message text.
+ DETAIL:  This is detail text
+ HINT:  This is hint text.
+ CONTEXT:  Traceback (most recent call last):
+   PL/Python anonymous code block, line 4, in <module>
+     hint = 'This is hint text.')
+ PL/Python anonymous code block
+ \set VERBOSITY verbose
+ DO $$
+ raise plpy.Error('This is message text.',
+                     detail = 'This is detail text',
+                     hint = 'This is hint text.',
+                     sqlstate = 'SILLY',
+                     schema = 'any info about schema',
+                     table = 'any info about table',
+                     column = 'any info about column',
+                     datatype = 'any info about datatype',
+                     constraint = 'any info about constraint')
+ $$ LANGUAGE plpython3u;
+ ERROR:  SILLY: plpy.Error: This is message text.
+ DETAIL:  This is detail text
+ HINT:  This is hint text.
+ CONTEXT:  Traceback (most recent call last):
+   PL/Python anonymous code block, line 10, in <module>
+     constraint = 'any info about constraint')
+ PL/Python anonymous code block
+ SCHEMA NAME:  any info about schema
+ TABLE NAME:  any info about table
+ COLUMN NAME:  any info about column
+ DATATYPE NAME:  any info about datatype
+ CONSTRAINT NAME:  any info about constraint
+ LOCATION:  PLy_elog, plpy_elog.c:122
+ \set VERBOSITY default
+ DO $$
+ raise plpy.Error(detail = 'This is detail text')
+ $$ LANGUAGE plpython3u;
+ ERROR:  plpy.Error: 
+ DETAIL:  This is detail text
+ CONTEXT:  Traceback (most recent call last):
+   PL/Python anonymous code block, line 2, in <module>
+     raise plpy.Error(detail = 'This is detail text')
+ PL/Python anonymous code block
+ DO $$
+ raise plpy.Error();
+ $$ LANGUAGE plpython3u;
+ ERROR:  plpy.Error: 
+ CONTEXT:  Traceback (most recent call last):
+   PL/Python anonymous code block, line 2, in <module>
+     raise plpy.Error();
+ PL/Python anonymous code block
+ DO $$
+ raise plpy.Error(sqlstate = 'wrong sql state');
+ $$ LANGUAGE plpython3u;
+ ERROR:  could not create Error object (invalid SQLSTATE code)
+ CONTEXT:  PL/Python anonymous code block
diff --git a/src/pl/plpython/expected/plpython_test.out b/src/pl/plpython/expected/plpython_test.out
new file mode 100644
index 7b76faf..a63f699
*** 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, cursor, debug, error, execute, fatal, info, log, notice, prepare, quote_ident, quote_literal, quote_nullable, spiexceptions, subtransaction, warning
  (1 row)
  
  CREATE FUNCTION elog_test() RETURNS void
--- 43,51 ----
  return ", ".join(contents)
  $$ LANGUAGE plpythonu;
  select module_contents();
!                                                                                                                                   module_contents                                                                                                                                   
! ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
!  BaseError, Error, Fatal, SPIError, cursor, debug, error, execute, fatal, info, log, notice, prepare, quote_ident, quote_literal, quote_nullable, raise_debug, raise_error, raise_fatal, raise_info, raise_log, raise_notice, raise_warning, spiexceptions, subtransaction, warning
  (1 row)
  
  CREATE FUNCTION elog_test() RETURNS void
*************** CONTEXT:  Traceback (most recent call la
*** 72,74 ****
--- 72,111 ----
    PL/Python function "elog_test", line 10, in <module>
      plpy.error('error')
  PL/Python function "elog_test"
+ CREATE FUNCTION elog_test2() RETURNS void
+ AS $$
+ plpy.raise_debug('debug','some detail')
+ plpy.raise_log('log','some detail')
+ plpy.raise_info('info','some detail')
+ plpy.raise_info()
+ plpy.raise_info('This is message text.',
+                     detail = 'This is detail text',
+                     hint = 'This is hint text.',
+                     sqlstate = 'SILLY',
+                     schema = 'any info about schema',
+                     table = 'any info about table',
+                     column = 'any info about column',
+                     datatype = 'any info about datatype',
+                     constraint = 'any info about constraint')
+ plpy.raise_notice('notice','some detail')
+ plpy.raise_warning('warning','some detail')
+ plpy.raise_error('stop on error', 'some detail','some hint')
+ $$ LANGUAGE plpythonu;
+ SELECT elog_test2();
+ INFO:  info
+ DETAIL:  some detail
+ INFO:  missing error text
+ INFO:  This is message text.
+ DETAIL:  This is detail text
+ HINT:  This is hint text.
+ NOTICE:  notice
+ DETAIL:  some detail
+ WARNING:  warning
+ DETAIL:  some detail
+ ERROR:  plpy.Error: stop on error
+ DETAIL:  some detail
+ HINT:  some hint
+ CONTEXT:  Traceback (most recent call last):
+   PL/Python function "elog_test2", line 17, in <module>
+     plpy.raise_error('stop on error', 'some detail','some hint')
+ PL/Python function "elog_test2"
diff --git a/src/pl/plpython/plpy_elog.c b/src/pl/plpython/plpy_elog.c
new file mode 100644
index 15406d6..54594d2
*** a/src/pl/plpython/plpy_elog.c
--- b/src/pl/plpython/plpy_elog.c
***************
*** 15,29 ****
  #include "plpy_main.h"
  #include "plpy_procedure.h"
  
! 
  PyObject   *PLy_exc_error = NULL;
  PyObject   *PLy_exc_fatal = NULL;
  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);
  
  
--- 15,32 ----
  #include "plpy_main.h"
  #include "plpy_procedure.h"
  
! PyObject   *PLy_exc_base = NULL;
  PyObject   *PLy_exc_error = NULL;
  PyObject   *PLy_exc_fatal = NULL;
  PyObject   *PLy_exc_spi_error = NULL;
  
  
  static void PLy_traceback(char **xmsg, char **tbmsg, int *tb_depth);
! static void PLy_get_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,63 ****
  	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;
  	}
  	PyErr_Restore(exc, val, tb);
--- 54,74 ----
  	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_base))
! 			PLy_get_error_data(val, &sqlerrcode, &detail, &hint, &query, &position,
! 						&schema_name, &table_name, &column_name,
! 						&datatype_name, &constraint_name);
! 
! 		if (PyErr_GivenExceptionMatches(val, PLy_exc_fatal))
  			elevel = FATAL;
  	}
  	PyErr_Restore(exc, val, tb);
*************** 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();
  	{
--- 114,126 ----
  				 (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_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;
  
--- 382,390 ----
   * Extract the error data from a SPIError
   */
  static void
! PLy_get_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
  	{
--- 392,400 ----
  
  	if (spidata != NULL)
  	{
! 		PyArg_ParseTuple(spidata, "izzzizzzzz", sqlerrcode, detail, hint, query, position,
! 						    schema_name, table_name, column_name,
! 						    datatype_name, constraint_name);
  	}
  	else
  	{
*************** PLy_exception_set_plural(PyObject *exc,
*** 464,466 ****
--- 485,532 ----
  
  	PyErr_SetString(exc, buf);
  }
+ 
+ /*
+  * Raise a BaseError, passing in it more error details, like the
+  * internal query and error position.
+  */
+ void
+ PLy_base_exception_set(PyObject *excclass, ErrorData *edata)
+ {
+ 	PyObject   *args = NULL;
+ 	PyObject   *excpt = NULL;
+ 	PyObject   *excpt_data = NULL;
+ 
+ 	args = Py_BuildValue("(s)", edata->message);
+ 	if (!args)
+ 		goto failure;
+ 
+ 	/* create a new SPI exception with the error message as the parameter */
+ 	excpt = PyObject_CallObject(excclass, args);
+ 	if (!excpt)
+ 		goto failure;
+ 
+ 	excpt_data = 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 (!excpt_data)
+ 		goto failure;
+ 
+ 	if (PyObject_SetAttrString(excpt, "spidata", excpt_data) == -1)
+ 		goto failure;
+ 
+ 	PyErr_SetObject(excclass, excpt);
+ 
+ 	Py_DECREF(args);
+ 	Py_DECREF(excpt);
+ 	Py_DECREF(excpt_data);
+ 	return;
+ 
+ failure:
+ 	Py_XDECREF(args);
+ 	Py_XDECREF(excpt);
+ 	Py_XDECREF(excpt_data);
+ 	elog(ERROR, "could not convert PostgreSQL error to Python exception");
+ }
diff --git a/src/pl/plpython/plpy_elog.h b/src/pl/plpython/plpy_elog.h
new file mode 100644
index 94725c2..eccdd44
*** a/src/pl/plpython/plpy_elog.h
--- b/src/pl/plpython/plpy_elog.h
***************
*** 6,11 ****
--- 6,12 ----
  #define PLPY_ELOG_H
  
  /* global exception classes */
+ extern PyObject *PLy_exc_base;
  extern PyObject *PLy_exc_error;
  extern PyObject *PLy_exc_fatal;
  extern PyObject *PLy_exc_spi_error;
*************** extern void PLy_exception_set(PyObject *
*** 17,20 ****
--- 18,23 ----
  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_base_exception_set(PyObject *excclass, ErrorData *edata);
+ 
  #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..d0bc519
*** a/src/pl/plpython/plpy_plpymodule.c
--- b/src/pl/plpython/plpy_plpymodule.c
*************** HTAB	   *PLy_spi_exceptions = NULL;
*** 26,31 ****
--- 26,32 ----
  
  static void PLy_add_exceptions(PyObject *plpy);
  static void PLy_generate_spi_exceptions(PyObject *mod, PyObject *base);
+ static void PLy_add_methods_to_dictionary(PyObject *dict, PyMethodDef *methods);
  
  /* module functions */
  static PyObject *PLy_debug(PyObject *self, PyObject *args);
*************** static PyObject *PLy_quote_literal(PyObj
*** 39,44 ****
--- 40,57 ----
  static PyObject *PLy_quote_nullable(PyObject *self, PyObject *args);
  static PyObject *PLy_quote_ident(PyObject *self, PyObject *args);
  
+ /* module functions with keyword argument support */
+ static PyObject *PLy_raise_debug(PyObject *self, PyObject *args, PyObject *kw);
+ static PyObject *PLy_raise_log(PyObject *self, PyObject *args, PyObject *kw);
+ static PyObject *PLy_raise_info(PyObject *self, PyObject *args, PyObject *kw);
+ static PyObject *PLy_raise_notice(PyObject *self, PyObject *args, PyObject *kw);
+ static PyObject *PLy_raise_warning(PyObject *self, PyObject *args, PyObject *kw);
+ static PyObject *PLy_raise_error(PyObject *self, PyObject *args, PyObject *kw);
+ static PyObject *PLy_raise_fatal(PyObject *self, PyObject *args, PyObject *kw);
+ 
+ /* methods */
+ static PyObject *PLy_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_methods[] = {
*** 64,69 ****
--- 77,89 ----
  	{"warning", PLy_warning, METH_VARARGS, NULL},
  	{"error", PLy_error, METH_VARARGS, NULL},
  	{"fatal", PLy_fatal, METH_VARARGS, NULL},
+ 	{"raise_debug", (PyCFunction) PLy_raise_debug, METH_VARARGS | METH_KEYWORDS, NULL},
+ 	{"raise_log", (PyCFunction) PLy_raise_log, METH_VARARGS | METH_KEYWORDS, NULL},
+ 	{"raise_info", (PyCFunction) PLy_raise_info, METH_VARARGS | METH_KEYWORDS, NULL},
+ 	{"raise_notice", (PyCFunction) PLy_raise_notice, METH_VARARGS | METH_KEYWORDS, NULL},
+ 	{"raise_warning", (PyCFunction) PLy_raise_warning, METH_VARARGS | METH_KEYWORDS, NULL},
+ 	{"raise_error", (PyCFunction) PLy_raise_error, METH_VARARGS | METH_KEYWORDS, NULL},
+ 	{"raise_fatal", (PyCFunction) PLy_raise_fatal, METH_VARARGS | METH_KEYWORDS, NULL},
  
  	/*
  	 * create a stored plan
*************** static PyMethodDef PLy_methods[] = {
*** 86,92 ****
  	 * create the subtransaction context manager
  	 */
  	{"subtransaction", PLy_subtransaction_new, METH_NOARGS, NULL},
- 
  	/*
  	 * create a cursor
  	 */
--- 106,111 ----
*************** static PyMethodDef PLy_exc_methods[] = {
*** 99,104 ****
--- 118,128 ----
  	{NULL, NULL, 0, NULL}
  };
  
+ static PyMethodDef PLy_error_methods[] = {
+ 	{"__init__", (PyCFunction) PLy_error__init__, METH_VARARGS | METH_KEYWORDS, NULL},
+ 	{NULL, NULL, 0, NULL}
+ };
+ 
  #if PY_MAJOR_VERSION >= 3
  static PyModuleDef PLy_module = {
  	PyModuleDef_HEAD_INIT,		/* m_base */
*************** static void
*** 185,190 ****
--- 209,215 ----
  PLy_add_exceptions(PyObject *plpy)
  {
  	PyObject   *excmod;
+ 	PyObject   *error_dict;
  	HASHCTL		hash_ctl;
  
  #if PY_MAJOR_VERSION < 3
*************** PLy_add_exceptions(PyObject *plpy)
*** 207,220 ****
  	 */
  	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 ||
  		PLy_exc_spi_error == NULL)
! 		PLy_elog(ERROR, "could not create the base SPI exceptions");
  
  	Py_INCREF(PLy_exc_error);
  	PyModule_AddObject(plpy, "Error", PLy_exc_error);
--- 232,260 ----
  	 */
  	Py_INCREF(excmod);
  
! 	/* prepare dictionary with __init__ method for SPIError class */
! 	error_dict = PyDict_New();
! 	if (error_dict == NULL)
! 		PLy_elog(ERROR, "could not create dictionary for BaseError class");
! 	PLy_add_methods_to_dictionary(error_dict, PLy_error_methods);
  
! 	/* create common ancestor for exception classes */
! 	PLy_exc_base = PyErr_NewException("plpy.BaseError", NULL, error_dict);
! 
! 	/* create all other builtin exception classes */
! 	PLy_exc_error = PyErr_NewException("plpy.Error", PLy_exc_base, NULL);
! 	PLy_exc_fatal = PyErr_NewException("plpy.Fatal", PLy_exc_base, NULL);
! 	PLy_exc_spi_error = PyErr_NewException("plpy.SPIError", PLy_exc_base, NULL);
! 	Py_DECREF(error_dict);
! 
! 	if (PLy_exc_base == NULL ||
! 		PLy_exc_error == NULL ||
  		PLy_exc_fatal == NULL ||
  		PLy_exc_spi_error == NULL)
! 		PLy_elog(ERROR, "could not create the plpy base exceptions");
! 
! 	Py_INCREF(PLy_exc_base);
! 	PyModule_AddObject(plpy, "BaseError", PLy_exc_base);
  
  	Py_INCREF(PLy_exc_error);
  	PyModule_AddObject(plpy, "Error", PLy_exc_error);
*************** PLy_generate_spi_exceptions(PyObject *mo
*** 266,277 ****
--- 306,467 ----
  	}
  }
  
+ /*
+  * Returns dictionary with specified set of methods. It is used for
+  * definition __init__ method of SPIError class. Our __init__ method
+  * supports keyword parameters and allows to set all available PostgreSQL
+  * Error fields.
+  */
+ static void
+ PLy_add_methods_to_dictionary(PyObject *dict, PyMethodDef *methods)
+ {
+ 	PyMethodDef	*method;
+ 
+ 	for (method = methods; method->ml_name != NULL; method++)
+ 	{
+ 		PyObject   *func;
+ 		PyObject   *meth;
+ 
+ 		func = PyCFunction_New(method, NULL);
+ 		if (func == NULL)
+ 			PLy_elog(ERROR, "could not create function wrapper for \"%s\"", method->ml_name);
+ 
+ #if PY_MAJOR_VERSION < 3
+ 		meth = PyMethod_New(func, NULL, NULL);
+ #else
+ 		meth =  PyInstanceMethod_New(func);
+ #endif
+ 		if (meth == NULL)
+ 		{
+ 			Py_DECREF(func);
+ 			PLy_elog(ERROR, "could not create method wrapper for \"%s\"", method->ml_name);
+ 		}
+ 
+ 		if (PyDict_SetItemString(dict, method->ml_name, meth))
+ 		{
+ 			Py_DECREF(func);
+ 			Py_DECREF(meth);
+ 			PLy_elog(ERROR, "could not add method \"%s\" to class dictionary", method->ml_name);
+ 		}
+ 
+ 		Py_DECREF(func);
+ 		Py_DECREF(meth);
+ 	}
+ }
+ 
+ /*
+  * Init method for SPIError class.
+  *
+  * This constructor allows to enter all user fields in PostgreSQL exception.
+  * Keywords parameters are supported.
+  */
+ static PyObject *
+ PLy_error__init__(PyObject *self, PyObject *args, PyObject *kw)
+ {
+ 	int sqlstate = 0;
+ 	bool	sqlstate_is_invalid = false;
+ 	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   *spidata = NULL;
+ 
+ 	static char *kwlist[] = { "self", "message", "detail", "hint",
+ 				  "sqlstate",
+ 				  "schema","table", "column",
+ 				  "datatype", "constraint",
+ 				  NULL };
+ 
+ 	/*
+ 	 * don't try to overwrite default sqlstate field, when constructor
+ 	 * is called without any parameter. Important for predefined
+ 	 * spiexception.* exceptions.
+ 	 */
+ 	if (PyTuple_Size(args) > 1 || (kw != NULL && PyDict_Size(kw) >= 1))
+ 	{
+ 		if (!PyArg_ParseTupleAndKeywords(args, kw, "O|sssssssss",
+ 						     kwlist, &self,
+ 							    &message, &detail, &hint,
+ 							    &sqlstatestr,
+ 							    &schema, &table, &column,
+ 							    &datatype, &constraint))
+ 			return NULL;
+ 
+ 		if (message != NULL)
+ 		{
+ 			exc_args = Py_BuildValue("(s)", message);
+ 			if (!exc_args)
+ 				goto failure;
+ 
+ 			if (PyObject_SetAttrString(self, "args", exc_args) == -1)
+ 				goto failure;
+ 		}
+ 
+ 		if (sqlstatestr != NULL)
+ 		{
+ 			if (strlen(sqlstatestr) != 5)
+ 			{
+ 				sqlstate_is_invalid = true;
+ 				goto failure;
+ 			}
+ 
+ 			if (strspn(sqlstatestr, "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ") != 5)
+ 			{
+ 				sqlstate_is_invalid = true;
+ 				goto failure;
+ 			}
+ 
+ 			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_XDECREF(exc_args);
+ 		Py_DECREF(spidata);
+ 	}
+ 
+ 	Py_INCREF(Py_None);
+ 	return Py_None;
+ 
+ failure:
+ 	Py_XDECREF(exc_args);
+ 	Py_XDECREF(spidata);
+ 
+ 	if (sqlstate_is_invalid)
+ 		PLy_elog(ERROR, "could not create Error object (invalid SQLSTATE code)");
+ 	else
+ 		PLy_elog(ERROR, "could not create Error object");
+ 
+ 	return NULL;
+ }
+ 
  
  /*
   * the python interface to the elog function
   * don't confuse these with PLy_elog
   */
  static PyObject *PLy_output(volatile int, PyObject *, PyObject *);
+ static PyObject *PLy_output_kw(volatile int, PyObject *, PyObject *, PyObject *);
  
  static PyObject *
  PLy_debug(PyObject *self, PyObject *args)
*************** PLy_fatal(PyObject *self, PyObject *args
*** 316,321 ****
--- 506,553 ----
  }
  
  static PyObject *
+ PLy_raise_debug(PyObject *self, PyObject *args, PyObject *kw)
+ {
+ 	return PLy_output_kw(DEBUG2, self, args, kw);
+ }
+ 
+ static PyObject *
+ PLy_raise_log(PyObject *self, PyObject *args, PyObject *kw)
+ {
+ 	return PLy_output_kw(LOG, self, args, kw);
+ }
+ 
+ static PyObject *
+ PLy_raise_info(PyObject *self, PyObject *args, PyObject *kw)
+ {
+ 	return PLy_output_kw(INFO, self, args, kw);
+ }
+ 
+ static PyObject *
+ PLy_raise_notice(PyObject *self, PyObject *args, PyObject *kw)
+ {
+ 	return PLy_output_kw(NOTICE, self, args, kw);
+ }
+ 
+ static PyObject *
+ PLy_raise_warning(PyObject *self, PyObject *args, PyObject *kw)
+ {
+ 	return PLy_output_kw(WARNING, self, args, kw);
+ }
+ 
+ static PyObject *
+ PLy_raise_error(PyObject *self, PyObject *args, PyObject *kw)
+ {
+ 	return PLy_output_kw(ERROR, self, args, kw);
+ }
+ 
+ static PyObject *
+ PLy_raise_fatal(PyObject *self, PyObject *args, PyObject *kw)
+ {
+ 	return PLy_output_kw(FATAL, self, args, kw);
+ }
+ 
+ static PyObject *
  PLy_quote_literal(PyObject *self, PyObject *args)
  {
  	const char *str;
*************** PLy_output(volatile int level, PyObject
*** 429,431 ****
--- 661,760 ----
  	Py_INCREF(Py_None);
  	return Py_None;
  }
+ 
+ static PyObject *
+ PLy_output_kw(volatile int level, 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;
+ 	MemoryContext oldcontext ;
+ 
+ 	static char *kwlist[] = { "message", "detail", "hint",
+ 				  "sqlstate",
+ 				  "schema","table", "column",
+ 				  "datatype", "constraint",
+ 				  NULL };
+ 
+ 	if (!PyArg_ParseTupleAndKeywords(args, kw, "|sssssssss", kwlist,
+ 			 &message, &detail, &hint,
+ 			 &sqlstatestr,
+ 			 &schema, &table, &column,
+ 			 &datatype, &constraint))
+ 		return NULL;
+ 
+ 	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]);
+ 	}
+ 
+ 	oldcontext = CurrentMemoryContext;
+ 	PG_TRY();
+ 	{
+ 		if (message != NULL)
+ 			pg_verifymbstr(message, strlen(message), false);
+ 		if (detail != NULL)
+ 			pg_verifymbstr(detail, strlen(detail), false);
+ 		if (hint != NULL)
+ 			pg_verifymbstr(hint, strlen(hint), false);
+ 		if (schema != NULL)
+ 			pg_verifymbstr(schema, strlen(schema), false);
+ 		if (table != NULL)
+ 			pg_verifymbstr(table, strlen(table), false);
+ 		if (column != NULL)
+ 			pg_verifymbstr(column, strlen(column), false);
+ 		if (datatype != NULL)
+ 			pg_verifymbstr(datatype, strlen(datatype), false);
+ 		if (constraint != NULL)
+ 			pg_verifymbstr(constraint, strlen(constraint), false);
+ 
+ 		ereport(level,
+ 				((sqlstate != 0) ? errcode(sqlstate) : 0,
+ 				 (message != NULL) ? errmsg_internal("%s", message) : 0,
+ 				 (detail != NULL) ? errdetail_internal("%s", detail) : 0,
+ 				 (hint != NULL) ? errhint("%s", hint) : 0,
+ 				 (column != NULL) ?
+ 				 err_generic_string(PG_DIAG_COLUMN_NAME, column) : 0,
+ 				 (constraint != NULL) ?
+ 				 err_generic_string(PG_DIAG_CONSTRAINT_NAME, constraint) : 0,
+ 				 (datatype != NULL) ?
+ 				 err_generic_string(PG_DIAG_DATATYPE_NAME, datatype) : 0,
+ 				 (table != NULL) ?
+ 				 err_generic_string(PG_DIAG_TABLE_NAME, table) : 0,
+ 				 (schema != NULL) ?
+ 				 err_generic_string(PG_DIAG_SCHEMA_NAME, schema) : 0));
+ 	}
+ 	PG_CATCH();
+ 	{
+ 		ErrorData	*edata;
+ 
+ 		MemoryContextSwitchTo(oldcontext);
+ 		edata = CopyErrorData();
+ 		FlushErrorState();
+ 
+ 		PLy_base_exception_set(PLy_exc_error, edata);
+ 		FreeErrorData(edata);
+ 		return NULL;
+ 	}
+ 	PG_END_TRY();
+ 
+ 	Py_INCREF(Py_None);
+ 	return Py_None;
+ }
diff --git a/src/pl/plpython/plpy_spi.c b/src/pl/plpython/plpy_spi.c
new file mode 100644
index 58e78ec..8d4604b
*** 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
*** 540,587 ****
  	Assert(entry != NULL);
  	exc = entry ? entry->exc : PLy_exc_spi_error;
  	/* Make Python raise the exception */
! 	PLy_spi_exception_set(exc, edata);
  	FreeErrorData(edata);
  }
- 
- /*
-  * 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;
- 	PyObject   *spierror = NULL;
- 	PyObject   *spidata = NULL;
- 
- 	args = Py_BuildValue("(s)", edata->message);
- 	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;
- 
- 	spidata = Py_BuildValue("(izzzi)", edata->sqlerrcode, edata->detail, edata->hint,
- 							edata->internalquery, edata->internalpos);
- 	if (!spidata)
- 		goto failure;
- 
- 	if (PyObject_SetAttrString(spierror, "spidata", spidata) == -1)
- 		goto failure;
- 
- 	PyErr_SetObject(excclass, spierror);
- 
- 	Py_DECREF(args);
- 	Py_DECREF(spierror);
- 	Py_DECREF(spidata);
- 	return;
- 
- failure:
- 	Py_XDECREF(args);
- 	Py_XDECREF(spierror);
- 	Py_XDECREF(spidata);
- 	elog(ERROR, "could not convert SPI error to Python exception");
- }
--- 539,544 ----
  	Assert(entry != NULL);
  	exc = entry ? entry->exc : PLy_exc_spi_error;
  	/* Make Python raise the exception */
! 	PLy_base_exception_set(exc, edata);
  	FreeErrorData(edata);
  }
diff --git a/src/pl/plpython/sql/plpython_error.sql b/src/pl/plpython/sql/plpython_error.sql
new file mode 100644
index d0df7e6..4657ce6
*** a/src/pl/plpython/sql/plpython_error.sql
--- b/src/pl/plpython/sql/plpython_error.sql
*************** EXCEPTION WHEN SQLSTATE 'SILLY' THEN
*** 328,330 ****
--- 328,365 ----
  	-- NOOP
  END
  $$ LANGUAGE plpgsql;
+ 
+ /* the possibility to set fields of custom exception
+  */
+ DO $$
+ raise plpy.Error('This is message text.',
+                     detail = 'This is detail text',
+                     hint = 'This is hint text.')
+ $$ LANGUAGE plpythonu;
+ 
+ \set VERBOSITY verbose
+ DO $$
+ raise plpy.Error('This is message text.',
+                     detail = 'This is detail text',
+                     hint = 'This is hint text.',
+                     sqlstate = 'SILLY',
+                     schema = 'any info about schema',
+                     table = 'any info about table',
+                     column = 'any info about column',
+                     datatype = 'any info about datatype',
+                     constraint = 'any info about constraint')
+ $$ LANGUAGE plpythonu;
+ 
+ \set VERBOSITY default
+ 
+ DO $$
+ raise plpy.Error(detail = 'This is detail text')
+ $$ LANGUAGE plpythonu;
+ 
+ DO $$
+ raise plpy.Error();
+ $$ LANGUAGE plpythonu;
+ 
+ DO $$
+ raise plpy.Error(sqlstate = 'wrong sql state');
+ $$ LANGUAGE plpythonu;
diff --git a/src/pl/plpython/sql/plpython_test.sql b/src/pl/plpython/sql/plpython_test.sql
new file mode 100644
index c8d5ef5..5eac2f5
*** a/src/pl/plpython/sql/plpython_test.sql
--- b/src/pl/plpython/sql/plpython_test.sql
*************** plpy.error('error')
*** 51,53 ****
--- 51,75 ----
  $$ LANGUAGE plpythonu;
  
  SELECT elog_test();
+ 
+ CREATE FUNCTION elog_test2() RETURNS void
+ AS $$
+ plpy.raise_debug('debug','some detail')
+ plpy.raise_log('log','some detail')
+ plpy.raise_info('info','some detail')
+ plpy.raise_info()
+ plpy.raise_info('This is message text.',
+                     detail = 'This is detail text',
+                     hint = 'This is hint text.',
+                     sqlstate = 'SILLY',
+                     schema = 'any info about schema',
+                     table = 'any info about table',
+                     column = 'any info about column',
+                     datatype = 'any info about datatype',
+                     constraint = 'any info about constraint')
+ plpy.raise_notice('notice','some detail')
+ plpy.raise_warning('warning','some detail')
+ plpy.raise_error('stop on error', 'some detail','some hint')
+ $$ LANGUAGE plpythonu;
+ 
+ SELECT elog_test2();
-- 
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