Work-in-progress support for retrieving PG_DIAG result error fields.

This commit is contained in:
Matthew Woodcraft 2013-03-17 16:41:15 +00:00
parent e8db9954d1
commit c75a3bbab4
6 changed files with 279 additions and 3 deletions

38
psycopg/diagnostics.h Normal file
View File

@ -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 */

204
psycopg/diagnostics_type.c Normal file
View File

@ -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*/
};

View File

@ -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;
}

View File

@ -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;

View File

@ -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',

View File

@ -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