summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--psycopg/diagnostics.h38
-rw-r--r--psycopg/diagnostics_type.c204
-rw-r--r--psycopg/pqpath.c3
-rw-r--r--psycopg/psycopgmodule.c13
-rw-r--r--setup.py3
-rwxr-xr-xtests/test_module.py21
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;
diff --git a/setup.py b/setup.py
index 3791c14..e270689 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 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