2016-02-25 7:06 GMT+01:00 Catalin Iacob <iacobcata...@gmail.com>:

> On Fri, Feb 19, 2016 at 9:41 PM, Pavel Stehule <pavel.steh...@gmail.com>
> wrote:
> >  It looks like good idea. Last version are not breaking compatibility -
> and
> > I think so it can works.
> >
> > I wrote the code, that works on Python2 and Python3
>
> Hi,
>
> I've attached a patch on top of yours with some documentation
> improvements, feel free to fold it in your next version.
>

merged


>
> I think the concept is good. We're down to code level changes. Most of
> them are cosmetical, misleading comments and so on but there are some
> bugs in there as far as I see.
>
>
> In object_to_string you don't need to test for Py_None. PyObject_Str
> will return NULL on failure and whatever str() returns on the
> underlying object. No need to special case None.
>
> In object_to_string, when you're already in a (so != NULL) block, you
> can use Py_DECREF instead of XDECREF.
>

fixed


>
> object_to_string seems buggy to me: it returns the pointer returned by
> PyString_AsString which points to the internal buffer of the Python
> object but it also XDECREFs that object. You seem to be returning a
> pointer to freed space.
>

fixed


>
> get_string_attr seems to have the same issue as object_to_string.
>

use pstrdup, but the test on Py_None is necessary


PyObject_GetAttrString can returns Py_None, and 3.4's PyString_AsString
produce a error "ERROR:  could not convert Python Unicode object to bytes"
when object is None.

So minimally in "get_string_attr" the test on Py_None is necessary


>
> In PLy_get_error_data query and position will never be set for
> plpy.Error since you don't offer them to Python and therefore don't
> set them in PLy_output_kw. So I think you should remove the code
> related to them, including the char **query, int *position parameters
> for PLy_get_error_data. Removing position also allows removing
> get_int_attr and the need to exercise this function in the tests.
>

has sense, removed


>
> You're using PLy_get_spi_sqlerrcode in PLy_get_error_data which makes
> the spi in the name unsuitable. I would rename it to just
> PLy_get_sqlerrcode. At least the comment at the top of
> PLy_get_spi_sqlerrcode needs to change since it now also extracts info
> from Error not just SPIError.
>

renamed


>
> /* set value of string pointer on object field */ comments are weird
> for a function that gets a value. But those functions need to change
> anyway since they're buggy (see above).
>

fixed


>
> The only change in plpy_plpymodule.h is the removal of an empty line
> unrelated to this patch, probably from previous versions. You should
> undo it to leave plpy_plpymodule.h untouched.
>

fixed

>
> Why rename PLy_output to PLy_output_kw? It only makes the patch bigger
> without being an improvement. Maybe you also have this from previous
> versions.
>

renamed to PLy_output only


>
> In PLy_output_kw you don't need to check message for NULL, just as sv
> wasn't checked before.
>

It is not necessary, but I am thinking so it better - it is maybe too
defensive - but the message can be taken from more sources than in old
code, and one check on NULL is correct


> In PLy_output_kw you added a FreeErrorData(edata) which didn't exist
> before. I'm not familiar with that API but I'm wondering if it's
> needed or not, good to have it or not etc.
>

The previous code used Python memory for message. Now, I used PostgreSQL
memory (via pstrdup), so now I have to call FreeErrorData.



>
> In PLy_output_kw you didn't update the "Note: If sv came from
> PyString_AsString(), it points into storage..." comment which still
> refers to sv which is now deleted.
>

I moved Py_XDECREF(so) up, and removed comment


>
> In PLy_output_kw you removed the "return a legal object so the
> interpreter will continue on its merry way" comment which might not be
> the most valuable comment in the world but removing it is still
> unrelated to this patch.
>

returned


>
> In PLy_output_kw you test for kw != NULL && kw != Py_None. The kw !=
> NULL is definitely needed but I'm quite sure Python will never pass
> Py_None into it so the != Py_None isn't needed. Can't find a reference
> to prove this at the moment.
>

cleaned

>
>
> Some more tests could be added to exercise more parts of the patch:
> - check that SPIError is enhanced with all the new things:
> schema_name, table_name etc.
> - in the plpy.error call use every keyword argument not just detail
> and hint: it proves you save and restore every field correctly from
> the Error fields since that's not exercised by the info call above
> which does use every argument
>



> - use a wrong keyword argument to see it gets rejected with you error
> message
> - try some other types than str (like detail=42) for error as well
> since error goes on another code path than info
> - a test exercising the "invalid sqlstate" code
>

done

Sending updated version

Regards

Pavel
diff --git a/doc/src/sgml/plpython.sgml b/doc/src/sgml/plpython.sgml
new file mode 100644
index 015bbad..4ed34d6
*** a/doc/src/sgml/plpython.sgml
--- b/doc/src/sgml/plpython.sgml
*************** $$ LANGUAGE plpythonu;
*** 1341,1353 ****
    <title>Utility Functions</title>
    <para>
     The <literal>plpy</literal> module also provides the functions
!    <literal>plpy.debug(<replaceable>msg</>)</literal>,
!    <literal>plpy.log(<replaceable>msg</>)</literal>,
!    <literal>plpy.info(<replaceable>msg</>)</literal>,
!    <literal>plpy.notice(<replaceable>msg</>)</literal>,
!    <literal>plpy.warning(<replaceable>msg</>)</literal>,
!    <literal>plpy.error(<replaceable>msg</>)</literal>, and
!    <literal>plpy.fatal(<replaceable>msg</>)</literal>.<indexterm><primary>elog</><secondary>in PL/Python</></indexterm>
     <function>plpy.error</function> and
     <function>plpy.fatal</function> actually raise a Python exception
     which, if uncaught, propagates out to the calling query, causing
