2015-10-16 8:12 GMT+02:00 Craig Ringer <cr...@2ndquadrant.com>: > On 16 October 2015 at 02:47, Pavel Stehule <pavel.steh...@gmail.com> > wrote: > > > postgres=# do $$ > > x = plpy.SPIError('Nazdarek'); > > x.spidata = (100, "Some detail", "some hint", None, None); > > raise x; > > $$ language plpythonu; > > Shouldn't that look more like > > raise plpy.SPIError(msg="Message", sqlstate="0P001", hint="Turn it on > and off again") ? > > Keyword args are very much the norm for this sort of thing. I recall > them being pretty reasonable to deal with in the CPython API too, but > otherwise a trivial Python wrapper in the module can easily adapt the > interface. >
I wrote a constructor for SPIError with keyword parameters support - see attached patch The code is working postgres=# do $$ raise plpy.SPIError("pokus",hint = "some info"); $$ language plpythonu; ERROR: plpy.SPIError: pokus HINT: some info CONTEXT: Traceback (most recent call last): PL/Python anonymous code block, line 2, in <module> raise plpy.SPIError("pokus",hint = "some info"); PL/Python anonymous code block but the implementation is pretty ugly :( - I didn't write C extensions for Python before, and the extending exception class with some methods isn't well supported and well documented. Any help is welcome Regards Pavel > > > -- > Craig Ringer http://www.2ndQuadrant.com/ > PostgreSQL Development, 24x7 Support, Training & Services >
diff --git a/src/pl/plpython/expected/plpython_error.out b/src/pl/plpython/expected/plpython_error.out new file mode 100644 index 1f52af7..c9b5e69 *** a/src/pl/plpython/expected/plpython_error.out --- b/src/pl/plpython/expected/plpython_error.out *************** EXCEPTION WHEN division_by_zero THEN *** 408,413 **** --- 408,420 ---- -- NOOP END $$ LANGUAGE plpgsql; + ERROR: spiexceptions.DivisionByZero: None + CONTEXT: Traceback (most recent call last): + PL/Python function "plpy_raise_spiexception", line 2, in <module> + raise plpy.spiexceptions.DivisionByZero() + PL/Python function "plpy_raise_spiexception" + SQL statement "SELECT plpy_raise_spiexception()" + PL/pgSQL function inline_code_block line 3 at SQL statement /* setting a custom sqlstate should be handled */ CREATE FUNCTION plpy_raise_spiexception_override() RETURNS void AS $$ *************** EXCEPTION WHEN SQLSTATE 'SILLY' THEN *** 422,424 **** --- 429,487 ---- -- NOOP END $$ LANGUAGE plpgsql; + ERROR: spiexceptions.DivisionByZero: None + CONTEXT: Traceback (most recent call last): + PL/Python function "plpy_raise_spiexception_override", line 4, in <module> + raise exc + PL/Python function "plpy_raise_spiexception_override" + SQL statement "SELECT plpy_raise_spiexception_override()" + PL/pgSQL function inline_code_block line 3 at SQL statement + CREATE FUNCTION nested_error_ereport() RETURNS text + AS + 'def fun1(): + raise plpy.SPIError("HiHi"); + + def fun2(): + fun1() + + def fun3(): + fun2() + + fun3() + return "not reached" + ' + LANGUAGE plpythonu; + SELECT nested_error_ereport(); + ERROR: plpy.SPIError: HiHi + 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.SPIError("HiHi"); + PL/Python function "nested_error_ereport" + \set VERBOSITY verbose + do $$ + x = plpy.SPIError("pokus",hint = "some info", sqlstate='AA234'); raise x; + $$ language plpythonu; + ERROR: AA234: plpy.SPIError: pokus + HINT: some info + CONTEXT: Traceback (most recent call last): + PL/Python anonymous code block, line 2, in <module> + x = plpy.SPIError("pokus",hint = "some info", sqlstate='AA234'); raise x; + PL/Python anonymous code block + LOCATION: PLy_elog, plpy_elog.c:119 + do $$ + raise plpy.SPIError("pokus",hint = "some info", sqlstate='AA234'); + $$ language plpythonu; + ERROR: AA234: plpy.SPIError: pokus + HINT: some info + CONTEXT: Traceback (most recent call last): + PL/Python anonymous code block, line 2, in <module> + raise plpy.SPIError("pokus",hint = "some info", sqlstate='AA234'); + PL/Python anonymous code block + LOCATION: PLy_elog, plpy_elog.c:119 + \set VERBOSITY default diff --git a/src/pl/plpython/plpy_elog.c b/src/pl/plpython/plpy_elog.c new file mode 100644 index 15406d6..6825732 *** a/src/pl/plpython/plpy_elog.c --- b/src/pl/plpython/plpy_elog.c *************** PyObject *PLy_exc_fatal = NULL; *** 21,29 **** 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); --- 21,30 ---- PyObject *PLy_exc_spi_error = NULL; static void 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); static char *get_source_line(const char *src, int lineno); *************** PLy_elog(int elevel, const char *fmt,... *** 51,62 **** 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; } --- 52,70 ---- 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, ! &schema_name, &table_name, &column_name, ! &datatype_name, &constraint_name); else if (PyErr_GivenExceptionMatches(val, PLy_exc_fatal)) elevel = FATAL; } *************** 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(); { --- 111,122 ---- (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_elog(int elevel, const char *fmt,... *** 132,138 **** * tbmsg (both as palloc'd strings) and the traceback depth in * tb_depth. */ ! static void PLy_traceback(char **xmsg, char **tbmsg, int *tb_depth) { PyObject *e, --- 145,151 ---- * tbmsg (both as palloc'd strings) and the traceback depth in * tb_depth. */ ! void PLy_traceback(char **xmsg, char **tbmsg, int *tb_depth) { PyObject *e, *************** 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; --- 378,386 ---- * 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, ! 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 { --- 388,396 ---- 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_elog.h b/src/pl/plpython/plpy_elog.h new file mode 100644 index 94725c2..29a0d9b *** a/src/pl/plpython/plpy_elog.h --- b/src/pl/plpython/plpy_elog.h *************** extern void PLy_exception_set(PyObject * *** 17,20 **** --- 17,22 ---- 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 new file mode 100644 index a44b7fb..e8bf582 *** a/src/pl/plpython/plpy_plpymodule.c --- b/src/pl/plpython/plpy_plpymodule.c *************** *** 23,29 **** HTAB *PLy_spi_exceptions = NULL; - static void PLy_add_exceptions(PyObject *plpy); static void PLy_generate_spi_exceptions(PyObject *mod, PyObject *base); --- 23,28 ---- *************** static PyObject *PLy_quote_literal(PyObj *** 39,44 **** --- 38,45 ---- static PyObject *PLy_quote_nullable(PyObject *self, PyObject *args); static PyObject *PLy_quote_ident(PyObject *self, PyObject *args); + /* class 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 *************** static PyMethodDef PLy_exc_methods[] = { *** 99,104 **** --- 100,110 ---- {NULL, NULL, 0, NULL} }; + static PyMethodDef PLy_exc_spi_error_methods[] = { + {"__init__", (PyCFunction) PLy_spi_error_init, METH_KEYWORDS, NULL}, + {NULL, NULL, 0, NULL} + }; + #if PY_MAJOR_VERSION >= 3 static PyModuleDef PLy_module = { PyModuleDef_HEAD_INIT, /* m_base */ *************** PLy_add_exceptions(PyObject *plpy) *** 187,192 **** --- 193,201 ---- PyObject *excmod; HASHCTL hash_ctl; + PyObject *spi_error_dict; + PyMethodDef *method; + #if PY_MAJOR_VERSION < 3 excmod = Py_InitModule("spiexceptions", PLy_exc_methods); #else *************** 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 || --- 216,256 ---- */ Py_INCREF(excmod); + spi_error_dict = PyDict_New(); + PLy_exc_error = PyErr_NewException("plpy.Error", NULL, NULL); PLy_exc_fatal = PyErr_NewException("plpy.Fatal", NULL, NULL); ! ! /* generate constructor for plpy.SPIError class */ ! for (method = PLy_exc_spi_error_methods; method->ml_name != NULL; method++) ! { ! PyObject *func; ! PyObject *meth; ! ! func = PyCFunction_New(method, NULL); ! if (func == NULL) ! PLy_elog(ERROR, "could not import function \"%s\"", method->ml_name); ! ! /* ! * It is not probably correct, last parameter class is null, should be ! * PLy_exc_spi_error, but I have to create class after dictionary ! * setting :(. ! */ ! meth = PyMethod_New(func, NULL, NULL); ! if (meth == NULL) ! PLy_elog(ERROR, "could not import method \"%s\"", method->ml_name); ! ! if (PyDict_SetItemString(spi_error_dict, method->ml_name, meth)) ! PLy_elog(ERROR, "could public method \"%s\" in dictionary", method->ml_name); ! ! Py_DECREF(meth); ! Py_DECREF(func); ! } ! ! /* ! * It doesn't work with empty spi_error_dic, so dictionary must be set first. ! */ ! PLy_exc_spi_error = PyErr_NewException("plpy.SPIError", NULL, spi_error_dict); if (PLy_exc_error == NULL || PLy_exc_fatal == NULL || *************** PLy_add_exceptions(PyObject *plpy) *** 223,228 **** --- 264,271 ---- Py_INCREF(PLy_exc_spi_error); PyModule_AddObject(plpy, "SPIError", PLy_exc_spi_error); + Py_DECREF(spi_error_dict); + memset(&hash_ctl, 0, sizeof(hash_ctl)); hash_ctl.keysize = sizeof(int); hash_ctl.entrysize = sizeof(PLyExceptionEntry); *************** PLy_fatal(PyObject *self, PyObject *args *** 316,321 **** --- 359,446 ---- } static PyObject * + PLy_spi_error_init(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; + PyObject *exc_args = NULL; + PyObject *spierror = NULL; + PyObject *spidata = NULL; + + PyObject *_self; + + static char *kwlist[] = { "self", "message", "detail", "hint", + "sqlstate", + "schema","table", "column", + "datatype", "constraint", + NULL }; + + if (!PyArg_ParseTupleAndKeywords(args, kw, "O|ssssssssssss", kwlist, + &_self, + &message, &detail, &hint, + &sqlstatestr, + &schema, &table, &column, + &datatype, &constraint)) + return NULL; + + exc_args = Py_BuildValue("(s)", message); + if (!exc_args) + goto failure; + + /* create a new SPI exception with the error message as the parameter */ + if (PyObject_SetAttrString(_self, "args", exc_args) == -1) + goto failure; + + 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]); + } + + 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_DECREF(exc_args); + Py_DECREF(spidata); + + Py_INCREF(Py_None); + return Py_None; + + failure: + Py_XDECREF(args); + Py_XDECREF(spidata); + + PLy_elog(ERROR, "could not create SPIError object"); + + return NULL; + } + + 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 new file mode 100644 index d0e255f..4f143d6 *** 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 *** 532,538 **** * 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; --- 531,537 ---- * Raise a SPIError, passing in it more error details, like the * internal query and error position. */ ! void PLy_spi_exception_set(PyObject *excclass, ErrorData *edata) { PyObject *args = NULL; *************** PLy_spi_exception_set(PyObject *excclass *** 548,555 **** if (!spierror) goto failure; ! spidata = Py_BuildValue("(izzzi)", edata->sqlerrcode, edata->detail, edata->hint, ! edata->internalquery, edata->internalpos); if (!spidata) goto failure; --- 547,556 ---- 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/plpy_spi.h b/src/pl/plpython/plpy_spi.h new file mode 100644 index b042794..e9a5991 *** a/src/pl/plpython/plpy_spi.h --- b/src/pl/plpython/plpy_spi.h *************** extern void PLy_spi_subtransaction_begin *** 22,25 **** --- 22,28 ---- 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 new file mode 100644 index d0df7e6..90da808 *** a/src/pl/plpython/sql/plpython_error.sql --- b/src/pl/plpython/sql/plpython_error.sql *************** EXCEPTION WHEN SQLSTATE 'SILLY' THEN *** 328,330 **** --- 328,362 ---- -- NOOP END $$ LANGUAGE plpgsql; + + + CREATE FUNCTION nested_error_ereport() RETURNS text + AS + 'def fun1(): + raise plpy.SPIError("HiHi"); + + def fun2(): + fun1() + + def fun3(): + fun2() + + fun3() + return "not reached" + ' + LANGUAGE plpythonu; + + SELECT nested_error_ereport(); + + \set VERBOSITY verbose + + do $$ + x = plpy.SPIError("pokus",hint = "some info", sqlstate='AA234'); raise x; + $$ language plpythonu; + + do $$ + raise plpy.SPIError("pokus",hint = "some info", sqlstate='AA234'); + $$ language plpythonu; + + \set VERBOSITY default +
-- Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers