Merge branch 'diagnostics' into devel

This commit is contained in:
Daniele Varrazzo 2013-03-20 01:47:14 +00:00
commit 73949cd1b8
16 changed files with 477 additions and 191 deletions

View File

@ -165,9 +165,9 @@ functionalities defined by the |DBAPI|_.
table_name table_name
A string with the error field if available; `!None` if not available. A string with the error field if available; `!None` if not available.
The attribute value is available only if the error sent by the server The attribute value is available only if the error sent by the server:
includes the specified field and should remain available until the not all the fields are available for all the errors and for all the
cursor that generated the exception executes another query. server versions.
.. autofunction:: set_wait_callback(f) .. autofunction:: set_wait_callback(f)

View File

@ -118,7 +118,7 @@ _mogrify(PyObject *var, PyObject *fmt, cursorObject *curs, PyObject **new)
if (kind == 2) { if (kind == 2) {
Py_XDECREF(n); Py_XDECREF(n);
psyco_set_error(ProgrammingError, curs, psyco_set_error(ProgrammingError, curs,
"argument formats can't be mixed", NULL, NULL); "argument formats can't be mixed");
return -1; return -1;
} }
kind = 1; kind = 1;
@ -190,7 +190,7 @@ _mogrify(PyObject *var, PyObject *fmt, cursorObject *curs, PyObject **new)
/* we found %( but not a ) */ /* we found %( but not a ) */
Py_XDECREF(n); Py_XDECREF(n);
psyco_set_error(ProgrammingError, curs, psyco_set_error(ProgrammingError, curs,
"incomplete placeholder: '%(' without ')'", NULL, NULL); "incomplete placeholder: '%(' without ')'");
return -1; return -1;
} }
c = d + 1; /* after the ) */ c = d + 1; /* after the ) */
@ -205,7 +205,7 @@ _mogrify(PyObject *var, PyObject *fmt, cursorObject *curs, PyObject **new)
if (kind == 1) { if (kind == 1) {
Py_XDECREF(n); Py_XDECREF(n);
psyco_set_error(ProgrammingError, curs, psyco_set_error(ProgrammingError, curs,
"argument formats can't be mixed", NULL, NULL); "argument formats can't be mixed");
return -1; return -1;
} }
kind = 2; kind = 2;
@ -267,7 +267,7 @@ static PyObject *_psyco_curs_validate_sql_basic(
if (!sql || !PyObject_IsTrue(sql)) { if (!sql || !PyObject_IsTrue(sql)) {
psyco_set_error(ProgrammingError, self, psyco_set_error(ProgrammingError, self,
"can't execute an empty query", NULL, NULL); "can't execute an empty query");
goto fail; goto fail;
} }
@ -338,8 +338,7 @@ _psyco_curs_merge_query_args(cursorObject *self,
if (!strcmp(s, "not enough arguments for format string") if (!strcmp(s, "not enough arguments for format string")
|| !strcmp(s, "not all arguments converted")) { || !strcmp(s, "not all arguments converted")) {
Dprintf("psyco_curs_execute: -> got a match"); Dprintf("psyco_curs_execute: -> got a match");
psyco_set_error(ProgrammingError, self, psyco_set_error(ProgrammingError, self, s);
s, NULL, NULL);
pe = 1; pe = 1;
} }
@ -482,13 +481,12 @@ psyco_curs_execute(cursorObject *self, PyObject *args, PyObject *kwargs)
if (self->name != NULL) { if (self->name != NULL) {
if (self->query != Py_None) { if (self->query != Py_None) {
psyco_set_error(ProgrammingError, self, psyco_set_error(ProgrammingError, self,
"can't call .execute() on named cursors more than once", "can't call .execute() on named cursors more than once");
NULL, NULL);
return NULL; return NULL;
} }
if (self->conn->autocommit) { if (self->conn->autocommit) {
psyco_set_error(ProgrammingError, self, psyco_set_error(ProgrammingError, self,
"can't use a named cursor outside of transactions", NULL, NULL); "can't use a named cursor outside of transactions");
return NULL; return NULL;
} }
EXC_IF_NO_MARK(self); EXC_IF_NO_MARK(self);
@ -533,7 +531,7 @@ psyco_curs_executemany(cursorObject *self, PyObject *args, PyObject *kwargs)
if (self->name != NULL) { if (self->name != NULL) {
psyco_set_error(ProgrammingError, self, psyco_set_error(ProgrammingError, self,
"can't call .executemany() on named cursors", NULL, NULL); "can't call .executemany() on named cursors");
return NULL; return NULL;
} }
@ -1038,7 +1036,7 @@ psyco_curs_callproc(cursorObject *self, PyObject *args)
if (self->name != NULL) { if (self->name != NULL) {
psyco_set_error(ProgrammingError, self, psyco_set_error(ProgrammingError, self,
"can't call .callproc() on named cursors", NULL, NULL); "can't call .callproc() on named cursors");
goto exit; goto exit;
} }
@ -1165,13 +1163,13 @@ psyco_curs_scroll(cursorObject *self, PyObject *args, PyObject *kwargs)
newpos = value; newpos = value;
} else { } else {
psyco_set_error(ProgrammingError, self, psyco_set_error(ProgrammingError, self,
"scroll mode must be 'relative' or 'absolute'", NULL, NULL); "scroll mode must be 'relative' or 'absolute'");
return NULL; return NULL;
} }
if (newpos < 0 || newpos >= self->rowcount ) { if (newpos < 0 || newpos >= self->rowcount ) {
psyco_set_error(ProgrammingError, self, psyco_set_error(ProgrammingError, self,
"scroll destination out of bounds", NULL, NULL); "scroll destination out of bounds");
return NULL; return NULL;
} }

View File

@ -26,12 +26,14 @@
#ifndef PSYCOPG_DIAGNOSTICS_H #ifndef PSYCOPG_DIAGNOSTICS_H
#define PSYCOPG_DIAGNOSTICS_H 1 #define PSYCOPG_DIAGNOSTICS_H 1
#include "psycopg/error.h"
extern HIDDEN PyTypeObject diagnosticsType; extern HIDDEN PyTypeObject diagnosticsType;
typedef struct { typedef struct {
PyObject_HEAD PyObject_HEAD
PyObject *err; /* exception to retrieve the diagnostics from */ errorObject *err; /* exception to retrieve the diagnostics from */
} diagnosticsObject; } diagnosticsObject;

View File

@ -27,7 +27,7 @@
#include "psycopg/psycopg.h" #include "psycopg/psycopg.h"
#include "psycopg/diagnostics.h" #include "psycopg/diagnostics.h"
#include "psycopg/cursor.h" #include "psycopg/error.h"
/* These are new in PostgreSQL 9.3. Defining them here so that psycopg2 can /* 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. */ * use them with a 9.3+ server even if compiled against pre-9.3 headers. */
@ -55,27 +55,15 @@
static PyObject * static PyObject *
psyco_diagnostics_get_field(diagnosticsObject *self, void *closure) psyco_diagnostics_get_field(diagnosticsObject *self, void *closure)
{ {
// closure contains the field code.
PyObject *rv = NULL;
PyObject *curs = NULL;
const char *errortext; const char *errortext;
if (!(curs = PyObject_GetAttrString(self->err, "cursor")) ||
!PyObject_TypeCheck(curs, &cursorType) || if (!self->err->pgres) {
((cursorObject *)curs)->pgres == NULL) { Py_INCREF(Py_None);
goto exit; return Py_None;
} }
errortext = PQresultErrorField(
((cursorObject *)curs)->pgres, (Py_intptr_t) closure); errortext = PQresultErrorField(self->err->pgres, (Py_intptr_t) closure);
if (errortext) { return error_text_from_chars(self->err, errortext);
rv = conn_text_from_chars(((cursorObject *)curs)->conn, errortext);
}
exit:
if (!rv) {
rv = Py_None;
Py_INCREF(rv);
}
Py_XDECREF(curs);
return rv;
} }
@ -134,8 +122,14 @@ diagnostics_init(diagnosticsObject *self, PyObject *args, PyObject *kwds)
if (!PyArg_ParseTuple(args, "O", &err)) if (!PyArg_ParseTuple(args, "O", &err))
return -1; return -1;
if (!PyObject_TypeCheck(err, &errorType)) {
PyErr_SetString(PyExc_TypeError,
"The argument must be a psycopg2.Error");
return -1;
}
Py_INCREF(err); Py_INCREF(err);
self->err = err; self->err = (errorObject *)err;
return 0; return 0;
} }

