From c75a3bbab4a257745fc2342c1be2b643f4a59e8f Mon Sep 17 00:00:00 2001 From: Matthew Woodcraft Date: Sun, 17 Mar 2013 16:41:15 +0000 Subject: [PATCH 1/9] Work-in-progress support for retrieving PG_DIAG result error fields. --- psycopg/diagnostics.h | 38 +++++++ psycopg/diagnostics_type.c | 204 +++++++++++++++++++++++++++++++++++++ psycopg/pqpath.c | 3 +- psycopg/psycopgmodule.c | 13 +++ setup.py | 3 +- tests/test_module.py | 21 +++- 6 files changed, 279 insertions(+), 3 deletions(-) create mode 100644 psycopg/diagnostics.h create mode 100644 psycopg/diagnostics_type.c diff --git a/psycopg/diagnostics.h b/psycopg/diagnostics.h new file mode 100644 index 00000000..adaf7580 --- /dev/null +++ b/psycopg/diagnostics.h @@ -0,0 +1,38 @@ +/* diagnostics.c - definition for the psycopg Diagnostics type + * + * Copyright (C) 2013 Matthew Woodcraft + * + * This file is part of psycopg. + * + * psycopg2 is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * In addition, as a special exception, the copyright holders give + * permission to link this program with the OpenSSL library (or with + * modified versions of OpenSSL that use the same license as OpenSSL), + * and distribute linked combinations including the two. + * + * You must obey the GNU Lesser General Public License in all respects for + * all of the code used other than OpenSSL. + * + * psycopg2 is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +#ifndef PSYCOPG_DIAGNOSTICS_H +#define PSYCOPG_DIAGNOSTICS_H 1 + +extern HIDDEN PyTypeObject diagnosticsType; + +typedef struct { + PyObject_HEAD + + PyObject *err; /* exception to retrieve the diagnostics from */ + +} diagnosticsObject; + +#endif /* PSYCOPG_DIAGNOSTICS_H */ diff --git a/psycopg/diagnostics_type.c b/psycopg/diagnostics_type.c new file mode 100644 index 00000000..cce4b304 --- /dev/null +++ b/psycopg/diagnostics_type.c @@ -0,0 +1,204 @@ +/* diagnostics.c - present information from libpq error responses + * + * Copyright (C) 2013 Matthew Woodcraft + * + * This file is part of psycopg. + * + * psycopg2 is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * In addition, as a special exception, the copyright holders give + * permission to link this program with the OpenSSL library (or with + * modified versions of OpenSSL that use the same license as OpenSSL), + * and distribute linked combinations including the two. + * + * You must obey the GNU Lesser General Public License in all respects for + * all of the code used other than OpenSSL. + * + * psycopg2 is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +#define PSYCOPG_MODULE +#include "psycopg/psycopg.h" + +#include "psycopg/diagnostics.h" +#include "psycopg/cursor.h" + +/* These are new in PostgreSQL 9.3. Defining them here so that psycopg2 can + * use them with a 9.3+ server even if compiled against pre-9.3 headers. */ +#ifndef PG_DIAG_SCHEMA_NAME +#define PG_DIAG_SCHEMA_NAME 's' +#endif +#ifndef PG_DIAG_TABLE_NAME +#define PG_DIAG_TABLE_NAME 't' +#endif +#ifndef PG_DIAG_COLUMN_NAME +#define PG_DIAG_COLUMN_NAME 'c' +#endif +#ifndef PG_DIAG_DATATYPE_NAME +#define PG_DIAG_DATATYPE_NAME 'd' +#endif +#ifndef PG_DIAG_CONSTRAINT_NAME +#define PG_DIAG_CONSTRAINT_NAME 'n' +#endif + + +/* Retrieve an error string from the exception's cursor. + * + * If the cursor or its result isn't available, return None. + */ +static PyObject * +psyco_diagnostics_get_field(diagnosticsObject *self, void *closure) +{ + // closure contains the field code. + PyObject *rv = NULL; + PyObject *curs = NULL; + const char* errortext; + if (!(curs = PyObject_GetAttrString(self->err, "cursor")) || + !PyObject_TypeCheck(curs, &cursorType) || + ((cursorObject *)curs)->pgres == NULL) { + goto exit; + } + errortext = PQresultErrorField( + ((cursorObject *)curs)->pgres, (Py_intptr_t) closure); + if (errortext) { + // FIXME: does this need to use conn_text_from_chars()? + rv = PyString_FromString(errortext); + } +exit: + if (!rv) { + rv = Py_None; + Py_INCREF(rv); + } + Py_XDECREF(curs); + return rv; +} + + +/* object calculated member list */ +static struct PyGetSetDef diagnosticsObject_getsets[] = { + { "schema_name", (getter)psyco_diagnostics_get_field, NULL, + NULL, (void*) PG_DIAG_SCHEMA_NAME }, + { "table_name", (getter)psyco_diagnostics_get_field, NULL, + NULL, (void*) PG_DIAG_TABLE_NAME }, + { "column_name", (getter)psyco_diagnostics_get_field, NULL, + NULL, (void*) PG_DIAG_COLUMN_NAME }, + { "datatype_name", (getter)psyco_diagnostics_get_field, NULL, + NULL, (void*) PG_DIAG_DATATYPE_NAME }, + { "constraint_name", (getter)psyco_diagnostics_get_field, NULL, + NULL, (void*) PG_DIAG_CONSTRAINT_NAME }, + {NULL} +}; + +/* initialization and finalization methods */ + +static int +diagnostics_setup(diagnosticsObject *self, PyObject *err) +{ + self->err = err; + Py_INCREF(err); + + return 0; +} + +static void +diagnostics_dealloc(PyObject* obj) +{ + diagnosticsObject *self = (diagnosticsObject *)obj; + + Py_XDECREF(self->err); + + Py_TYPE(obj)->tp_free(obj); +} + +static int +diagnostics_init(PyObject *obj, PyObject *args, PyObject *kwds) +{ + PyObject *err = NULL; + + if (!PyArg_ParseTuple(args, "O", &err)) + return -1; + + return diagnostics_setup((diagnosticsObject *)obj, err); +} + +static PyObject * +diagnostics_new(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + return type->tp_alloc(type, 0); +} + +static void +diagnostics_del(PyObject* self) +{ + PyObject_Del(self); +} + + +/* object type */ + +#define diagnosticsType_doc \ +"Details from a database error report." + +PyTypeObject diagnosticsType = { + PyVarObject_HEAD_INIT(NULL, 0) + "psycopg2._psycopg.Diagnostics", + sizeof(diagnosticsObject), + 0, + diagnostics_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash */ + + 0, /*tp_call*/ + 0, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + + Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, /*tp_flags*/ + diagnosticsType_doc, /*tp_doc*/ + + 0, /*tp_traverse*/ + 0, /*tp_clear*/ + + 0, /*tp_richcompare*/ + 0, /*tp_weaklistoffset*/ + + 0, /*tp_iter*/ + 0, /*tp_iternext*/ + + /* Attribute descriptor and subclassing stuff */ + + 0, /*tp_methods*/ + 0, /*tp_members*/ + diagnosticsObject_getsets, /*tp_getset*/ + 0, /*tp_base*/ + 0, /*tp_dict*/ + + 0, /*tp_descr_get*/ + 0, /*tp_descr_set*/ + 0, /*tp_dictoffset*/ + + diagnostics_init, /*tp_init*/ + 0, /*tp_alloc will be set to PyType_GenericAlloc in module init*/ + diagnostics_new, /*tp_new*/ + (freefunc)diagnostics_del, /*tp_free Low-level free-memory routine */ + 0, /*tp_is_gc For PyObject_IS_GC */ + 0, /*tp_bases*/ + 0, /*tp_mro method resolution order */ + 0, /*tp_cache*/ + 0, /*tp_subclasses*/ + 0 /*tp_weaklist*/ +}; diff --git a/psycopg/pqpath.c b/psycopg/pqpath.c index 641a5af6..62b8c157 100644 --- a/psycopg/pqpath.c +++ b/psycopg/pqpath.c @@ -1509,7 +1509,8 @@ pq_fetch(cursorObject *curs, int no_result) default: Dprintf("pq_fetch: uh-oh, something FAILED: pgconn = %p", curs->conn); pq_raise(curs->conn, curs, NULL); - IFCLEARPGRES(curs->pgres); + /* don't clear curs->pgres, because it contains detailed error + information */ ex = -1; break; } diff --git a/psycopg/psycopgmodule.c b/psycopg/psycopgmodule.c index b1b4979f..34985254 100644 --- a/psycopg/psycopgmodule.c +++ b/psycopg/psycopgmodule.c @@ -35,6 +35,7 @@ #include "psycopg/typecast.h" #include "psycopg/microprotocols.h" #include "psycopg/microprotocols_proto.h" +#include "psycopg/diagnostics.h" #include "psycopg/adapter_qstring.h" #include "psycopg/adapter_binary.h" @@ -500,6 +501,7 @@ psyco_errors_init(void) PyObject *base; PyObject *str = NULL; PyObject *descr = NULL; + PyObject *diag_property = NULL; int rv = -1; #if PY_VERSION_HEX >= 0x02050000 @@ -541,6 +543,12 @@ psyco_errors_init(void) PyObject_SetAttrString(Error, "pgcode", Py_None); PyObject_SetAttrString(Error, "cursor", Py_None); + if (!(diag_property = PyObject_CallFunctionObjArgs( + (PyObject *) &PyProperty_Type, &diagnosticsType, NULL))) { + goto exit; + } + PyObject_SetAttrString(Error, "diag", diag_property); + /* install __reduce_ex__ on Error to make all the subclasses picklable. * * Don't install it on Py 2.4: it is not used by the pickle @@ -560,6 +568,7 @@ psyco_errors_init(void) rv = 0; exit: + Py_XDECREF(diag_property); Py_XDECREF(descr); Py_XDECREF(str); Py_XDECREF(dict); @@ -882,6 +891,7 @@ INIT_MODULE(_psycopg)(void) Py_TYPE(&chunkType) = &PyType_Type; Py_TYPE(&NotifyType) = &PyType_Type; Py_TYPE(&XidType) = &PyType_Type; + Py_TYPE(&diagnosticsType) = &PyType_Type; if (PyType_Ready(&connectionType) == -1) goto exit; if (PyType_Ready(&cursorType) == -1) goto exit; @@ -898,6 +908,7 @@ INIT_MODULE(_psycopg)(void) if (PyType_Ready(&chunkType) == -1) goto exit; if (PyType_Ready(&NotifyType) == -1) goto exit; if (PyType_Ready(&XidType) == -1) goto exit; + if (PyType_Ready(&diagnosticsType) == -1) goto exit; #ifdef PSYCOPG_EXTENSIONS Py_TYPE(&lobjectType) = &PyType_Type; @@ -987,6 +998,7 @@ INIT_MODULE(_psycopg)(void) PyModule_AddObject(module, "ISQLQuote", (PyObject*)&isqlquoteType); PyModule_AddObject(module, "Notify", (PyObject*)&NotifyType); PyModule_AddObject(module, "Xid", (PyObject*)&XidType); + PyModule_AddObject(module, "Diagnostics", (PyObject*)&diagnosticsType); #ifdef PSYCOPG_EXTENSIONS PyModule_AddObject(module, "lobject", (PyObject*)&lobjectType); #endif @@ -1031,6 +1043,7 @@ INIT_MODULE(_psycopg)(void) pydatetimeType.tp_alloc = PyType_GenericAlloc; NotifyType.tp_alloc = PyType_GenericAlloc; XidType.tp_alloc = PyType_GenericAlloc; + diagnosticsType.tp_alloc = PyType_GenericAlloc; #ifdef PSYCOPG_EXTENSIONS lobjectType.tp_alloc = PyType_GenericAlloc; diff --git a/setup.py b/setup.py index 3791c149..e270689f 100644 --- a/setup.py +++ b/setup.py @@ -424,6 +424,7 @@ sources = [ 'connection_int.c', 'connection_type.c', 'cursor_int.c', 'cursor_type.c', + 'diagnostics_type.c', 'lobject_int.c', 'lobject_type.c', 'notify_type.c', 'xid_type.c', @@ -437,7 +438,7 @@ sources = [ depends = [ # headers 'config.h', 'pgtypes.h', 'psycopg.h', 'python.h', - 'connection.h', 'cursor.h', 'green.h', 'lobject.h', + 'connection.h', 'cursor.h', 'diagnostics.h', 'green.h', 'lobject.h', 'notify.h', 'pqpath.h', 'xid.h', 'adapter_asis.h', 'adapter_binary.h', 'adapter_datetime.h', diff --git a/tests/test_module.py b/tests/test_module.py index 4083c368..f6b3631b 100755 --- a/tests/test_module.py +++ b/tests/test_module.py @@ -22,7 +22,8 @@ # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public # License for more details. -from testutils import unittest, skip_before_python +from testutils import unittest, skip_before_python, skip_before_postgres + from testconfig import dsn import psycopg2 @@ -154,6 +155,24 @@ class ExceptionsTestCase(unittest.TestCase): self.assert_(e.pgerror) self.assert_(e.cursor is cur) + @skip_before_postgres(9, 3) + def test_diagnostics(self): + cur = self.conn.cursor() + cur.execute(""" + create temp table test_exc ( + data int constraint chk_eq1 check (data = 1) + )""") + try: + cur.execute("insert into test_exc values(2)") + except psycopg2.Error, exc: + e = exc + self.assertEqual(e.pgcode, '23514') + self.assertEqual(e.diag.schema_name[:7], "pg_temp") + self.assertEqual(e.diag.table_name, "test_exc") + self.assertEqual(e.diag.column_name, None) + self.assertEqual(e.diag.constraint_name, "chk_eq1") + self.assertEqual(e.diag.datatype_name, None) + @skip_before_python(2, 5) def test_pickle(self): import pickle From 9e79112e25d6931614266250b047c2ae2c6334f8 Mon Sep 17 00:00:00 2001 From: Daniele Varrazzo Date: Sun, 17 Mar 2013 23:57:27 +0000 Subject: [PATCH 2/9] Expose the Diagnostics object in the extensions module --- lib/extensions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/extensions.py b/lib/extensions.py index ca467d5a..8159a228 100644 --- a/lib/extensions.py +++ b/lib/extensions.py @@ -58,7 +58,7 @@ except ImportError: from psycopg2._psycopg import adapt, adapters, encodings, connection, cursor, lobject, Xid from psycopg2._psycopg import string_types, binary_types, new_type, new_array_type, register_type -from psycopg2._psycopg import ISQLQuote, Notify +from psycopg2._psycopg import ISQLQuote, Notify, Diagnostics from psycopg2._psycopg import QueryCanceledError, TransactionRollbackError From 42b063b562d544a017bfd468c8973d2cbed049c4 Mon Sep 17 00:00:00 2001 From: Daniele Varrazzo Date: Sun, 17 Mar 2013 23:31:53 +0000 Subject: [PATCH 3/9] Added all supported properties to the Diagnostic object --- psycopg/diagnostics_type.c | 24 ++++++++++++++++++++++++ tests/test_module.py | 32 +++++++++++++++++++++++++++++++- 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/psycopg/diagnostics_type.c b/psycopg/diagnostics_type.c index cce4b304..fb0244ca 100644 --- a/psycopg/diagnostics_type.c +++ b/psycopg/diagnostics_type.c @@ -82,6 +82,24 @@ exit: /* object calculated member list */ static struct PyGetSetDef diagnosticsObject_getsets[] = { + { "severity", (getter)psyco_diagnostics_get_field, NULL, + NULL, (void*) PG_DIAG_SEVERITY }, + { "sqlstate", (getter)psyco_diagnostics_get_field, NULL, + NULL, (void*) PG_DIAG_SQLSTATE }, + { "message_primary", (getter)psyco_diagnostics_get_field, NULL, + NULL, (void*) PG_DIAG_MESSAGE_PRIMARY }, + { "message_detail", (getter)psyco_diagnostics_get_field, NULL, + NULL, (void*) PG_DIAG_MESSAGE_DETAIL }, + { "message_hint", (getter)psyco_diagnostics_get_field, NULL, + NULL, (void*) PG_DIAG_MESSAGE_HINT }, + { "statement_position", (getter)psyco_diagnostics_get_field, NULL, + NULL, (void*) PG_DIAG_STATEMENT_POSITION }, + { "internal_position", (getter)psyco_diagnostics_get_field, NULL, + NULL, (void*) PG_DIAG_INTERNAL_POSITION }, + { "internal_query", (getter)psyco_diagnostics_get_field, NULL, + NULL, (void*) PG_DIAG_INTERNAL_QUERY }, + { "context", (getter)psyco_diagnostics_get_field, NULL, + NULL, (void*) PG_DIAG_CONTEXT }, { "schema_name", (getter)psyco_diagnostics_get_field, NULL, NULL, (void*) PG_DIAG_SCHEMA_NAME }, { "table_name", (getter)psyco_diagnostics_get_field, NULL, @@ -92,6 +110,12 @@ static struct PyGetSetDef diagnosticsObject_getsets[] = { NULL, (void*) PG_DIAG_DATATYPE_NAME }, { "constraint_name", (getter)psyco_diagnostics_get_field, NULL, NULL, (void*) PG_DIAG_CONSTRAINT_NAME }, + { "source_file", (getter)psyco_diagnostics_get_field, NULL, + NULL, (void*) PG_DIAG_SOURCE_FILE }, + { "source_line", (getter)psyco_diagnostics_get_field, NULL, + NULL, (void*) PG_DIAG_SOURCE_LINE }, + { "source_function", (getter)psyco_diagnostics_get_field, NULL, + NULL, (void*) PG_DIAG_SOURCE_FUNCTION }, {NULL} }; diff --git a/tests/test_module.py b/tests/test_module.py index f6b3631b..e92e3b97 100755 --- a/tests/test_module.py +++ b/tests/test_module.py @@ -155,8 +155,38 @@ class ExceptionsTestCase(unittest.TestCase): self.assert_(e.pgerror) self.assert_(e.cursor is cur) + def test_diagnostics_attributes(self): + cur = self.conn.cursor() + try: + cur.execute("select * from nonexist") + except psycopg2.Error, exc: + e = exc + + diag = e.diag + self.assert_(isinstance(diag, psycopg2.extensions.Diagnostics)) + for attr in [ + 'column_name', 'constraint_name', 'context', 'datatype_name', + 'internal_position', 'internal_query', 'message_detail', + 'message_hint', 'message_primary', 'schema_name', 'severity', + 'source_file', 'source_function', 'source_line', 'sqlstate', + 'statement_position', 'table_name', ]: + v = getattr(diag, attr) + if v is not None: + self.assert_(isinstance(v, str)) + + def test_diagnostics_values(self): + cur = self.conn.cursor() + try: + cur.execute("select * from nonexist") + except psycopg2.Error, exc: + e = exc + + self.assertEqual(e.diag.sqlstate, '42P01') + self.assertEqual(e.diag.severity, 'ERROR') + self.assertEqual(e.diag.statement_position, '15') + @skip_before_postgres(9, 3) - def test_diagnostics(self): + def test_9_3_diagnostics(self): cur = self.conn.cursor() cur.execute(""" create temp table test_exc ( From 660386929f26a75f47264cad36e81305e4cc2a83 Mon Sep 17 00:00:00 2001 From: Daniele Varrazzo Date: Mon, 18 Mar 2013 00:24:46 +0000 Subject: [PATCH 4/9] Added test to verify Diagnostics reference disposal --- tests/test_module.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/tests/test_module.py b/tests/test_module.py index e92e3b97..168ddaa7 100755 --- a/tests/test_module.py +++ b/tests/test_module.py @@ -185,6 +185,31 @@ class ExceptionsTestCase(unittest.TestCase): self.assertEqual(e.diag.severity, 'ERROR') self.assertEqual(e.diag.statement_position, '15') + def test_diagnostics_life(self): + import gc + from weakref import ref + + def tmp(): + cur = self.conn.cursor() + try: + cur.execute("select * from nonexist") + except psycopg2.Error, exc: + return cur, exc + + cur, e = tmp() + diag = e.diag + w = ref(cur) + + del e, cur + gc.collect() + assert(w() is not None) + + self.assertEqual(diag.sqlstate, '42P01') + + del diag + gc.collect() + assert(w() is None) + @skip_before_postgres(9, 3) def test_9_3_diagnostics(self): cur = self.conn.cursor() From 70b756b8c7c221218cb2743fcf7607a261aee055 Mon Sep 17 00:00:00 2001 From: Daniele Varrazzo Date: Mon, 18 Mar 2013 00:31:25 +0000 Subject: [PATCH 5/9] Added test to verify Diagnostics works after copy errors --- tests/test_module.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/test_module.py b/tests/test_module.py index 168ddaa7..1acdb84e 100755 --- a/tests/test_module.py +++ b/tests/test_module.py @@ -210,6 +210,17 @@ class ExceptionsTestCase(unittest.TestCase): gc.collect() assert(w() is None) + def test_diagnostics_copy(self): + from StringIO import StringIO + f = StringIO() + cur = self.conn.cursor() + try: + cur.copy_to(f, 'nonexist') + except psycopg2.Error, exc: + diag = exc.diag + + self.assertEqual(diag.sqlstate, '42P01') + @skip_before_postgres(9, 3) def test_9_3_diagnostics(self): cur = self.conn.cursor() From 819a551d01045a0634ce3433e79242ceb4489954 Mon Sep 17 00:00:00 2001 From: Daniele Varrazzo Date: Mon, 18 Mar 2013 00:38:28 +0000 Subject: [PATCH 6/9] Decode Diagnostics result on Python 3 --- psycopg/diagnostics_type.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/psycopg/diagnostics_type.c b/psycopg/diagnostics_type.c index fb0244ca..18223179 100644 --- a/psycopg/diagnostics_type.c +++ b/psycopg/diagnostics_type.c @@ -67,8 +67,7 @@ psyco_diagnostics_get_field(diagnosticsObject *self, void *closure) errortext = PQresultErrorField( ((cursorObject *)curs)->pgres, (Py_intptr_t) closure); if (errortext) { - // FIXME: does this need to use conn_text_from_chars()? - rv = PyString_FromString(errortext); + rv = conn_text_from_chars(((cursorObject *)curs)->conn, errortext); } exit: if (!rv) { From 678f0dc9493bdac4ba6312c5da26e74a7e6be792 Mon Sep 17 00:00:00 2001 From: Daniele Varrazzo Date: Mon, 18 Mar 2013 01:33:23 +0000 Subject: [PATCH 7/9] Added documentation about the Diagnostics object --- doc/src/extensions.rst | 31 +++++++++++++++++++++++++++++++ doc/src/module.rst | 21 +++++++++++++++++++-- psycopg/diagnostics_type.c | 15 +++++++++++++-- 3 files changed, 63 insertions(+), 4 deletions(-) diff --git a/doc/src/extensions.rst b/doc/src/extensions.rst index b0a68ce4..c810ae81 100644 --- a/doc/src/extensions.rst +++ b/doc/src/extensions.rst @@ -139,6 +139,37 @@ functionalities defined by the |DBAPI|_. .. automethod:: from_string(s) +.. autoclass:: Diagnostics(exception) + + .. versionadded:: 2.5 + + The attributes currently available are: + + .. attribute:: + column_name + constraint_name + context + datatype_name + internal_position + internal_query + message_detail + message_hint + message_primary + schema_name + severity + source_file + source_function + source_line + sqlstate + statement_position + table_name + + A string with the error field if available; `!None` if not available. + The attribute value is available only if the error sent by the server + includes the specified field and should remain available until the + cursor that generated the exception executes another query. + + .. autofunction:: set_wait_callback(f) .. versionadded:: 2.2.0 diff --git a/doc/src/module.rst b/doc/src/module.rst index b7436762..a998015a 100644 --- a/doc/src/module.rst +++ b/doc/src/module.rst @@ -157,10 +157,27 @@ available through the following exceptions: The cursor the exception was raised from; `None` if not applicable. + .. attribute:: diag + + A `~psycopg2.extensions.Diagnostics` object containing further + information about the error. :: + + >>> try: + ... cur.execute("SELECT * FROM barf") + ... except Exception, e: + ... pass + + >>> e.diag.severity + 'ERROR' + >>> e.diag.message_primary + 'relation "barf" does not exist' + + .. versionadded:: 2.5 + .. extension:: - The `~Error.pgerror`, `~Error.pgcode`, and `~Error.cursor` attributes - are Psycopg extensions. + The `~Error.pgerror`, `~Error.pgcode`, `~Error.cursor`, and + `~Error.diag` attributes are Psycopg extensions. .. exception:: InterfaceError diff --git a/psycopg/diagnostics_type.c b/psycopg/diagnostics_type.c index 18223179..45620b39 100644 --- a/psycopg/diagnostics_type.c +++ b/psycopg/diagnostics_type.c @@ -165,8 +165,19 @@ diagnostics_del(PyObject* self) /* object type */ -#define diagnosticsType_doc \ -"Details from a database error report." +static const char diagnosticsType_doc[] = + "Details from a database error report.\n\n" + "The object is returned by the `~psycopg2.Error.diag` attribute of the\n" + "`!Error` object.\n" + "All the information available from the |PQresultErrorField|_ function\n" + "are exposed as attributes by the object, e.g. the `!severity` attribute\n" + "returns the `!PG_DIAG_SEVERITY` code. " + "Please refer to the `PostgreSQL documentation`__ for the meaning of all" + " the attributes.\n\n" + ".. |PQresultErrorField| replace:: `!PQresultErrorField()`\n" + ".. _PQresultErrorField: http://www.postgresql.org/docs/current/static/" + "libpq-exec.html#LIBPQ-PQRESULTERRORFIELD\n" + ".. __: PQresultErrorField_\n"; PyTypeObject diagnosticsType = { PyVarObject_HEAD_INIT(NULL, 0) From 1cf98250354a50436d4bd82fe3a95a1cb32808ba Mon Sep 17 00:00:00 2001 From: Daniele Varrazzo Date: Mon, 18 Mar 2013 01:58:27 +0000 Subject: [PATCH 8/9] Diagnostics is more GC friendly --- psycopg/diagnostics_type.c | 67 ++++++++++++++++++-------------------- 1 file changed, 32 insertions(+), 35 deletions(-) diff --git a/psycopg/diagnostics_type.c b/psycopg/diagnostics_type.c index 45620b39..30ef2d46 100644 --- a/psycopg/diagnostics_type.c +++ b/psycopg/diagnostics_type.c @@ -120,46 +120,43 @@ static struct PyGetSetDef diagnosticsObject_getsets[] = { /* initialization and finalization methods */ -static int -diagnostics_setup(diagnosticsObject *self, PyObject *err) -{ - self->err = err; - Py_INCREF(err); - - return 0; -} - -static void -diagnostics_dealloc(PyObject* obj) -{ - diagnosticsObject *self = (diagnosticsObject *)obj; - - Py_XDECREF(self->err); - - Py_TYPE(obj)->tp_free(obj); -} - -static int -diagnostics_init(PyObject *obj, PyObject *args, PyObject *kwds) -{ - PyObject *err = NULL; - - if (!PyArg_ParseTuple(args, "O", &err)) - return -1; - - return diagnostics_setup((diagnosticsObject *)obj, err); -} - static PyObject * diagnostics_new(PyTypeObject *type, PyObject *args, PyObject *kwds) { return type->tp_alloc(type, 0); } +static int +diagnostics_init(diagnosticsObject *self, PyObject *args, PyObject *kwds) +{ + PyObject *err = NULL; + + if (!PyArg_ParseTuple(args, "O", &err)) + return -1; + + Py_INCREF(err); + self->err = err; + return 0; +} + +static int +diagnostics_traverse(diagnosticsObject *self, visitproc visit, void *arg) +{ + Py_VISIT(self->err); + return 0; +} + +static void +diagnostics_dealloc(diagnosticsObject* self) +{ + Py_CLEAR(self->err); + Py_TYPE(self)->tp_free((PyObject *)self); +} + static void diagnostics_del(PyObject* self) { - PyObject_Del(self); + PyObject_GC_Del(self); } @@ -184,7 +181,7 @@ PyTypeObject diagnosticsType = { "psycopg2._psycopg.Diagnostics", sizeof(diagnosticsObject), 0, - diagnostics_dealloc, /*tp_dealloc*/ + (destructor)diagnostics_dealloc, /*tp_dealloc*/ 0, /*tp_print*/ 0, /*tp_getattr*/ 0, /*tp_setattr*/ @@ -201,10 +198,10 @@ PyTypeObject diagnosticsType = { 0, /*tp_setattro*/ 0, /*tp_as_buffer*/ - Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, /*tp_flags*/ + Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE|Py_TPFLAGS_HAVE_GC, /*tp_flags*/ diagnosticsType_doc, /*tp_doc*/ - 0, /*tp_traverse*/ + (traverseproc)diagnostics_traverse, /*tp_traverse*/ 0, /*tp_clear*/ 0, /*tp_richcompare*/ @@ -225,7 +222,7 @@ PyTypeObject diagnosticsType = { 0, /*tp_descr_set*/ 0, /*tp_dictoffset*/ - diagnostics_init, /*tp_init*/ + (initproc)diagnostics_init, /*tp_init*/ 0, /*tp_alloc will be set to PyType_GenericAlloc in module init*/ diagnostics_new, /*tp_new*/ (freefunc)diagnostics_del, /*tp_free Low-level free-memory routine */ From 49c3569919db5ec0876be5e8b9865af2b54049e2 Mon Sep 17 00:00:00 2001 From: Daniele Varrazzo Date: Mon, 18 Mar 2013 02:04:21 +0000 Subject: [PATCH 9/9] Diagnostics added to the NEWS file --- NEWS | 2 ++ 1 file changed, 2 insertions(+) diff --git a/NEWS b/NEWS index c128ceca..9eed8ed3 100644 --- a/NEWS +++ b/NEWS @@ -6,6 +6,8 @@ What's new in psycopg 2.5 - Added support for Python 3.3. - 'connection' and 'cursor' objects can be used in 'with' statements as context managers as specified by recent DBAPI extension. + - Added Diagnostics object to get extended info from a database error. + Many thanks to Matthew Woodcraft for the implementation (ticket #149). - Added support for backward scrollable cursors. Thanks to Jon Nelson for the initial patch (ticket #108). - Added a simple way to customize casting of composite types into Python