Hi > >> > Do you have some ideas about the name of this class? >> >> I think plpy.Error is fine. >> >> > here is updated patch - work with 2.x Python. > > I have 3.x Python broken on my fedora, so I should not do tests on 3.x. >
here is complete patch - regress tests for all supported Python branches > > Regards > > Pavel >
diff --git a/doc/src/sgml/plpython.sgml b/doc/src/sgml/plpython.sgml new file mode 100644 index 015bbad..ee6a42a *** a/doc/src/sgml/plpython.sgml --- b/doc/src/sgml/plpython.sgml *************** $$ LANGUAGE plpythonu; *** 1205,1210 **** --- 1205,1228 ---- approximately the same functionality </para> </sect2> + + <sect2 id="plpython-raising"> + <title>Raising Errors</title> + + <para> + A plpy.Error can be raised from PL/Python, the constructor accepts + keyword parameters: + <literal><function>plpy.Error</function>([ <replaceable>message</replaceable> [, <replaceable>detail</replaceable> [, <replaceable>hint</replaceable> [, <replaceable>sqlstate</replaceable> [, <replaceable>schema</replaceable> [, <replaceable>table</replaceable> [, <replaceable>column</replaceable> [, <replaceable>datatype</replaceable> [, <replaceable>constraint</replaceable> ]]]]]]]]])</literal>. + </para> + <para> + An example of raising custom exception could be written as: + <programlisting> + DO $$ + raise plpy.Error('custom message', hint = 'It is test only'); + $$ LANGUAGE plpythonu; + </programlisting> + </para> + </sect2> </sect1> <sect1 id="plpython-subtransaction"> diff --git a/src/pl/plpython/expected/plpython_error.out b/src/pl/plpython/expected/plpython_error.out new file mode 100644 index 1f52af7..cb792eb *** a/src/pl/plpython/expected/plpython_error.out --- b/src/pl/plpython/expected/plpython_error.out *************** EXCEPTION WHEN SQLSTATE 'SILLY' THEN *** 422,424 **** --- 422,486 ---- -- NOOP END $$ LANGUAGE plpgsql; + /* the possibility to set fields of custom exception + */ + DO $$ + raise plpy.Error('This is message text.', + detail = 'This is detail text', + hint = 'This is hint text.') + $$ LANGUAGE plpythonu; + ERROR: plpy.Error: This is message text. + DETAIL: This is detail text + HINT: This is hint text. + CONTEXT: Traceback (most recent call last): + PL/Python anonymous code block, line 4, in <module> + hint = 'This is hint text.') + PL/Python anonymous code block + \set VERBOSITY verbose + DO $$ + raise plpy.Error('This is message text.', + detail = 'This is detail text', + hint = 'This is hint text.', + sqlstate = 'SILLY', + schema = 'any info about schema', + table = 'any info about table', + column = 'any info about column', + datatype = 'any info about datatype', + constraint = 'any info about constraint') + $$ LANGUAGE plpythonu; + ERROR: SILLY: plpy.Error: This is message text. + DETAIL: This is detail text + HINT: This is hint text. + CONTEXT: Traceback (most recent call last): + PL/Python anonymous code block, line 10, in <module> + constraint = 'any info about constraint') + PL/Python anonymous code block + SCHEMA NAME: any info about schema + TABLE NAME: any info about table + COLUMN NAME: any info about column + DATATYPE NAME: any info about datatype + CONSTRAINT NAME: any info about constraint + LOCATION: PLy_elog, plpy_elog.c:122 + \set VERBOSITY default + DO $$ + raise plpy.Error(detail = 'This is detail text') + $$ LANGUAGE plpythonu; + ERROR: plpy.Error: + DETAIL: This is detail text + CONTEXT: Traceback (most recent call last): + PL/Python anonymous code block, line 2, in <module> + raise plpy.Error(detail = 'This is detail text') + PL/Python anonymous code block + DO $$ + raise plpy.Error(); + $$ LANGUAGE plpythonu; + ERROR: plpy.Error: + CONTEXT: Traceback (most recent call last): + PL/Python anonymous code block, line 2, in <module> + raise plpy.Error(); + PL/Python anonymous code block + DO $$ + raise plpy.Error(sqlstate = 'wrong sql state'); + $$ LANGUAGE plpythonu; + ERROR: could not create Error object (invalid SQLSTATE code) + CONTEXT: PL/Python anonymous code block diff --git a/src/pl/plpython/expected/plpython_error_0.out b/src/pl/plpython/expected/plpython_error_0.out new file mode 100644 index 5323906..3d83a53 *** a/src/pl/plpython/expected/plpython_error_0.out --- b/src/pl/plpython/expected/plpython_error_0.out *************** EXCEPTION WHEN SQLSTATE 'SILLY' THEN *** 422,424 **** --- 422,486 ---- -- NOOP END $$ LANGUAGE plpgsql; + /* the possibility to set fields of custom exception + */ + DO $$ + raise plpy.Error('This is message text.', + detail = 'This is detail text', + hint = 'This is hint text.') + $$ LANGUAGE plpythonu; + ERROR: plpy.Error: This is message text. + DETAIL: This is detail text + HINT: This is hint text. + CONTEXT: Traceback (most recent call last): + PL/Python anonymous code block, line 4, in <module> + hint = 'This is hint text.') + PL/Python anonymous code block + \set VERBOSITY verbose + DO $$ + raise plpy.Error('This is message text.', + detail = 'This is detail text', + hint = 'This is hint text.', + sqlstate = 'SILLY', + schema = 'any info about schema', + table = 'any info about table', + column = 'any info about column', + datatype = 'any info about datatype', + constraint = 'any info about constraint') + $$ LANGUAGE plpythonu; + ERROR: SILLY: plpy.Error: This is message text. + DETAIL: This is detail text + HINT: This is hint text. + CONTEXT: Traceback (most recent call last): + PL/Python anonymous code block, line 10, in <module> + constraint = 'any info about constraint') + PL/Python anonymous code block + SCHEMA NAME: any info about schema + TABLE NAME: any info about table + COLUMN NAME: any info about column + DATATYPE NAME: any info about datatype + CONSTRAINT NAME: any info about constraint + LOCATION: PLy_elog, plpy_elog.c:122 + \set VERBOSITY default + DO $$ + raise plpy.Error(detail = 'This is detail text') + $$ LANGUAGE plpythonu; + ERROR: plpy.Error: unknown + DETAIL: This is detail text + CONTEXT: Traceback (most recent call last): + PL/Python anonymous code block, line 2, in <module> + raise plpy.Error(detail = 'This is detail text') + PL/Python anonymous code block + DO $$ + raise plpy.Error(); + $$ LANGUAGE plpythonu; + ERROR: plpy.Error: unknown + CONTEXT: Traceback (most recent call last): + PL/Python anonymous code block, line 2, in <module> + raise plpy.Error(); + PL/Python anonymous code block + DO $$ + raise plpy.Error(sqlstate = 'wrong sql state'); + $$ LANGUAGE plpythonu; + ERROR: could not create Error object (invalid SQLSTATE code) + CONTEXT: PL/Python anonymous code block diff --git a/src/pl/plpython/expected/plpython_error_5.out b/src/pl/plpython/expected/plpython_error_5.out new file mode 100644 index 5ff46ca..098e506 *** a/src/pl/plpython/expected/plpython_error_5.out --- b/src/pl/plpython/expected/plpython_error_5.out *************** EXCEPTION WHEN SQLSTATE 'SILLY' THEN *** 422,424 **** --- 422,486 ---- -- NOOP END $$ LANGUAGE plpgsql; + /* the possibility to set fields of custom exception + */ + DO $$ + raise plpy.Error('This is message text.', + detail = 'This is detail text', + hint = 'This is hint text.') + $$ LANGUAGE plpython3u; + ERROR: plpy.Error: This is message text. + DETAIL: This is detail text + HINT: This is hint text. + CONTEXT: Traceback (most recent call last): + PL/Python anonymous code block, line 4, in <module> + hint = 'This is hint text.') + PL/Python anonymous code block + \set VERBOSITY verbose + DO $$ + raise plpy.Error('This is message text.', + detail = 'This is detail text', + hint = 'This is hint text.', + sqlstate = 'SILLY', + schema = 'any info about schema', + table = 'any info about table', + column = 'any info about column', + datatype = 'any info about datatype', + constraint = 'any info about constraint') + $$ LANGUAGE plpython3u; + ERROR: SILLY: plpy.Error: This is message text. + DETAIL: This is detail text + HINT: This is hint text. + CONTEXT: Traceback (most recent call last): + PL/Python anonymous code block, line 10, in <module> + constraint = 'any info about constraint') + PL/Python anonymous code block + SCHEMA NAME: any info about schema + TABLE NAME: any info about table + COLUMN NAME: any info about column + DATATYPE NAME: any info about datatype + CONSTRAINT NAME: any info about constraint + LOCATION: PLy_elog, plpy_elog.c:122 + \set VERBOSITY default + DO $$ + raise plpy.Error(detail = 'This is detail text') + $$ LANGUAGE plpython3u; + ERROR: plpy.Error: + DETAIL: This is detail text + CONTEXT: Traceback (most recent call last): + PL/Python anonymous code block, line 2, in <module> + raise plpy.Error(detail = 'This is detail text') + PL/Python anonymous code block + DO $$ + raise plpy.Error(); + $$ LANGUAGE plpython3u; + ERROR: plpy.Error: + CONTEXT: Traceback (most recent call last): + PL/Python anonymous code block, line 2, in <module> + raise plpy.Error(); + PL/Python anonymous code block + DO $$ + raise plpy.Error(sqlstate = 'wrong sql state'); + $$ LANGUAGE plpython3u; + ERROR: could not create Error object (invalid SQLSTATE code) + CONTEXT: PL/Python anonymous code block diff --git a/src/pl/plpython/plpy_elog.c b/src/pl/plpython/plpy_elog.c new file mode 100644 index 15406d6..742b341 *** a/src/pl/plpython/plpy_elog.c --- b/src/pl/plpython/plpy_elog.c *************** PyObject *PLy_exc_spi_error = NULL; *** 22,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); --- 22,32 ---- static void PLy_traceback(char **xmsg, char **tbmsg, int *tb_depth); ! static void PLy_get_error_data(PyObject *exc, int *sqlerrcode, char **detail, ! char **hint, char **query, int *position, ! char **schema_name, char **table_name, char **column_name, ! char **datatype_name, char **constraint_name); ! static char *get_source_line(const char *src, int lineno); *************** PLy_elog(int elevel, const char *fmt,... *** 51,63 **** char *hint = NULL; char *query = NULL; int position = 0; PyErr_Fetch(&exc, &val, &tb); if (exc != NULL) { ! if (PyErr_GivenExceptionMatches(val, PLy_exc_spi_error)) ! PLy_get_spi_error_data(val, &sqlerrcode, &detail, &hint, &query, &position); ! else if (PyErr_GivenExceptionMatches(val, PLy_exc_fatal)) elevel = FATAL; } PyErr_Restore(exc, val, tb); --- 54,74 ---- char *hint = NULL; char *query = NULL; int position = 0; + char *schema_name = NULL; + char *table_name = NULL; + char *column_name = NULL; + char *datatype_name = NULL; + char *constraint_name = NULL; PyErr_Fetch(&exc, &val, &tb); if (exc != NULL) { ! if (PyErr_GivenExceptionMatches(val, PLy_exc_error)) ! PLy_get_error_data(val, &sqlerrcode, &detail, &hint, &query, &position, ! &schema_name, &table_name, &column_name, ! &datatype_name, &constraint_name); ! ! if (PyErr_GivenExceptionMatches(val, PLy_exc_fatal)) elevel = FATAL; } PyErr_Restore(exc, val, tb); *************** PLy_elog(int elevel, const char *fmt,... *** 103,109 **** (tb_depth > 0 && tbmsg) ? errcontext("%s", tbmsg) : 0, (hint) ? errhint("%s", hint) : 0, (query) ? internalerrquery(query) : 0, ! (position) ? internalerrposition(position) : 0)); } PG_CATCH(); { --- 114,126 ---- (tb_depth > 0 && tbmsg) ? errcontext("%s", tbmsg) : 0, (hint) ? errhint("%s", hint) : 0, (query) ? internalerrquery(query) : 0, ! (position) ? internalerrposition(position) : 0, ! (schema_name) ? err_generic_string(PG_DIAG_SCHEMA_NAME, schema_name) : 0, ! (table_name) ? err_generic_string(PG_DIAG_TABLE_NAME, table_name) : 0, ! (column_name) ? err_generic_string(PG_DIAG_COLUMN_NAME, column_name) : 0, ! (datatype_name) ? err_generic_string(PG_DIAG_DATATYPE_NAME, datatype_name) : 0, ! (constraint_name) ? err_generic_string(PG_DIAG_CONSTRAINT_NAME, constraint_name) : 0)); ! } PG_CATCH(); { *************** PLy_get_spi_sqlerrcode(PyObject *exc, in *** 365,371 **** * Extract the error data from a SPIError */ static void ! PLy_get_spi_error_data(PyObject *exc, int *sqlerrcode, char **detail, char **hint, char **query, int *position) { PyObject *spidata = NULL; --- 382,390 ---- * Extract the error data from a SPIError */ static void ! PLy_get_error_data(PyObject *exc, int *sqlerrcode, char **detail, char **hint, char **query, int *position, ! char **schema_name, char **table_name, char **column_name, ! char **datatype_name, char **constraint_name) { PyObject *spidata = NULL; *************** PLy_get_spi_error_data(PyObject *exc, in *** 373,379 **** if (spidata != NULL) { ! PyArg_ParseTuple(spidata, "izzzi", sqlerrcode, detail, hint, query, position); } else { --- 392,400 ---- if (spidata != NULL) { ! PyArg_ParseTuple(spidata, "izzzizzzzz", sqlerrcode, detail, hint, query, position, ! schema_name, table_name, column_name, ! datatype_name, constraint_name); } else { diff --git a/src/pl/plpython/plpy_plpymodule.c b/src/pl/plpython/plpy_plpymodule.c new file mode 100644 index a44b7fb..e8cdd80 *** 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_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_error_methods[] = { + {"__init__", (PyCFunction) PLy_error__init__, METH_VARARGS | METH_KEYWORDS, NULL}, + {NULL, NULL, 0, NULL} + }; + #if PY_MAJOR_VERSION >= 3 static PyModuleDef PLy_module = { PyModuleDef_HEAD_INIT, /* m_base */ *************** static void *** 185,190 **** --- 194,200 ---- PLy_add_exceptions(PyObject *plpy) { PyObject *excmod; + PyObject *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,232 ---- */ Py_INCREF(excmod); ! /* prepare dictionary with __init__ method for SPIError class */ ! error_dict = PyDict_New(); ! if (error_dict == NULL) ! PLy_elog(ERROR, "could not create dictionary for Error class"); ! PLy_add_methods_to_dictionary(error_dict, PLy_error_methods); ! ! PLy_exc_error = PyErr_NewException("plpy.Error", NULL, error_dict); ! PLy_exc_fatal = PyErr_NewException("plpy.Fatal", PLy_exc_error, NULL); ! PLy_exc_spi_error = PyErr_NewException("plpy.SPIError", PLy_exc_error, NULL); ! Py_DECREF(error_dict); if (PLy_exc_error == NULL || PLy_exc_fatal == NULL || *************** PLy_generate_spi_exceptions(PyObject *mo *** 266,271 **** --- 283,437 ---- } } + /* + * Returns dictionary with specified set of methods. It is used for + * definition __init__ method of SPIError class. Our __init__ method + * supports keyword parameters and allows to set all available PostgreSQL + * Error fields. + */ + static void + PLy_add_methods_to_dictionary(PyObject *dict, PyMethodDef *methods) + { + PyMethodDef *method; + + for (method = methods; method->ml_name != NULL; method++) + { + PyObject *func; + PyObject *meth; + + func = PyCFunction_New(method, NULL); + if (func == NULL) + PLy_elog(ERROR, "could not create function wrapper for \"%s\"", method->ml_name); + + #if PY_MAJOR_VERSION < 3 + meth = PyMethod_New(func, NULL, NULL); + #else + meth = PyInstanceMethod_New(func); + #endif + if (meth == NULL) + { + Py_DECREF(func); + PLy_elog(ERROR, "could not create method wrapper for \"%s\"", method->ml_name); + } + + if (PyDict_SetItemString(dict, method->ml_name, meth)) + { + Py_DECREF(func); + Py_DECREF(meth); + PLy_elog(ERROR, "could not add method \"%s\" to class dictionary", method->ml_name); + } + + Py_DECREF(func); + Py_DECREF(meth); + } + } + + /* + * Init method for SPIError class. + * + * This constructor allows to enter all user fields in PostgreSQL exception. + * Keywords parameters are supported. + */ + static PyObject * + PLy_error__init__(PyObject *self, PyObject *args, PyObject *kw) + { + int sqlstate = 0; + bool sqlstate_is_invalid = false; + const char *sqlstatestr = NULL; + const char *message = NULL; + const char *detail = NULL; + const char *hint = NULL; + const char *column = NULL; + const char *constraint = NULL; + const char *datatype = NULL; + const char *table = NULL; + const char *schema = NULL; + + PyObject *exc_args = NULL; + PyObject *spidata = NULL; + + static char *kwlist[] = { "self", "message", "detail", "hint", + "sqlstate", + "schema","table", "column", + "datatype", "constraint", + NULL }; + + /* + * don't try to overwrite default sqlstate field, when constructor + * is called without any parameter. Important for predefined + * spiexception.* exceptions. + */ + if (PyTuple_Size(args) > 1 || (kw != NULL && PyDict_Size(kw) >= 1)) + { + if (!PyArg_ParseTupleAndKeywords(args, kw, "O|sssssssss", + kwlist, &self, + &message, &detail, &hint, + &sqlstatestr, + &schema, &table, &column, + &datatype, &constraint)) + return NULL; + + if (message != NULL) + { + exc_args = Py_BuildValue("(s)", message); + if (!exc_args) + goto failure; + + if (PyObject_SetAttrString(self, "args", exc_args) == -1) + goto failure; + } + + if (sqlstatestr != NULL) + { + if (strlen(sqlstatestr) != 5) + { + sqlstate_is_invalid = true; + goto failure; + } + + if (strspn(sqlstatestr, "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ") != 5) + { + sqlstate_is_invalid = true; + goto failure; + } + + sqlstate = MAKE_SQLSTATE(sqlstatestr[0], + sqlstatestr[1], + sqlstatestr[2], + sqlstatestr[3], + sqlstatestr[4]); + } + + spidata = Py_BuildValue("(izzzizzzzz)", + sqlstate, detail, hint, + NULL, -1, + schema, table, column, + datatype, constraint); + if (!spidata) + goto failure; + + if (PyObject_SetAttrString(self, "spidata", spidata) == -1) + goto failure; + + Py_XDECREF(exc_args); + Py_DECREF(spidata); + } + + Py_INCREF(Py_None); + return Py_None; + + failure: + Py_XDECREF(exc_args); + Py_XDECREF(spidata); + + if (sqlstate_is_invalid) + PLy_elog(ERROR, "could not create Error object (invalid SQLSTATE code)"); + else + PLy_elog(ERROR, "could not create Error object"); + + return NULL; + } + /* * the python interface to the elog function diff --git a/src/pl/plpython/plpy_spi.c b/src/pl/plpython/plpy_spi.c new file mode 100644 index 58e78ec..4d2b903 *** a/src/pl/plpython/plpy_spi.c --- b/src/pl/plpython/plpy_spi.c *************** 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; --- 564,574 ---- 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..4657ce6 *** a/src/pl/plpython/sql/plpython_error.sql --- b/src/pl/plpython/sql/plpython_error.sql *************** EXCEPTION WHEN SQLSTATE 'SILLY' THEN *** 328,330 **** --- 328,365 ---- -- NOOP END $$ LANGUAGE plpgsql; + + /* the possibility to set fields of custom exception + */ + DO $$ + raise plpy.Error('This is message text.', + detail = 'This is detail text', + hint = 'This is hint text.') + $$ LANGUAGE plpythonu; + + \set VERBOSITY verbose + DO $$ + raise plpy.Error('This is message text.', + detail = 'This is detail text', + hint = 'This is hint text.', + sqlstate = 'SILLY', + schema = 'any info about schema', + table = 'any info about table', + column = 'any info about column', + datatype = 'any info about datatype', + constraint = 'any info about constraint') + $$ LANGUAGE plpythonu; + + \set VERBOSITY default + + DO $$ + raise plpy.Error(detail = 'This is detail text') + $$ LANGUAGE plpythonu; + + DO $$ + raise plpy.Error(); + $$ LANGUAGE plpythonu; + + DO $$ + raise plpy.Error(sqlstate = 'wrong sql state'); + $$ LANGUAGE plpythonu;
-- Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers