summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--psycopg/connection.h18
-rw-r--r--psycopg/connection_int.c163
-rw-r--r--psycopg/connection_type.c71
-rw-r--r--psycopg/cursor_type.c2
-rw-r--r--psycopg/lobject.h2
-rw-r--r--psycopg/lobject_int.c2
-rw-r--r--psycopg/lobject_type.c4
-rw-r--r--psycopg/pqpath.c53
-rw-r--r--psycopg/python.h1
9 files changed, 186 insertions, 130 deletions
diff --git a/psycopg/connection.h b/psycopg/connection.h
index a5aa267..d79392b 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 5e2ba02..de7083f 100644
--- a/psycopg/connection_int.c
+++ b/psycopg/connection_int.c
@@ -34,6 +34,19 @@
#include <string.h>
+/* 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;
+
+ /* this may get called by async connections too: here's your result */
+ if (self->autocommit) {
+ return 0;
+ }
+
+ Py_BEGIN_ALLOW_THREADS;
+ pthread_mutex_lock(&self->lock);
+ Py_BLOCK_THREADS;
+
+ 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;
+ }
- char *isolation_level = PQgetvalue(pgres, 0, 0);
+ /* 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 ((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;
+ if (-1 == rv) {
+ char msg[256];
+ snprintf(msg, sizeof(msg), "unexpected isolation level: '%s'", lname);
+ PyErr_SetString(OperationalError, msg);
+ }
- CLEARPGRES(pgres);
+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 (-1 == (curr_level = conn_get_isolation_level(self))) {
+ return -1;
+ }
- /* if the current isolation level is equal to the requested one don't switch */
- if (self->isolation_level != level) {
+ if (curr_level == level) {
+ /* no need to change level */
+ return 0;
+ }
- /* 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;
+ /* Emulate the previous semantic of set_isolation_level() using the
+ * functions currently available. */
- Dprintf("conn_switch_isolation_level: switched to level %d", level);
- }
+ /* terminate the current transaction if any */
+ pq_abort(self);
- pthread_mutex_unlock(&self->lock);
- Py_END_ALLOW_THREADS;
+ 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 (res < 0)
- pq_complete_error(self, &pgres, &error);
+ if (0 != conn_set(self, "default_transaction_isolation", isolevel->name)) {
+ return -1;
+ }
+ if (0 != conn_set_autocommit(self, 0)) {
+ return -1;
+ }
+ }
- return res;
+ 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 33a6955..455abda 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 d166de7..8ede845 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 84fd3b5..f52d85c 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 3fe1f86..e6ad1b6 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 ba45de2..a55272c 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 f945448..a188deb 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 fed0303..7d3f8ba 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