2015-10-08 12:11 GMT+02:00 Pavel Stehule <pavel.steh...@gmail.com>: > Hi > > We cannot to raise PostgreSQL exception with setting all possible fields. > I propose new function > > plpy.ereport(level, [ message [, detail [, hint [, sqlstate, ... ]]]]) > > The implementation will be based on keyword parameters, so only required > parameters should be used. > > Examples: > > plpy.ereport(plpy.NOTICE, 'some message', 'some detai') > plpy.ereport(plpy.ERROR, 'some message', sqlstate = 'zx243'); >
patch attached regards Pavel > > Comments, notices, objections? > > Regards > > Pavel >
commit 2593493f583f06a784b5d0b56f5efa04e62ad07a Author: Pavel Stehule <pavel.steh...@gooddata.com> Date: Thu Oct 8 20:51:17 2015 +0200 initial diff --git a/src/pl/plpython/expected/plpython_error.out b/src/pl/plpython/expected/plpython_error.out index 1f52af7..76d8b2c 100644 --- a/src/pl/plpython/expected/plpython_error.out +++ b/src/pl/plpython/expected/plpython_error.out @@ -422,3 +422,59 @@ EXCEPTION WHEN SQLSTATE 'SILLY' THEN -- NOOP END $$ LANGUAGE plpgsql; +CREATE FUNCTION nested_error_ereport() RETURNS text + AS +'def fun1(): + raise plpy.ereport(plpy.ERROR, "boom") + +def fun2(): + fun1() + +def fun3(): + fun2() + +fun3() +return "not reached" +' + LANGUAGE plpythonu; +SELECT nested_error_ereport(); +ERROR: plpy.SPIError: boom +CONTEXT: Traceback (most recent call last): + PL/Python function "nested_error_ereport", line 10, in <module> + fun3() + PL/Python function "nested_error_ereport", line 8, in fun3 + fun2() + PL/Python function "nested_error_ereport", line 5, in fun2 + fun1() + PL/Python function "nested_error_ereport", line 2, in fun1 + raise plpy.ereport(plpy.ERROR, "boom") +PL/Python function "nested_error_ereport" +CREATE FUNCTION ereport_test() +RETURNS void AS $$ + plpy.ereport(plpy.NOTICE, "some notice") +$$ LANGUAGE plpythonu; +SELECT ereport_test(); +NOTICE: some notice + ereport_test +-------------- + +(1 row) + +\set VERBOSITY verbose +CREATE OR REPLACE FUNCTION ereport_test() +RETURNS void AS $$ + plpy.ereport(plpy.NOTICE, "some notice", "some detail", "any hint", schema='schema_yy', table='table_xx', column='column_zz') +$$ LANGUAGE plpythonu; +SELECT ereport_test(); +NOTICE: 00000: some notice +DETAIL: some detail +HINT: any hint +SCHEMA NAME: schema_yy +TABLE NAME: table_xx +COLUMN NAME: column_zz +LOCATION: PLy_ereport, plpy_plpymodule.c:428 + ereport_test +-------------- + +(1 row) + diff --git a/src/pl/plpython/expected/plpython_test.out b/src/pl/plpython/expected/plpython_test.out index 7b76faf..cceb5b8 100644 --- a/src/pl/plpython/expected/plpython_test.out +++ b/src/pl/plpython/expected/plpython_test.out @@ -43,9 +43,9 @@ contents.sort() 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 + module_contents +--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + DEBUG, ERROR, Error, Fatal, INFO, LOG, NOTICE, SPIError, WARNING, cursor, debug, ereport, error, execute, fatal, info, log, notice, prepare, quote_ident, quote_literal, quote_nullable, spiexceptions, subtransaction, warning (1 row) CREATE FUNCTION elog_test() RETURNS void diff --git a/src/pl/plpython/plpy_elog.c b/src/pl/plpython/plpy_elog.c index 15406d6..6825732 100644 --- a/src/pl/plpython/plpy_elog.c +++ b/src/pl/plpython/plpy_elog.c @@ -21,9 +21,10 @@ 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); + 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 +52,19 @@ 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 +111,12 @@ 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(); { @@ -132,7 +145,7 @@ PLy_elog(int elevel, const char *fmt,...) * tbmsg (both as palloc'd strings) and the traceback depth in * tb_depth. */ -static void +void PLy_traceback(char **xmsg, char **tbmsg, int *tb_depth) { PyObject *e, @@ -365,7 +378,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 +388,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_elog.h b/src/pl/plpython/plpy_elog.h index 94725c2..29a0d9b 100644 --- a/src/pl/plpython/plpy_elog.h +++ b/src/pl/plpython/plpy_elog.h @@ -17,4 +17,6 @@ extern void PLy_exception_set(PyObject *exc, const char *fmt,...) pg_attribute_p 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_traceback(char **xmsg, char **tbmsg, int *tb_depth); + #endif /* PLPY_ELOG_H */ diff --git a/src/pl/plpython/plpy_plpymodule.c b/src/pl/plpython/plpy_plpymodule.c index a44b7fb..7bc775c 100644 --- a/src/pl/plpython/plpy_plpymodule.c +++ b/src/pl/plpython/plpy_plpymodule.c @@ -23,7 +23,7 @@ HTAB *PLy_spi_exceptions = NULL; - +static void PLy_add_constants(PyObject *plpy); static void PLy_add_exceptions(PyObject *plpy); static void PLy_generate_spi_exceptions(PyObject *mod, PyObject *base); @@ -35,6 +35,7 @@ static PyObject *PLy_notice(PyObject *self, PyObject *args); static PyObject *PLy_warning(PyObject *self, PyObject *args); static PyObject *PLy_error(PyObject *self, PyObject *args); static PyObject *PLy_fatal(PyObject *self, PyObject *args); +static PyObject *PLy_ereport(PyObject *self, PyObject *args, PyObject *kw); 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); @@ -64,6 +65,7 @@ static PyMethodDef PLy_methods[] = { {"warning", PLy_warning, METH_VARARGS, NULL}, {"error", PLy_error, METH_VARARGS, NULL}, {"fatal", PLy_fatal, METH_VARARGS, NULL}, + {"ereport", (PyCFunction) PLy_ereport, METH_VARARGS | METH_KEYWORDS, NULL}, /* * create a stored plan @@ -133,6 +135,7 @@ PyInit_plpy(void) if (m == NULL) return NULL; + PLy_add_constants(m); PLy_add_exceptions(m); return m; @@ -163,6 +166,7 @@ PLy_init_plpy(void) /* for Python 3 we initialized the exceptions in PyInit_plpy */ #else plpy = Py_InitModule("plpy", PLy_methods); + PLy_add_constants(plpy); PLy_add_exceptions(plpy); #endif @@ -181,6 +185,21 @@ PLy_init_plpy(void) PLy_elog(ERROR, "could not import \"plpy\" module"); } +#define PYMODULE_ADD_CONSTANT(m, name, value) \ +if (PyModule_AddIntConstant(m, name, value) != 0) \ + PLy_elog(ERROR, "could not import \"plpy\" constant") + +static void +PLy_add_constants(PyObject *plpy) +{ + PYMODULE_ADD_CONSTANT(plpy, "DEBUG", DEBUG2); + PYMODULE_ADD_CONSTANT(plpy, "LOG", LOG); + PYMODULE_ADD_CONSTANT(plpy, "INFO", INFO); + PYMODULE_ADD_CONSTANT(plpy, "NOTICE", NOTICE); + PYMODULE_ADD_CONSTANT(plpy, "WARNING", WARNING); + PYMODULE_ADD_CONSTANT(plpy, "ERROR", ERROR); +} + static void PLy_add_exceptions(PyObject *plpy) { @@ -316,6 +335,118 @@ PLy_fatal(PyObject *self, PyObject *args) } static PyObject * +PLy_ereport(PyObject *self, PyObject *args, PyObject *kw) +{ + int level; + 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[] = { "level","message", "detail", "hint", + "sqlstate", + "schema","table", "column", + "datatype", "constraint", + NULL }; + + if (!PyArg_ParseTupleAndKeywords(args, kw, "i|ssssssssssss", kwlist, + &level, &message, &detail, &hint, + &sqlstatestr, + &schema, &table, &column, + &datatype, &constraint)) + return NULL; + + switch (level) + { + case DEBUG2: + case LOG: + case INFO: + case NOTICE: + case WARNING: + case ERROR: + break; + default: + PLy_elog(ERROR, "ereport level is not valid in plpy.ereport"); + } + + 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_spi_exception_set(PLy_exc_spi_error, edata); + FreeErrorData(edata); + return NULL; + } + PG_END_TRY(); + + Py_INCREF(Py_None); + return Py_None; + +} + +static PyObject * PLy_quote_literal(PyObject *self, PyObject *args) { const char *str; diff --git a/src/pl/plpython/plpy_spi.c b/src/pl/plpython/plpy_spi.c index d0e255f..4f143d6 100644 --- a/src/pl/plpython/plpy_spi.c +++ b/src/pl/plpython/plpy_spi.c @@ -30,7 +30,6 @@ 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") @@ -532,7 +531,7 @@ PLy_spi_subtransaction_abort(MemoryContext oldcontext, ResourceOwner oldowner) * Raise a SPIError, passing in it more error details, like the * internal query and error position. */ -static void +void PLy_spi_exception_set(PyObject *excclass, ErrorData *edata) { PyObject *args = NULL; @@ -548,8 +547,10 @@ 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/plpy_spi.h b/src/pl/plpython/plpy_spi.h index b042794..e9a5991 100644 --- a/src/pl/plpython/plpy_spi.h +++ b/src/pl/plpython/plpy_spi.h @@ -22,4 +22,7 @@ extern void PLy_spi_subtransaction_begin(MemoryContext oldcontext, ResourceOwner extern void PLy_spi_subtransaction_commit(MemoryContext oldcontext, ResourceOwner oldowner); extern void PLy_spi_subtransaction_abort(MemoryContext oldcontext, ResourceOwner oldowner); +/* set spi exception */ +extern void PLy_spi_exception_set(PyObject *excclass, ErrorData *edata); + #endif /* PLPY_SPI_H */ diff --git a/src/pl/plpython/sql/plpython_error.sql b/src/pl/plpython/sql/plpython_error.sql index d0df7e6..45c6b25 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; + + +CREATE FUNCTION nested_error_ereport() RETURNS text + AS +'def fun1(): + raise plpy.ereport(plpy.ERROR, "boom") + +def fun2(): + fun1() + +def fun3(): + fun2() + +fun3() +return "not reached" +' + LANGUAGE plpythonu; + +SELECT nested_error_ereport(); + +CREATE FUNCTION ereport_test() +RETURNS void AS $$ + plpy.ereport(plpy.NOTICE, "some notice") +$$ LANGUAGE plpythonu; + +SELECT ereport_test(); + +\set VERBOSITY verbose + +CREATE OR REPLACE FUNCTION ereport_test() +RETURNS void AS $$ + plpy.ereport(plpy.NOTICE, "some notice", "some detail", "any hint", schema='schema_yy', table='table_xx', column='column_zz') +$$ LANGUAGE plpythonu; + +SELECT ereport_test();
-- Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers