diff options
-rw-r--r-- | psycopg/diagnostics.h | 38 | ||||
-rw-r--r-- | psycopg/diagnostics_type.c | 204 | ||||
-rw-r--r-- | psycopg/pqpath.c | 3 | ||||
-rw-r--r-- | psycopg/psycopgmodule.c | 13 | ||||
-rw-r--r-- | setup.py | 3 | ||||
-rwxr-xr-x | tests/test_module.py | 21 |
6 files changed, 279 insertions, 3 deletions
diff --git a/psycopg/diagnostics.h b/psycopg/diagnostics.h new file mode 100644 index 0000000..adaf758 --- /dev/null +++ b/psycopg/diagnostics.h @@ -0,0 +1,38 @@ +/* diagnostics.c - definition for the psycopg Diagnostics type + * + * Copyright (C) 2013 Matthew Woodcraft <matthew@woodcraft.me.uk> + * + * 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 0000000..cce4b30 --- /dev/null +++ b/psycopg/diagnostics_type.c @@ -0,0 +1,204 @@ +/* diagnostics.c - present information from libpq error responses + * + * Copyright (C) 2013 Matthew Woodcraft <matthew@woodcraft.me.uk> + * + * 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 641a5af..62b8c15 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 b1b4979..3498525 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; @@ -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 4083c36..f6b3631 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 |