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.
>

here is new patch

cleaned, all unwanted artefacts removed. I am not sure if used way for
method registration is 100% valid, but I didn't find any related
documentation.

Regards

Pavel


>
> Any help is welcome
>
> Regards
>
> Pavel
>
>
>>
>>
>> --
>>  Craig Ringer                   http://www.2ndQuadrant.com/
>>  PostgreSQL Development, 24x7 Support, Training & Services
>>
>
>
diff --git a/doc/src/sgml/plpython.sgml b/doc/src/sgml/plpython.sgml
new file mode 100644
index 015bbad..bf468e1
*** a/doc/src/sgml/plpython.sgml
--- b/doc/src/sgml/plpython.sgml
*************** $$ LANGUAGE plpythonu;
*** 1205,1210 ****
--- 1205,1235 ----
      approximately the same functionality
     </para>
    </sect2>
+ 
+   <sect2 id="plpython-raising">
+    <title>Raising Errors</title>
+ 
+    <para>
+     The <literal>plpy</literal> module provides several possibilities to
+     to raise a exception:
+    </para>
+ 
+    <variablelist>
+     <varlistentry>
+      <term><literal><function>SPIError</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></term>
+      <listitem>
+        <para>
+         The constructor of SPIError exception (class) supports keyword parameters. 
+ <programlisting>
+ DO $$
+   raise plpy.SPIError('custom message', hint = 'It is test only');
+ $$ LANGUAGE plpythonu;
+ </programlisting>
+        </para>
+      </listitem>
+     </varlistentry>
+    </variablelist>
+   </sect2>
   </sect1>
  
   <sect1 id="plpython-subtransaction">
diff --git a/src/pl/plpython/expected/plpython_error.out b/src/pl/plpython/expected/plpython_error.out
new file mode 100644
index 1f52af7..ac985c6
*** a/src/pl/plpython/expected/plpython_error.out
--- b/src/pl/plpython/expected/plpython_error.out
*************** EXCEPTION WHEN SQLSTATE 'SILLY' THEN
*** 422,424 ****
--- 422,473 ----
  	-- NOOP
  END
  $$ LANGUAGE plpgsql;
+ /* possibility to set all accessable fields in custom exception
+  */
+ DO $$
+ raise plpy.SPIError('This is message text.',
+                     detail = 'This is detail text',
+                     hint = 'This is hint text.')
+ $$ LANGUAGE plpythonu;
+ ERROR:  plpy.SPIError: 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.SPIError('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.SPIError: 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.SPIError(detail = 'This is detail text')
+ $$ LANGUAGE plpythonu;
+ ERROR:  plpy.SPIError: 
+ DETAIL:  This is detail text
+ CONTEXT:  Traceback (most recent call last):
+   PL/Python anonymous code block, line 2, in <module>
+     raise plpy.SPIError(detail = 'This is detail text')
+ PL/Python anonymous code block
diff --git a/src/pl/plpython/plpy_elog.c b/src/pl/plpython/plpy_elog.c
new file mode 100644
index 15406d6..a835af9
*** a/src/pl/plpython/plpy_elog.c
--- b/src/pl/plpython/plpy_elog.c
*************** PyObject   *PLy_exc_spi_error = NULL;
*** 23,29 ****
  
  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);
  
  
--- 23,32 ----
  
  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 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;
  	}
