2015-11-02 17:01 GMT+01:00 Catalin Iacob <iacobcata...@gmail.com>: > Hello, > > Here's a detailed review: > > 1. in PLy_spi_error__init__ you need to check kw for NULL before doing > PyDict_Size(kw) otherwise for plpy.SPIError() you get Bad internal > call because PyDict_Size expects a real dictionary not NULL >
PyDict_Size returns -1 when the dictionary is NULL http://grokbase.com/t/python/python-dev/042ft6qaqq/pydict-size-error-return done > > 2. a test with just plpy.SPIError() is still missing, this would have > caught 1. > please, can you write some example - I am not able raise described error > > 3. the tests have "possibility to set all accessable fields in custom > exception" above a test that doesn't set all fields, it's confusing > (and accessible is spelled wrong) > > done > 4. in the docs, "The constructor of SPIError exception (class) > supports keyword parameters." sounds better as "An SPIError instance > can be constructed using keyword parameters." > done > > 5. there is conceptual code duplication between PLy_spi_exception_set > in plpy_spi.c, since that code also constructs an SPIError but from C > and with more info available (edata->internalquery and > edata->internalpos). But making a tuple and assigning it to spidata is > in both. Not sure how this can be improved. > I see it, but I don't think, so current code should be changed. PLy_spi_exception_set is controlled directly by fully filled ErrorData structure, __init__ is based only on keyword parameters with possibility use inherited data. If I'll build ErrorData in __init__ function and call PLy_spi_exception_set, then the code will be longer and more complex. > > 6. __init__ can use METH_VARARGS | METH_KEYWORDS in both Python2 and > 3, no need for the #if > > done > 7. "could not create dictionary for SPI exception" would be more > precise as "could not create dictionary for SPIError" right? > done > > 8. in PLy_add_methods_to_dictionary I would avoid import since it > makes one think of importing modules; maybe "could not create > functionwrapper for \"%s\"", "could not create method wrapper for \"%s\"" done > > 9. also in PLy_add_methods_to_dictionary "could public method \"%s\" > in dictionary" is not proper English and is missing not, maybe "could > not add method \"%s\" to class dictionary"? > done > > 10. in PLy_add_methods_to_dictionary if PyCFunction_New succeeds but > PyMethod_New fails, func will leak > done > > 11. it would be nice to have a test for the invalid SQLSTATE code part > done > > 12. similar to 10, in PLy_spi_error__init__ taking the "invalid > SQLSTATE" branch leaks exc_args, in general early returns via PLy_elog > will leave the intermediate Python objects leaking > dome > > > Will mark the patch as Waiting for author. > attached new update Regards Pavel
diff --git a/doc/src/sgml/plpython.sgml b/doc/src/sgml/plpython.sgml new file mode 100644 index 015bbad..9333fbe *** a/doc/src/sgml/plpython.sgml --- b/doc/src/sgml/plpython.sgml *************** $$ LANGUAGE plpythonu; *** 1205,1210 **** --- 1205,1235 ---- approximately the same functionality </para> </sect2> + + <sect2 id="plpython-raising"> + <title>Raising Errors</title> + + <para> + The <literal>plpy</literal> module provides several possibilities to + to raise a exception: + </para> + + <variablelist> + <varlistentry> + <term><literal><function>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></term> + <listitem> + <para> + An SPIError instance can be constructed using keyword parameters. + <programlisting> + DO $$ + raise plpy.SPIError('custom message', hint = 'It is test only'); + $$ LANGUAGE plpythonu; + </programlisting> + </para> + </listitem> + </varlistentry> + </variablelist> + </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..435a5c2 *** 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.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/plpy_elog.c b/src/pl/plpython/plpy_elog.c new file mode 100644 index 15406d6..a835af9 *** a/src/pl/plpython/plpy_elog.c --- b/src/pl/plpython/plpy_elog.c *************** PyObject *PLy_exc_spi_error = NULL; *** 23,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); --- 23,32 ---- 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 **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; } --- 54,73 ---- 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(); { --- 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_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 { --- 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..c4b2ae8 *** 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_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 **** --- 103,113 ---- {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 */ *************** static void *** 185,190 **** --- 194,200 ---- PLy_add_exceptions(PyObject *plpy) { PyObject *excmod; + PyObject *spi_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 */ + 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, spi_error_dict); ! Py_DECREF(spi_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_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 new file mode 100644 index d0e255f..4419099 *** a/src/pl/plpython/plpy_spi.c --- b/src/pl/plpython/plpy_spi.c *************** 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; --- 548,558 ---- 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..1d1a049 *** 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.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