diff --git a/doc/src/sgml/plpython.sgml b/doc/src/sgml/plpython.sgml new file mode 100644 index 015bbad..089b143 *** a/doc/src/sgml/plpython.sgml --- b/doc/src/sgml/plpython.sgml *************** $$ LANGUAGE plpythonu; *** 1205,1210 **** --- 1205,1228 ---- approximately the same functionality + + + Raising Errors + + + A plpy.Error can be raised from PL/Python, the constructor accepts + keyword parameters: + plpy.Error([ message [, detail [, hint [, sqlstate [, schema [, table [, column [, datatype [, constraint ]]]]]]]]]). + + + An example of raising custom exception could be written as: + + DO $$ + raise plpy.Error('custom message', hint = 'It is test only'); + $$ LANGUAGE plpythonu; + + + *************** $$ LANGUAGE plpythonu; *** 1367,1372 **** --- 1385,1421 ---- + The plpy module also provides the functions + plpy.raise_debug(args), + plpy.raise_log(args), + plpy.raise_info(args), + plpy.raise_notice(args), + plpy.raise_warning(args), + plpy.raise_error(args), and + plpy.raise_fatal(args).elogin PL/Python + plpy.raise_error and + plpy.raise_fatal actually raise a Python exception + which, if uncaught, propagates out to the calling query, causing + the current transaction or subtransaction to be aborted. + raise plpy.Error(msg) and + raise plpy.Fatal(msg) are + equivalent to calling + plpy.raise_error and + plpy.raise_fatal, respectively. + The other functions only generate messages of different + priority levels. + Whether messages of a particular priority are reported to the client, + written to the server log, or both is controlled by the + and + configuration + variables. See for more information. + These functions allows to using keyword parameters: + [ message [, detail [, hint [, sqlstate [, schema [, table [, column [, datatype [, constraint ]]]]]]]]]. + + + + + Another set of utility functions are plpy.quote_literal(string), plpy.quote_nullable(string), and 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 + 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 + 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 + 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 + 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 + 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 + 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 + 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 + 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 + 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 + 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 + 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 + 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/expected/plpython_test.out b/src/pl/plpython/expected/plpython_test.out new file mode 100644 index 7b76faf..a63f699 *** a/src/pl/plpython/expected/plpython_test.out --- b/src/pl/plpython/expected/plpython_test.out *************** contents.sort() *** 43,51 **** return ", ".join(contents) $$ LANGUAGE plpythonu; select module_contents(); ! module_contents ! ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ! 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 --- 43,51 ---- return ", ".join(contents) $$ LANGUAGE plpythonu; select module_contents(); ! module_contents ! ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ! BaseError, Error, Fatal, SPIError, cursor, debug, error, execute, fatal, info, log, notice, prepare, quote_ident, quote_literal, quote_nullable, raise_debug, raise_error, raise_fatal, raise_info, raise_log, raise_notice, raise_warning, spiexceptions, subtransaction, warning (1 row) CREATE FUNCTION elog_test() RETURNS void *************** CONTEXT: Traceback (most recent call la *** 72,74 **** --- 72,111 ---- PL/Python function "elog_test", line 10, in plpy.error('error') PL/Python function "elog_test" + CREATE FUNCTION elog_test2() RETURNS void + AS $$ + plpy.raise_debug('debug','some detail') + plpy.raise_log('log','some detail') + plpy.raise_info('info','some detail') + plpy.raise_info() + plpy.raise_info('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') + plpy.raise_notice('notice','some detail') + plpy.raise_warning('warning','some detail') + plpy.raise_error('stop on error', 'some detail','some hint') + $$ LANGUAGE plpythonu; + SELECT elog_test2(); + INFO: info + DETAIL: some detail + INFO: missing error text + 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_test2", line 17, in + plpy.raise_error('stop on error', 'some detail','some hint') + PL/Python function "elog_test2" diff --git a/src/pl/plpython/plpy_elog.c b/src/pl/plpython/plpy_elog.c new file mode 100644 index 15406d6..54594d2 *** a/src/pl/plpython/plpy_elog.c --- b/src/pl/plpython/plpy_elog.c *************** *** 15,29 **** #include "plpy_main.h" #include "plpy_procedure.h" ! PyObject *PLy_exc_error = NULL; PyObject *PLy_exc_fatal = NULL; 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); static char *get_source_line(const char *src, int lineno); --- 15,32 ---- #include "plpy_main.h" #include "plpy_procedure.h" ! PyObject *PLy_exc_base = NULL; PyObject *PLy_exc_error = NULL; PyObject *PLy_exc_fatal = NULL; PyObject *PLy_exc_spi_error = NULL; 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_base)) ! 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 { *************** PLy_exception_set_plural(PyObject *exc, *** 464,466 **** --- 485,532 ---- PyErr_SetString(exc, buf); } + + /* + * Raise a BaseError, passing in it more error details, like the + * internal query and error position. + */ + void + PLy_base_exception_set(PyObject *excclass, ErrorData *edata) + { + PyObject *args = NULL; + PyObject *excpt = NULL; + PyObject *excpt_data = NULL; + + args = Py_BuildValue("(s)", edata->message); + if (!args) + goto failure; + + /* create a new SPI exception with the error message as the parameter */ + excpt = PyObject_CallObject(excclass, args); + if (!excpt) + goto failure; + + excpt_data = 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 (!excpt_data) + goto failure; + + if (PyObject_SetAttrString(excpt, "spidata", excpt_data) == -1) + goto failure; + + PyErr_SetObject(excclass, excpt); + + Py_DECREF(args); + Py_DECREF(excpt); + Py_DECREF(excpt_data); + return; + + failure: + Py_XDECREF(args); + Py_XDECREF(excpt); + Py_XDECREF(excpt_data); + elog(ERROR, "could not convert PostgreSQL error to Python exception"); + } diff --git a/src/pl/plpython/plpy_elog.h b/src/pl/plpython/plpy_elog.h new file mode 100644 index 94725c2..eccdd44 *** a/src/pl/plpython/plpy_elog.h --- b/src/pl/plpython/plpy_elog.h *************** *** 6,11 **** --- 6,12 ---- #define PLPY_ELOG_H /* global exception classes */ + extern PyObject *PLy_exc_base; extern PyObject *PLy_exc_error; extern PyObject *PLy_exc_fatal; extern PyObject *PLy_exc_spi_error; *************** extern void PLy_exception_set(PyObject * *** 17,20 **** --- 18,23 ---- 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_base_exception_set(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..d0bc519 *** 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,57 ---- static PyObject *PLy_quote_nullable(PyObject *self, PyObject *args); static PyObject *PLy_quote_ident(PyObject *self, PyObject *args); + /* module functions with keyword argument support */ + static PyObject *PLy_raise_debug(PyObject *self, PyObject *args, PyObject *kw); + static PyObject *PLy_raise_log(PyObject *self, PyObject *args, PyObject *kw); + static PyObject *PLy_raise_info(PyObject *self, PyObject *args, PyObject *kw); + static PyObject *PLy_raise_notice(PyObject *self, PyObject *args, PyObject *kw); + static PyObject *PLy_raise_warning(PyObject *self, PyObject *args, PyObject *kw); + static PyObject *PLy_raise_error(PyObject *self, PyObject *args, PyObject *kw); + static PyObject *PLy_raise_fatal(PyObject *self, PyObject *args, PyObject *kw); + + /* 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_methods[] = { *** 64,69 **** --- 77,89 ---- {"warning", PLy_warning, METH_VARARGS, NULL}, {"error", PLy_error, METH_VARARGS, NULL}, {"fatal", PLy_fatal, METH_VARARGS, NULL}, + {"raise_debug", (PyCFunction) PLy_raise_debug, METH_VARARGS | METH_KEYWORDS, NULL}, + {"raise_log", (PyCFunction) PLy_raise_log, METH_VARARGS | METH_KEYWORDS, NULL}, + {"raise_info", (PyCFunction) PLy_raise_info, METH_VARARGS | METH_KEYWORDS, NULL}, + {"raise_notice", (PyCFunction) PLy_raise_notice, METH_VARARGS | METH_KEYWORDS, NULL}, + {"raise_warning", (PyCFunction) PLy_raise_warning, METH_VARARGS | METH_KEYWORDS, NULL}, + {"raise_error", (PyCFunction) PLy_raise_error, METH_VARARGS | METH_KEYWORDS, NULL}, + {"raise_fatal", (PyCFunction) PLy_raise_fatal, METH_VARARGS | METH_KEYWORDS, NULL}, /* * create a stored plan *************** static PyMethodDef PLy_methods[] = { *** 86,92 **** * create the subtransaction context manager */ {"subtransaction", PLy_subtransaction_new, METH_NOARGS, NULL}, - /* * create a cursor */ --- 106,111 ---- *************** static PyMethodDef PLy_exc_methods[] = { *** 99,104 **** --- 118,128 ---- {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 **** --- 209,215 ---- PLy_add_exceptions(PyObject *plpy) { PyObject *excmod; + PyObject *error_dict; HASHCTL hash_ctl; #if PY_MAJOR_VERSION < 3 *************** PLy_add_exceptions(PyObject *plpy) *** 207,220 **** */ 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 || PLy_exc_spi_error == NULL) ! PLy_elog(ERROR, "could not create the base SPI exceptions"); Py_INCREF(PLy_exc_error); PyModule_AddObject(plpy, "Error", PLy_exc_error); --- 232,260 ---- */ 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 BaseError class"); ! PLy_add_methods_to_dictionary(error_dict, PLy_error_methods); ! /* create common ancestor for exception classes */ ! PLy_exc_base = PyErr_NewException("plpy.BaseError", NULL, error_dict); ! ! /* create all other builtin exception classes */ ! PLy_exc_error = PyErr_NewException("plpy.Error", PLy_exc_base, NULL); ! PLy_exc_fatal = PyErr_NewException("plpy.Fatal", PLy_exc_base, NULL); ! PLy_exc_spi_error = PyErr_NewException("plpy.SPIError", PLy_exc_base, NULL); ! Py_DECREF(error_dict); ! ! if (PLy_exc_base == NULL || ! PLy_exc_error == NULL || PLy_exc_fatal == NULL || PLy_exc_spi_error == NULL) ! PLy_elog(ERROR, "could not create the plpy base exceptions"); ! ! Py_INCREF(PLy_exc_base); ! PyModule_AddObject(plpy, "BaseError", PLy_exc_base); Py_INCREF(PLy_exc_error); PyModule_AddObject(plpy, "Error", PLy_exc_error); *************** PLy_generate_spi_exceptions(PyObject *mo *** 266,277 **** --- 306,467 ---- } } + /* + * 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 * don't confuse these with PLy_elog */ static PyObject *PLy_output(volatile int, PyObject *, PyObject *); + static PyObject *PLy_output_kw(volatile int, PyObject *, PyObject *, PyObject *); static PyObject * PLy_debug(PyObject *self, PyObject *args) *************** PLy_fatal(PyObject *self, PyObject *args *** 316,321 **** --- 506,553 ---- } static PyObject * + PLy_raise_debug(PyObject *self, PyObject *args, PyObject *kw) + { + return PLy_output_kw(DEBUG2, self, args, kw); + } + + static PyObject * + PLy_raise_log(PyObject *self, PyObject *args, PyObject *kw) + { + return PLy_output_kw(LOG, self, args, kw); + } + + static PyObject * + PLy_raise_info(PyObject *self, PyObject *args, PyObject *kw) + { + return PLy_output_kw(INFO, self, args, kw); + } + + static PyObject * + PLy_raise_notice(PyObject *self, PyObject *args, PyObject *kw) + { + return PLy_output_kw(NOTICE, self, args, kw); + } + + static PyObject * + PLy_raise_warning(PyObject *self, PyObject *args, PyObject *kw) + { + return PLy_output_kw(WARNING, self, args, kw); + } + + static PyObject * + PLy_raise_error(PyObject *self, PyObject *args, PyObject *kw) + { + return PLy_output_kw(ERROR, self, args, kw); + } + + static PyObject * + PLy_raise_fatal(PyObject *self, PyObject *args, PyObject *kw) + { + return PLy_output_kw(FATAL, self, args, kw); + } + + static PyObject * PLy_quote_literal(PyObject *self, PyObject *args) { const char *str; *************** PLy_output(volatile int level, PyObject *** 429,431 **** --- 661,760 ---- Py_INCREF(Py_None); return Py_None; } + + static PyObject * + PLy_output_kw(volatile int level, 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; + MemoryContext oldcontext ; + + static char *kwlist[] = { "message", "detail", "hint", + "sqlstate", + "schema","table", "column", + "datatype", "constraint", + NULL }; + + if (!PyArg_ParseTupleAndKeywords(args, kw, "|sssssssss", kwlist, + &message, &detail, &hint, + &sqlstatestr, + &schema, &table, &column, + &datatype, &constraint)) + return NULL; + + 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_base_exception_set(PLy_exc_error, edata); + FreeErrorData(edata); + return NULL; + } + PG_END_TRY(); + + Py_INCREF(Py_None); + return Py_None; + } diff --git a/src/pl/plpython/plpy_spi.c b/src/pl/plpython/plpy_spi.c new file mode 100644 index 58e78ec..8d4604b *** a/src/pl/plpython/plpy_spi.c --- b/src/pl/plpython/plpy_spi.c *************** *** 30,36 **** static PyObject *PLy_spi_execute_query(char *query, long limit); static PyObject *PLy_spi_execute_plan(PyObject *ob, PyObject *list, long limit); static PyObject *PLy_spi_execute_fetch_result(SPITupleTable *tuptable, int rows, int status); - static void PLy_spi_exception_set(PyObject *excclass, ErrorData *edata); /* prepare(query="select * from foo") --- 30,35 ---- *************** PLy_spi_subtransaction_abort(MemoryConte *** 540,587 **** Assert(entry != NULL); exc = entry ? entry->exc : PLy_exc_spi_error; /* Make Python raise the exception */ ! PLy_spi_exception_set(exc, edata); FreeErrorData(edata); } - - /* - * Raise a SPIError, passing in it more error details, like the - * internal query and error position. - */ - static void - PLy_spi_exception_set(PyObject *excclass, ErrorData *edata) - { - PyObject *args = NULL; - PyObject *spierror = NULL; - PyObject *spidata = NULL; - - args = Py_BuildValue("(s)", edata->message); - if (!args) - goto failure; - - /* create a new SPI exception with the error message as the parameter */ - spierror = PyObject_CallObject(excclass, args); - if (!spierror) - goto failure; - - spidata = Py_BuildValue("(izzzi)", edata->sqlerrcode, edata->detail, edata->hint, - edata->internalquery, edata->internalpos); - if (!spidata) - goto failure; - - if (PyObject_SetAttrString(spierror, "spidata", spidata) == -1) - goto failure; - - PyErr_SetObject(excclass, spierror); - - Py_DECREF(args); - Py_DECREF(spierror); - Py_DECREF(spidata); - return; - - failure: - Py_XDECREF(args); - Py_XDECREF(spierror); - Py_XDECREF(spidata); - elog(ERROR, "could not convert SPI error to Python exception"); - } --- 539,544 ---- Assert(entry != NULL); exc = entry ? entry->exc : PLy_exc_spi_error; /* Make Python raise the exception */ ! PLy_base_exception_set(exc, edata); FreeErrorData(edata); } 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; diff --git a/src/pl/plpython/sql/plpython_test.sql b/src/pl/plpython/sql/plpython_test.sql new file mode 100644 index c8d5ef5..5eac2f5 *** a/src/pl/plpython/sql/plpython_test.sql --- b/src/pl/plpython/sql/plpython_test.sql *************** plpy.error('error') *** 51,53 **** --- 51,75 ---- $$ LANGUAGE plpythonu; SELECT elog_test(); + + CREATE FUNCTION elog_test2() RETURNS void + AS $$ + plpy.raise_debug('debug','some detail') + plpy.raise_log('log','some detail') + plpy.raise_info('info','some detail') + plpy.raise_info() + plpy.raise_info('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') + plpy.raise_notice('notice','some detail') + plpy.raise_warning('warning','some detail') + plpy.raise_error('stop on error', 'some detail','some hint') + $$ LANGUAGE plpythonu; + + SELECT elog_test2();