--- 1341,1353 ----
    <title>Utility Functions</title>
    <para>
     The <literal>plpy</literal> module also provides the functions
!    <literal>plpy.debug(<replaceable>msg, **kwargs</>)</literal>,
!    <literal>plpy.log(<replaceable>msg, **kwargs</>)</literal>,
!    <literal>plpy.info(<replaceable>msg, **kwargs</>)</literal>,
!    <literal>plpy.notice(<replaceable>msg, **kwargs</>)</literal>,
!    <literal>plpy.warning(<replaceable>msg, **kwargs</>)</literal>,
!    <literal>plpy.error(<replaceable>msg, **kwargs</>)</literal>, and
!    <literal>plpy.fatal(<replaceable>msg, **kwargs</>)</literal>.<indexterm><primary>elog</><secondary>in PL/Python</></indexterm>
     <function>plpy.error</function> and
     <function>plpy.fatal</function> actually raise a Python exception
     which, if uncaught, propagates out to the calling query, causing
*************** $$ LANGUAGE plpythonu;
*** 1355,1362 ****
     <literal>raise plpy.Error(<replaceable>msg</>)</literal> and
     <literal>raise plpy.Fatal(<replaceable>msg</>)</literal> are
     equivalent to calling
!    <function>plpy.error</function> and
!    <function>plpy.fatal</function>, respectively.
     The other functions only generate messages of different
     priority levels.
     Whether messages of a particular priority are reported to the client,
--- 1355,1363 ----
     <literal>raise plpy.Error(<replaceable>msg</>)</literal> and
     <literal>raise plpy.Fatal(<replaceable>msg</>)</literal> are
     equivalent to calling
!    <literal>plpy.error(<replaceable>msg</>)</literal> and
!    <literal>plpy.fatal(<replaceable>msg</>)</literal>, respectively but
!    the <literal>raise</literal> form does not allow passing keyword arguments.
     The other functions only generate messages of different
     priority levels.
     Whether messages of a particular priority are reported to the client,
*************** $$ LANGUAGE plpythonu;
*** 1367,1372 ****
--- 1368,1401 ----
    </para>
  
    <para>
+ 
+    The <replaceable>msg</> argument is given as a positional argument.  For
+    backward compatibility, more than one positional argument can be given. In
+    that case, the string representation of the tuple of positional arguments
+    becomes the message reported to the client.
+    The following keyword-only arguments are accepted:
+    <literal><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>.
+    The string representation of the objects passed as keyword-only arguments
+    is used to enrich the messages reported to the client. For example:
+ 
+ <programlisting>
+ CREATE FUNCTION raise_custom_exception() RETURNS void AS $$
+ plpy.error("custom exception message", detail = "some info about exception", hint = "hint for users")
+ $$ LANGUAGE plpythonu;
+ 
+ postgres=# select raise_custom_exception();
+ ERROR:  XX000: plpy.Error: custom exception message
+ DETAIL:  some info about exception
+ HINT:  hint for users
+ CONTEXT:  Traceback (most recent call last):
+   PL/Python function "raise_custom_exception", line 2, in &lt;module&gt;
+     plpy.error("custom exception message", detail = "some info about exception", hint = "hint for users")
+ PL/Python function "raise_custom_exception"
+ LOCATION:  PLy_elog, plpy_elog.c:132
+ </programlisting>
+   </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_test.out b/src/pl/plpython/expected/plpython_test.out
new file mode 100644
index f8270a7..c385256
*** a/src/pl/plpython/expected/plpython_test.out
--- b/src/pl/plpython/expected/plpython_test.out
*************** select module_contents();
*** 48,54 ****
   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
  AS $$
  plpy.debug('debug')
  plpy.log('log')
--- 48,54 ----
   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_basic() RETURNS void
  AS $$
  plpy.debug('debug')
  plpy.log('log')
*************** plpy.notice('notice')
*** 60,66 ****
  plpy.warning('warning')
  plpy.error('error')
  $$ LANGUAGE plpythonu;
! SELECT elog_test();
  INFO:  info
  INFO:  37
  INFO:  ()
--- 60,66 ----
  plpy.warning('warning')
  plpy.error('error')
  $$ LANGUAGE plpythonu;
! SELECT elog_test_basic();
  INFO:  info
  INFO:  37
  INFO:  ()
*************** NOTICE:  notice
*** 69,74 ****
  WARNING:  warning
  ERROR:  plpy.Error: error
  CONTEXT:  Traceback (most recent call last):
!   PL/Python function "elog_test", line 10, in <module>
      plpy.error('error')
  PL/Python function "elog_test"
--- 69,255 ----
  WARNING:  warning
  ERROR:  plpy.Error: error
  CONTEXT:  Traceback (most recent call last):
!   PL/Python function "elog_test_basic", line 10, in <module>
      plpy.error('error')
