mirror of
https://github.com/psycopg/psycopg2.git
synced 2024-11-25 18:33:44 +03:00
Work-in-progress support for retrieving PG_DIAG result error fields.
This commit is contained in:
parent
e8db9954d1
commit
c75a3bbab4
38
psycopg/diagnostics.h
Normal file
38
psycopg/diagnostics.h
Normal 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
204
psycopg/diagnostics_type.c
Normal 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*/
|
||||
};
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
3
setup.py
3
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',
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue
Block a user