43
psycopg/error.h Normal file
View File

@ -0,0 +1,43 @@
/* error.h - definition for the psycopg base Error type
*
* Copyright (C) 2013 Daniele Varrazzo <daniele.varrazzo@gmail.com>
*
* 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_ERROR_H
#define PSYCOPG_ERROR_H 1
extern HIDDEN PyTypeObject errorType;
typedef struct {
PyBaseExceptionObject exc;
PyObject *pgerror;
PyObject *pgcode;
cursorObject *cursor;
char *codec;
PGresult *pgres;
} errorObject;
HIDDEN PyObject *error_text_from_chars(errorObject *self, const char *str);
#endif /* PSYCOPG_ERROR_H */

291
psycopg/error_type.c Normal file
View File

@ -0,0 +1,291 @@
/* error_type.c - python interface to the Error objects
*
* Copyright (C) 2013 Daniele Varrazzo <daniele.varrazzo@gmail.com>
*
* 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/error.h"
#include "psycopg/diagnostics.h"
#include "psycopg/pqpath.h"
PyObject *
error_text_from_chars(errorObject *self, const char *str)
{
if (str == NULL) {
Py_INCREF(Py_None);
return (Py_None);
}
#if PY_MAJOR_VERSION < 3
return PyString_FromString(str);
#else
return PyUnicode_Decode(str, strlen(str),
self->codec ? self->codec : "ascii", "replace");
#endif
}
static const char pgerror_doc[] =
"The error message returned by the backend, if available, else None";
static const char pgcode_doc[] =
"The error code returned by the backend, if available, else None";
static const char cursor_doc[] =
"The cursor that raised the exception, if available, else None";
static const char diag_doc[] =
"A Diagnostics object to get further information about the error";
static PyMemberDef error_members[] = {
{ "pgerror", T_OBJECT, offsetof(errorObject, pgerror),
READONLY, (char *)pgerror_doc },
{ "pgcode", T_OBJECT, offsetof(errorObject, pgcode),
READONLY, (char *)pgcode_doc },
{ "cursor", T_OBJECT, offsetof(errorObject, cursor),
READONLY, (char *)cursor_doc },
{ NULL }
};
static PyObject *
error_new(PyTypeObject *type, PyObject *args, PyObject *kwargs)
{
return ((PyTypeObject *)PyExc_StandardError)->tp_new(
type, args, kwargs);
}
static int
error_init(errorObject *self, PyObject *args, PyObject *kwargs)
{
if (((PyTypeObject *)PyExc_StandardError)->tp_init(
(PyObject *)self, args, kwargs) < 0) {
return -1;
}
return 0;
}
static int
error_traverse(errorObject *self, visitproc visit, void *arg)
{
Py_VISIT(self->cursor);
return ((PyTypeObject *)PyExc_StandardError)->tp_traverse(
(PyObject *)self, visit, arg);
}
static int
error_clear(errorObject *self)
{
Py_CLEAR(self->pgerror);
Py_CLEAR(self->pgcode);
Py_CLEAR(self->cursor);
PyMem_Free(self->codec);
IFCLEARPGRES(self->pgres);
return ((PyTypeObject *)PyExc_StandardError)->tp_clear((PyObject *)self);
}
static void
error_dealloc(errorObject *self)
{
error_clear(self);
return Py_TYPE(self)->tp_free((PyObject *)self);
}
static PyObject *
error_get_diag(errorObject *self, void *closure)
{
return PyObject_CallFunctionObjArgs(
(PyObject *)&diagnosticsType, (PyObject *)self, NULL);
}
static struct PyGetSetDef error_getsets[] = {
{ "diag", (getter)error_get_diag, NULL, (char *)diag_doc },
{ NULL }
};
#if PY_VERSION_HEX >= 0x02050000
/* Error.__reduce__
*
* The method is required to make exceptions picklable: set the cursor
* attribute to None. Only working from Py 2.5: previous versions
* would require implementing __getstate__, and as of 2012 it's a little
* bit too late to care. */
static PyObject *
psyco_error_reduce(errorObject *self)
{
PyObject *meth = NULL;
PyObject *tuple = NULL;
PyObject *dict = NULL;
PyObject *rv = NULL;
if (!(meth = PyObject_GetAttrString(PyExc_StandardError, "__reduce__"))) {
goto error;
}
if (!(tuple = PyObject_CallFunctionObjArgs(meth, self, NULL))) {
goto error;
}
/* tuple is (type, args): convert it to (type, args, dict)
* with our extra items in the dict.
*
* If these checks fail, we can still return a valid object. Pickle
* will likely fail downstream, but there's nothing else we can do here */
if (!PyTuple_Check(tuple)) { goto exit; }
if (2 != PyTuple_GET_SIZE(tuple)) { goto exit; }
if (!(dict = PyDict_New())) { goto error; }
if (0 != PyDict_SetItemString(dict, "pgerror", self->pgerror)) { goto error; }
if (0 != PyDict_SetItemString(dict, "pgcode", self->pgcode)) { goto error; }
{
PyObject *newtuple;
if (!(newtuple = PyTuple_Pack(3,
PyTuple_GET_ITEM(tuple, 0),
PyTuple_GET_ITEM(tuple, 1),
dict))) {
goto error;
}
Py_DECREF(tuple);
tuple = newtuple;
}
exit:
rv = tuple;
tuple = NULL;
error:
Py_XDECREF(dict);
Py_XDECREF(tuple);
Py_XDECREF(meth);
return rv;
}
PyObject *
psyco_error_setstate(errorObject *self, PyObject *state)
{
PyObject *rv = NULL;
/* we don't call the StandartError's setstate as it would try to load the
* dict content as attributes */
if (state == Py_None) {
goto exit;
}
if (!PyDict_Check(state)) {
PyErr_SetString(PyExc_TypeError, "state is not a dictionary");
goto error;
}
/* load the dict content in the structure */
Py_CLEAR(self->pgerror);
self->pgerror = PyDict_GetItemString(state, "pgerror");
Py_XINCREF(self->pgerror);
Py_CLEAR(self->pgcode);
self->pgcode = PyDict_GetItemString(state, "pgcode");
Py_XINCREF(self->pgcode);
Py_CLEAR(self->cursor);
/* We never expect a cursor in the state as it's not picklable.
* at most there could be a None here, coming from Psycopg < 2.5 */
exit:
rv = Py_None;
Py_INCREF(rv);
error:
return rv;
}
#endif /* PY_VERSION_HEX >= 0x02050000 */
static PyMethodDef error_methods[] = {
#if PY_VERSION_HEX >= 0x02050000
/* Make Error and all its subclasses picklable.
*
* TODO: this comment applied to the __reduce_ex__ implementation: now
* pickling may work on Py 2.4 too... but it's 2013 now.
*
* Don't do it it on Py 2.4: [__reduce_ex__] it is not used by the pickle
* protocol, and if called manually fails in an unsettling way,
* probably because the exceptions were old-style classes. */
{"__reduce__", (PyCFunction)psyco_error_reduce, METH_NOARGS },
{"__setstate__", (PyCFunction)psyco_error_setstate, METH_O },
#endif
{NULL}
};
PyTypeObject errorType = {
PyVarObject_HEAD_INIT(NULL, 0)
"psycopg2.Error",
sizeof(errorObject),
0,
(destructor)error_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|Py_TPFLAGS_HAVE_GC, /*tp_flags*/
Error_doc, /*tp_doc*/
(traverseproc)error_traverse, /*tp_traverse*/
(inquiry)error_clear, /*tp_clear*/
0, /*tp_richcompare*/
0, /*tp_weaklistoffset*/
0, /*tp_iter*/
0, /*tp_iternext*/
error_methods, /*tp_methods*/
error_members, /*tp_members*/
error_getsets, /*tp_getset*/
0, /*tp_base Will be set to StandardError in module init */
0, /*tp_dict*/
0, /*tp_descr_get*/
0, /*tp_descr_set*/
0, /*tp_dictoffset*/
(initproc)error_init, /*tp_init*/
0, /*tp_alloc will be set to PyType_GenericAlloc in module init*/
error_new, /*tp_new*/
0, /*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

@ -78,13 +78,13 @@ RAISES_NEG HIDDEN int lobject_close(lobjectObject *self);
#define EXC_IF_LOBJ_LEVEL0(self) \ #define EXC_IF_LOBJ_LEVEL0(self) \
if (self->conn->autocommit) { \ if (self->conn->autocommit) { \
psyco_set_error(ProgrammingError, NULL, \ psyco_set_error(ProgrammingError, NULL, \
"can't use a lobject outside of transactions", NULL, NULL); \ "can't use a lobject outside of transactions"); \
return NULL; \ return NULL; \
} }
#define EXC_IF_LOBJ_UNMARKED(self) \ #define EXC_IF_LOBJ_UNMARKED(self) \
if (self->conn->mark != self->mark) { \ if (self->conn->mark != self->mark) { \
psyco_set_error(ProgrammingError, NULL, \ psyco_set_error(ProgrammingError, NULL, \
"lobject isn't valid anymore", NULL, NULL); \ "lobject isn't valid anymore"); \
return NULL; \ return NULL; \
} }

View File

@ -333,7 +333,7 @@ lobject_setup(lobjectObject *self, connectionObject *conn,
if (conn->autocommit) { if (conn->autocommit) {
psyco_set_error(ProgrammingError, NULL, psyco_set_error(ProgrammingError, NULL,
"can't use a lobject outside of transactions", NULL, NULL); "can't use a lobject outside of transactions");
return -1; return -1;
} }

View File

@ -203,7 +203,7 @@ microprotocols_adapt(PyObject *obj, PyObject *proto, PyObject *alt)
/* else set the right exception and return NULL */ /* else set the right exception and return NULL */
PyOS_snprintf(buffer, 255, "can't adapt type '%s'", PyOS_snprintf(buffer, 255, "can't adapt type '%s'",
Py_TYPE(obj)->tp_name); Py_TYPE(obj)->tp_name);
psyco_set_error(ProgrammingError, NULL, buffer, NULL, NULL); psyco_set_error(ProgrammingError, NULL, buffer);
return NULL; return NULL;
} }