+ PL/Python function "elog_test_basic"
+ CREATE FUNCTION elog_test() RETURNS void
+ AS $$
+ plpy.debug('debug', detail = 'some detail')
+ plpy.log('log', detail = 'some detail')
+ plpy.info('info', detail = 'some detail')
+ plpy.info()
+ plpy.info('the question', detail = 42);
+ plpy.info('This is message text.',
+                     detail = 'This is detail text',
+                     hint = 'This is hint text.',
+                     sqlstate = 'XX000',
+                     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.notice('notice', detail = 'some detail')
+ plpy.warning('warning', detail = 'some detail')
+ plpy.error('stop on error', detail = 'some detail', hint = 'some hint')
+ $$ LANGUAGE plpythonu;
+ SELECT elog_test();
+ INFO:  info
+ DETAIL:  some detail
+ INFO:  ()
+ INFO:  the question
+ DETAIL:  42
+ 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_test", line 18, in <module>
+     plpy.error('stop on error', detail = 'some detail', hint = 'some hint')
  PL/Python function "elog_test"
+ do $$ plpy.info('other types', detail = (10,20)) $$ LANGUAGE plpythonu;
+ INFO:  other types
+ DETAIL:  (10, 20)
+ do $$
+ import time;
+ from datetime import date
+ plpy.info('other types', detail = date(2016,2,26))
+ $$ LANGUAGE plpythonu;
+ INFO:  other types
+ DETAIL:  2016-02-26
+ do $$
+ basket = ['apple', 'orange', 'apple', 'pear', 'orange', 'banana']
+ plpy.info('other types', detail = basket)
+ $$ LANGUAGE plpythonu;
+ INFO:  other types
+ DETAIL:  ['apple', 'orange', 'apple', 'pear', 'orange', 'banana']
+ -- should fail
+ do $$ plpy.info('wrong sqlstate', sqlstate='54444A') $$ LANGUAGE plpythonu;
+ ERROR:  invalid SQLSTATE code
+ CONTEXT:  PL/Python anonymous code block
+ do $$ plpy.info('unsupported argument', blabla='fooboo') $$ LANGUAGE plpythonu;
+ ERROR:  'blabla' is an invalid keyword argument for this function
+ CONTEXT:  PL/Python anonymous code block
+ -- raise exception i n python, handle exception in plgsql
+ CREATE OR REPLACE FUNCTION raise_exception(_message text, _detail text DEFAULT NULL, _hint text DEFAULT NULL,
+ 						_sqlstate text DEFAULT NULL,
+ 						_schema text DEFAULT NULL, _table text DEFAULT NULL, _column text DEFAULT NULL,
+ 						_datatype text DEFAULT NULL, _constraint text DEFAULT NULL)
+ RETURNS void AS $$
+ kwargs = { "message":_message, "detail":_detail, "hint":_hint,
+ 			"sqlstate":_sqlstate, "schema":_schema, "table":_table,
+ 			"column":_column, "datatype":_datatype, "constraint":_constraint }
+ # ignore None values
+ plpy.error(**dict((k, v) for k, v in iter(kwargs.items()) if v))
+ $$ LANGUAGE plpythonu;
+ SELECT raise_exception('hello', 'world');
+ ERROR:  plpy.Error: hello
+ DETAIL:  world
+ CONTEXT:  Traceback (most recent call last):
+   PL/Python function "raise_exception", line 6, in <module>
+     plpy.error(**dict((k, v) for k, v in iter(kwargs.items()) if v))
+ PL/Python function "raise_exception"
+ SELECT raise_exception('message text', 'detail text', _sqlstate => 'YY333');
+ ERROR:  plpy.Error: message text
+ DETAIL:  detail text
+ CONTEXT:  Traceback (most recent call last):
+   PL/Python function "raise_exception", line 6, in <module>
+     plpy.error(**dict((k, v) for k, v in iter(kwargs.items()) if v))
+ PL/Python function "raise_exception"
+ SELECT raise_exception(_message => 'message text',
+ 						_detail => 'detail text',
+ 						_hint => 'hint text',
+ 						_sqlstate => 'XX555',
+ 						_schema => 'schema text',
+ 						_table => 'table text',
+ 						_column => 'column text',
+ 						_datatype => 'datatype text',
+ 						_constraint => 'constraint text');
+ ERROR:  plpy.Error: message text
+ DETAIL:  detail text
+ HINT:  hint text
+ CONTEXT:  Traceback (most recent call last):
+   PL/Python function "raise_exception", line 6, in <module>
+     plpy.error(**dict((k, v) for k, v in iter(kwargs.items()) if v))
+ PL/Python function "raise_exception"
+ SELECT raise_exception(_message => 'message text',
+ 						_hint => 'hint text',
+ 						_schema => 'schema text',
+ 						_column => 'column text',
+ 						_constraint => 'constraint text');
+ ERROR:  plpy.Error: message text
+ HINT:  hint text
+ CONTEXT:  Traceback (most recent call last):
+   PL/Python function "raise_exception", line 6, in <module>
+     plpy.error(**dict((k, v) for k, v in iter(kwargs.items()) if v))
+ PL/Python function "raise_exception"
+ DO $$
+ DECLARE
+   __message text;
+   __detail text;
+   __hint text;
+   __sqlstate text;
+   __schema_name text;
+   __table_name text;
+   __column_name text;
+   __datatype text;
+   __constraint text;
+ BEGIN
+   BEGIN
+     PERFORM raise_exception(_message => 'message text',
+                             _detail => 'detail text',
+                             _hint => 'hint text',
+                             _sqlstate => 'XX555',
+                             _schema => 'schema text',
+                             _table => 'table text',
+                             _column => 'column text',
+                             _datatype => 'datatype text',
+                             _constraint => 'constraint text');
+   EXCEPTION WHEN SQLSTATE 'XX555' THEN
+     GET STACKED DIAGNOSTICS __message = MESSAGE_TEXT,
+                             __detail = PG_EXCEPTION_DETAIL,
+                             __hint = PG_EXCEPTION_HINT,
+                             __sqlstate = RETURNED_SQLSTATE,
+                             __schema_name = SCHEMA_NAME,
+                             __table_name = TABLE_NAME,
+                             __column_name = COLUMN_NAME,
+                             __datatype = PG_DATATYPE_NAME,
+                             __constraint = CONSTRAINT_NAME;
+     RAISE NOTICE 'handled exception'
+        USING DETAIL = format('message:(%s), detail:(%s), hint: (%s), sqlstate: (%s), '
+                              'schema:(%s), table:(%s), column:(%s), datatype:(%s), constraint:(%s)',
+                              __message, __detail, __hint, __sqlstate, __schema_name,
+                              __table_name, __column_name, __datatype, __constraint);
+   END;
+ END;
+ $$;
+ NOTICE:  handled exception
+ DETAIL:  message:(plpy.Error: message text), detail:(detail text), hint: (hint text), sqlstate: (XX555), schema:(schema text), table:(table text), column:(column text), datatype:(datatype text), constraint:(constraint text)
+ -- the displayed context is different between Python2 and Python3,
+ -- but for this test is not important.
+ \set SHOW_CONTEXT never
+ do $$
+ try:
+ 	plpy.execute("select raise_exception(_message => 'my message', _sqlstate => 'XX987', _hint => 'some hint', _table=> 'users_tab', _datatype => 'user_type')")
+ except Exception as e:
+ 	plpy.info(e.spidata)
+ 	raise e
+ $$ LANGUAGE plpythonu;
+ INFO:  (119577128, None, 'some hint', None, 0, None, 'users_tab', None, 'user_type', None)
+ ERROR:  plpy.SPIError: plpy.Error: my message
+ HINT:  some hint
+ do $$
+ try:
+   plpy.error(message  = 'my message', sqlstate = 'XX987', hint = 'some hint', table = 'users_tab', datatype = 'user_type')
+ except Exception as e:
+   plpy.info('sqlstate: {0}, hint: {1}, tablename: {2}, datatype: {3}'.format(e.sqlstate, e.hint, e.table_name, e.datatype_name))
+   raise e
+ $$ LANGUAGE plpythonu;
+ INFO:  sqlstate: XX987, hint: some hint, tablename: users_tab, datatype: user_type
+ ERROR:  plpy.Error: my message
+ HINT:  some hint
diff --git a/src/pl/plpython/plpy_elog.c b/src/pl/plpython/plpy_elog.c
new file mode 100644
index 15406d6..7554840
*** a/src/pl/plpython/plpy_elog.c
--- b/src/pl/plpython/plpy_elog.c
*************** PyObject   *PLy_exc_spi_error = NULL;
*** 23,31 ****
  
  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);
  
  
  /*
   * Emit a PG error or notice, together with any available info about
--- 23,39 ----
  
  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,
! 			char **schema_name, char **table_name, char **column_name,
! 			char **datatype_name, char **constraint_name);
! static void PLy_get_error_data(PyObject *exc, int *sqlerrcode, char **detail,
! 			char **hint, 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);
  
+ static void get_string_attr(PyObject *obj, char *attrname, char **str);
+ static bool set_string_attr(PyObject *obj, char *attrname, char *str);
+ static bool set_int_attr(PyObject *obj, char *attrname, int iv);
  
  /*
   * Emit a PG error or notice, together with any available info about
*************** 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;
  	}
--- 59,81 ----
  	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_error))
! 			PLy_get_error_data(val, &sqlerrcode, &detail, &hint,
! 						&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();
  	{
--- 122,133 ----
  				 (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_traceback(char **xmsg, char **tbmsg,
*** 340,346 ****
   * Extract error code from SPIError's sqlstate attribute.
   */
  static void
