2015-11-05 7:24 GMT+01:00 Catalin Iacob <iacobcata...@gmail.com>:

> On Wed, Nov 4, 2015 at 10:12 AM, Pavel Stehule <pavel.steh...@gmail.com>
> wrote:
> > It helped me lot of, thank you
>
> Welcome, I learned quite some from the process as well.
>
> >>
> >>
> >> There's just the doc part left then.
> >
> >
> > done
>
> We're almost there but not quite.
>
> There's still a typo in the docs: excation.
>
> A plpy.SPIError can be raised should be A
> <literal>plpy.SPIError</literal> can be raised right?
>
> And most importantly, for Python 3.5 there is a plpython_error_5.out
> which is needed because of an alternative exception message in that
> version. You didn't update this file, this makes the tests fail on
> Python3.5.
>
> Since you might not have Python 3.5 easily available I've attached a
> patch to plpython_error_5.out which makes the tests pass, you can fold
> this into your patch.
>

I needed to understand the support for Python 3.5.

The patch with the fix is attached regress tests 3.5 Python

Regards

Pavel
commit f3ab75afe0d9b31118d461a346e3c56ceb09c238
Author: Pavel Stehule <pavel.steh...@gooddata.com>
Date:   Sat Oct 17 20:11:56 2015 +0200

    inital

diff --git a/doc/src/sgml/plpython.sgml b/doc/src/sgml/plpython.sgml
index 015bbad..51bc48e 100644
--- a/doc/src/sgml/plpython.sgml
+++ b/doc/src/sgml/plpython.sgml
@@ -1205,6 +1205,24 @@ $$ LANGUAGE plpythonu;
     approximately the same functionality
    </para>
   </sect2>
+
+  <sect2 id="plpython-raising">
+   <title>Raising Errors</title>
+
+   <para>
+    A plpy.SPIError can be raised from PL/Python, the constructor accepts
+    keyword parameters:
+    <literal><function>plpy.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>.
+   </para>
+   <para>
+    An example of raising custom exception could be written as:
+<programlisting>
+DO $$
+  raise plpy.SPIError('custom message', hint = 'It is test only');
+$$ LANGUAGE plpythonu;
+</programlisting>
+   </para>
+  </sect2>
  </sect1>
 
  <sect1 id="plpython-subtransaction">
diff --git a/src/pl/plpython/expected/plpython_error.out b/src/pl/plpython/expected/plpython_error.out
index 1f52af7..435a5c2 100644
--- a/src/pl/plpython/expected/plpython_error.out
+++ b/src/pl/plpython/expected/plpython_error.out
@@ -422,3 +422,65 @@ EXCEPTION WHEN SQLSTATE 'SILLY' THEN
 	-- NOOP
 END
 $$ LANGUAGE plpgsql;
+/* the possibility to set fields of 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
+DO $$
+raise plpy.SPIError();
+$$ LANGUAGE plpythonu;
+ERROR:  plpy.SPIError: 
+CONTEXT:  Traceback (most recent call last):
+  PL/Python anonymous code block, line 2, in <module>
+    raise plpy.SPIError();
+PL/Python anonymous code block
+DO $$
+raise plpy.SPIError(sqlstate = 'wrong sql state');
+$$ LANGUAGE plpythonu;
+ERROR:  could not create SPIError 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
index 5ff46ca..8788bde 100644
--- a/src/pl/plpython/expected/plpython_error_5.out
+++ b/src/pl/plpython/expected/plpython_error_5.out
@@ -422,3 +422,65 @@ EXCEPTION WHEN SQLSTATE 'SILLY' THEN
 	-- NOOP
 END
 $$ LANGUAGE plpgsql;
+/* the possibility to set fields of custom exception
+ */
+DO $$
+raise plpy.SPIError('This is message text.',
+                    detail = 'This is detail text',
+                    hint = 'This is hint text.')
+$$ LANGUAGE plpython3u;
+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 plpython3u;
+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 plpython3u;
+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
+DO $$
+raise plpy.SPIError();
+$$ LANGUAGE plpython3u;
+ERROR:  plpy.SPIError: 
+CONTEXT:  Traceback (most recent call last):
+  PL/Python anonymous code block, line 2, in <module>
+    raise plpy.SPIError();
+PL/Python anonymous code block
+DO $$
+raise plpy.SPIError(sqlstate = 'wrong sql state');
+$$ LANGUAGE plpython3u;
+ERROR:  could not create SPIError object (invalid SQLSTATE code)
+CONTEXT:  PL/Python anonymous code block
diff --git a/src/pl/plpython/plpy_elog.c b/src/pl/plpython/plpy_elog.c
index 15406d6..a835af9 100644
--- a/src/pl/plpython/plpy_elog.c
+++ b/src/pl/plpython/plpy_elog.c
@@ -23,7 +23,10 @@ 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);
+					   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);
 
 
@@ -51,12 +54,20 @@ PLy_elog(int elevel, const char *fmt,...)
 	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);
