From c2d1f1f2e6832384ca01466cfbefecfa877e6850 Mon Sep 17 00:00:00 2001 From: Daniele Varrazzo Date: Fri, 3 Jun 2011 00:10:24 +0100 Subject: [PATCH] Dropped isolation level from the connection object Don't issue a SET TRANSACTION ISOLATION LEVEL at every begin: use PG's GUC default, eventually set by set_transaction. Dropped the last query at connection, yay! Method set_isolation_level() and property isolation_level refactored using the new structures, keeping the previous semantic. --- psycopg/connection.h | 18 ++-- psycopg/connection_int.c | 167 ++++++++++++++++++++++++++------------ psycopg/connection_type.c | 71 +++++++++------- psycopg/cursor_type.c | 2 +- psycopg/lobject.h | 2 +- psycopg/lobject_int.c | 2 +- psycopg/lobject_type.c | 4 +- psycopg/pqpath.c | 53 ++++-------- psycopg/python.h | 1 + 9 files changed, 188 insertions(+), 132 deletions(-) diff --git a/psycopg/connection.h b/psycopg/connection.h index a5aa267a..d79392bc 100644 --- a/psycopg/connection.h +++ b/psycopg/connection.h @@ -61,15 +61,6 @@ extern "C" { #define psyco_datestyle "SET DATESTYLE TO 'ISO'" #define psyco_transaction_isolation "SHOW default_transaction_isolation" -/* possible values for isolation_level */ -typedef enum { - ISOLATION_LEVEL_AUTOCOMMIT = 0, - ISOLATION_LEVEL_READ_UNCOMMITTED = 1, - ISOLATION_LEVEL_READ_COMMITTED = 2, - ISOLATION_LEVEL_REPEATABLE_READ = 3, - ISOLATION_LEVEL_SERIALIZABLE = 4, -} conn_isolation_level_t; - extern HIDDEN PyTypeObject connectionType; struct connectionObject_notice { @@ -89,7 +80,6 @@ typedef struct { long int closed; /* 1 means connection has been closed; 2 that something horrible happened */ - long int isolation_level; /* isolation level for this connection */ long int mark; /* number of commits/rollbacks done so far */ int status; /* status of the connection */ XidObject *tpc_xid; /* Transaction ID in two-phase commit */ @@ -123,10 +113,16 @@ typedef struct { } connectionObject; +/* map isolation level values into a numeric const */ +typedef struct { + char *name; + int value; +} IsolationLevel; + /* C-callable functions in connection_int.c and connection_ext.c */ HIDDEN PyObject *conn_text_from_chars(connectionObject *pgconn, const char *str); HIDDEN int conn_get_standard_conforming_strings(PGconn *pgconn); -HIDDEN int conn_get_isolation_level(PGresult *pgres); +HIDDEN int conn_get_isolation_level(connectionObject *self); HIDDEN int conn_get_protocol_version(PGconn *pgconn); HIDDEN int conn_get_server_version(PGconn *pgconn); HIDDEN PGcancel *conn_get_cancel(PGconn *pgconn); diff --git a/psycopg/connection_int.c b/psycopg/connection_int.c index 5e2ba023..de7083f7 100644 --- a/psycopg/connection_int.c +++ b/psycopg/connection_int.c @@ -34,6 +34,19 @@ #include +/* Mapping from isolation level name to value exposed by Python. + * Only used for backward compatibility by the isolation_level property */ + +const IsolationLevel conn_isolevels[] = { + {"", 0}, /* autocommit */ + {"read uncommitted", 1}, + {"read committed", 2}, + {"repeatable read", 3}, + {"serializable", 4}, + {"default", -1}, + { NULL } +}; + /* Return a new "string" from a char* from the database. * @@ -358,22 +371,60 @@ exit: return rv; } + int -conn_get_isolation_level(PGresult *pgres) +conn_get_isolation_level(connectionObject *self) { - static const char lvl1a[] = "read uncommitted"; - static const char lvl1b[] = "read committed"; - int rv; + PGresult *pgres; + int rv = -1; + char *lname; + const IsolationLevel *level; - char *isolation_level = PQgetvalue(pgres, 0, 0); + /* this may get called by async connections too: here's your result */ + if (self->autocommit) { + return 0; + } - if ((strcmp(lvl1b, isolation_level) == 0) /* most likely */ - || (strcmp(lvl1a, isolation_level) == 0)) - rv = ISOLATION_LEVEL_READ_COMMITTED; - else /* if it's not one of the lower ones, it's SERIALIZABLE */ - rv = ISOLATION_LEVEL_SERIALIZABLE; + Py_BEGIN_ALLOW_THREADS; + pthread_mutex_lock(&self->lock); + Py_BLOCK_THREADS; - CLEARPGRES(pgres); + if (!psyco_green()) { + Py_UNBLOCK_THREADS; + pgres = PQexec(self->pgconn, psyco_transaction_isolation); + Py_BLOCK_THREADS; + } else { + pgres = psyco_exec_green(self, psyco_transaction_isolation); + } + + if (pgres == NULL || PQresultStatus(pgres) != PGRES_TUPLES_OK) { + PyErr_SetString(OperationalError, + "can't fetch default_transaction_isolation"); + goto endlock; + } + + /* find the value for the requested isolation level */ + lname = PQgetvalue(pgres, 0, 0); + level = conn_isolevels; + while ((++level)->name) { + if (0 == strcasecmp(level->name, lname)) { + rv = level->value; + break; + } + } + + if (-1 == rv) { + char msg[256]; + snprintf(msg, sizeof(msg), "unexpected isolation level: '%s'", lname); + PyErr_SetString(OperationalError, msg); + } + +endlock: + IFCLEARPGRES(pgres); + + Py_UNBLOCK_THREADS; + pthread_mutex_unlock(&self->lock); + Py_END_ALLOW_THREADS; return rv; } @@ -477,24 +528,8 @@ conn_setup(connectionObject *self, PGconn *pgconn) CLEARPGRES(pgres); } - if (!green) { - Py_UNBLOCK_THREADS; - pgres = PQexec(pgconn, psyco_transaction_isolation); - Py_BLOCK_THREADS; - } else { - pgres = psyco_exec_green(self, psyco_transaction_isolation); - } - - if (pgres == NULL || PQresultStatus(pgres) != PGRES_TUPLES_OK) { - PyErr_SetString(OperationalError, - "can't fetch default_isolation_level"); - IFCLEARPGRES(pgres); - Py_UNBLOCK_THREADS; - pthread_mutex_unlock(&self->lock); - Py_BLOCK_THREADS; - return -1; - } - self->isolation_level = conn_get_isolation_level(pgres); + /* for reset */ + self->autocommit = 0; Py_UNBLOCK_THREADS; pthread_mutex_unlock(&self->lock); @@ -779,7 +814,7 @@ _conn_poll_setup_async(connectionObject *self) * expected to manage the transactions himself, by sending * (asynchronously) BEGIN and COMMIT statements. */ - self->isolation_level = ISOLATION_LEVEL_AUTOCOMMIT; + self->autocommit = 1; /* If the datestyle is ISO or anything else good, * we can skip the CONN_STATUS_DATESTYLE step. */ @@ -989,7 +1024,14 @@ conn_set(connectionObject *self, const char *param, const char *value) int conn_set_autocommit(connectionObject *self, int value) { + Py_BEGIN_ALLOW_THREADS; + pthread_mutex_lock(&self->lock); + self->autocommit = value; + + pthread_mutex_unlock(&self->lock); + Py_END_ALLOW_THREADS; + return 0; } @@ -998,33 +1040,54 @@ conn_set_autocommit(connectionObject *self, int value) int conn_switch_isolation_level(connectionObject *self, int level) { - PGresult *pgres = NULL; - char *error = NULL; - int res = 0; + int curr_level; - Py_BEGIN_ALLOW_THREADS; - 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 - transaction before changing; that all folks! */ - if (self->isolation_level != ISOLATION_LEVEL_AUTOCOMMIT) { - res = pq_abort_locked(self, &pgres, &error, &_save); - } - self->isolation_level = level; - - Dprintf("conn_switch_isolation_level: switched to level %d", level); + if (-1 == (curr_level = conn_get_isolation_level(self))) { + return -1; } - pthread_mutex_unlock(&self->lock); - Py_END_ALLOW_THREADS; + if (curr_level == level) { + /* no need to change level */ + return 0; + } - if (res < 0) - pq_complete_error(self, &pgres, &error); + /* Emulate the previous semantic of set_isolation_level() using the + * functions currently available. */ - return res; + /* terminate the current transaction if any */ + pq_abort(self); + + if (level == 0) { + if (0 != conn_set(self, "default_transaction_isolation", "default")) { + return -1; + } + if (0 != conn_set_autocommit(self, 1)) { + return -1; + } + } + else { + /* find the name of the requested level */ + const IsolationLevel *isolevel = conn_isolevels; + while ((++isolevel)->name) { + if (level == isolevel->value) { + break; + } + } + if (!isolevel->name) { + PyErr_SetString(OperationalError, "bad isolation level value"); + return -1; + } + + if (0 != conn_set(self, "default_transaction_isolation", isolevel->name)) { + return -1; + } + if (0 != conn_set_autocommit(self, 0)) { + return -1; + } + } + + Dprintf("conn_switch_isolation_level: switched to level %d", level); + return 0; } /* conn_set_client_encoding - switch client encoding on connection */ diff --git a/psycopg/connection_type.c b/psycopg/connection_type.c index 33a69555..455abdae 100644 --- a/psycopg/connection_type.c +++ b/psycopg/connection_type.c @@ -198,7 +198,7 @@ psyco_conn_tpc_begin(connectionObject *self, PyObject *args) } /* two phase commit and autocommit make no point */ - if (self->isolation_level == ISOLATION_LEVEL_AUTOCOMMIT) { + if (self->autocommit) { PyErr_SetString(ProgrammingError, "tpc_begin can't be called in autocommit mode"); goto exit; @@ -381,50 +381,51 @@ psyco_conn_tpc_recover(connectionObject *self, PyObject *args) /* parse a python object into one of the possible isolation level values */ +extern const IsolationLevel conn_isolevels[]; + static const char * _psyco_conn_parse_isolevel(PyObject *pyval) { - const char **value = NULL; + const char *rv = NULL; + const IsolationLevel *value = NULL; - static const char *isolevels[] = { - "", /* autocommit */ - "read uncommitted", - "read committed", - "repeatable read", - "serializable", - "default", - NULL }; + Py_INCREF(pyval); /* for ensure_bytes */ /* parse from one of the level constants */ if (PyInt_Check(pyval)) { long level = PyInt_AsLong(pyval); - if (level == -1 && PyErr_Occurred()) { return NULL; } + if (level == -1 && PyErr_Occurred()) { goto exit; } if (level < 1 || level > 4) { PyErr_SetString(PyExc_ValueError, "isolation_level must be between 1 and 4"); - return NULL; + goto exit; } - value = isolevels + level; + rv = conn_isolevels[level].name; } /* parse from the string -- this includes "default" */ else { - value = isolevels; - while (*(++value)) { - int cmp; - PyObject *pylevel; - if (!(pylevel = Text_FromUTF8(*value))) { return NULL; } - cmp = PyObject_RichCompareBool(pylevel, pyval, Py_EQ); - Py_DECREF(pylevel); - if (-1 == cmp) { return NULL; } - if (cmp) { break; } + value = conn_isolevels; + while ((++value)->name) { + if (!(pyval = psycopg_ensure_bytes(pyval))) { + goto exit; + } + if (0 == strcasecmp(value->name, Bytes_AS_STRING(pyval))) { + rv = value->name; + break; + } } - if (!*value) { - PyErr_SetString(PyExc_ValueError, - "bad value for isolation_level"); /* TODO: emit value */ + if (!rv) { + char msg[256]; + snprintf(msg, sizeof(msg), + "bad value for isolation_level: '%s'", Bytes_AS_STRING(pyval)); + PyErr_SetString(PyExc_ValueError, msg); } } - return *value; + +exit: + Py_XDECREF(pyval); + return rv; } /* convert True/False/"default" into a C string */ @@ -553,6 +554,17 @@ psyco_conn_autocommit_set(connectionObject *self, PyObject *pyvalue) } +/* isolation_level - return the current isolation level */ + +static PyObject * +psyco_conn_isolation_level_get(connectionObject *self) +{ + int rv = conn_get_isolation_level(self); + if (-1 == rv) { return NULL; } + return PyInt_FromLong((long)rv); +} + + /* set_isolation_level method - switch connection isolation level */ #define psyco_conn_set_isolation_level_doc \ @@ -920,9 +932,6 @@ static struct PyMemberDef connectionObject_members[] = { #ifdef PSYCOPG_EXTENSIONS {"closed", T_LONG, offsetof(connectionObject, closed), READONLY, "True if the connection is closed."}, - {"isolation_level", T_LONG, - offsetof(connectionObject, isolation_level), READONLY, - "The current isolation level."}, {"encoding", T_STRING, offsetof(connectionObject, encoding), READONLY, "The current client encoding."}, {"notices", T_OBJECT, offsetof(connectionObject, notice_list), READONLY}, @@ -968,6 +977,10 @@ static struct PyGetSetDef connectionObject_getsets[] = { (getter)psyco_conn_autocommit_get, (setter)psyco_conn_autocommit_set, psyco_conn_autocommit_doc }, + { "isolation_level", + (getter)psyco_conn_isolation_level_get, + (setter)NULL, + "The current isolation level." }, #endif {NULL} }; diff --git a/psycopg/cursor_type.c b/psycopg/cursor_type.c index d166de73..8ede845f 100644 --- a/psycopg/cursor_type.c +++ b/psycopg/cursor_type.c @@ -456,7 +456,7 @@ psyco_curs_execute(cursorObject *self, PyObject *args, PyObject *kwargs) NULL, NULL); return NULL; } - if (self->conn->isolation_level == ISOLATION_LEVEL_AUTOCOMMIT) { + if (self->conn->autocommit) { psyco_set_error(ProgrammingError, self, "can't use a named cursor outside of transactions", NULL, NULL); return NULL; diff --git a/psycopg/lobject.h b/psycopg/lobject.h index 84fd3b52..f52d85cf 100644 --- a/psycopg/lobject.h +++ b/psycopg/lobject.h @@ -76,7 +76,7 @@ HIDDEN int lobject_close(lobjectObject *self); return NULL; } #define EXC_IF_LOBJ_LEVEL0(self) \ -if (self->conn->isolation_level == 0) { \ +if (self->conn->autocommit) { \ psyco_set_error(ProgrammingError, NULL, \ "can't use a lobject outside of transactions", NULL, NULL); \ return NULL; \ diff --git a/psycopg/lobject_int.c b/psycopg/lobject_int.c index 3fe1f86e..e6ad1b6c 100644 --- a/psycopg/lobject_int.c +++ b/psycopg/lobject_int.c @@ -252,7 +252,7 @@ lobject_close_locked(lobjectObject *self, char **error) break; } - if (self->conn->isolation_level == ISOLATION_LEVEL_AUTOCOMMIT || + if (self->conn->autocommit || self->conn->mark != self->mark || self->fd == -1) return 0; diff --git a/psycopg/lobject_type.c b/psycopg/lobject_type.c index ba45de2d..a55272ca 100644 --- a/psycopg/lobject_type.c +++ b/psycopg/lobject_type.c @@ -51,7 +51,7 @@ psyco_lobj_close(lobjectObject *self, PyObject *args) closing the current transaction is equivalent to close all the opened large objects */ if (!lobject_is_closed(self) - && self->conn->isolation_level != ISOLATION_LEVEL_AUTOCOMMIT + && !self->conn->autocommit && self->conn->mark == self->mark) { Dprintf("psyco_lobj_close: closing lobject at %p", self); @@ -331,7 +331,7 @@ lobject_setup(lobjectObject *self, connectionObject *conn, { Dprintf("lobject_setup: init lobject object at %p", self); - if (conn->isolation_level == ISOLATION_LEVEL_AUTOCOMMIT) { + if (conn->autocommit) { psyco_set_error(ProgrammingError, NULL, "can't use a lobject outside of transactions", NULL, NULL); return -1; diff --git a/psycopg/pqpath.c b/psycopg/pqpath.c index f945448c..a188debc 100644 --- a/psycopg/pqpath.c +++ b/psycopg/pqpath.c @@ -406,30 +406,17 @@ int pq_begin_locked(connectionObject *conn, PGresult **pgres, char **error, PyThreadState **tstate) { - const char *query[] = { - NULL, - "BEGIN; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED", - "BEGIN; SET TRANSACTION ISOLATION LEVEL READ COMMITTED", - "BEGIN; SET TRANSACTION ISOLATION LEVEL REPEATABLE READ", - "BEGIN; SET TRANSACTION ISOLATION LEVEL SERIALIZABLE"}; int result; - Dprintf("pq_begin_locked: pgconn = %p, isolevel = %ld, status = %d", - conn->pgconn, conn->isolation_level, conn->status); + Dprintf("pq_begin_locked: pgconn = %p, autocommit = %d, status = %d", + conn->pgconn, conn->autocommit, conn->status); - if (conn->autocommit) { - Dprintf("pq_begin_locked: autocommit"); - return 0; - } - - if (conn->isolation_level == ISOLATION_LEVEL_AUTOCOMMIT - || conn->status != CONN_STATUS_READY) { + if (conn->autocommit || conn->status != CONN_STATUS_READY) { Dprintf("pq_begin_locked: transaction in progress"); return 0; } - result = pq_execute_command_locked(conn, query[conn->isolation_level], - pgres, error, tstate); + result = pq_execute_command_locked(conn, "BEGIN;", pgres, error, tstate); if (result == 0) conn->status = CONN_STATUS_BEGIN; @@ -449,11 +436,10 @@ pq_commit(connectionObject *conn) PGresult *pgres = NULL; char *error = NULL; - Dprintf("pq_commit: pgconn = %p, isolevel = %ld, status = %d", - conn->pgconn, conn->isolation_level, conn->status); + Dprintf("pq_commit: pgconn = %p, autocommit = %d, status = %d", + conn->pgconn, conn->autocommit, conn->status); - if (conn->isolation_level == ISOLATION_LEVEL_AUTOCOMMIT - || conn->status != CONN_STATUS_BEGIN) { + if (conn->autocommit || conn->status != CONN_STATUS_BEGIN) { Dprintf("pq_commit: no transaction to commit"); return 0; } @@ -485,11 +471,10 @@ pq_abort_locked(connectionObject *conn, PGresult **pgres, char **error, { int retvalue = -1; - Dprintf("pq_abort_locked: pgconn = %p, isolevel = %ld, status = %d", - conn->pgconn, conn->isolation_level, conn->status); + Dprintf("pq_abort_locked: pgconn = %p, autocommit = %d, status = %d", + conn->pgconn, conn->autocommit, conn->status); - if (conn->isolation_level == ISOLATION_LEVEL_AUTOCOMMIT - || conn->status != CONN_STATUS_BEGIN) { + if (conn->autocommit || conn->status != CONN_STATUS_BEGIN) { Dprintf("pq_abort_locked: no transaction to abort"); return 0; } @@ -514,11 +499,10 @@ pq_abort(connectionObject *conn) PGresult *pgres = NULL; char *error = NULL; - Dprintf("pq_abort: pgconn = %p, isolevel = %ld, status = %d", - conn->pgconn, conn->isolation_level, conn->status); + Dprintf("pq_abort: pgconn = %p, autocommit = %d, status = %d", + conn->pgconn, conn->autocommit, conn->status); - if (conn->isolation_level == ISOLATION_LEVEL_AUTOCOMMIT - || conn->status != CONN_STATUS_BEGIN) { + if (conn->autocommit || conn->status != CONN_STATUS_BEGIN) { Dprintf("pq_abort: no transaction to abort"); return 0; } @@ -554,13 +538,12 @@ pq_reset_locked(connectionObject *conn, PGresult **pgres, char **error, { int retvalue = -1; - Dprintf("pq_reset_locked: pgconn = %p, isolevel = %ld, status = %d", - conn->pgconn, conn->isolation_level, conn->status); + Dprintf("pq_reset_locked: pgconn = %p, autocommit = %d, status = %d", + conn->pgconn, conn->autocommit, conn->status); conn->mark += 1; - if (conn->isolation_level != ISOLATION_LEVEL_AUTOCOMMIT - && conn->status == CONN_STATUS_BEGIN) { + if (!conn->autocommit && conn->status == CONN_STATUS_BEGIN) { retvalue = pq_execute_command_locked(conn, "ABORT", pgres, error, tstate); if (retvalue != 0) return retvalue; } @@ -585,8 +568,8 @@ pq_reset(connectionObject *conn) PGresult *pgres = NULL; char *error = NULL; - Dprintf("pq_reset: pgconn = %p, isolevel = %ld, status = %d", - conn->pgconn, conn->isolation_level, conn->status); + Dprintf("pq_reset: pgconn = %p, autocommit = %d, status = %d", + conn->pgconn, conn->autocommit, conn->status); Py_BEGIN_ALLOW_THREADS; pthread_mutex_lock(&conn->lock); diff --git a/psycopg/python.h b/psycopg/python.h index fed0303e..7d3f8bae 100644 --- a/psycopg/python.h +++ b/psycopg/python.h @@ -105,6 +105,7 @@ typedef unsigned long Py_uhash_t; #if PY_MAJOR_VERSION > 2 #define PyInt_Type PyLong_Type +#define PyInt_Check PyLong_Check #define PyInt_AsLong PyLong_AsLong #define PyInt_FromLong PyLong_FromLong #define PyInt_FromSsize_t PyLong_FromSsize_t