mirror of
https://github.com/psycopg/psycopg2.git
synced 2024-11-22 17:06:33 +03:00
* tests/test_transaction.py (DeadlockSerializationTestCase): port
over some tests for serialisation and deadlock errors, demonstrating that TransactionRollbackError is generated. (QueryCancelationTests): add a test to show that QueryCanceledError is raised on statement timeouts. * psycopg2da/adapter.py (_handle_psycopg_exception): rather than checking exception messages, check for TransactionRollbackError. * psycopg/pqpath.c (exception_from_sqlstate): return TransactionRollbackError for 40xxx errors, and QueryCanceledError for 57014 errors. (pq_raise): If we are using an old server, use TransactionRollbackError if the error message contains "could not serialize" or "deadlock detected". * psycopg/psycopgmodule.c (_psyco_connect_fill_exc): remove function, since we no longer need to store pointers to the exceptions in the connection. This also fixes a reference leak. (psyco_connect): remove _psyco_connect_fill_exc() function call. * psycopg/connection.h (connectionObject): remove exception members from struct. * psycopg/connection_type.c (connectionObject_getsets): modify the exception attributes on the connection object from members to getsets. This reduces the size of the struct. * lib/extensions.py: import the two new extensions. * psycopg/psycopgmodule.c (exctable): add new QueryCanceledError and TransactionRollbackError exceptions.
This commit is contained in:
parent
46bf23caf4
commit
f64cbeda46
35
ChangeLog
35
ChangeLog
|
@ -1,3 +1,38 @@
|
||||||
|
2008-01-16 James Henstridge <james@jamesh.id.au>
|
||||||
|
|
||||||
|
* tests/test_transaction.py (DeadlockSerializationTestCase): port
|
||||||
|
over some tests for serialisation and deadlock errors,
|
||||||
|
demonstrating that TransactionRollbackError is generated.
|
||||||
|
(QueryCancelationTests): add a test to show that
|
||||||
|
QueryCanceledError is raised on statement timeouts.
|
||||||
|
|
||||||
|
* psycopg2da/adapter.py (_handle_psycopg_exception): rather than
|
||||||
|
checking exception messages, check for TransactionRollbackError.
|
||||||
|
|
||||||
|
* psycopg/pqpath.c (exception_from_sqlstate): return
|
||||||
|
TransactionRollbackError for 40xxx errors, and QueryCanceledError
|
||||||
|
for 57014 errors.
|
||||||
|
(pq_raise): If we are using an old server, use
|
||||||
|
TransactionRollbackError if the error message contains "could not
|
||||||
|
serialize" or "deadlock detected".
|
||||||
|
|
||||||
|
* psycopg/psycopgmodule.c (_psyco_connect_fill_exc): remove
|
||||||
|
function, since we no longer need to store pointers to the
|
||||||
|
exceptions in the connection. This also fixes a reference leak.
|
||||||
|
(psyco_connect): remove _psyco_connect_fill_exc() function call.
|
||||||
|
|
||||||
|
* psycopg/connection.h (connectionObject): remove exception
|
||||||
|
members from struct.
|
||||||
|
|
||||||
|
* psycopg/connection_type.c (connectionObject_getsets): modify the
|
||||||
|
exception attributes on the connection object from members to
|
||||||
|
getsets. This reduces the size of the struct.
|
||||||
|
|
||||||
|
* lib/extensions.py: import the two new extensions.
|
||||||
|
|
||||||
|
* psycopg/psycopgmodule.c (exctable): add new QueryCanceledError
|
||||||
|
and TransactionRollbackError exceptions.
|
||||||
|
|
||||||
2008-01-16 James Henstridge <james@jamesh.id.au>
|
2008-01-16 James Henstridge <james@jamesh.id.au>
|
||||||
|
|
||||||
* tests/__init__.py (test_suite): add date tests to test suite.
|
* tests/__init__.py (test_suite): add date tests to test suite.
|
||||||
|
|
|
@ -42,6 +42,8 @@ from _psycopg import adapt, adapters, encodings, connection, cursor
|
||||||
from _psycopg import string_types, binary_types, new_type, register_type
|
from _psycopg import string_types, binary_types, new_type, register_type
|
||||||
from _psycopg import ISQLQuote
|
from _psycopg import ISQLQuote
|
||||||
|
|
||||||
|
from _psycopg import QueryCanceledError, TransactionRollbackError
|
||||||
|
|
||||||
"""Isolation level values."""
|
"""Isolation level values."""
|
||||||
ISOLATION_LEVEL_AUTOCOMMIT = 0
|
ISOLATION_LEVEL_AUTOCOMMIT = 0
|
||||||
ISOLATION_LEVEL_READ_COMMITTED = 1
|
ISOLATION_LEVEL_READ_COMMITTED = 1
|
||||||
|
|
|
@ -67,18 +67,6 @@ typedef struct {
|
||||||
/* notifies */
|
/* notifies */
|
||||||
PyObject *notifies;
|
PyObject *notifies;
|
||||||
|
|
||||||
/* errors (DBAPI-2.0 extension) */
|
|
||||||
PyObject *exc_Error;
|
|
||||||
PyObject *exc_Warning;
|
|
||||||
PyObject *exc_InterfaceError;
|
|
||||||
PyObject *exc_DatabaseError;
|
|
||||||
PyObject *exc_InternalError;
|
|
||||||
PyObject *exc_OperationalError;
|
|
||||||
PyObject *exc_ProgrammingError;
|
|
||||||
PyObject *exc_IntegrityError;
|
|
||||||
PyObject *exc_DataError;
|
|
||||||
PyObject *exc_NotSupportedError;
|
|
||||||
|
|
||||||
/* per-connection typecasters */
|
/* per-connection typecasters */
|
||||||
PyObject *string_types; /* a set of typecasters for string types */
|
PyObject *string_types; /* a set of typecasters for string types */
|
||||||
PyObject *binary_types; /* a set of typecasters for binary types */
|
PyObject *binary_types; /* a set of typecasters for binary types */
|
||||||
|
|
|
@ -231,6 +231,14 @@ psyco_conn_get_transaction_status(connectionObject *self, PyObject *args)
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
psyco_conn_get_exception(PyObject *self, void *closure)
|
||||||
|
{
|
||||||
|
PyObject *exception = *(PyObject **)closure;
|
||||||
|
|
||||||
|
Py_INCREF(exception);
|
||||||
|
return exception;
|
||||||
|
}
|
||||||
|
|
||||||
/** the connection object **/
|
/** the connection object **/
|
||||||
|
|
||||||
|
@ -260,32 +268,6 @@ static struct PyMethodDef connectionObject_methods[] = {
|
||||||
/* object member list */
|
/* object member list */
|
||||||
|
|
||||||
static struct PyMemberDef connectionObject_members[] = {
|
static struct PyMemberDef connectionObject_members[] = {
|
||||||
/* DBAPI-2.0 extensions (exception objects) */
|
|
||||||
{"Error", T_OBJECT,
|
|
||||||
offsetof(connectionObject, exc_Error), RO, Error_doc},
|
|
||||||
{"Warning",
|
|
||||||
T_OBJECT, offsetof(connectionObject, exc_Warning), RO, Warning_doc},
|
|
||||||
{"InterfaceError", T_OBJECT,
|
|
||||||
offsetof(connectionObject, exc_InterfaceError), RO,
|
|
||||||
InterfaceError_doc},
|
|
||||||
{"DatabaseError", T_OBJECT,
|
|
||||||
offsetof(connectionObject, exc_DatabaseError), RO, DatabaseError_doc},
|
|
||||||
{"InternalError", T_OBJECT,
|
|
||||||
offsetof(connectionObject, exc_InternalError), RO, InternalError_doc},
|
|
||||||
{"OperationalError", T_OBJECT,
|
|
||||||
offsetof(connectionObject, exc_OperationalError), RO,
|
|
||||||
OperationalError_doc},
|
|
||||||
{"ProgrammingError", T_OBJECT,
|
|
||||||
offsetof(connectionObject, exc_ProgrammingError), RO,
|
|
||||||
ProgrammingError_doc},
|
|
||||||
{"IntegrityError", T_OBJECT,
|
|
||||||
offsetof(connectionObject, exc_IntegrityError), RO,
|
|
||||||
IntegrityError_doc},
|
|
||||||
{"DataError", T_OBJECT,
|
|
||||||
offsetof(connectionObject, exc_DataError), RO, DataError_doc},
|
|
||||||
{"NotSupportedError", T_OBJECT,
|
|
||||||
offsetof(connectionObject, exc_NotSupportedError), RO,
|
|
||||||
NotSupportedError_doc},
|
|
||||||
#ifdef PSYCOPG_EXTENSIONS
|
#ifdef PSYCOPG_EXTENSIONS
|
||||||
{"closed", T_LONG, offsetof(connectionObject, closed), RO,
|
{"closed", T_LONG, offsetof(connectionObject, closed), RO,
|
||||||
"True if the connection is closed."},
|
"True if the connection is closed."},
|
||||||
|
@ -309,6 +291,25 @@ static struct PyMemberDef connectionObject_members[] = {
|
||||||
{NULL}
|
{NULL}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#define EXCEPTION_GETTER(exc) \
|
||||||
|
{ #exc, psyco_conn_get_exception, NULL, exc ## _doc, &exc }
|
||||||
|
|
||||||
|
static struct PyGetSetDef connectionObject_getsets[] = {
|
||||||
|
/* DBAPI-2.0 extensions (exception objects) */
|
||||||
|
EXCEPTION_GETTER(Error),
|
||||||
|
EXCEPTION_GETTER(Warning),
|
||||||
|
EXCEPTION_GETTER(InterfaceError),
|
||||||
|
EXCEPTION_GETTER(DatabaseError),
|
||||||
|
EXCEPTION_GETTER(InternalError),
|
||||||
|
EXCEPTION_GETTER(OperationalError),
|
||||||
|
EXCEPTION_GETTER(ProgrammingError),
|
||||||
|
EXCEPTION_GETTER(IntegrityError),
|
||||||
|
EXCEPTION_GETTER(DataError),
|
||||||
|
EXCEPTION_GETTER(NotSupportedError),
|
||||||
|
{NULL}
|
||||||
|
};
|
||||||
|
#undef EXCEPTION_GETTER
|
||||||
|
|
||||||
/* initialization and finalization methods */
|
/* initialization and finalization methods */
|
||||||
|
|
||||||
static int
|
static int
|
||||||
|
@ -465,7 +466,7 @@ PyTypeObject connectionType = {
|
||||||
|
|
||||||
connectionObject_methods, /*tp_methods*/
|
connectionObject_methods, /*tp_methods*/
|
||||||
connectionObject_members, /*tp_members*/
|
connectionObject_members, /*tp_members*/
|
||||||
0, /*tp_getset*/
|
connectionObject_getsets, /*tp_getset*/
|
||||||
0, /*tp_base*/
|
0, /*tp_base*/
|
||||||
0, /*tp_dict*/
|
0, /*tp_dict*/
|
||||||
|
|
||||||
|
|
|
@ -107,9 +107,13 @@ exception_from_sqlstate(const char *sqlstate)
|
||||||
case '4':
|
case '4':
|
||||||
switch (sqlstate[1]) {
|
switch (sqlstate[1]) {
|
||||||
case '0': /* Class 40 - Transaction Rollback */
|
case '0': /* Class 40 - Transaction Rollback */
|
||||||
|
#ifdef PSYCOPG_EXTENSIONS
|
||||||
|
return TransactionRollbackError;
|
||||||
|
#else
|
||||||
return OperationalError;
|
return OperationalError;
|
||||||
|
#endif
|
||||||
case '2': /* Class 42 - Syntax Error or Access Rule Violation */
|
case '2': /* Class 42 - Syntax Error or Access Rule Violation */
|
||||||
case '4': /* Class 44 — WITH CHECK OPTION Violation */
|
case '4': /* Class 44 - WITH CHECK OPTION Violation */
|
||||||
return ProgrammingError;
|
return ProgrammingError;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -119,7 +123,12 @@ exception_from_sqlstate(const char *sqlstate)
|
||||||
Class 55 - Object Not In Prerequisite State
|
Class 55 - Object Not In Prerequisite State
|
||||||
Class 57 - Operator Intervention
|
Class 57 - Operator Intervention
|
||||||
Class 58 - System Error (errors external to PostgreSQL itself) */
|
Class 58 - System Error (errors external to PostgreSQL itself) */
|
||||||
return OperationalError;
|
#ifdef PSYCOPG_EXTENSIONS
|
||||||
|
if (!strcmp(sqlstate, "57014"))
|
||||||
|
return QueryCanceledError;
|
||||||
|
else
|
||||||
|
#endif
|
||||||
|
return OperationalError;
|
||||||
case 'F': /* Class F0 - Configuration File Error */
|
case 'F': /* Class F0 - Configuration File Error */
|
||||||
return InternalError;
|
return InternalError;
|
||||||
case 'P': /* Class P0 - PL/pgSQL Error */
|
case 'P': /* Class P0 - PL/pgSQL Error */
|
||||||
|
@ -188,6 +197,9 @@ pq_raise(connectionObject *conn, cursorObject *curs, PGresult *pgres,
|
||||||
|| !strncmp(err, "ERROR: ExecAppend: Fail to add null", 36)
|
|| !strncmp(err, "ERROR: ExecAppend: Fail to add null", 36)
|
||||||
|| strstr(err, "referential integrity violation"))
|
|| strstr(err, "referential integrity violation"))
|
||||||
exc = IntegrityError;
|
exc = IntegrityError;
|
||||||
|
else if (strstr(err, "could not serialize") ||
|
||||||
|
strstr(err, "deadlock detected"))
|
||||||
|
exc = TransactionRollbackError;
|
||||||
else
|
else
|
||||||
exc = ProgrammingError;
|
exc = ProgrammingError;
|
||||||
}
|
}
|
||||||
|
|
|
@ -85,6 +85,9 @@ extern psyco_errors_set_RETURN psyco_errors_set psyco_errors_set_PROTO;
|
||||||
extern PyObject *Error, *Warning, *InterfaceError, *DatabaseError,
|
extern PyObject *Error, *Warning, *InterfaceError, *DatabaseError,
|
||||||
*InternalError, *OperationalError, *ProgrammingError,
|
*InternalError, *OperationalError, *ProgrammingError,
|
||||||
*IntegrityError, *DataError, *NotSupportedError;
|
*IntegrityError, *DataError, *NotSupportedError;
|
||||||
|
#ifdef PSYCOPG_EXTENSIONS
|
||||||
|
extern PyObject *QueryCanceledError, *TransactionRollbackError;
|
||||||
|
#endif
|
||||||
|
|
||||||
/* python versions and compatibility stuff */
|
/* python versions and compatibility stuff */
|
||||||
#ifndef PyMODINIT_FUNC
|
#ifndef PyMODINIT_FUNC
|
||||||
|
@ -167,6 +170,14 @@ extern void psyco_set_error(PyObject *exc, PyObject *curs, const char *msg,
|
||||||
#define NotSupportedError_doc \
|
#define NotSupportedError_doc \
|
||||||
"A not supported datbase API was called."
|
"A not supported datbase API was called."
|
||||||
|
|
||||||
|
#ifdef PSYCOPG_EXTENSIONS
|
||||||
|
#define QueryCanceledError_doc \
|
||||||
|
"Error related to SQL query cancelation."
|
||||||
|
|
||||||
|
#define TransactionRollbackError_doc \
|
||||||
|
"Error causing transaction rollback (deadlocks, serialisation failures, etc)."
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -100,32 +100,6 @@ _psyco_connect_fill_dsn(char *dsn, char *kw, char *v, size_t i)
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
|
||||||
_psyco_connect_fill_exc(connectionObject *conn)
|
|
||||||
{
|
|
||||||
/* fill the connection object with the exceptions */
|
|
||||||
conn->exc_Error = Error;
|
|
||||||
Py_INCREF(Error);
|
|
||||||
conn->exc_Warning = Warning;
|
|
||||||
Py_INCREF(Warning);
|
|
||||||
conn->exc_InterfaceError = InterfaceError;
|
|
||||||
Py_INCREF(InterfaceError);
|
|
||||||
conn->exc_DatabaseError = DatabaseError;
|
|
||||||
Py_INCREF(DatabaseError);
|
|
||||||
conn->exc_InternalError = InternalError;
|
|
||||||
Py_INCREF(InternalError);
|
|
||||||
conn->exc_ProgrammingError = ProgrammingError;
|
|
||||||
Py_INCREF(ProgrammingError);
|
|
||||||
conn->exc_IntegrityError = IntegrityError;
|
|
||||||
Py_INCREF(IntegrityError);
|
|
||||||
conn->exc_DataError = DataError;
|
|
||||||
Py_INCREF(DataError);
|
|
||||||
conn->exc_NotSupportedError = NotSupportedError;
|
|
||||||
Py_INCREF(NotSupportedError);
|
|
||||||
conn->exc_OperationalError = OperationalError;
|
|
||||||
Py_INCREF(OperationalError);
|
|
||||||
}
|
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
psyco_connect(PyObject *self, PyObject *args, PyObject *keywds)
|
psyco_connect(PyObject *self, PyObject *args, PyObject *keywds)
|
||||||
{
|
{
|
||||||
|
@ -215,7 +189,6 @@ psyco_connect(PyObject *self, PyObject *args, PyObject *keywds)
|
||||||
/* allocate connection, fill with errors and return it */
|
/* allocate connection, fill with errors and return it */
|
||||||
if (factory == NULL) factory = (PyObject *)&connectionType;
|
if (factory == NULL) factory = (PyObject *)&connectionType;
|
||||||
conn = PyObject_CallFunction(factory, "s", dsn);
|
conn = PyObject_CallFunction(factory, "s", dsn);
|
||||||
if (conn) _psyco_connect_fill_exc((connectionObject*)conn);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
|
@ -433,6 +406,9 @@ static void psyco_encodings_fill(PyObject *dict)
|
||||||
PyObject *Error, *Warning, *InterfaceError, *DatabaseError,
|
PyObject *Error, *Warning, *InterfaceError, *DatabaseError,
|
||||||
*InternalError, *OperationalError, *ProgrammingError,
|
*InternalError, *OperationalError, *ProgrammingError,
|
||||||
*IntegrityError, *DataError, *NotSupportedError;
|
*IntegrityError, *DataError, *NotSupportedError;
|
||||||
|
#ifdef PSYCOPG_EXTENSIONS
|
||||||
|
PyObject *QueryCanceledError, *TransactionRollbackError;
|
||||||
|
#endif
|
||||||
|
|
||||||
/* mapping between exception names and their PyObject */
|
/* mapping between exception names and their PyObject */
|
||||||
static struct {
|
static struct {
|
||||||
|
@ -455,6 +431,13 @@ static struct {
|
||||||
{ "psycopg2.DataError", &DataError, &DatabaseError, DataError_doc },
|
{ "psycopg2.DataError", &DataError, &DatabaseError, DataError_doc },
|
||||||
{ "psycopg2.NotSupportedError", &NotSupportedError, &DatabaseError,
|
{ "psycopg2.NotSupportedError", &NotSupportedError, &DatabaseError,
|
||||||
NotSupportedError_doc },
|
NotSupportedError_doc },
|
||||||
|
#ifdef PSYCOPG_EXTENSIONS
|
||||||
|
{ "psycopg2.extensions.QueryCanceledError", &QueryCanceledError,
|
||||||
|
&OperationalError, OperationalError_doc },
|
||||||
|
{ "psycopg2.extensions.TransactionRollbackError",
|
||||||
|
&TransactionRollbackError, &OperationalError,
|
||||||
|
TransactionRollbackError_doc },
|
||||||
|
#endif
|
||||||
{NULL} /* Sentinel */
|
{NULL} /* Sentinel */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -507,6 +490,11 @@ psyco_errors_fill(PyObject *dict)
|
||||||
PyDict_SetItemString(dict, "IntegrityError", IntegrityError);
|
PyDict_SetItemString(dict, "IntegrityError", IntegrityError);
|
||||||
PyDict_SetItemString(dict, "DataError", DataError);
|
PyDict_SetItemString(dict, "DataError", DataError);
|
||||||
PyDict_SetItemString(dict, "NotSupportedError", NotSupportedError);
|
PyDict_SetItemString(dict, "NotSupportedError", NotSupportedError);
|
||||||
|
#ifdef PSYCOPG_EXTENSIONS
|
||||||
|
PyDict_SetItemString(dict, "QueryCanceledError", QueryCanceledError);
|
||||||
|
PyDict_SetItemString(dict, "TransactionRollbackError",
|
||||||
|
TransactionRollbackError);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
@ -522,6 +510,11 @@ psyco_errors_set(PyObject *type)
|
||||||
PyObject_SetAttrString(type, "IntegrityError", IntegrityError);
|
PyObject_SetAttrString(type, "IntegrityError", IntegrityError);
|
||||||
PyObject_SetAttrString(type, "DataError", DataError);
|
PyObject_SetAttrString(type, "DataError", DataError);
|
||||||
PyObject_SetAttrString(type, "NotSupportedError", NotSupportedError);
|
PyObject_SetAttrString(type, "NotSupportedError", NotSupportedError);
|
||||||
|
#ifdef PSYCOPG_EXTENSIONS
|
||||||
|
PyObject_SetAttrString(type, "QueryCanceledError", QueryCanceledError);
|
||||||
|
PyObject_SetAttrString(type, "TransactionRollbackError",
|
||||||
|
TransactionRollbackError);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
/* psyco_error_new
|
/* psyco_error_new
|
||||||
|
|
|
@ -372,14 +372,7 @@ def _handle_psycopg_exception(error):
|
||||||
If we have a serialization exception or a deadlock, we should retry the
|
If we have a serialization exception or a deadlock, we should retry the
|
||||||
transaction by raising a Retry exception. Otherwise, we reraise.
|
transaction by raising a Retry exception. Otherwise, we reraise.
|
||||||
"""
|
"""
|
||||||
if not error.args:
|
if isinstance(error, psycopg2.extensions.TransactionRollbackError):
|
||||||
raise
|
|
||||||
msg = error.args[0]
|
|
||||||
# These messages are from PostgreSQL 8.0. They may change between
|
|
||||||
# PostgreSQL releases - if so, the different messages should be added
|
|
||||||
# rather than the existing ones changed so this logic works with
|
|
||||||
# different versions.
|
|
||||||
if 'could not serialize' in msg or 'deadlock detected' in msg:
|
|
||||||
raise Retry(sys.exc_info())
|
raise Retry(sys.exc_info())
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
import psycopg2
|
import threading
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
|
import psycopg2
|
||||||
|
import psycopg2
|
||||||
import tests
|
import tests
|
||||||
|
|
||||||
from psycopg2.extensions import (
|
from psycopg2.extensions import (
|
||||||
|
@ -69,6 +72,154 @@ class TransactionTestCase(unittest.TestCase):
|
||||||
self.assertEqual(curs.fetchone()[0], 1)
|
self.assertEqual(curs.fetchone()[0], 1)
|
||||||
|
|
||||||
|
|
||||||
|
class DeadlockSerializationTestCase(unittest.TestCase):
|
||||||
|
"""Test deadlock and serialization failure errors."""
|
||||||
|
|
||||||
|
def connect(self):
|
||||||
|
conn = psycopg2.connect("dbname=%s" % tests.dbname)
|
||||||
|
conn.set_isolation_level(ISOLATION_LEVEL_SERIALIZABLE)
|
||||||
|
return conn
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.conn = self.connect()
|
||||||
|
curs = self.conn.cursor()
|
||||||
|
# Drop table if it already exists
|
||||||
|
try:
|
||||||
|
curs.execute("DROP TABLE table1")
|
||||||
|
self.conn.commit()
|
||||||
|
except psycopg2.DatabaseError:
|
||||||
|
self.conn.rollback()
|
||||||
|
try:
|
||||||
|
curs.execute("DROP TABLE table2")
|
||||||
|
self.conn.commit()
|
||||||
|
except psycopg2.DatabaseError:
|
||||||
|
self.conn.rollback()
|
||||||
|
# Create sample data
|
||||||
|
curs.execute("""
|
||||||
|
CREATE TABLE table1 (
|
||||||
|
id int PRIMARY KEY,
|
||||||
|
name text)
|
||||||
|
""")
|
||||||
|
curs.execute("INSERT INTO table1 VALUES (1, 'hello')")
|
||||||
|
curs.execute("CREATE TABLE table2 (id int PRIMARY KEY)")
|
||||||
|
self.conn.commit()
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
curs = self.conn.cursor()
|
||||||
|
curs.execute("DROP TABLE table1")
|
||||||
|
curs.execute("DROP TABLE table2")
|
||||||
|
self.conn.commit()
|
||||||
|
self.conn.close()
|
||||||
|
|
||||||
|
def test_deadlock(self):
|
||||||
|
self.thread1_error = self.thread2_error = None
|
||||||
|
step1 = threading.Event()
|
||||||
|
step2 = threading.Event()
|
||||||
|
|
||||||
|
def task1():
|
||||||
|
try:
|
||||||
|
conn = self.connect()
|
||||||
|
curs = conn.cursor()
|
||||||
|
curs.execute("LOCK table1 IN ACCESS EXCLUSIVE MODE")
|
||||||
|
step1.set()
|
||||||
|
step2.wait()
|
||||||
|
curs.execute("LOCK table2 IN ACCESS EXCLUSIVE MODE")
|
||||||
|
except psycopg2.DatabaseError, exc:
|
||||||
|
self.thread1_error = exc
|
||||||
|
step1.set()
|
||||||
|
conn.close()
|
||||||
|
def task2():
|
||||||
|
try:
|
||||||
|
conn = self.connect()
|
||||||
|
curs = conn.cursor()
|
||||||
|
step1.wait()
|
||||||
|
curs.execute("LOCK table2 IN ACCESS EXCLUSIVE MODE")
|
||||||
|
step2.set()
|
||||||
|
curs.execute("LOCK table1 IN ACCESS EXCLUSIVE MODE")
|
||||||
|
except psycopg2.DatabaseError, exc:
|
||||||
|
self.thread2_error = exc
|
||||||
|
step2.set()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
# Run the threads in parallel. The "step1" and "step2" events
|
||||||
|
# ensure that the two transactions overlap.
|
||||||
|
thread1 = threading.Thread(target=task1)
|
||||||
|
thread2 = threading.Thread(target=task2)
|
||||||
|
thread1.start()
|
||||||
|
thread2.start()
|
||||||
|
thread1.join()
|
||||||
|
thread2.join()
|
||||||
|
|
||||||
|
# Exactly one of the threads should have failed with
|
||||||
|
# TransactionRollbackError:
|
||||||
|
self.assertFalse(self.thread1_error and self.thread2_error)
|
||||||
|
error = self.thread1_error or self.thread2_error
|
||||||
|
self.assertTrue(isinstance(
|
||||||
|
error, psycopg2.extensions.TransactionRollbackError))
|
||||||
|
|
||||||
|
def test_serialisation_failure(self):
|
||||||
|
self.thread1_error = self.thread2_error = None
|
||||||
|
step1 = threading.Event()
|
||||||
|
step2 = threading.Event()
|
||||||
|
|
||||||
|
def task1():
|
||||||
|
try:
|
||||||
|
conn = self.connect()
|
||||||
|
curs = conn.cursor()
|
||||||
|
curs.execute("SELECT name FROM table1 WHERE id = 1")
|
||||||
|
curs.fetchall()
|
||||||
|
step1.set()
|
||||||
|
step2.wait()
|
||||||
|
curs.execute("UPDATE table1 SET name='task1' WHERE id = 1")
|
||||||
|
conn.commit()
|
||||||
|
except psycopg2.DatabaseError, exc:
|
||||||
|
self.thread1_error = exc
|
||||||
|
step1.set()
|
||||||
|
conn.close()
|
||||||
|
def task2():
|
||||||
|
try:
|
||||||
|
conn = self.connect()
|
||||||
|
curs = conn.cursor()
|
||||||
|
step1.wait()
|
||||||
|
curs.execute("UPDATE table1 SET name='task2' WHERE id = 1")
|
||||||
|
conn.commit()
|
||||||
|
except psycopg2.DatabaseError, exc:
|
||||||
|
self.thread2_error = exc
|
||||||
|
step2.set()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
# Run the threads in parallel. The "step1" and "step2" events
|
||||||
|
# ensure that the two transactions overlap.
|
||||||
|
thread1 = threading.Thread(target=task1)
|
||||||
|
thread2 = threading.Thread(target=task2)
|
||||||
|
thread1.start()
|
||||||
|
thread2.start()
|
||||||
|
thread1.join()
|
||||||
|
thread2.join()
|
||||||
|
|
||||||
|
# Exactly one of the threads should have failed with
|
||||||
|
# TransactionRollbackError:
|
||||||
|
self.assertFalse(self.thread1_error and self.thread2_error)
|
||||||
|
error = self.thread1_error or self.thread2_error
|
||||||
|
self.assertTrue(isinstance(
|
||||||
|
error, psycopg2.extensions.TransactionRollbackError))
|
||||||
|
|
||||||
|
|
||||||
|
class QueryCancelationTests(unittest.TestCase):
|
||||||
|
"""Tests for query cancelation."""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.conn = psycopg2.connect("dbname=%s" % tests.dbname)
|
||||||
|
self.conn.set_isolation_level(ISOLATION_LEVEL_SERIALIZABLE)
|
||||||
|
|
||||||
|
def test_statement_timeout(self):
|
||||||
|
curs = self.conn.cursor()
|
||||||
|
# Set a low statement timeout, then sleep for a longer period.
|
||||||
|
curs.execute('SET statement_timeout TO 10')
|
||||||
|
self.assertRaises(psycopg2.extensions.QueryCanceledError,
|
||||||
|
curs.execute, 'SELECT pg_sleep(50)')
|
||||||
|
|
||||||
|
|
||||||
def test_suite():
|
def test_suite():
|
||||||
return unittest.TestLoader().loadTestsFromName(__name__)
|
return unittest.TestLoader().loadTestsFromName(__name__)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user