Added enum with possilbe isolation level states.

Also, general isolation levels cleanup and tests added.
This commit is contained in:
Daniele Varrazzo 2010-11-18 00:20:39 +00:00
parent 4074635629
commit bcacdc8461
9 changed files with 133 additions and 40 deletions

View File

@ -1,3 +1,8 @@
2010-11-18 Daniele Varrazzo <daniele.varrazzo@gmail.com>
* psycopg/connection.h: Added enum with possilbe isolation level states.
Also, general isolation levels cleanup and tests added.
2010-11-17 Daniele Varrazzo <daniele.varrazzo@gmail.com> 2010-11-17 Daniele Varrazzo <daniele.varrazzo@gmail.com>
* psycopg/connection_type.c: don't clobber exception if * psycopg/connection_type.c: don't clobber exception if

View File

@ -66,6 +66,13 @@ extern "C" {
#define psyco_datestyle "SET DATESTYLE TO 'ISO'" #define psyco_datestyle "SET DATESTYLE TO 'ISO'"
#define psyco_transaction_isolation "SHOW default_transaction_isolation" #define psyco_transaction_isolation "SHOW default_transaction_isolation"
/* possible values for isolation_level */
typedef enum {
ISOLATION_LEVEL_AUTOCOMMIT = 0,
ISOLATION_LEVEL_READ_COMMITTED = 1,
ISOLATION_LEVEL_SERIALIZABLE = 2,
} conn_isolation_level_t;
extern HIDDEN PyTypeObject connectionType; extern HIDDEN PyTypeObject connectionType;
struct connectionObject_notice { struct connectionObject_notice {

View File

@ -265,11 +265,11 @@ conn_get_isolation_level(PGresult *pgres)
char *isolation_level = PQgetvalue(pgres, 0, 0); char *isolation_level = PQgetvalue(pgres, 0, 0);
if ((strncmp(lvl1a, isolation_level, strlen(isolation_level)) == 0) if ((strcmp(lvl1b, isolation_level) == 0) /* most likely */
|| (strncmp(lvl1b, isolation_level, strlen(isolation_level)) == 0)) || (strcmp(lvl1a, isolation_level) == 0))
rv = 1; rv = ISOLATION_LEVEL_READ_COMMITTED;
else /* if it's not one of the lower ones, it's SERIALIZABLE */ else /* if it's not one of the lower ones, it's SERIALIZABLE */
rv = 2; rv = ISOLATION_LEVEL_SERIALIZABLE;
CLEARPGRES(pgres); CLEARPGRES(pgres);
@ -659,7 +659,7 @@ _conn_poll_setup_async(connectionObject *self)
* expected to manage the transactions himself, by sending * expected to manage the transactions himself, by sending
* (asynchronously) BEGIN and COMMIT statements. * (asynchronously) BEGIN and COMMIT statements.
*/ */
self->isolation_level = 0; self->isolation_level = ISOLATION_LEVEL_AUTOCOMMIT;
/* If the datestyle is ISO or anything else good, /* If the datestyle is ISO or anything else good,
* we can skip the CONN_STATUS_DATESTYLE step. */ * we can skip the CONN_STATUS_DATESTYLE step. */
@ -830,20 +830,21 @@ conn_switch_isolation_level(connectionObject *self, int level)
char *error = NULL; char *error = NULL;
int res = 0; int res = 0;
/* if the current isolation level is equal to the requested one don't switch */
if (self->isolation_level == level) return 0;
Py_BEGIN_ALLOW_THREADS; Py_BEGIN_ALLOW_THREADS;
pthread_mutex_lock(&self->lock); pthread_mutex_lock(&self->lock);
/* if the current isolation level is equal to the requested one don't switch */
if (self->isolation_level != level) {
/* if the current isolation level is > 0 we need to abort the current /* if the current isolation level is > 0 we need to abort the current
transaction before changing; that all folks! */ transaction before changing; that all folks! */
if (self->isolation_level != level && self->isolation_level > 0) { if (self->isolation_level != ISOLATION_LEVEL_AUTOCOMMIT) {
res = pq_abort_locked(self, &pgres, &error, &_save); res = pq_abort_locked(self, &pgres, &error, &_save);
} }
self->isolation_level = level; self->isolation_level = level;
Dprintf("conn_switch_isolation_level: switched to level %d", level); Dprintf("conn_switch_isolation_level: switched to level %d", level);
}
pthread_mutex_unlock(&self->lock); pthread_mutex_unlock(&self->lock);
Py_END_ALLOW_THREADS; Py_END_ALLOW_THREADS;

View File

@ -209,7 +209,7 @@ psyco_conn_tpc_begin(connectionObject *self, PyObject *args)
} }
/* two phase commit and autocommit make no point */ /* two phase commit and autocommit make no point */
if (self->isolation_level == 0) { if (self->isolation_level == ISOLATION_LEVEL_AUTOCOMMIT) {
PyErr_SetString(ProgrammingError, PyErr_SetString(ProgrammingError,
"tpc_begin can't be called in autocommit mode"); "tpc_begin can't be called in autocommit mode");
goto exit; goto exit;
@ -410,7 +410,7 @@ psyco_conn_set_isolation_level(connectionObject *self, PyObject *args)
if (level < 0 || level > 2) { if (level < 0 || level > 2) {
PyErr_SetString(PyExc_ValueError, PyErr_SetString(PyExc_ValueError,
"isolation level out of bounds (0,3)"); "isolation level must be between 0 and 2");
return NULL; return NULL;
} }

View File

@ -476,7 +476,7 @@ psyco_curs_execute(cursorObject *self, PyObject *args, PyObject *kwargs)
NULL, NULL); NULL, NULL);
return NULL; return NULL;
} }
if (self->conn->isolation_level == 0) { if (self->conn->isolation_level == ISOLATION_LEVEL_AUTOCOMMIT) {
psyco_set_error(ProgrammingError, (PyObject*)self, psyco_set_error(ProgrammingError, (PyObject*)self,
"can't use a named cursor outside of transactions", NULL, NULL); "can't use a named cursor outside of transactions", NULL, NULL);
return NULL; return NULL;

View File

@ -129,7 +129,7 @@ lobject_close_locked(lobjectObject *self, char **error)
{ {
int retvalue; int retvalue;
if (self->conn->isolation_level == 0 || if (self->conn->isolation_level == ISOLATION_LEVEL_AUTOCOMMIT ||
self->conn->mark != self->mark || self->conn->mark != self->mark ||
self->fd == -1) self->fd == -1)
return 0; return 0;

View File

@ -55,7 +55,7 @@ psyco_lobj_close(lobjectObject *self, PyObject *args)
closing the current transaction is equivalent to close all the closing the current transaction is equivalent to close all the
opened large objects */ opened large objects */
if (!lobject_is_closed(self) if (!lobject_is_closed(self)
&& self->conn->isolation_level > 0 && self->conn->isolation_level != ISOLATION_LEVEL_AUTOCOMMIT
&& self->conn->mark == self->mark) && self->conn->mark == self->mark)
{ {
Dprintf("psyco_lobj_close: closing lobject at %p", self); Dprintf("psyco_lobj_close: closing lobject at %p", self);
@ -300,7 +300,7 @@ lobject_setup(lobjectObject *self, connectionObject *conn,
{ {
Dprintf("lobject_setup: init lobject object at %p", self); Dprintf("lobject_setup: init lobject object at %p", self);
if (conn->isolation_level == 0) { if (conn->isolation_level == ISOLATION_LEVEL_AUTOCOMMIT) {
psyco_set_error(ProgrammingError, (PyObject*)self, psyco_set_error(ProgrammingError, (PyObject*)self,
"can't use a lobject outside of transactions", NULL, NULL); "can't use a lobject outside of transactions", NULL, NULL);
return -1; return -1;

View File

@ -409,7 +409,8 @@ pq_begin_locked(connectionObject *conn, PGresult **pgres, char **error,
Dprintf("pq_begin_locked: pgconn = %p, isolevel = %ld, status = %d", Dprintf("pq_begin_locked: pgconn = %p, isolevel = %ld, status = %d",
conn->pgconn, conn->isolation_level, conn->status); conn->pgconn, conn->isolation_level, conn->status);
if (conn->isolation_level == 0 || conn->status != CONN_STATUS_READY) { if (conn->isolation_level == ISOLATION_LEVEL_AUTOCOMMIT
|| conn->status != CONN_STATUS_READY) {
Dprintf("pq_begin_locked: transaction in progress"); Dprintf("pq_begin_locked: transaction in progress");
return 0; return 0;
} }
@ -438,7 +439,8 @@ pq_commit(connectionObject *conn)
Dprintf("pq_commit: pgconn = %p, isolevel = %ld, status = %d", Dprintf("pq_commit: pgconn = %p, isolevel = %ld, status = %d",
conn->pgconn, conn->isolation_level, conn->status); conn->pgconn, conn->isolation_level, conn->status);
if (conn->isolation_level == 0 || conn->status != CONN_STATUS_BEGIN) { if (conn->isolation_level == ISOLATION_LEVEL_AUTOCOMMIT
|| conn->status != CONN_STATUS_BEGIN) {
Dprintf("pq_commit: no transaction to commit"); Dprintf("pq_commit: no transaction to commit");
return 0; return 0;
} }
@ -473,7 +475,8 @@ pq_abort_locked(connectionObject *conn, PGresult **pgres, char **error,
Dprintf("pq_abort_locked: pgconn = %p, isolevel = %ld, status = %d", Dprintf("pq_abort_locked: pgconn = %p, isolevel = %ld, status = %d",
conn->pgconn, conn->isolation_level, conn->status); conn->pgconn, conn->isolation_level, conn->status);
if (conn->isolation_level == 0 || conn->status != CONN_STATUS_BEGIN) { if (conn->isolation_level == ISOLATION_LEVEL_AUTOCOMMIT
|| conn->status != CONN_STATUS_BEGIN) {
Dprintf("pq_abort_locked: no transaction to abort"); Dprintf("pq_abort_locked: no transaction to abort");
return 0; return 0;
} }
@ -501,7 +504,8 @@ pq_abort(connectionObject *conn)
Dprintf("pq_abort: pgconn = %p, isolevel = %ld, status = %d", Dprintf("pq_abort: pgconn = %p, isolevel = %ld, status = %d",
conn->pgconn, conn->isolation_level, conn->status); conn->pgconn, conn->isolation_level, conn->status);
if (conn->isolation_level == 0 || conn->status != CONN_STATUS_BEGIN) { if (conn->isolation_level == ISOLATION_LEVEL_AUTOCOMMIT
|| conn->status != CONN_STATUS_BEGIN) {
Dprintf("pq_abort: no transaction to abort"); Dprintf("pq_abort: no transaction to abort");
return 0; return 0;
} }
@ -542,7 +546,8 @@ pq_reset_locked(connectionObject *conn, PGresult **pgres, char **error,
conn->mark += 1; conn->mark += 1;
if (conn->isolation_level > 0 && conn->status == CONN_STATUS_BEGIN) { if (conn->isolation_level != ISOLATION_LEVEL_AUTOCOMMIT
&& conn->status == CONN_STATUS_BEGIN) {
retvalue = pq_execute_command_locked(conn, "ABORT", pgres, error, tstate); retvalue = pq_execute_command_locked(conn, "ABORT", pgres, error, tstate);
if (retvalue != 0) return retvalue; if (retvalue != 0) return retvalue;
} }

View File

@ -82,6 +82,20 @@ class ConnectionTests(unittest.TestCase):
conn = self.connect() conn = self.connect()
self.assert_(conn.protocol_version in (2,3), conn.protocol_version) self.assert_(conn.protocol_version in (2,3), conn.protocol_version)
class IsolationLevelsTestCase(unittest.TestCase):
def setUp(self):
conn = self.connect()
cur = conn.cursor()
cur.execute("drop table if exists isolevel;")
cur.execute("create table isolevel (id integer);")
conn.commit()
conn.close()
def connect(self):
return psycopg2.connect(tests.dsn)
def test_isolation_level(self): def test_isolation_level(self):
conn = self.connect() conn = self.connect()
self.assertEqual( self.assertEqual(
@ -92,22 +106,78 @@ class ConnectionTests(unittest.TestCase):
conn = self.connect() conn = self.connect()
self.assert_(conn.encoding in psycopg2.extensions.encodings) self.assert_(conn.encoding in psycopg2.extensions.encodings)
def test_set_isolation_level(self):
conn = self.connect()
conn.set_isolation_level(
psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT)
self.assertEqual(conn.isolation_level,
psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT)
conn.set_isolation_level(
psycopg2.extensions.ISOLATION_LEVEL_READ_COMMITTED)
self.assertEqual(conn.isolation_level,
psycopg2.extensions.ISOLATION_LEVEL_READ_COMMITTED)
conn.set_isolation_level(
psycopg2.extensions.ISOLATION_LEVEL_SERIALIZABLE)
self.assertEqual(conn.isolation_level,
psycopg2.extensions.ISOLATION_LEVEL_SERIALIZABLE)
self.assertRaises(ValueError, conn.set_isolation_level, -1)
self.assertRaises(ValueError, conn.set_isolation_level, 3)
def test_set_isolation_level_abort(self):
conn = self.connect()
cur = conn.cursor()
self.assertEqual(psycopg2.extensions.TRANSACTION_STATUS_IDLE,
conn.get_transaction_status())
cur.execute("insert into isolevel values (10);")
self.assertEqual(psycopg2.extensions.TRANSACTION_STATUS_INTRANS,
conn.get_transaction_status())
conn.set_isolation_level(
psycopg2.extensions.ISOLATION_LEVEL_SERIALIZABLE)
self.assertEqual(psycopg2.extensions.TRANSACTION_STATUS_IDLE,
conn.get_transaction_status())
cur.execute("select count(*) from isolevel;")
self.assertEqual(0, cur.fetchone()[0])
cur.execute("insert into isolevel values (10);")
self.assertEqual(psycopg2.extensions.TRANSACTION_STATUS_INTRANS,
conn.get_transaction_status())
conn.set_isolation_level(
psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT)
self.assertEqual(psycopg2.extensions.TRANSACTION_STATUS_IDLE,
conn.get_transaction_status())
cur.execute("select count(*) from isolevel;")
self.assertEqual(0, cur.fetchone()[0])
cur.execute("insert into isolevel values (10);")
self.assertEqual(psycopg2.extensions.TRANSACTION_STATUS_IDLE,
conn.get_transaction_status())
conn.set_isolation_level(
psycopg2.extensions.ISOLATION_LEVEL_READ_COMMITTED)
self.assertEqual(psycopg2.extensions.TRANSACTION_STATUS_IDLE,
conn.get_transaction_status())
cur.execute("select count(*) from isolevel;")
self.assertEqual(1, cur.fetchone()[0])
def test_isolation_level_autocommit(self): def test_isolation_level_autocommit(self):
cnn1 = self.connect() cnn1 = self.connect()
cnn2 = self.connect() cnn2 = self.connect()
cnn2.set_isolation_level(psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT) cnn2.set_isolation_level(psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT)
cur1 = cnn1.cursor() cur1 = cnn1.cursor()
cur1.execute("drop table if exists isolevel;")
cur1.execute("create table isolevel (id integer);")
cur1.execute("select count(*) from isolevel;") cur1.execute("select count(*) from isolevel;")
self.assertEqual(0, cur1.fetchone()[0]) self.assertEqual(0, cur1.fetchone()[0])
cnn1.commit() cnn1.commit()
cur2 = cnn2.cursor() cur2 = cnn2.cursor()
cur2.execute("insert into isolevel values (10);") cur2.execute("insert into isolevel values (10);")
cur1.execute("select count(*) from isolevel;")
cur1.execute("select count(*) from isolevel;")
self.assertEqual(1, cur1.fetchone()[0]) self.assertEqual(1, cur1.fetchone()[0])
def test_isolation_level_read_committed(self): def test_isolation_level_read_committed(self):
@ -116,47 +186,52 @@ class ConnectionTests(unittest.TestCase):
cnn2.set_isolation_level(psycopg2.extensions.ISOLATION_LEVEL_READ_COMMITTED) cnn2.set_isolation_level(psycopg2.extensions.ISOLATION_LEVEL_READ_COMMITTED)
cur1 = cnn1.cursor() cur1 = cnn1.cursor()
cur1.execute("drop table if exists isolevel;")
cur1.execute("create table isolevel (id integer);")
cur1.execute("select count(*) from isolevel;") cur1.execute("select count(*) from isolevel;")
self.assertEqual(0, cur1.fetchone()[0]) self.assertEqual(0, cur1.fetchone()[0])
cnn1.commit() cnn1.commit()
cur2 = cnn2.cursor() cur2 = cnn2.cursor()
cur2.execute("insert into isolevel values (10);") cur2.execute("insert into isolevel values (10);")
cur1.execute("insert into isolevel values (20);")
cur2.execute("select count(*) from isolevel;") cur2.execute("select count(*) from isolevel;")
self.assertEqual(1, cur2.fetchone()[0]) self.assertEqual(1, cur2.fetchone()[0])
cur1.execute("insert into isolevel values (20);")
cnn1.commit() cnn1.commit()
cur2.execute("select count(*) from isolevel;") cur2.execute("select count(*) from isolevel;")
self.assertEqual(2, cur2.fetchone()[0]) self.assertEqual(2, cur2.fetchone()[0])
cur1.execute("select count(*) from isolevel;")
self.assertEqual(1, cur1.fetchone()[0])
cnn2.commit()
cur1.execute("select count(*) from isolevel;")
self.assertEqual(2, cur1.fetchone()[0])
def test_isolation_level_serializable(self): def test_isolation_level_serializable(self):
cnn1 = self.connect() cnn1 = self.connect()
cnn2 = self.connect() cnn2 = self.connect()
cnn2.set_isolation_level(psycopg2.extensions.ISOLATION_LEVEL_SERIALIZABLE) cnn2.set_isolation_level(psycopg2.extensions.ISOLATION_LEVEL_SERIALIZABLE)
cur1 = cnn1.cursor() cur1 = cnn1.cursor()
cur1.execute("drop table if exists isolevel;")
cur1.execute("create table isolevel (id integer);")
cur1.execute("select count(*) from isolevel;") cur1.execute("select count(*) from isolevel;")
self.assertEqual(0, cur1.fetchone()[0]) self.assertEqual(0, cur1.fetchone()[0])
cnn1.commit() cnn1.commit()
cur2 = cnn2.cursor() cur2 = cnn2.cursor()
cur2.execute("insert into isolevel values (10);") cur2.execute("insert into isolevel values (10);")
cur2.execute("select count(*) from isolevel;")
self.assertEqual(1, cur2.fetchone()[0])
cur1.execute("insert into isolevel values (20);") cur1.execute("insert into isolevel values (20);")
cnn1.commit()
cur2.execute("select count(*) from isolevel;")
self.assertEqual(1, cur2.fetchone()[0])
cnn1.commit()
cur2.execute("select count(*) from isolevel;") cur2.execute("select count(*) from isolevel;")
self.assertEqual(1, cur2.fetchone()[0]) self.assertEqual(1, cur2.fetchone()[0])
cur1.execute("select count(*) from isolevel;")
self.assertEqual(1, cur1.fetchone()[0])
cnn2.commit() cnn2.commit()
cur1.execute("select count(*) from isolevel;")
self.assertEqual(2, cur1.fetchone()[0])
cur2.execute("select count(*) from isolevel;") cur2.execute("select count(*) from isolevel;")
self.assertEqual(2, cur2.fetchone()[0]) self.assertEqual(2, cur2.fetchone()[0])