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