! PLy_get_spi_sqlerrcode(PyObject *exc, int *sqlerrcode)
  {
  	PyObject   *sqlstate;
  	char	   *buffer;
--- 364,370 ----
   * Extract error code from SPIError's sqlstate attribute.
   */
  static void
! PLy_get_sqlerrcode(PyObject *exc, int *sqlerrcode)
  {
  	PyObject   *sqlstate;
  	char	   *buffer;
*************** PLy_get_spi_sqlerrcode(PyObject *exc, in
*** 360,371 ****
  	Py_DECREF(sqlstate);
  }
  
- 
  /*
   * 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;
  
--- 384,397 ----
  	Py_DECREF(sqlstate);
  }
  
  /*
   * 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
  	{
--- 399,407 ----
  
  	if (spidata != NULL)
  	{
! 		PyArg_ParseTuple(spidata, "izzzizzzzz", sqlerrcode, detail, hint, query, position,
! 												schema_name, table_name, column_name,
! 												datatype_name, constraint_name);
  	}
  	else
  	{
*************** PLy_get_spi_error_data(PyObject *exc, in
*** 381,387 ****
  		 * If there's no spidata, at least set the sqlerrcode. This can happen
  		 * if someone explicitly raises a SPI exception from Python code.
  		 */
! 		PLy_get_spi_sqlerrcode(exc, sqlerrcode);
  	}
  
  	PyErr_Clear();
--- 409,415 ----
  		 * If there's no spidata, at least set the sqlerrcode. This can happen
  		 * if someone explicitly raises a SPI exception from Python code.
  		 */
! 		PLy_get_sqlerrcode(exc, sqlerrcode);
  	}
  
  	PyErr_Clear();
*************** PLy_get_spi_error_data(PyObject *exc, in
*** 390,395 ****
--- 418,446 ----
  }
  
  /*
+  * Extract the error data from an Error.
+  * Note: position and query fields are not used in Python Error exception ever.
+  */
+ static void
+ PLy_get_error_data(PyObject *exc, int *sqlerrcode, char **detail, char **hint,
+ 			char **schema_name, char **table_name, char **column_name,
+ 			char **datatype_name, char **constraint_name)
+ {
+ 	PLy_get_sqlerrcode(exc, sqlerrcode);
+ 
+ 	get_string_attr(exc, "detail", detail);
+ 	get_string_attr(exc, "hint", hint);
+ 	get_string_attr(exc, "schema_name", schema_name);
+ 	get_string_attr(exc, "table_name", table_name);
+ 	get_string_attr(exc, "column_name", column_name);
+ 	get_string_attr(exc, "datatype_name", datatype_name);
+ 	get_string_attr(exc, "constraint_name", constraint_name);
+ 
+ 	PyErr_Clear();
+ 	/* no elog here, we simply won't report the errhint, errposition etc */
+ }
+ 
+ /*
   * Get the given source line as a palloc'd string
   */
  static char *