View File

@ -38,6 +38,7 @@
#include "psycopg/green.h" #include "psycopg/green.h"
#include "psycopg/typecast.h" #include "psycopg/typecast.h"
#include "psycopg/pgtypes.h" #include "psycopg/pgtypes.h"
#include "psycopg/error.h"
#include <string.h> #include <string.h>
@ -149,15 +150,20 @@ exception_from_sqlstate(const char *sqlstate)
/* pq_raise - raise a python exception of the right kind /* pq_raise - raise a python exception of the right kind
This function should be called while holding the GIL. */ This function should be called while holding the GIL.
The function passes the ownership of the pgres to the returned exception,
wherer the pgres was the explicit argument or taken from the cursor.
So, after calling it curs->pgres will be set to null */
RAISES static void RAISES static void
pq_raise(connectionObject *conn, cursorObject *curs, PGresult *pgres) pq_raise(connectionObject *conn, cursorObject *curs, PGresult **pgres)
{ {
PyObject *exc = NULL; PyObject *exc = NULL;
const char *err = NULL; const char *err = NULL;
const char *err2 = NULL; const char *err2 = NULL;
const char *code = NULL; const char *code = NULL;
PyObject *pyerr = NULL;
if (conn == NULL) { if (conn == NULL) {
PyErr_SetString(DatabaseError, PyErr_SetString(DatabaseError,
@ -171,13 +177,13 @@ pq_raise(connectionObject *conn, cursorObject *curs, PGresult *pgres)
conn->closed = 2; conn->closed = 2;
if (pgres == NULL && curs != NULL) if (pgres == NULL && curs != NULL)
pgres = curs->pgres; pgres = &curs->pgres;
if (pgres) { if (pgres && *pgres) {
err = PQresultErrorMessage(pgres); err = PQresultErrorMessage(*pgres);
if (err != NULL) { if (err != NULL) {
Dprintf("pq_raise: PQresultErrorMessage: err=%s", err); Dprintf("pq_raise: PQresultErrorMessage: err=%s", err);
code = PQresultErrorField(pgres, PG_DIAG_SQLSTATE); code = PQresultErrorField(*pgres, PG_DIAG_SQLSTATE);
} }
} }
if (err == NULL) { if (err == NULL) {
@ -210,7 +216,26 @@ pq_raise(connectionObject *conn, cursorObject *curs, PGresult *pgres)
err2 = strip_severity(err); err2 = strip_severity(err);
Dprintf("pq_raise: err2=%s", err2); Dprintf("pq_raise: err2=%s", err2);
psyco_set_error(exc, curs, err2, err, code); pyerr = psyco_set_error(exc, curs, err2);
if (pyerr && PyObject_TypeCheck(pyerr, &errorType)) {
errorObject *perr = (errorObject *)pyerr;
PyMem_Free(perr->codec);
psycopg_strdup(&perr->codec, conn->codec, 0);
Py_CLEAR(perr->pgerror);
perr->pgerror = error_text_from_chars(perr, err);
Py_CLEAR(perr->pgcode);
perr->pgcode = error_text_from_chars(perr, code);
IFCLEARPGRES(perr->pgres);
if (pgres && *pgres) {
perr->pgres = *pgres;
*pgres = NULL;
}
}
} }
/* pq_set_critical, pq_resolve_critical - manage critical errors /* pq_set_critical, pq_resolve_critical - manage critical errors
@ -388,14 +413,16 @@ pq_complete_error(connectionObject *conn, PGresult **pgres, char **error)
{ {
Dprintf("pq_complete_error: pgconn = %p, pgres = %p, error = %s", Dprintf("pq_complete_error: pgconn = %p, pgres = %p, error = %s",
conn->pgconn, *pgres, *error ? *error : "(null)"); conn->pgconn, *pgres, *error ? *error : "(null)");
if (*pgres != NULL) if (*pgres != NULL) {
pq_raise(conn, NULL, *pgres); pq_raise(conn, NULL, pgres);
/* now *pgres is null */
}
else if (*error != NULL) { else if (*error != NULL) {
PyErr_SetString(OperationalError, *error); PyErr_SetString(OperationalError, *error);
} else { } else {
PyErr_SetString(OperationalError, "unknown error"); PyErr_SetString(OperationalError, "unknown error");
} }
IFCLEARPGRES(*pgres);
if (*error) { if (*error) {
free(*error); free(*error);
*error = NULL; *error = NULL;
@ -1509,8 +1536,6 @@ pq_fetch(cursorObject *curs, int no_result)
default: default:
Dprintf("pq_fetch: uh-oh, something FAILED: pgconn = %p", curs->conn); Dprintf("pq_fetch: uh-oh, something FAILED: pgconn = %p", curs->conn);
pq_raise(curs->conn, curs, NULL); pq_raise(curs->conn, curs, NULL);
/* don't clear curs->pgres, because it contains detailed error
information */
ex = -1; ex = -1;
break; break;
} }

View File

@ -120,8 +120,7 @@ HIDDEN PyObject *psyco_GetDecimalType(void);
typedef struct cursorObject cursorObject; typedef struct cursorObject cursorObject;
/* some utility functions */ /* some utility functions */
RAISES HIDDEN void psyco_set_error(PyObject *exc, cursorObject *curs, const char *msg, RAISES HIDDEN PyObject *psyco_set_error(PyObject *exc, cursorObject *curs, const char *msg);
const char *pgerror, const char *pgcode);
HIDDEN char *psycopg_escape_string(PyObject *conn, HIDDEN char *psycopg_escape_string(PyObject *conn,
const char *from, Py_ssize_t len, char *to, Py_ssize_t *tolen); const char *from, Py_ssize_t len, char *to, Py_ssize_t *tolen);

View File

@ -35,6 +35,7 @@
#include "psycopg/typecast.h" #include "psycopg/typecast.h"
#include "psycopg/microprotocols.h" #include "psycopg/microprotocols.h"
#include "psycopg/microprotocols_proto.h" #include "psycopg/microprotocols_proto.h"
#include "psycopg/error.h"
#include "psycopg/diagnostics.h" #include "psycopg/diagnostics.h"
#include "psycopg/adapter_qstring.h" #include "psycopg/adapter_qstring.h"
@ -409,8 +410,8 @@ static struct {
PyObject **base; PyObject **base;
const char *docstr; const char *docstr;
} exctable[] = { } exctable[] = {
{ "psycopg2.Error", &Error, 0, Error_doc }, { "psycopg2.Error", &Error, &PyExc_StandardError, Error_doc },
{ "psycopg2.Warning", &Warning, 0, Warning_doc }, { "psycopg2.Warning", &Warning, &PyExc_StandardError, Warning_doc },
{ "psycopg2.InterfaceError", &InterfaceError, &Error, InterfaceError_doc }, { "psycopg2.InterfaceError", &InterfaceError, &Error, InterfaceError_doc },
{ "psycopg2.DatabaseError", &DatabaseError, &Error, DatabaseError_doc }, { "psycopg2.DatabaseError", &DatabaseError, &Error, DatabaseError_doc },
{ "psycopg2.InternalError", &InternalError, &DatabaseError, InternalError_doc }, { "psycopg2.InternalError", &InternalError, &DatabaseError, InternalError_doc },
@ -434,61 +435,6 @@ static struct {
}; };
#if PY_VERSION_HEX >= 0x02050000
/* Error.__reduce_ex__
*
* The method is required to make exceptions picklable: set the cursor
* attribute to None. Only working from Py 2.5: previous versions
* would require implementing __getstate__, and as of 2012 it's a little
* bit too late to care. */
static PyObject *
psyco_error_reduce_ex(PyObject *self, PyObject *args)
{
PyObject *proto = NULL;
PyObject *super = NULL;
PyObject *tuple = NULL;
PyObject *dict = NULL;
PyObject *rv = NULL;
/* tuple = Exception.__reduce_ex__(self, proto) */
if (!PyArg_ParseTuple(args, "O", &proto)) {
goto error;
}
if (!(super = PyObject_GetAttrString(PyExc_Exception, "__reduce_ex__"))) {
goto error;
}
if (!(tuple = PyObject_CallFunctionObjArgs(super, self, proto, NULL))) {
goto error;
}
/* tuple[2]['cursor'] = None
*
* If these checks fail, we can still return a valid object. Pickle
* will likely fail downstream, but there's nothing else we can do here */
if (!PyTuple_Check(tuple)) { goto exit; }
if (3 > PyTuple_GET_SIZE(tuple)) { goto exit; }
dict = PyTuple_GET_ITEM(tuple, 2); /* borrowed */
if (!PyDict_Check(dict)) { goto exit; }
/* Modify the tuple inplace and return it */
if (0 != PyDict_SetItemString(dict, "cursor", Py_None)) {
goto error;
}
exit:
rv = tuple;
tuple = NULL;
error:
Py_XDECREF(tuple);
Py_XDECREF(super);
return rv;
}
#endif /* PY_VERSION_HEX >= 0x02050000 */
static int static int
psyco_errors_init(void) psyco_errors_init(void)
{ {
@ -498,18 +444,13 @@ psyco_errors_init(void)
int i; int i;
PyObject *dict = NULL; PyObject *dict = NULL;
PyObject *base;
PyObject *str = NULL; PyObject *str = NULL;
PyObject *descr = NULL;
PyObject *diag_property = NULL;
int rv = -1; int rv = -1;
#if PY_VERSION_HEX >= 0x02050000 /* 'Error' has been defined elsewhere: only init the other classes */
static PyMethodDef psyco_error_reduce_ex_def = Error = (PyObject *)&errorType;
{"__reduce_ex__", psyco_error_reduce_ex, METH_VARARGS, "pickle helper"};
#endif
for (i=0; exctable[i].name; i++) { for (i = 1; exctable[i].name; i++) {
if (!(dict = PyDict_New())) { goto exit; } if (!(dict = PyDict_New())) { goto exit; }
if (exctable[i].docstr) { if (exctable[i].docstr) {
@ -518,58 +459,16 @@ psyco_errors_init(void)
Py_CLEAR(str); Py_CLEAR(str);
} }
if (exctable[i].base == 0) {
#if PY_MAJOR_VERSION < 3
base = PyExc_StandardError;
#else
/* StandardError is gone in 3.0 */
base = NULL;
#endif
}
else
base = *exctable[i].base;
if (!(*exctable[i].exc = PyErr_NewException( if (!(*exctable[i].exc = PyErr_NewException(
exctable[i].name, base, dict))) { exctable[i].name, *exctable[i].base, dict))) {
goto exit; goto exit;
} }
Py_CLEAR(dict); Py_CLEAR(dict);
} }
/* Make pgerror, pgcode and cursor default to None on psycopg
error objects. This simplifies error handling code that checks
these attributes. */
PyObject_SetAttrString(Error, "pgerror", Py_None);
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
* protocol, and if called manually fails in an unsettling way,
* probably because the exceptions were old-style classes. */
#if PY_VERSION_HEX >= 0x02050000
if (!(descr = PyDescr_NewMethod((PyTypeObject *)Error,
&psyco_error_reduce_ex_def))) {
goto exit;
}
if (0 != PyObject_SetAttrString(Error,
psyco_error_reduce_ex_def.ml_name, descr)) {
goto exit;
}
#endif
rv = 0; rv = 0;
exit: exit:
Py_XDECREF(diag_property);
Py_XDECREF(descr);
Py_XDECREF(str); Py_XDECREF(str);
Py_XDECREF(dict); Py_XDECREF(dict);
return rv; return rv;
@ -613,11 +512,10 @@ psyco_errors_set(PyObject *type)
Create a new error of the given type with extra attributes. */ Create a new error of the given type with extra attributes. */
RAISES void /* TODO: may have been changed to BORROWED */
psyco_set_error(PyObject *exc, cursorObject *curs, const char *msg, RAISES PyObject *
const char *pgerror, const char *pgcode) psyco_set_error(PyObject *exc, cursorObject *curs, const char *msg)
{ {
PyObject *t;
PyObject *pymsg; PyObject *pymsg;
PyObject *err = NULL; PyObject *err = NULL;
connectionObject *conn = NULL; connectionObject *conn = NULL;
@ -633,31 +531,24 @@ psyco_set_error(PyObject *exc, cursorObject *curs, const char *msg,
else { else {
/* what's better than an error in an error handler in the morning? /* what's better than an error in an error handler in the morning?
* Anyway, some error was set, refcount is ok... get outta here. */ * Anyway, some error was set, refcount is ok... get outta here. */
return; return NULL;
}
if (err && PyObject_TypeCheck(err, &errorType)) {
errorObject *perr = (errorObject *)err;
if (curs) {
Py_CLEAR(perr->cursor);
Py_INCREF(curs);
perr->cursor = curs;
}
} }
if (err) { if (err) {
if (curs) {
PyObject_SetAttrString(err, "cursor", (PyObject *)curs);
}
if (pgerror) {
if ((t = conn_text_from_chars(conn, pgerror))) {
PyObject_SetAttrString(err, "pgerror", t);
Py_DECREF(t);
}
}
if (pgcode) {
if ((t = conn_text_from_chars(conn, pgcode))) {
PyObject_SetAttrString(err, "pgcode", t);
Py_DECREF(t);
}
}
PyErr_SetObject(exc, err); PyErr_SetObject(exc, err);
Py_DECREF(err); Py_DECREF(err);
} }
return err;
} }
@ -891,6 +782,7 @@ INIT_MODULE(_psycopg)(void)
Py_TYPE(&chunkType) = &PyType_Type; Py_TYPE(&chunkType) = &PyType_Type;
Py_TYPE(&NotifyType) = &PyType_Type; Py_TYPE(&NotifyType) = &PyType_Type;
Py_TYPE(&XidType) = &PyType_Type; Py_TYPE(&XidType) = &PyType_Type;
Py_TYPE(&errorType) = &PyType_Type;
Py_TYPE(&diagnosticsType) = &PyType_Type; Py_TYPE(&diagnosticsType) = &PyType_Type;
if (PyType_Ready(&connectionType) == -1) goto exit; if (PyType_Ready(&connectionType) == -1) goto exit;
@ -908,6 +800,8 @@ INIT_MODULE(_psycopg)(void)
if (PyType_Ready(&chunkType) == -1) goto exit; if (PyType_Ready(&chunkType) == -1) goto exit;
if (PyType_Ready(&NotifyType) == -1) goto exit; if (PyType_Ready(&NotifyType) == -1) goto exit;
if (PyType_Ready(&XidType) == -1) goto exit; if (PyType_Ready(&XidType) == -1) goto exit;
errorType.tp_base = (PyTypeObject *)PyExc_StandardError;
if (PyType_Ready(&errorType) == -1) goto exit;
if (PyType_Ready(&diagnosticsType) == -1) goto exit; if (PyType_Ready(&diagnosticsType) == -1) goto exit;
#ifdef PSYCOPG_EXTENSIONS #ifdef PSYCOPG_EXTENSIONS
@ -1043,6 +937,7 @@ INIT_MODULE(_psycopg)(void)
pydatetimeType.tp_alloc = PyType_GenericAlloc; pydatetimeType.tp_alloc = PyType_GenericAlloc;
NotifyType.tp_alloc = PyType_GenericAlloc; NotifyType.tp_alloc = PyType_GenericAlloc;
XidType.tp_alloc = PyType_GenericAlloc; XidType.tp_alloc = PyType_GenericAlloc;
errorType.tp_alloc = PyType_GenericAlloc;
diagnosticsType.tp_alloc = PyType_GenericAlloc; diagnosticsType.tp_alloc = PyType_GenericAlloc;
#ifdef PSYCOPG_EXTENSIONS #ifdef PSYCOPG_EXTENSIONS

View File

@ -114,6 +114,7 @@ typedef unsigned long Py_uhash_t;
#define PyInt_AsLong PyLong_AsLong #define PyInt_AsLong PyLong_AsLong
#define PyInt_FromLong PyLong_FromLong #define PyInt_FromLong PyLong_FromLong
#define PyInt_FromSsize_t PyLong_FromSsize_t #define PyInt_FromSsize_t PyLong_FromSsize_t
#define PyExc_StandardError PyExc_Exception
#define PyString_FromFormat PyUnicode_FromFormat #define PyString_FromFormat PyUnicode_FromFormat
#define Py_TPFLAGS_HAVE_ITER 0L #define Py_TPFLAGS_HAVE_ITER 0L
#define Py_TPFLAGS_HAVE_RICHCOMPARE 0L #define Py_TPFLAGS_HAVE_RICHCOMPARE 0L

View File

@ -115,10 +115,16 @@ psycopg_escape_identifier_easy(const char *from, Py_ssize_t len)
* *
* Store the return in 'to' and return 0 in case of success, else return -1 * Store the return in 'to' and return 0 in case of success, else return -1
* and raise an exception. * and raise an exception.
*
* If from is null, store null into to.
*/ */
RAISES_NEG int RAISES_NEG int
psycopg_strdup(char **to, const char *from, Py_ssize_t len) psycopg_strdup(char **to, const char *from, Py_ssize_t len)
{ {
if (!from) {
*to = NULL;
return 0;
}
if (!len) { len = strlen(from); } if (!len) { len = strlen(from); }
if (!(*to = PyMem_Malloc(len + 1))) { if (!(*to = PyMem_Malloc(len + 1))) {
PyErr_NoMemory(); PyErr_NoMemory();

View File

@ -428,7 +428,7 @@ sources = [
'connection_int.c', 'connection_type.c', 'connection_int.c', 'connection_type.c',
'cursor_int.c', 'cursor_type.c', 'cursor_int.c', 'cursor_type.c',
'diagnostics_type.c', 'diagnostics_type.c', 'error_type.c',
'lobject_int.c', 'lobject_type.c', 'lobject_int.c', 'lobject_type.c',
'notify_type.c', 'xid_type.c', 'notify_type.c', 'xid_type.c',
@ -441,8 +441,8 @@ sources = [
depends = [ depends = [
# headers # headers
'config.h', 'pgtypes.h', 'psycopg.h', 'python.h', 'config.h', 'pgtypes.h', 'psycopg.h', 'python.h', 'connection.h',
'connection.h', 'cursor.h', 'diagnostics.h', 'green.h', 'lobject.h', 'cursor.h', 'diagnostics.h', 'error.h', 'green.h', 'lobject.h',
'notify.h', 'pqpath.h', 'xid.h', 'notify.h', 'pqpath.h', 'xid.h',
'adapter_asis.h', 'adapter_binary.h', 'adapter_datetime.h', 'adapter_asis.h', 'adapter_binary.h', 'adapter_datetime.h',

View File

@ -222,6 +222,38 @@ class ExceptionsTestCase(unittest.TestCase):
self.assertEqual(diag.sqlstate, '42P01') self.assertEqual(diag.sqlstate, '42P01')
def test_diagnostics_independent(self):
cur = self.conn.cursor()
try:
cur.execute("l'acqua e' poca e 'a papera nun galleggia")
except Exception, exc:
diag1 = exc.diag
self.conn.rollback()
try:
cur.execute("select level from water where ducks > 1")
except psycopg2.Error, exc:
diag2 = exc.diag
self.assertEqual(diag1.sqlstate, '42601')
self.assertEqual(diag2.sqlstate, '42P01')
def test_diagnostics_from_commit(self):
cur = self.conn.cursor()
cur.execute("""
create temp table test_deferred (
data int primary key,
ref int references test_deferred (data)
deferrable initially deferred)
""")
cur.execute("insert into test_deferred values (1,2)")
try:
self.conn.commit()
except psycopg2.Error, exc:
e = exc
self.assertEqual(e.diag.sqlstate, '23503')
@skip_before_postgres(9, 3) @skip_before_postgres(9, 3)
def test_9_3_diagnostics(self): def test_9_3_diagnostics(self):
cur = self.conn.cursor() cur = self.conn.cursor()