mirror of
				https://github.com/psycopg/psycopg2.git
				synced 2025-11-04 09:47:30 +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