summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniele Varrazzo <daniele.varrazzo@gmail.com>2017-02-04 02:25:48 +0000
committerDaniele Varrazzo <daniele.varrazzo@gmail.com>2017-02-04 02:38:56 +0000
commitc1e016e597e1ee2b52e5277a09793caa9401f39d (patch)
tree3719e64a5efcdbe66652e19a4e38d531ef2ba479
parent9863637f309e1e89523007c40e9dcdde69f60bc0 (diff)
downloadpsycopg2-c1e016e597e1ee2b52e5277a09793caa9401f39d.tar.gz
Don't use default_transaction_* for session characteristics
Store the state in the connection object and set the params on BEGIN Some tests fail: a few can be fixed reading transaction_* instead of default_transaction_*; but the behaviour of tx characteristics with autocommit is effectively changed. It may be addressed by setting default_transaction_* if autocommit is set.
-rw-r--r--psycopg/connection.h16
-rw-r--r--psycopg/connection_int.c280
-rw-r--r--psycopg/connection_type.c107
-rw-r--r--psycopg/pqpath.c15
4 files changed, 159 insertions, 259 deletions
diff --git a/psycopg/connection.h b/psycopg/connection.h
index 2e2d51d..6c39263 100644
--- a/psycopg/connection.h
+++ b/psycopg/connection.h
@@ -38,6 +38,12 @@ extern "C" {
#define ISOLATION_LEVEL_READ_COMMITTED 1
#define ISOLATION_LEVEL_REPEATABLE_READ 2
#define ISOLATION_LEVEL_SERIALIZABLE 3
+#define ISOLATION_LEVEL_DEFAULT 5
+
+/* 3-state values on/off/default */
+#define STATE_OFF 0
+#define STATE_ON 1
+#define STATE_DEFAULT 2
/* connection status */
#define CONN_STATUS_SETUP 0
@@ -129,6 +135,11 @@ struct connectionObject {
* codecs.getdecoder('utf8') */
PyObject *pyencoder; /* python codec encoding function */
PyObject *pydecoder; /* python codec decoding function */
+
+ /* Values for the transactions characteristics */
+ int isolevel;
+ int readonly;
+ int deferrable;
};
/* map isolation level values into a numeric const */
@@ -155,10 +166,9 @@ HIDDEN void conn_close(connectionObject *self);
HIDDEN void conn_close_locked(connectionObject *self);
RAISES_NEG HIDDEN int conn_commit(connectionObject *self);
RAISES_NEG HIDDEN int conn_rollback(connectionObject *self);
-RAISES_NEG HIDDEN int conn_set_session(connectionObject *self, const char *isolevel,
- const char *readonly, const char *deferrable,
- int autocommit);
HIDDEN int conn_set_autocommit(connectionObject *self, int value);
+RAISES_NEG HIDDEN int conn_parse_isolevel(connectionObject *self, PyObject *pyval);
+RAISES_NEG HIDDEN int conn_parse_onoff(PyObject *pyval);
RAISES_NEG HIDDEN int conn_switch_isolation_level(connectionObject *self, int level);
RAISES_NEG HIDDEN int conn_set_client_encoding(connectionObject *self, const char *enc);
HIDDEN int conn_poll(connectionObject *self);
diff --git a/psycopg/connection_int.c b/psycopg/connection_int.c
index f92a658..d8d693f 100644
--- a/psycopg/connection_int.c
+++ b/psycopg/connection_int.c
@@ -34,20 +34,29 @@
#include <string.h>
-/* Mapping from isolation level name to value exposed by Python.
- *
- * Note: ordering matters: to get a valid pre-PG 8 level from one not valid,
- * we increase a pointer in this list by one position. */
-const IsolationLevel conn_isolevels[] = {
- {"", ISOLATION_LEVEL_AUTOCOMMIT},
- {"read uncommitted", ISOLATION_LEVEL_READ_UNCOMMITTED},
- {"read committed", ISOLATION_LEVEL_READ_COMMITTED},
- {"repeatable read", ISOLATION_LEVEL_REPEATABLE_READ},
- {"serializable", ISOLATION_LEVEL_SERIALIZABLE},
- {"default", -1}, /* never to be found on the server */
- { NULL }
+/* String indexes match the ISOLATION_LEVEL_* consts */
+const char *srv_isolevels[] = {
+ NULL, /* autocommit */
+ "READ COMMITTED",
+ "REPEATABLE READ",
+ "SERIALIZABLE",
+ "READ UNCOMMITTED",
+ "" /* default */
};
+/* Read only false, true */
+const char *srv_readonly[] = {
+ " READ WRITE",
+ " READ ONLY",
+ "" /* default */
+};
+
+/* Deferrable false, true */
+const char *srv_deferrable[] = {
+ " NOT DEFERRABLE",
+ " DEFERRABLE",
+ "" /* default */
+};
/* Return a new "string" from a char* from the database.
*
@@ -553,50 +562,13 @@ exit:
RAISES_NEG int
conn_get_isolation_level(connectionObject *self)
{
- PGresult *pgres = NULL;
- char *error = NULL;
- 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);
-
- if (!(lname = pq_get_guc_locked(self, "default_transaction_isolation",
- &pgres, &error, &_save))) {
- goto endlock;
- }
-
- /* find the value for the requested isolation level */
- level = conn_isolevels;
- while ((++level)->name) {
- if (0 == strcasecmp(level->name, lname)) {
- rv = level->value;
- break;
- }
- }
- if (-1 == rv) {
- error = malloc(256);
- PyOS_snprintf(error, 256,
- "unexpected isolation level: '%s'", lname);
+ return ISOLATION_LEVEL_AUTOCOMMIT;
}
-
- free(lname);
-
-endlock:
- pthread_mutex_unlock(&self->lock);
- Py_END_ALLOW_THREADS;
-
- if (rv < 0) {
- pq_complete_error(self, &pgres, &error);
+ else {
+ return self->isolevel;
}
-
- return rv;
}
@@ -1208,156 +1180,140 @@ conn_rollback(connectionObject *self)
return res;
}
-RAISES_NEG int
-conn_set_session(connectionObject *self,
- const char *isolevel, const char *readonly, const char *deferrable,
- int autocommit)
+int
+conn_set_autocommit(connectionObject *self, int value)
{
- PGresult *pgres = NULL;
- char *error = NULL;
- int res = -1;
-
Py_BEGIN_ALLOW_THREADS;
pthread_mutex_lock(&self->lock);
- if (isolevel) {
- Dprintf("conn_set_session: setting isolation to %s", isolevel);
- if ((res = pq_set_guc_locked(self,
- "default_transaction_isolation", isolevel,
- &pgres, &error, &_save))) {
- goto endlock;
- }
- }
+ self->autocommit = value;
- if (readonly) {
- Dprintf("conn_set_session: setting read only to %s", readonly);
- if ((res = pq_set_guc_locked(self,
- "default_transaction_read_only", readonly,
- &pgres, &error, &_save))) {
- goto endlock;
- }
- }
+ pthread_mutex_unlock(&self->lock);
+ Py_END_ALLOW_THREADS;
+
+ return 0;
+}
- if (deferrable) {
- Dprintf("conn_set_session: setting deferrable to %s", deferrable);
- if ((res = pq_set_guc_locked(self,
- "default_transaction_deferrable", deferrable,
- &pgres, &error, &_save))) {
- goto endlock;
+/* Promote an isolation level to one of the levels supported by the server */
+
+static int _adjust_isolevel(connectionObject *self, int level) {
+ if (self->server_version < 80000) {
+ if (level == ISOLATION_LEVEL_READ_UNCOMMITTED) {
+ level = ISOLATION_LEVEL_READ_COMMITTED;
+ }
+ else if (level == ISOLATION_LEVEL_REPEATABLE_READ) {
+ level = ISOLATION_LEVEL_SERIALIZABLE;
}
}
+ return level;
+}
- if (self->autocommit != autocommit) {
- Dprintf("conn_set_session: setting autocommit to %d", autocommit);
- self->autocommit = autocommit;
- }
- res = 0;
+/* parse a python object into one of the possible isolation level values */
-endlock:
- pthread_mutex_unlock(&self->lock);
- Py_END_ALLOW_THREADS;
+RAISES_NEG int
+conn_parse_isolevel(connectionObject *self, PyObject *pyval)
+{
+ int rv = -1;
+ long level;
- if (res < 0) {
- pq_complete_error(self, &pgres, &error);
+ Py_INCREF(pyval); /* for ensure_bytes */
+
+ /* parse from one of the level constants */
+ if (PyInt_Check(pyval)) {
+ level = PyInt_AsLong(pyval);
+ if (level == -1 && PyErr_Occurred()) { goto exit; }
+ if (level < 1 || level > 4) {
+ PyErr_SetString(PyExc_ValueError,
+ "isolation_level must be between 1 and 4");
+ goto exit;
+ }
+
+ rv = level;
}
- return res;
-}
+ /* parse from the string -- this includes "default" */
-int
-conn_set_autocommit(connectionObject *self, int value)
-{
- Py_BEGIN_ALLOW_THREADS;
- pthread_mutex_lock(&self->lock);
+ else {
+ if (!(pyval = psycopg_ensure_bytes(pyval))) {
+ goto exit;
+ }
+ for (level = 1; level <= 4; level++) {
+ if (0 == strcasecmp(srv_isolevels[level], Bytes_AS_STRING(pyval))) {
+ rv = level;
+ break;
+ }
+ }
+ if (rv < 0 && 0 == strcasecmp("default", Bytes_AS_STRING(pyval))) {
+ rv = ISOLATION_LEVEL_DEFAULT;
+ }
+ if (rv < 0) {
+ PyErr_Format(PyExc_ValueError,
+ "bad value for isolation_level: '%s'", Bytes_AS_STRING(pyval));
+ goto exit;
+ }
+ }
- self->autocommit = value;
+ rv = _adjust_isolevel(self, rv);
- pthread_mutex_unlock(&self->lock);
- Py_END_ALLOW_THREADS;
+exit:
+ Py_XDECREF(pyval);
- return 0;
+ return rv;
}
-/* conn_switch_isolation_level - switch isolation level on the connection */
+/* convert False/True/"default" -> 0/1/2 */
RAISES_NEG int
-conn_switch_isolation_level(connectionObject *self, int level)
+conn_parse_onoff(PyObject *pyval)
{
- PGresult *pgres = NULL;
- char *error = NULL;
- int curr_level;
- int ret = -1;
+ int rv = -1;
- /* use only supported levels on older PG versions */
- if (self->server_version < 80000) {
- if (level == ISOLATION_LEVEL_READ_UNCOMMITTED)
- level = ISOLATION_LEVEL_READ_COMMITTED;
- else if (level == ISOLATION_LEVEL_REPEATABLE_READ)
- level = ISOLATION_LEVEL_SERIALIZABLE;
- }
+ Py_INCREF(pyval); /* for ensure_bytes */
- if (-1 == (curr_level = conn_get_isolation_level(self))) {
- return -1;
+ if (PyUnicode_CheckExact(pyval) || Bytes_CheckExact(pyval)) {
+ if (!(pyval = psycopg_ensure_bytes(pyval))) {
+ goto exit;
+ }
+ if (0 == strcasecmp("default", Bytes_AS_STRING(pyval))) {
+ rv = STATE_DEFAULT;
+ }
+ else {
+ PyErr_Format(PyExc_ValueError,
+ "the only string accepted is 'default'; got %s",
+ Bytes_AS_STRING(pyval));
+ goto exit;
+ }
}
-
- if (curr_level == level) {
- /* no need to change level */
- return 0;
+ else {
+ int istrue;
+ if (0 > (istrue = PyObject_IsTrue(pyval))) { goto exit; }
+ rv = istrue ? STATE_ON : STATE_OFF;
}
- /* Emulate the previous semantic of set_isolation_level() using the
- * functions currently available. */
+exit:
+ Py_XDECREF(pyval);
- Py_BEGIN_ALLOW_THREADS;
- pthread_mutex_lock(&self->lock);
+ return rv;
+}
- /* terminate the current transaction if any */
- if ((ret = pq_abort_locked(self, &pgres, &error, &_save))) {
- goto endlock;
- }
+/* conn_switch_isolation_level - switch isolation level on the connection */
+RAISES_NEG int
+conn_switch_isolation_level(connectionObject *self, int level)
+{
if (level == 0) {
- if ((ret = pq_set_guc_locked(self,
- "default_transaction_isolation", "default",
- &pgres, &error, &_save))) {
- goto endlock;
- }
self->autocommit = 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) {
- ret = -1;
- error = strdup("bad isolation level value");
- goto endlock;
- }
-
- if ((ret = pq_set_guc_locked(self,
- "default_transaction_isolation", isolevel->name,
- &pgres, &error, &_save))) {
- goto endlock;
- }
+ level = _adjust_isolevel(self, level);
+ self->isolevel = level;
self->autocommit = 0;
}
Dprintf("conn_switch_isolation_level: switched to level %d", level);
-endlock:
- pthread_mutex_unlock(&self->lock);
- Py_END_ALLOW_THREADS;
-
- if (ret < 0) {
- pq_complete_error(self, &pgres, &error);
- }
-
- return ret;
+ return 0;
}
diff --git a/psycopg/connection_type.c b/psycopg/connection_type.c
index 2066579..48bebf4 100644
--- a/psycopg/connection_type.c
+++ b/psycopg/connection_type.c
@@ -442,86 +442,6 @@ exit:
}
-/* parse a python object into one of the possible isolation level values */
-
-extern const IsolationLevel conn_isolevels[];
-
-static const char *
-_psyco_conn_parse_isolevel(connectionObject *self, PyObject *pyval)
-{
- const IsolationLevel *isolevel = 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()) { goto exit; }
- if (level < 1 || level > 4) {
- PyErr_SetString(PyExc_ValueError,
- "isolation_level must be between 1 and 4");
- goto exit;
- }
-
- isolevel = conn_isolevels;
- while ((++isolevel)->value != level)
- ; /* continue */
- }
-
- /* parse from the string -- this includes "default" */
- else {
- isolevel = conn_isolevels;
- while ((++isolevel)->name) {
- if (!(pyval = psycopg_ensure_bytes(pyval))) {
- goto exit;
- }
- if (0 == strcasecmp(isolevel->name, Bytes_AS_STRING(pyval))) {
- break;
- }
- }
- if (!isolevel->name) {
- char msg[256];
- snprintf(msg, sizeof(msg),
- "bad value for isolation_level: '%s'", Bytes_AS_STRING(pyval));
- PyErr_SetString(PyExc_ValueError, msg);
- }
- }
-
- /* use only supported levels on older PG versions */
- if (isolevel && self->server_version < 80000) {
- if (isolevel->value == ISOLATION_LEVEL_READ_UNCOMMITTED
- || isolevel->value == ISOLATION_LEVEL_REPEATABLE_READ) {
- ++isolevel;
- }
- }
-
-exit:
- Py_XDECREF(pyval);
-
- return isolevel ? isolevel->name : NULL;
-}
-
-/* convert True/False/"default" into a C string */
-
-static const char *
-_psyco_conn_parse_onoff(PyObject *pyval)
-{
- int istrue = PyObject_IsTrue(pyval);
- if (-1 == istrue) { return NULL; }
- if (istrue) {
- int cmp;
- PyObject *pydef;
- if (!(pydef = Text_FromUTF8("default"))) { return NULL; }
- cmp = PyObject_RichCompareBool(pyval, pydef, Py_EQ);
- Py_DECREF(pydef);
- if (-1 == cmp) { return NULL; }
- return cmp ? "default" : "on";
- }
- else {
- return "off";
- }
-}
-
/* set_session - set default transaction characteristics */
#define psyco_conn_set_session_doc \
@@ -536,9 +456,9 @@ psyco_conn_set_session(connectionObject *self, PyObject *args, PyObject *kwargs)
PyObject *deferrable = Py_None;
PyObject *autocommit = Py_None;
- const char *c_isolevel = NULL;
- const char *c_readonly = NULL;
- const char *c_deferrable = NULL;
+ int c_isolevel = self->isolevel;
+ int c_readonly = self->readonly;
+ int c_deferrable = self->deferrable;
int c_autocommit = self->autocommit;
static char *kwlist[] =
@@ -554,13 +474,13 @@ psyco_conn_set_session(connectionObject *self, PyObject *args, PyObject *kwargs)
}
if (Py_None != isolevel) {
- if (!(c_isolevel = _psyco_conn_parse_isolevel(self, isolevel))) {
+ if (0 > (c_isolevel = conn_parse_isolevel(self, isolevel))) {
return NULL;
}
}
if (Py_None != readonly) {
- if (!(c_readonly = _psyco_conn_parse_onoff(readonly))) {
+ if (0 > (c_readonly = conn_parse_onoff(readonly))) {
return NULL;
}
}
@@ -571,19 +491,19 @@ psyco_conn_set_session(connectionObject *self, PyObject *args, PyObject *kwargs)
" from PostgreSQL 9.1");
return NULL;
}
- if (!(c_deferrable = _psyco_conn_parse_onoff(deferrable))) {
+ if (0 > (c_deferrable = conn_parse_onoff(readonly))) {
return NULL;
}
}
+
if (Py_None != autocommit) {
- c_autocommit = PyObject_IsTrue(autocommit);
- if (-1 == c_autocommit) { return NULL; }
+ if (-1 == (c_autocommit = PyObject_IsTrue(autocommit))) { return NULL; }
}
- if (0 > conn_set_session(self,
- c_isolevel, c_readonly, c_deferrable, c_autocommit)) {
- return NULL;
- }
+ self->isolevel = c_isolevel;
+ self->readonly = c_readonly;
+ self->deferrable = c_deferrable;
+ self->autocommit = c_autocommit;
Py_RETURN_NONE;
}
@@ -1107,6 +1027,9 @@ connection_setup(connectionObject *self, const char *dsn, long int async)
self->async_status = ASYNC_DONE;
if (!(self->string_types = PyDict_New())) { goto exit; }
if (!(self->binary_types = PyDict_New())) { goto exit; }
+ self->isolevel = ISOLATION_LEVEL_DEFAULT;
+ self->readonly = STATE_DEFAULT;
+ self->deferrable = STATE_DEFAULT;
/* other fields have been zeroed by tp_alloc */
pthread_mutex_init(&(self->lock), NULL);
diff --git a/psycopg/pqpath.c b/psycopg/pqpath.c
index 328a2b2..8a4d78f 100644
--- a/psycopg/pqpath.c
+++ b/psycopg/pqpath.c
@@ -53,7 +53,9 @@
#endif
extern HIDDEN PyObject *psyco_DescriptionType;
-
+extern HIDDEN const char *srv_isolevels[];
+extern HIDDEN const char *srv_readonly[];
+extern HIDDEN const char *srv_deferrable[];
/* Strip off the severity from a Postgres error message. */
static const char *
@@ -479,6 +481,8 @@ int
pq_begin_locked(connectionObject *conn, PGresult **pgres, char **error,
PyThreadState **tstate)
{
+ const size_t bufsize = 256;
+ char buf[bufsize];
int result;
Dprintf("pq_begin_locked: pgconn = %p, autocommit = %d, status = %d",
@@ -489,7 +493,14 @@ pq_begin_locked(connectionObject *conn, PGresult **pgres, char **error,
return 0;
}
- result = pq_execute_command_locked(conn, "BEGIN", pgres, error, tstate);
+ snprintf(buf, bufsize, "BEGIN%s%s%s%s%s",
+ conn->server_version < 80000 ? ";SET TRANSACTION" : "",
+ (conn->isolevel >= 1 && conn->isolevel <= 4) ? " ISOLATION LEVEL " : "",
+ srv_isolevels[conn->isolevel],
+ srv_readonly[conn->readonly],
+ srv_deferrable[conn->deferrable]);
+
+ result = pq_execute_command_locked(conn, buf, pgres, error, tstate);
if (result == 0)
conn->status = CONN_STATUS_BEGIN;