--- 54,73 ----
  	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();
  	{
--- 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_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
  	{
--- 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
  	{
diff --git a/src/pl/plpython/plpy_plpymodule.c b/src/pl/plpython/plpy_plpymodule.c
new file mode 100644
index a44b7fb..6881013
*** 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,48 ----
  static PyObject *PLy_quote_nullable(PyObject *self, PyObject *args);
  static PyObject *PLy_quote_ident(PyObject *self, PyObject *args);
  
+ /* 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 ****
--- 103,113 ----
  	{NULL, NULL, 0, NULL}
  };
  
+ static PyMethodDef PLy_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 */
*************** static void
*** 185,190 ****
--- 194,200 ----
  PLy_add_exceptions(PyObject *plpy)
  {
  	PyObject   *excmod;
+ 	PyObject   *spi_error_dict;
  	HASHCTL		hash_ctl;
  
  #if PY_MAJOR_VERSION < 3
*************** 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 ||
--- 217,230 ----
  	 */
  	Py_INCREF(excmod);
  
+ 	/* prepare dictionary with __init__ method for SPIError class */
+ 	spi_error_dict = PyDict_New();
+ 	PLy_add_methods_to_dictionary(spi_error_dict, PLy_spi_error_methods);
+ 
  	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, spi_error_dict);
! 	Py_DECREF(spi_error_dict);
  
  	if (PLy_exc_error == NULL ||
  		PLy_exc_fatal == NULL ||
*************** PLy_generate_spi_exceptions(PyObject *mo
*** 266,271 ****
--- 281,420 ----
  	}
  }
  
+ /*
+  * Returns dictionary with specified set of methods. It is used for
+  * definition __init__ method of SPIError class. That is necessary
+  * for keyword parameters support.
+  *
+  * SHOULD BE VERIFIED BEFORE MERGE: the function PyMethod_New is called without
+  * specified class (third parameter). I cannot to specify class there,
+  * because in this moment, this class doesn't exists. It is created
+  * via PyErr_NewException with last dict parameter. Late modifiction of
+  * used dictionary has zero effect on new class. So dictionary should be
+  * created before. Tested on Python 2.7.10.
+  */
+ 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 import function \"%s\"", method->ml_name);
+ 
+ 		meth = PyMethod_New(func, NULL, NULL);
+ 		if (meth == NULL)
+ 			PLy_elog(ERROR, "could not import method \"%s\"", method->ml_name);
+ 
+ 		if (PyDict_SetItemString(dict, method->ml_name, meth))
+ 			PLy_elog(ERROR, "could public method \"%s\" in dictionary", method->ml_name);
+ 
+ 		Py_DECREF(meth);
+ 		Py_DECREF(func);
+ 	}
+ }
+ 
+ /*
+  * Init method for SPIError class.
+  *
+  * This constructor allows to enter all user fields in PostgreSQL exception.
+  * Keywords parameters are supported.
+  */
+ 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   *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 || 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)
+ 				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_XDECREF(exc_args);
+ 		Py_DECREF(spidata);
+ 	}
+ 
+ 	Py_INCREF(Py_None);
+ 	return Py_None;
+ 
+ failure:
+ 	Py_XDECREF(exc_args);
+ 	Py_XDECREF(spidata);
+ 
+ 	PLy_elog(ERROR, "could not create SPIError object");
+ 
+ 	return NULL;
+ }
+ 
  
  /*
   * the python interface to the elog function
diff --git a/src/pl/plpython/plpy_spi.c b/src/pl/plpython/plpy_spi.c
new file mode 100644
index d0e255f..4419099
*** a/src/pl/plpython/plpy_spi.c
--- b/src/pl/plpython/plpy_spi.c
*************** 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;
  
--- 548,558 ----
  	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_error.sql b/src/pl/plpython/sql/plpython_error.sql
new file mode 100644
index d0df7e6..2d859af
*** a/src/pl/plpython/sql/plpython_error.sql
--- b/src/pl/plpython/sql/plpython_error.sql
*************** EXCEPTION WHEN SQLSTATE 'SILLY' THEN
*** 328,330 ****
--- 328,358 ----
  	-- NOOP
  END
  $$ LANGUAGE plpgsql;
+ 
+ /* possibility to set all accessable fields in custom exception
+  */
+ DO $$
+ raise plpy.SPIError('This is message text.',
+                     detail = 'This is detail text',
+                     hint = 'This is hint text.')
+ $$ LANGUAGE plpythonu;
+ 
+ \set VERBOSITY verbose
+ DO $$
+ raise plpy.SPIError('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.SPIError(detail = 'This is detail text')
+ $$ 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