+			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;
 	}
@@ -103,7 +114,13 @@ PLy_elog(int elevel, const char *fmt,...)
 				 (tb_depth > 0 && tbmsg) ? errcontext("%s", tbmsg) : 0,
 				 (hint) ? errhint("%s", hint) : 0,
 				 (query) ? internalerrquery(query) : 0,
-				 (position) ? internalerrposition(position) : 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();
 	{
@@ -365,7 +382,9 @@ PLy_get_spi_sqlerrcode(PyObject *exc, int *sqlerrcode)
  * 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)
+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;
 
@@ -373,7 +392,9 @@ PLy_get_spi_error_data(PyObject *exc, int *sqlerrcode, char **detail, char **hin
 
 	if (spidata != NULL)
 	{
-		PyArg_ParseTuple(spidata, "izzzi", sqlerrcode, detail, hint, query, position);
+		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
index a44b7fb..c4b2ae8 100644
--- a/src/pl/plpython/plpy_plpymodule.c
+++ b/src/pl/plpython/plpy_plpymodule.c
@@ -26,6 +26,7 @@ HTAB	   *PLy_spi_exceptions = NULL;
 
 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);
@@ -39,6 +40,9 @@ 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);
 
+/* 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
@@ -99,6 +103,11 @@ static PyMethodDef PLy_exc_methods[] = {
 	{NULL, NULL, 0, NULL}
 };
 
+static PyMethodDef PLy_spi_error_methods[] = {
+	{"__init__", (PyCFunction) PLy_spi_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 */
@@ -185,6 +194,7 @@ static void
 PLy_add_exceptions(PyObject *plpy)
 {
 	PyObject   *excmod;
+	PyObject   *spi_error_dict;
 	HASHCTL		hash_ctl;
 
 #if PY_MAJOR_VERSION < 3
@@ -207,9 +217,16 @@ PLy_add_exceptions(PyObject *plpy)
 	 */
 	Py_INCREF(excmod);
 
+	/* prepare dictionary with __init__ method for SPIError class */
+	spi_error_dict = PyDict_New();
+	if (spi_error_dict == NULL)
+		PLy_elog(ERROR, "could not create dictionary for SPIError");
+	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, 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 ||
@@ -266,6 +283,155 @@ PLy_generate_spi_exceptions(PyObject *mod, PyObject *base)
 	}
 }
 
+/*
+ * 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_spi_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 SPIError object (invalid SQLSTATE code)");
+	else
+		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
index 58e78ec..4d2b903 100644
--- a/src/pl/plpython/plpy_spi.c
+++ b/src/pl/plpython/plpy_spi.c
@@ -564,8 +564,11 @@ PLy_spi_exception_set(PyObject *excclass, ErrorData *edata)
 	if (!spierror)
 		goto failure;
 
-	spidata = Py_BuildValue("(izzzi)", edata->sqlerrcode, edata->detail, edata->hint,
-							edata->internalquery, edata->internalpos);
+	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
index d0df7e6..1d1a049 100644
--- a/src/pl/plpython/sql/plpython_error.sql
+++ b/src/pl/plpython/sql/plpython_error.sql
@@ -328,3 +328,38 @@ EXCEPTION WHEN SQLSTATE 'SILLY' THEN
 	-- NOOP
 END
 $$ LANGUAGE plpgsql;
+
+/* the possibility to set fields of 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;
+
+DO $$
+raise plpy.SPIError();
+$$ LANGUAGE plpythonu;
+
+DO $$
+raise plpy.SPIError(sqlstate = 'wrong sql state');
+$$ 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