*************** PLy_exception_set_plural(PyObject *exc,
*** 464,466 ****
--- 515,634 ----
  
  	PyErr_SetString(exc, buf);
  }
+ 
+ /* set exceptions from an ErrorData */
+ void
+ PLy_exception_set_with_details(PyObject *excclass, ErrorData *edata)
+ {
+ 	PyObject   *args = NULL;
+ 	PyObject   *error = NULL;
+ 
+ 	args = Py_BuildValue("(s)", edata->message);
+ 	if (!args)
+ 		goto failure;
+ 
+ 	/* create a new exception with the error message as the parameter */
+ 	error = PyObject_CallObject(excclass, args);
+ 	if (!error)
+ 		goto failure;
+ 
+ 	if (!set_string_attr(error, "sqlstate",
+ 							unpack_sql_state(edata->sqlerrcode)))
+ 		goto failure;
+ 
+ 	if (!set_string_attr(error, "detail", edata->detail))
+ 		goto failure;
+ 
+ 	if (!set_string_attr(error, "hint", edata->hint))
+ 		goto failure;
+ 
+ 	if (!set_string_attr(error, "query", edata->internalquery))
+ 		goto failure;
+ 
+ 	if (!set_int_attr(error, "position", edata->internalpos))
+ 		goto failure;
+ 
+ 	if (!set_string_attr(error, "schema_name", edata->schema_name))
+ 		goto failure;
+ 
+ 	if (!set_string_attr(error, "table_name", edata->table_name))
+ 		goto failure;
+ 
+ 	if (!set_string_attr(error, "column_name", edata->column_name))
+ 		goto failure;
+ 
+ 	if (!set_string_attr(error, "datatype_name", edata->datatype_name))
+ 		goto failure;
+ 
+ 	if (!set_string_attr(error, "constraint_name", edata->constraint_name))
+ 		goto failure;
+ 
+ 	PyErr_SetObject(excclass, error);
+ 
+ 	Py_DECREF(args);
+ 	Py_DECREF(error);
+ 
+ 	return;
+ 
+ failure:
+ 	Py_XDECREF(args);
+ 	Py_XDECREF(error);
+ 
+ 	elog(ERROR, "could not convert error to Python exception");
+ }
+ 
+ /* get string value of object field */
+ static void
+ get_string_attr(PyObject *obj, char *attrname, char **str)
+ {
+ 	PyObject *val;
+ 
+ 	val = PyObject_GetAttrString(obj, attrname);
+ 	if (val != NULL && val != Py_None)
+ 	{
+ 		*str = pstrdup(PyString_AsString(val));
+ 	}
+ 	Py_XDECREF(val);
+ }
+ 
+ /* set a string field of object, returns true when the change was success */
+ static bool
+ set_string_attr(PyObject *obj, char *attrname, char *str)
+ {
+ 	int result;
+ 	PyObject *val;
+ 
+ 	if (str != NULL)
+ 	{
+ 		val = PyString_FromString(str);
+ 		if (!val)
+ 			return false;
+ 	}
+ 	else
+ 	{
+ 		val = Py_None;
+ 		Py_INCREF(Py_None);
+ 	}
+ 
+ 	result = PyObject_SetAttrString(obj, attrname, val);
+ 	Py_DECREF(val);
+ 
+ 	return result != -1;
+ }
+ 
+ /* same as previous for int */
+ static bool
+ set_int_attr(PyObject *obj, char *attrname, int iv)
+ {
+ 	int result;
+ 	PyObject *val;
+ 
+ 	val = PyInt_FromLong((long) iv);
+ 	if (!val)
+ 		return false;
+ 
+ 	result = PyObject_SetAttrString(obj, attrname, val);
+ 	Py_DECREF(val);
+ 
+ 	return result != -1;
+ }
diff --git a/src/pl/plpython/plpy_elog.h b/src/pl/plpython/plpy_elog.h
new file mode 100644
index 94725c2..5dd4ef7
*** 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_exception_set_with_details(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..0ad8611
*** a/src/pl/plpython/plpy_plpymodule.c
--- b/src/pl/plpython/plpy_plpymodule.c
*************** static void PLy_add_exceptions(PyObject
*** 28,40 ****
  static void PLy_generate_spi_exceptions(PyObject *mod, PyObject *base);
  
  /* module functions */
! static PyObject *PLy_debug(PyObject *self, PyObject *args);
! static PyObject *PLy_log(PyObject *self, PyObject *args);
! static PyObject *PLy_info(PyObject *self, PyObject *args);
! static PyObject *PLy_notice(PyObject *self, PyObject *args);
! static PyObject *PLy_warning(PyObject *self, PyObject *args);
! static PyObject *PLy_error(PyObject *self, PyObject *args);
! static PyObject *PLy_fatal(PyObject *self, PyObject *args);
  static PyObject *PLy_quote_literal(PyObject *self, PyObject *args);
  static PyObject *PLy_quote_nullable(PyObject *self, PyObject *args);
  static PyObject *PLy_quote_ident(PyObject *self, PyObject *args);
--- 28,40 ----
  static void PLy_generate_spi_exceptions(PyObject *mod, PyObject *base);
  
  /* module functions */
! static PyObject *PLy_debug(PyObject *self, PyObject *args, PyObject *kw);
! static PyObject *PLy_log(PyObject *self, PyObject *args, PyObject *kw);
! static PyObject *PLy_info(PyObject *self, PyObject *args, PyObject *kw);
! static PyObject *PLy_notice(PyObject *self, PyObject *args, PyObject *kw);
! static PyObject *PLy_warning(PyObject *self, PyObject *args, PyObject *kw);
! static PyObject *PLy_error(PyObject *self, PyObject *args, PyObject *kw);
! static PyObject *PLy_fatal(PyObject *self, PyObject *args, PyObject *kw);
  static PyObject *PLy_quote_literal(PyObject *self, PyObject *args);
  static PyObject *PLy_quote_nullable(PyObject *self, PyObject *args);
  static PyObject *PLy_quote_ident(PyObject *self, PyObject *args);
*************** static PyMethodDef PLy_methods[] = {
*** 57,69 ****
  	/*
  	 * logging methods
  	 */
! 	{"debug", PLy_debug, METH_VARARGS, NULL},
! 	{"log", PLy_log, METH_VARARGS, NULL},
! 	{"info", PLy_info, METH_VARARGS, NULL},
! 	{"notice", PLy_notice, METH_VARARGS, NULL},
! 	{"warning", PLy_warning, METH_VARARGS, NULL},
! 	{"error", PLy_error, METH_VARARGS, NULL},
! 	{"fatal", PLy_fatal, METH_VARARGS, NULL},
  
  	/*
  	 * create a stored plan
--- 57,69 ----
  	/*
  	 * logging methods
  	 */
! 	{"debug", (PyCFunction) PLy_debug, METH_VARARGS|METH_KEYWORDS, NULL},
! 	{"log", (PyCFunction) PLy_log, METH_VARARGS|METH_KEYWORDS, NULL},
! 	{"info", (PyCFunction) PLy_info, METH_VARARGS|METH_KEYWORDS, NULL},
! 	{"notice", (PyCFunction) PLy_notice, METH_VARARGS|METH_KEYWORDS, NULL},
! 	{"warning", (PyCFunction) PLy_warning, METH_VARARGS|METH_KEYWORDS, NULL},
! 	{"error", (PyCFunction) PLy_error, METH_VARARGS|METH_KEYWORDS, NULL},
! 	{"fatal", (PyCFunction) PLy_fatal, METH_VARARGS|METH_KEYWORDS, NULL},
  
  	/*
  	 * create a stored plan
*************** PLy_generate_spi_exceptions(PyObject *mo
*** 271,318 ****
   * 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_debug(PyObject *self, PyObject *args)
  {
! 	return PLy_output(DEBUG2, self, args);
  }
  
  static PyObject *
! PLy_log(PyObject *self, PyObject *args)
  {
! 	return PLy_output(LOG, self, args);
  }
  
  static PyObject *
! PLy_info(PyObject *self, PyObject *args)
  {
! 	return PLy_output(INFO, self, args);
  }
  
  static PyObject *
! PLy_notice(PyObject *self, PyObject *args)
  {
! 	return PLy_output(NOTICE, self, args);
  }
  
  static PyObject *
! PLy_warning(PyObject *self, PyObject *args)
  {
! 	return PLy_output(WARNING, self, args);
  }
  
  static PyObject *
! PLy_error(PyObject *self, PyObject *args)
  {
! 	return PLy_output(ERROR, self, args);
  }
  
  static PyObject *
! PLy_fatal(PyObject *self, PyObject *args)
  {
! 	return PLy_output(FATAL, self, args);
  }
  
  static PyObject *
--- 271,319 ----
   * the python interface to the elog function
   * don't confuse these with PLy_elog
   */
! static PyObject *PLy_output(volatile int level, PyObject *self,
! 										  PyObject *args, PyObject *kw);
  
  static PyObject *
! PLy_debug(PyObject *self, PyObject *args, PyObject *kw)
  {
! 	return PLy_output(DEBUG2, self, args, kw);
  }
  
  static PyObject *
! PLy_log(PyObject *self, PyObject *args, PyObject *kw)
  {
! 	return PLy_output(LOG, self, args, kw);
  }
  
  static PyObject *
! PLy_info(PyObject *self, PyObject *args, PyObject *kw)
  {
! 	return PLy_output(INFO, self, args, kw);
  }
  
  static PyObject *
! PLy_notice(PyObject *self, PyObject *args, PyObject *kw)
  {
! 	return PLy_output(NOTICE, self, args, kw);
  }
  
  static PyObject *
! PLy_warning(PyObject *self, PyObject *args, PyObject *kw)
  {
! 	return PLy_output(WARNING, self, args, kw);
  }
  
  static PyObject *
! PLy_error(PyObject *self, PyObject *args, PyObject *kw)
  {
! 	return PLy_output(ERROR, self, args, kw);
  }
  
  static PyObject *
! PLy_fatal(PyObject *self, PyObject *args, PyObject *kw)
  {
! 	return PLy_output(FATAL, self, args, kw);
  }
  
  static PyObject *
*************** PLy_quote_ident(PyObject *self, PyObject
*** 368,379 ****
  	return ret;
  }
  
  static PyObject *
! PLy_output(volatile int level, PyObject *self, PyObject *args)
  {
! 	PyObject   *volatile so;
! 	char	   *volatile sv;
! 	volatile MemoryContext oldcontext;
  
  	if (PyTuple_Size(args) == 1)
  	{
--- 369,413 ----
  	return ret;
  }
  
+ /* enforce cast of object to string */
+ static char *
+ object_to_string(PyObject *obj)
+ {
+ 	if (obj)
+ 	{
+ 		PyObject *so = PyObject_Str(obj);
+ 
+ 		if (so != NULL)
+ 		{
+ 			char *str;
+ 
+ 			str = pstrdup(PyString_AsString(so));
+ 			Py_DECREF(so);
+ 
+ 			return str;
+ 		}
+ 	}
+ 
+ 	return NULL;
+ }
+ 
  static PyObject *
! PLy_output(volatile int level, PyObject *self, PyObject *args, PyObject *kw)
  {
! 	int sqlstate = 0;
! 	char *volatile sqlstatestr = NULL;
! 	char *volatile message = NULL;
! 	char *volatile detail = NULL;
! 	char *volatile hint = NULL;
! 	char *volatile column = NULL;
! 	char *volatile constraint = NULL;
! 	char *volatile datatype = NULL;
! 	char *volatile table = NULL;
! 	char *volatile schema = NULL;
! 	MemoryContext oldcontext ;
! 	PyObject *key, *value;
! 	PyObject *volatile so;
! 	Py_ssize_t pos = 0;
  
  	if (PyTuple_Size(args) == 1)
  	{
*************** PLy_output(volatile int level, PyObject
*** 389,428 ****
  	}
  	else
  		so = PyObject_Str(args);
! 	if (so == NULL || ((sv = PyString_AsString(so)) == NULL))
  	{
  		level = ERROR;
! 		sv = dgettext(TEXTDOMAIN, "could not parse error message in plpy.elog");
  	}
  
  	oldcontext = CurrentMemoryContext;
  	PG_TRY();
  	{
! 		pg_verifymbstr(sv, strlen(sv), false);
! 		elog(level, "%s", sv);
  	}
  	PG_CATCH();
  	{
! 		ErrorData  *edata;
  
  		MemoryContextSwitchTo(oldcontext);
  		edata = CopyErrorData();
  		FlushErrorState();
  
! 		/*
! 		 * Note: If sv came from PyString_AsString(), it points into storage
! 		 * owned by so.  So free so after using sv.
! 		 */
! 		Py_XDECREF(so);
  
- 		/* Make Python raise the exception */
- 		PLy_exception_set(PLy_exc_error, "%s", edata->message);
  		return NULL;
  	}
  	PG_END_TRY();
  
- 	Py_XDECREF(so);
- 
  	/*
  	 * return a legal object so the interpreter will continue on its merry way
  	 */
--- 423,533 ----
  	}
  	else
  		so = PyObject_Str(args);
! 
! 	if (so == NULL || ((message = pstrdup(PyString_AsString(so))) == NULL))
  	{
  		level = ERROR;
! 		message = dgettext(TEXTDOMAIN, "could not parse error message in plpy.elog");
! 	}
! 
! 	Py_XDECREF(so);
! 
! 	if (kw != NULL)
! 	{
! 		while (PyDict_Next(kw, &pos, &key, &value))
! 		{
! 			char *keyword = PyString_AsString(key);
! 
! 			if (strcmp(keyword, "message") == 0)
! 				message = object_to_string(value);
! 			else if (strcmp(keyword, "detail") == 0)
! 				detail = object_to_string(value);
! 			else if (strcmp(keyword, "hint") == 0)
! 				hint = object_to_string(value);
! 			else if (strcmp(keyword, "sqlstate") == 0)
! 				sqlstatestr = object_to_string(value);
! 			else if (strcmp(keyword, "schema") == 0)
! 				schema = object_to_string(value);
! 			else if (strcmp(keyword, "table") == 0)
! 				table = object_to_string(value);
! 			else if (strcmp(keyword, "column") == 0)
! 				column = object_to_string(value);
! 			else if (strcmp(keyword, "datatype") == 0)
! 				datatype = object_to_string(value);
! 			else if (strcmp(keyword, "constraint") == 0)
! 				constraint = object_to_string(value);
! 		else
! 			PLy_elog(ERROR, "'%s' is an invalid keyword argument for this function",
! 								keyword);
! 		}
! 	}
! 
! 	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_exception_set_with_details(PLy_exc_error, edata);
! 		FreeErrorData(edata);
  
  		return NULL;
  	}
  	PG_END_TRY();
  
  	/*
  	 * return a legal object so the interpreter will continue on its merry way
  	 */
diff --git a/src/pl/plpython/plpy_spi.c b/src/pl/plpython/plpy_spi.c
new file mode 100644
index 58e78ec..6cdbd23
*** a/src/pl/plpython/plpy_spi.c
--- b/src/pl/plpython/plpy_spi.c
*************** PLy_spi_subtransaction_abort(MemoryConte
*** 536,543 ****
  	/* 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);
--- 536,542 ----
  	/* Look up the correct exception */
  	entry = hash_search(PLy_spi_exceptions, &(edata->sqlerrcode),
  						HASH_FIND, NULL);
! 	/* Now, the exception with custom code can come */
  	exc = entry ? entry->exc : PLy_exc_spi_error;
  	/* Make Python raise the exception */
  	PLy_spi_exception_set(exc, edata);
*************** PLy_spi_exception_set(PyObject *excclass
*** 564,571 ****
  	if (!spierror)
  		goto failure;
  
! 	spidata = Py_BuildValue("(izzzi)", edata->sqlerrcode, edata->detail, edata->hint,
! 							edata->internalquery, edata->internalpos);
  	if (!spidata)
  		goto failure;
  
--- 563,572 ----
  	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/sql/plpython_test.sql b/src/pl/plpython/sql/plpython_test.sql
new file mode 100644
index 3a76104..6f59cab
*** a/src/pl/plpython/sql/plpython_test.sql
--- b/src/pl/plpython/sql/plpython_test.sql
*************** $$ LANGUAGE plpythonu;
*** 36,43 ****
  
  select module_contents();
  
! 
! CREATE FUNCTION elog_test() RETURNS void
  AS $$
  plpy.debug('debug')
  plpy.log('log')
--- 36,42 ----
  
  select module_contents();
  
! CREATE FUNCTION elog_test_basic() RETURNS void
  AS $$
  plpy.debug('debug')
  plpy.log('log')
*************** plpy.warning('warning')
*** 50,53 ****
--- 49,184 ----
  plpy.error('error')
  $$ LANGUAGE plpythonu;
  
+ SELECT elog_test_basic();
+ 
+ CREATE FUNCTION elog_test() RETURNS void
+ AS $$
+ plpy.debug('debug', detail = 'some detail')
+ plpy.log('log', detail = 'some detail')
+ plpy.info('info', detail = 'some detail')
+ plpy.info()
+ plpy.info('the question', detail = 42);
+ plpy.info('This is message text.',
+                     detail = 'This is detail text',
+                     hint = 'This is hint text.',
+                     sqlstate = 'XX000',
+                     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.notice('notice', detail = 'some detail')
+ plpy.warning('warning', detail = 'some detail')
+ plpy.error('stop on error', detail = 'some detail', hint = 'some hint')
+ $$ LANGUAGE plpythonu;
+ 
  SELECT elog_test();
+ 
+ do $$ plpy.info('other types', detail = (10,20)) $$ LANGUAGE plpythonu;
+ 
+ do $$
+ import time;
+ from datetime import date
+ plpy.info('other types', detail = date(2016,2,26))
+ $$ LANGUAGE plpythonu;
+ 
+ do $$
+ basket = ['apple', 'orange', 'apple', 'pear', 'orange', 'banana']
+ plpy.info('other types', detail = basket)
+ $$ LANGUAGE plpythonu;
+ 
+ -- should fail
+ do $$ plpy.info('wrong sqlstate', sqlstate='54444A') $$ LANGUAGE plpythonu;
+ do $$ plpy.info('unsupported argument', blabla='fooboo') $$ LANGUAGE plpythonu;
+ 
+ -- raise exception i n python, handle exception in plgsql
+ CREATE OR REPLACE FUNCTION raise_exception(_message text, _detail text DEFAULT NULL, _hint text DEFAULT NULL,
+ 						_sqlstate text DEFAULT NULL,
+ 						_schema text DEFAULT NULL, _table text DEFAULT NULL, _column text DEFAULT NULL,
+ 						_datatype text DEFAULT NULL, _constraint text DEFAULT NULL)
+ RETURNS void AS $$
+ kwargs = { "message":_message, "detail":_detail, "hint":_hint,
+ 			"sqlstate":_sqlstate, "schema":_schema, "table":_table,
+ 			"column":_column, "datatype":_datatype, "constraint":_constraint }
+ # ignore None values
+ plpy.error(**dict((k, v) for k, v in iter(kwargs.items()) if v))
+ $$ LANGUAGE plpythonu;
+ 
+ SELECT raise_exception('hello', 'world');
+ SELECT raise_exception('message text', 'detail text', _sqlstate => 'YY333');
+ SELECT raise_exception(_message => 'message text',
+ 						_detail => 'detail text',
+ 						_hint => 'hint text',
+ 						_sqlstate => 'XX555',
+ 						_schema => 'schema text',
+ 						_table => 'table text',
+ 						_column => 'column text',
+ 						_datatype => 'datatype text',
+ 						_constraint => 'constraint text');
+ 
+ SELECT raise_exception(_message => 'message text',
+ 						_hint => 'hint text',
+ 						_schema => 'schema text',
+ 						_column => 'column text',
+ 						_constraint => 'constraint text');
+ 
+ DO $$
+ DECLARE
+   __message text;
+   __detail text;
+   __hint text;
+   __sqlstate text;
+   __schema_name text;
+   __table_name text;
+   __column_name text;
+   __datatype text;
+   __constraint text;
+ BEGIN
+   BEGIN
+     PERFORM raise_exception(_message => 'message text',
+                             _detail => 'detail text',
+                             _hint => 'hint text',
+                             _sqlstate => 'XX555',
+                             _schema => 'schema text',
+                             _table => 'table text',
+                             _column => 'column text',
+                             _datatype => 'datatype text',
+                             _constraint => 'constraint text');
+   EXCEPTION WHEN SQLSTATE 'XX555' THEN
+     GET STACKED DIAGNOSTICS __message = MESSAGE_TEXT,
+                             __detail = PG_EXCEPTION_DETAIL,
+                             __hint = PG_EXCEPTION_HINT,
+                             __sqlstate = RETURNED_SQLSTATE,
+                             __schema_name = SCHEMA_NAME,
+                             __table_name = TABLE_NAME,
+                             __column_name = COLUMN_NAME,
+                             __datatype = PG_DATATYPE_NAME,
+                             __constraint = CONSTRAINT_NAME;
+     RAISE NOTICE 'handled exception'
+        USING DETAIL = format('message:(%s), detail:(%s), hint: (%s), sqlstate: (%s), '
+                              'schema:(%s), table:(%s), column:(%s), datatype:(%s), constraint:(%s)',
+                              __message, __detail, __hint, __sqlstate, __schema_name,
+                              __table_name, __column_name, __datatype, __constraint);
+   END;
+ END;
+ $$;
+ 
+ -- the displayed context is different between Python2 and Python3,
+ -- but for this test is not important.
+ \set SHOW_CONTEXT never
+ 
+ do $$
+ try:
+ 	plpy.execute("select raise_exception(_message => 'my message', _sqlstate => 'XX987', _hint => 'some hint', _table=> 'users_tab', _datatype => 'user_type')")
+ except Exception as e:
+ 	plpy.info(e.spidata)
+ 	raise e
+ $$ LANGUAGE plpythonu;
+ 
+ do $$
+ try:
+   plpy.error(message  = 'my message', sqlstate = 'XX987', hint = 'some hint', table = 'users_tab', datatype = 'user_type')
+ except Exception as e:
+   plpy.info('sqlstate: {0}, hint: {1}, tablename: {2}, datatype: {3}'.format(e.sqlstate, e.hint, e.table_name, e.datatype_name))
+   raise e
+ $$ LANGUAGE plpythonu;
-- 
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