summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniele Varrazzo <daniele.varrazzo@gmail.com>2017-02-07 00:58:54 +0000
committerDaniele Varrazzo <daniele.varrazzo@gmail.com>2017-02-07 00:58:54 +0000
commit28c489f17e8db69a19c0f3b6b4e57dc76b8bbfbc (patch)
tree4e5832a91fbe3bf8225cafc3b46c4a02cb8da9c9
parent3ff350cd2474d1578714f3488ff5e32fee9bb844 (diff)
parent7485fabe4fb460fdab33296cc86fc33e17aaef47 (diff)
downloadpsycopg2-28c489f17e8db69a19c0f3b6b4e57dc76b8bbfbc.tar.gz
Merge branch 'no-set-default-session'
-rw-r--r--NEWS9
-rw-r--r--doc/src/connection.rst64
-rw-r--r--doc/src/extensions.rst23
-rw-r--r--doc/src/usage.rst3
-rw-r--r--lib/extensions.py1
-rw-r--r--psycopg/connection.h18
-rw-r--r--psycopg/connection_int.c269
-rw-r--r--psycopg/connection_type.c154
-rw-r--r--psycopg/pqpath.c25
-rwxr-xr-xtests/test_connection.py107
10 files changed, 360 insertions, 313 deletions
diff --git a/NEWS b/NEWS
index 42943a4..b87c110 100644
--- a/NEWS
+++ b/NEWS
@@ -35,6 +35,10 @@ New features:
(:ticket:`#491`).
- Added ``async_`` as an alias for ``async`` to support Python 3.7 where
``async`` will become a keyword (:ticket:`#495`).
+- Unless in autocommit, do not use :sql:`default_transaction_*` settings to
+ control the session characteristics as it may create problems with external
+ connection pools such as pgbouncer; use :sql:`BEGIN` options instead
+ (:ticket:`#503`).
Bug fixes:
@@ -46,6 +50,11 @@ Other changes:
- Dropped support for Python 2.5 and 3.1.
- Dropped support for client library older than PostgreSQL 9.1 (but older
server versions are still supported).
+- `~connection.isolation_level` doesn't read from the database but will return
+ `~psycopg2.extensions.ISOLATION_LEVEL_DEFAULT` if no value was set on the
+ connection.
+- `~connection.set_isolation_level()` will throw an exception if executed
+ inside a transaction; previously it would have silently rolled it back.
What's new in psycopg 2.6.3
diff --git a/doc/src/connection.rst b/doc/src/connection.rst
index 0bc584c..2adad59 100644
--- a/doc/src/connection.rst
+++ b/doc/src/connection.rst
@@ -400,6 +400,32 @@ The ``connection`` class
.. versionadded:: 2.4.2
+ .. versionchanged:: 2.7
+ Before this version, the function would have set
+ :sql:`default_transaction_*` attribute in the current session;
+ this implementation has the problem of not playing well with
+ external connection pooling working at transaction level and not
+ resetting the state of the session: changing the default
+ transaction would pollute the connections in the pool and create
+ problems to other applications using the same pool.
+
+ Starting from 2.7, if the connection is not autocommit, the
+ transaction characteristics are issued together with :sql:`BEGIN`
+ and will leave the :sql:`default_transaction_*` settings untouched.
+ For example::
+
+ conn.set_session(readonly=True)
+
+ will not change :sql:`default_transaction_read_only`, but
+ following transaction will start with a :sql:`BEGIN READ ONLY`.
+ Conversely, using::
+
+ conn.set_session(readonly=True, autocommit=True)
+
+ will set :sql:`default_transaction_read_only` to :sql:`on` and
+ rely on the server to apply the read only state to whatever
+ transaction, implicit or explicit, is executed in the connection.
+
.. attribute:: autocommit
@@ -428,32 +454,54 @@ The ``connection`` class
.. versionadded:: 2.4.2
- .. attribute:: isolation_level
.. method:: set_isolation_level(level)
.. note::
- From version 2.4.2, `set_session()` and `autocommit`, offer
+ From version 2.4.2, `set_session()` and `autocommit` offer
finer control on the transaction characteristics.
- Read or set the `transaction isolation level`_ for the current session.
+ Set the `transaction isolation level`_ for the current session.
The level defines the different phenomena that can happen in the
database between concurrent transactions.
- The value set or read is an integer: symbolic constants are defined in
+ The value set is an integer: symbolic constants are defined in
the module `psycopg2.extensions`: see
:ref:`isolation-level-constants` for the available values.
- The default level is :sql:`READ COMMITTED`: at this level a
- transaction is automatically started the first time a database command
- is executed. If you want an *autocommit* mode, switch to
- `~psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT` before
+ The default level is `~psycopg2.extensions.ISOLATION_LEVEL_DEFAULT`:
+ at this level a transaction is automatically started the first time a
+ database command is executed. If you want an *autocommit* mode,
+ switch to `~psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT` before
executing any command::
>>> conn.set_isolation_level(psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT)
See also :ref:`transactions-control`.
+ .. versionchanged:: 2.7
+
+ the function must be called outside a transaction; previously it
+ would have executed an implicit :sql:`ROLLBACK`; it will now raise
+ an exception.
+
+
+ .. attribute:: isolation_level
+
+ Read the `transaction isolation level`_ for the current session. The
+ value is one of the :ref:`isolation-level-constants` defined in the
+ `psycopg2.extensions` module.
+
+ .. versionchanged:: 2.7
+
+ the default value for `!isolation_level` is
+ `~psycopg2.extensions.ISOLATION_LEVEL_DEFAULT`; previously the
+ property would have queried the server and returned the real value
+ applied. To know this value you can run a query such as :sql:`show
+ transaction_isolation`. Usually the default value is `READ
+ COMMITTED`, but this may be changed in the server configuration.
+
+
.. index::
pair: Client; Encoding
diff --git a/doc/src/extensions.rst b/doc/src/extensions.rst
index 8d70ba3..ae40b72 100644
--- a/doc/src/extensions.rst
+++ b/doc/src/extensions.rst
@@ -567,15 +567,16 @@ Isolation level constants
-------------------------
Psycopg2 `connection` objects hold informations about the PostgreSQL
-`transaction isolation level`_. The current transaction level can be read
-from the `~connection.isolation_level` attribute. The default isolation
-level is :sql:`READ COMMITTED`. A different isolation level con be set
-through the `~connection.set_isolation_level()` method. The level can be
-set to one of the following constants:
+`transaction isolation level`_. By default Psycopg doesn't change the default
+configuration of the server (`ISOLATION_LEVEL_DEFAULT`); the default for
+PostgreSQL servers is typically :sql:`READ COMMITTED`, but this may be changed
+in the server configuration files. A different isolation level can be set
+through the `~connection.set_isolation_level()` or `~connection.set_session()`
+methods. The level can be set to one of the following constants:
.. data:: ISOLATION_LEVEL_AUTOCOMMIT
- No transaction is started when command are issued and no
+ No transaction is started when commands are executed and no
`~connection.commit()` or `~connection.rollback()` is required.
Some PostgreSQL command such as :sql:`CREATE DATABASE` or :sql:`VACUUM`
can't run into a transaction: to run such command use::
@@ -651,6 +652,16 @@ set to one of the following constants:
.. __: http://www.postgresql.org/docs/current/static/transaction-iso.html#XACT-SERIALIZABLE
+.. data:: ISOLATION_LEVEL_DEFAULT
+
+ A new transaction is started at the first `~cursor.execute()` command, but
+ the isolation level is not explicitly selected by Psycopg: the server will
+ use whatever level is defined in its configuration or by statements
+ executed within the session outside Pyscopg control. If you want to know
+ what the value is you can use a query such as :sql:`show
+ transaction_isolation`.
+
+ .. versionadded:: 2.7
.. index::
diff --git a/doc/src/usage.rst b/doc/src/usage.rst
index 1366485..6cb038b 100644
--- a/doc/src/usage.rst
+++ b/doc/src/usage.rst
@@ -676,8 +676,7 @@ commands executed will be immediately committed and no rollback is possible. A
few commands (e.g. :sql:`CREATE DATABASE`, :sql:`VACUUM`...) require to be run
outside any transaction: in order to be able to run these commands from
Psycopg, the connection must be in autocommit mode: you can use the
-`~connection.autocommit` property (`~connection.set_isolation_level()` in
-older versions).
+`~connection.autocommit` property.
.. warning::
diff --git a/lib/extensions.py b/lib/extensions.py
index b123e88..f4dc706 100644
--- a/lib/extensions.py
+++ b/lib/extensions.py
@@ -72,6 +72,7 @@ ISOLATION_LEVEL_READ_UNCOMMITTED = 4
ISOLATION_LEVEL_READ_COMMITTED = 1
ISOLATION_LEVEL_REPEATABLE_READ = 2
ISOLATION_LEVEL_SERIALIZABLE = 3
+ISOLATION_LEVEL_DEFAULT = 5
"""psycopg connection status values."""
diff --git a/psycopg/connection.h b/psycopg/connection.h
index 2e2d51d..65efcaf 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,11 +166,8 @@ 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_switch_isolation_level(connectionObject *self, int level);
+RAISES_NEG HIDDEN int conn_set_session(connectionObject *self, int autocommit,
+ int isolevel, int readonly, int deferrable);
RAISES_NEG HIDDEN int conn_set_client_encoding(connectionObject *self, const char *enc);
HIDDEN int conn_poll(connectionObject *self);
RAISES_NEG HIDDEN int conn_tpc_begin(connectionObject *self, xidObject *xid);
diff --git a/psycopg/connection_int.c b/psycopg/connection_int.c
index f92a658..e6906eb 100644
--- a/psycopg/connection_int.c
+++ b/psycopg/connection_int.c
@@ -34,18 +34,36 @@
#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" /* only to set GUC, not for BEGIN */
+};
+
+/* Read only false, true */
+const char *srv_readonly[] = {
+ " READ WRITE",
+ " READ ONLY",
+ "" /* default */
+};
+
+/* Deferrable false, true */
+const char *srv_deferrable[] = {
+ " NOT DEFERRABLE",
+ " DEFERRABLE",
+ "" /* default */
+};
+
+/* On/Off/Default GUC states
+ */
+const char *srv_state_guc[] = {
+ "off",
+ "on",
+ "default"
};
@@ -553,50 +571,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;
+ return ISOLATION_LEVEL_AUTOCOMMIT;
}
-
- 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);
- }
-
- 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 +1189,98 @@ 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)
-{
- 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;
- }
- }
-
- 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;
- }
- }
-
- 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;
- }
- }
-
- if (self->autocommit != autocommit) {
- Dprintf("conn_set_session: setting autocommit to %d", autocommit);
- self->autocommit = autocommit;
- }
-
- res = 0;
-
-endlock:
- pthread_mutex_unlock(&self->lock);
- Py_END_ALLOW_THREADS;
-
- if (res < 0) {
- pq_complete_error(self, &pgres, &error);
- }
-
- return res;
-}
-
-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;
-}
-
-/* conn_switch_isolation_level - switch isolation level on the connection */
+/* Change the state of the session */
RAISES_NEG int
-conn_switch_isolation_level(connectionObject *self, int level)
+conn_set_session(connectionObject *self, int autocommit,
+ int isolevel, int readonly, int deferrable)
{
+ int rv = -1;
PGresult *pgres = NULL;
char *error = NULL;
- int curr_level;
- int ret = -1;
- /* use only supported levels on older PG versions */
+ /* Promote an isolation level to one of the levels supported by the server */
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;
- }
-
- if (-1 == (curr_level = conn_get_isolation_level(self))) {
- return -1;
- }
-
- if (curr_level == level) {
- /* no need to change level */
- return 0;
+ if (isolevel == ISOLATION_LEVEL_READ_UNCOMMITTED) {
+ isolevel = ISOLATION_LEVEL_READ_COMMITTED;
+ }
+ else if (isolevel == ISOLATION_LEVEL_REPEATABLE_READ) {
+ isolevel = ISOLATION_LEVEL_SERIALIZABLE;
+ }
}
- /* Emulate the previous semantic of set_isolation_level() using the
- * functions currently available. */
-
Py_BEGIN_ALLOW_THREADS;
pthread_mutex_lock(&self->lock);
- /* terminate the current transaction if any */
- if ((ret = pq_abort_locked(self, &pgres, &error, &_save))) {
- goto endlock;
- }
-
- if (level == 0) {
- if ((ret = pq_set_guc_locked(self,
- "default_transaction_isolation", "default",
- &pgres, &error, &_save))) {
- goto endlock;
+ if (autocommit) {
+ /* we are in autocommit state, so no BEGIN will be issued:
+ * configure the session with the characteristics requested */
+ if (isolevel != self->isolevel) {
+ if (0 > pq_set_guc_locked(self,
+ "default_transaction_isolation", srv_isolevels[isolevel],
+ &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 (readonly != self->readonly) {
+ if (0 > pq_set_guc_locked(self,
+ "default_transaction_read_only", srv_state_guc[readonly],
+ &pgres, &error, &_save)) {
+ goto endlock;
}
}
- if (!isolevel->name) {
- ret = -1;
- error = strdup("bad isolation level value");
+ if (deferrable != self->deferrable && self->server_version >= 90100) {
+ if (0 > pq_set_guc_locked(self,
+ "default_transaction_deferrable", srv_state_guc[deferrable],
+ &pgres, &error, &_save)) {
+ goto endlock;
+ }
+ }
+ }
+ else if (self->autocommit) {
+ /* we are moving from autocommit to not autocommit, so revert the
+ * characteristics to defaults to let BEGIN do its work */
+ if (0 > pq_set_guc_locked(self,
+ "default_transaction_isolation", "default",
+ &pgres, &error, &_save)) {
goto endlock;
}
-
- if ((ret = pq_set_guc_locked(self,
- "default_transaction_isolation", isolevel->name,
- &pgres, &error, &_save))) {
+ if (0 > pq_set_guc_locked(self,
+ "default_transaction_read_only", "default",
+ &pgres, &error, &_save)) {
goto endlock;
}
- self->autocommit = 0;
+ if (self->server_version >= 90100) {
+ if (0 > pq_set_guc_locked(self,
+ "default_transaction_deferrable", "default",
+ &pgres, &error, &_save)) {
+ goto endlock;
+ }
+ }
}
- Dprintf("conn_switch_isolation_level: switched to level %d", level);
+ self->autocommit = autocommit;
+ self->isolevel = isolevel;
+ self->readonly = readonly;
+ self->deferrable = deferrable;
+ rv = 0;
endlock:
pthread_mutex_unlock(&self->lock);
Py_END_ALLOW_THREADS;
- if (ret < 0) {
+ if (rv < 0) {
pq_complete_error(self, &pgres, &error);
+ goto exit;
}
- return ret;
+ Dprintf(
+ "conn_set_session: autocommit %d, isolevel %d, readonly %d, deferrable %d",
+ autocommit, isolevel, readonly, deferrable);
+
+
+exit:
+ return rv;
}
diff --git a/psycopg/connection_type.c b/psycopg/connection_type.c
index 2066579..7647056 100644
--- a/psycopg/connection_type.c
+++ b/psycopg/connection_type.c
@@ -36,6 +36,9 @@
#include <string.h>
#include <ctype.h>
+extern HIDDEN const char *srv_isolevels[];
+extern HIDDEN const char *srv_readonly[];
+extern HIDDEN const char *srv_deferrable[];
/** DBAPI methods **/
@@ -444,18 +447,17 @@ 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)
+RAISES_NEG static int
+_psyco_conn_parse_isolevel(PyObject *pyval)
{
- const IsolationLevel *isolevel = NULL;
+ int rv = -1;
+ long level;
Py_INCREF(pyval); /* for ensure_bytes */
/* parse from one of the level constants */
if (PyInt_Check(pyval)) {
- long level = PyInt_AsLong(pyval);
+ level = PyInt_AsLong(pyval);
if (level == -1 && PyErr_Occurred()) { goto exit; }
if (level < 1 || level > 4) {
PyErr_SetString(PyExc_ValueError,
@@ -463,65 +465,80 @@ _psyco_conn_parse_isolevel(connectionObject *self, PyObject *pyval)
goto exit;
}
- isolevel = conn_isolevels;
- while ((++isolevel)->value != level)
- ; /* continue */
+ rv = level;
}
/* 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))) {
+ 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 (!isolevel->name) {
- char msg[256];
- snprintf(msg, sizeof(msg),
- "bad value for isolation_level: '%s'", Bytes_AS_STRING(pyval));
- PyErr_SetString(PyExc_ValueError, msg);
+ if (rv < 0 && 0 == strcasecmp("default", Bytes_AS_STRING(pyval))) {
+ rv = ISOLATION_LEVEL_DEFAULT;
}
- }
-
- /* 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;
+ if (rv < 0) {
+ PyErr_Format(PyExc_ValueError,
+ "bad value for isolation_level: '%s'", Bytes_AS_STRING(pyval));
+ goto exit;
}
}
exit:
Py_XDECREF(pyval);
- return isolevel ? isolevel->name : NULL;
+ return rv;
}
-/* convert True/False/"default" into a C string */
+/* convert False/True/"default" -> 0/1/2 */
-static const char *
+RAISES_NEG static int
_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";
+ int rv = -1;
+
+ Py_INCREF(pyval); /* for ensure_bytes */
+
+ 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;
+ }
}
else {
- return "off";
+ int istrue;
+ if (0 > (istrue = PyObject_IsTrue(pyval))) { goto exit; }
+ rv = istrue ? STATE_ON : STATE_OFF;
}
+
+exit:
+ Py_XDECREF(pyval);
+
+ return rv;
}
+#define _set_session_checks(self,what) \
+do { \
+ EXC_IF_CONN_CLOSED(self); \
+ EXC_IF_CONN_ASYNC(self, what); \
+ EXC_IF_IN_TRANSACTION(self, what); \
+ EXC_IF_TPC_PREPARED(self, what); \
+} while(0)
+
/* set_session - set default transaction characteristics */
#define psyco_conn_set_session_doc \
@@ -536,17 +553,15 @@ 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[] =
{"isolation_level", "readonly", "deferrable", "autocommit", NULL};
- EXC_IF_CONN_CLOSED(self);
- EXC_IF_CONN_ASYNC(self, set_session);
- EXC_IF_IN_TRANSACTION(self, set_session);
+ _set_session_checks(self, set_session);
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|OOOO", kwlist,
&isolevel, &readonly, &deferrable, &autocommit)) {
@@ -554,13 +569,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 = _psyco_conn_parse_isolevel(isolevel))) {
return NULL;
}
}
if (Py_None != readonly) {
- if (!(c_readonly = _psyco_conn_parse_onoff(readonly))) {
+ if (0 > (c_readonly = _psyco_conn_parse_onoff(readonly))) {
return NULL;
}
}
@@ -571,17 +586,17 @@ 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 = _psyco_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)) {
+ if (0 > conn_set_session(
+ self, c_autocommit, c_isolevel, c_readonly, c_deferrable)) {
return NULL;
}
@@ -606,9 +621,7 @@ _psyco_conn_autocommit_set_checks(connectionObject *self)
{
/* wrapper to use the EXC_IF macros.
* return NULL in case of error, else whatever */
- EXC_IF_CONN_CLOSED(self);
- EXC_IF_CONN_ASYNC(self, autocommit);
- EXC_IF_IN_TRANSACTION(self, autocommit);
+ _set_session_checks(self, autocommit);
return Py_None; /* borrowed */
}
@@ -619,7 +632,10 @@ psyco_conn_autocommit_set(connectionObject *self, PyObject *pyvalue)
if (!_psyco_conn_autocommit_set_checks(self)) { return -1; }
if (-1 == (value = PyObject_IsTrue(pyvalue))) { return -1; }
- if (0 != conn_set_autocommit(self, value)) { return -1; }
+ if (0 > conn_set_session(self, value,
+ self->isolevel, self->readonly, self->deferrable)) {
+ return -1;
+ }
return 0;
}
@@ -651,20 +667,27 @@ psyco_conn_set_isolation_level(connectionObject *self, PyObject *args)
{
int level = 1;
- EXC_IF_CONN_CLOSED(self);
- EXC_IF_CONN_ASYNC(self, set_isolation_level);
- EXC_IF_TPC_PREPARED(self, set_isolation_level);
+ _set_session_checks(self, set_isolation_level);
if (!PyArg_ParseTuple(args, "i", &level)) return NULL;
- if (level < 0 || level > 4) {
+ if (level < 0 || level > 5) {
PyErr_SetString(PyExc_ValueError,
"isolation level must be between 0 and 4");
return NULL;
}
- if (conn_switch_isolation_level(self, level) < 0) {
- return NULL;
+ if (level == 0) {
+ if (0 > conn_set_session(self, 1,
+ ISOLATION_LEVEL_DEFAULT, self->readonly, self->deferrable)) {
+ return NULL;
+ }
+ }
+ else {
+ if (0 > conn_set_session(self, 0,
+ level, self->readonly, self->deferrable)) {
+ return NULL;
+ }
}
Py_RETURN_NONE;
@@ -1107,6 +1130,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..50bd520 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,24 @@ pq_begin_locked(connectionObject *conn, PGresult **pgres, char **error,
return 0;
}
- result = pq_execute_command_locked(conn, "BEGIN", pgres, error, tstate);
+ if (conn->isolevel == ISOLATION_LEVEL_DEFAULT
+ && conn->readonly == STATE_DEFAULT
+ && conn->deferrable == STATE_DEFAULT) {
+ strcpy(buf, "BEGIN");
+ }
+ else {
+ snprintf(buf, bufsize,
+ conn->server_version >= 80000 ?
+ "BEGIN%s%s%s%s" : "BEGIN;SET TRANSACTION%s%s%s%s",
+ (conn->isolevel >= 1 && conn->isolevel <= 4)
+ ? " ISOLATION LEVEL " : "",
+ (conn->isolevel >= 1 && conn->isolevel <= 4)
+ ? 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;
diff --git a/tests/test_connection.py b/tests/test_connection.py
index 4b72be1..703d8f1 100755
--- a/tests/test_connection.py
+++ b/tests/test_connection.py
@@ -488,7 +488,7 @@ class IsolationLevelsTestCase(ConnectingTestCase):
conn = self.connect()
self.assertEqual(
conn.isolation_level,
- psycopg2.extensions.ISOLATION_LEVEL_READ_COMMITTED)
+ psycopg2.extensions.ISOLATION_LEVEL_DEFAULT)
def test_encoding(self):
conn = self.connect()
@@ -522,14 +522,33 @@ class IsolationLevelsTestCase(ConnectingTestCase):
got_name = curs.fetchone()[0]
if name is None:
- curs.execute('show default_transaction_isolation;')
+ curs.execute('show transaction_isolation;')
name = curs.fetchone()[0]
self.assertEqual(name, got_name)
conn.commit()
self.assertRaises(ValueError, conn.set_isolation_level, -1)
- self.assertRaises(ValueError, conn.set_isolation_level, 5)
+ self.assertRaises(ValueError, conn.set_isolation_level, 6)
+
+ def test_set_isolation_level_default(self):
+ conn = self.connect()
+ curs = conn.cursor()
+
+ conn.autocommit = True
+ curs.execute("set default_transaction_isolation to 'read committed'")
+
+ conn.autocommit = False
+ conn.set_isolation_level(psycopg2.extensions.ISOLATION_LEVEL_SERIALIZABLE)
+ self.assertEqual(conn.isolation_level,
+ psycopg2.extensions.ISOLATION_LEVEL_SERIALIZABLE)
+ curs.execute("show transaction_isolation")
+ self.assertEqual(curs.fetchone()[0], "serializable")
+
+ conn.rollback()
+ conn.set_isolation_level(psycopg2.extensions.ISOLATION_LEVEL_DEFAULT)
+ curs.execute("show transaction_isolation")
+ self.assertEqual(curs.fetchone()[0], "read committed")
def test_set_isolation_level_abort(self):
conn = self.connect()
@@ -541,32 +560,14 @@ class IsolationLevelsTestCase(ConnectingTestCase):
self.assertEqual(psycopg2.extensions.TRANSACTION_STATUS_INTRANS,
conn.get_transaction_status())
- conn.set_isolation_level(
+ # changed in psycopg 2.7
+ self.assertRaises(psycopg2.ProgrammingError,
+ 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])
+ self.assertEqual(conn.isolation_level,
+ psycopg2.extensions.ISOLATION_LEVEL_DEFAULT)
def test_isolation_level_autocommit(self):
cnn1 = self.connect()
@@ -1042,13 +1043,13 @@ class TransactionControlTests(ConnectingTestCase):
cur = self.conn.cursor()
self.conn.set_session(
psycopg2.extensions.ISOLATION_LEVEL_SERIALIZABLE)
- cur.execute("SHOW default_transaction_isolation;")
+ cur.execute("SHOW transaction_isolation;")
self.assertEqual(cur.fetchone()[0], 'serializable')
self.conn.rollback()
self.conn.set_session(
psycopg2.extensions.ISOLATION_LEVEL_REPEATABLE_READ)
- cur.execute("SHOW default_transaction_isolation;")
+ cur.execute("SHOW transaction_isolation;")
if self.conn.server_version > 80000:
self.assertEqual(cur.fetchone()[0], 'repeatable read')
else:
@@ -1057,13 +1058,13 @@ class TransactionControlTests(ConnectingTestCase):
self.conn.set_session(
isolation_level=psycopg2.extensions.ISOLATION_LEVEL_READ_COMMITTED)
- cur.execute("SHOW default_transaction_isolation;")
+ cur.execute("SHOW transaction_isolation;")
self.assertEqual(cur.fetchone()[0], 'read committed')
self.conn.rollback()
self.conn.set_session(
isolation_level=psycopg2.extensions.ISOLATION_LEVEL_READ_UNCOMMITTED)
- cur.execute("SHOW default_transaction_isolation;")
+ cur.execute("SHOW transaction_isolation;")
if self.conn.server_version > 80000:
self.assertEqual(cur.fetchone()[0], 'read uncommitted')
else:
@@ -1073,12 +1074,12 @@ class TransactionControlTests(ConnectingTestCase):
def test_set_isolation_level_str(self):
cur = self.conn.cursor()
self.conn.set_session("serializable")
- cur.execute("SHOW default_transaction_isolation;")
+ cur.execute("SHOW transaction_isolation;")
self.assertEqual(cur.fetchone()[0], 'serializable')
self.conn.rollback()
self.conn.set_session("repeatable read")
- cur.execute("SHOW default_transaction_isolation;")
+ cur.execute("SHOW transaction_isolation;")
if self.conn.server_version > 80000:
self.assertEqual(cur.fetchone()[0], 'repeatable read')
else:
@@ -1086,12 +1087,12 @@ class TransactionControlTests(ConnectingTestCase):
self.conn.rollback()
self.conn.set_session("read committed")
- cur.execute("SHOW default_transaction_isolation;")
+ cur.execute("SHOW transaction_isolation;")
self.assertEqual(cur.fetchone()[0], 'read committed')
self.conn.rollback()
self.conn.set_session("read uncommitted")
- cur.execute("SHOW default_transaction_isolation;")
+ cur.execute("SHOW transaction_isolation;")
if self.conn.server_version > 80000:
self.assertEqual(cur.fetchone()[0], 'read uncommitted')
else:
@@ -1106,57 +1107,57 @@ class TransactionControlTests(ConnectingTestCase):
def test_set_read_only(self):
cur = self.conn.cursor()
self.conn.set_session(readonly=True)
- cur.execute("SHOW default_transaction_read_only;")
+ cur.execute("SHOW transaction_read_only;")
self.assertEqual(cur.fetchone()[0], 'on')
self.conn.rollback()
- cur.execute("SHOW default_transaction_read_only;")
+ cur.execute("SHOW transaction_read_only;")
self.assertEqual(cur.fetchone()[0], 'on')
self.conn.rollback()
cur = self.conn.cursor()
self.conn.set_session(readonly=None)
- cur.execute("SHOW default_transaction_read_only;")
+ cur.execute("SHOW transaction_read_only;")
self.assertEqual(cur.fetchone()[0], 'on')
self.conn.rollback()
self.conn.set_session(readonly=False)
- cur.execute("SHOW default_transaction_read_only;")
+ cur.execute("SHOW transaction_read_only;")
self.assertEqual(cur.fetchone()[0], 'off')
self.conn.rollback()
def test_set_default(self):
cur = self.conn.cursor()
- cur.execute("SHOW default_transaction_isolation;")
- default_isolevel = cur.fetchone()[0]
- cur.execute("SHOW default_transaction_read_only;")
- default_readonly = cur.fetchone()[0]
+ cur.execute("SHOW transaction_isolation;")
+ isolevel = cur.fetchone()[0]
+ cur.execute("SHOW transaction_read_only;")
+ readonly = cur.fetchone()[0]
self.conn.rollback()
self.conn.set_session(isolation_level='serializable', readonly=True)
self.conn.set_session(isolation_level='default', readonly='default')
- cur.execute("SHOW default_transaction_isolation;")
- self.assertEqual(cur.fetchone()[0], default_isolevel)
- cur.execute("SHOW default_transaction_read_only;")
- self.assertEqual(cur.fetchone()[0], default_readonly)
+ cur.execute("SHOW transaction_isolation;")
+ self.assertEqual(cur.fetchone()[0], isolevel)
+ cur.execute("SHOW transaction_read_only;")
+ self.assertEqual(cur.fetchone()[0], readonly)
@skip_before_postgres(9, 1)
def test_set_deferrable(self):
cur = self.conn.cursor()
self.conn.set_session(readonly=True, deferrable=True)
- cur.execute("SHOW default_transaction_read_only;")
+ cur.execute("SHOW transaction_read_only;")
self.assertEqual(cur.fetchone()[0], 'on')
- cur.execute("SHOW default_transaction_deferrable;")
+ cur.execute("SHOW transaction_deferrable;")
self.assertEqual(cur.fetchone()[0], 'on')
self.conn.rollback()
- cur.execute("SHOW default_transaction_deferrable;")
+ cur.execute("SHOW transaction_deferrable;")
self.assertEqual(cur.fetchone()[0], 'on')
self.conn.rollback()
self.conn.set_session(deferrable=False)
- cur.execute("SHOW default_transaction_read_only;")
+ cur.execute("SHOW transaction_read_only;")
self.assertEqual(cur.fetchone()[0], 'on')
- cur.execute("SHOW default_transaction_deferrable;")
+ cur.execute("SHOW transaction_deferrable;")
self.assertEqual(cur.fetchone()[0], 'off')
self.conn.rollback()
@@ -1258,9 +1259,9 @@ class AutocommitTests(ConnectingTestCase):
self.assertEqual(self.conn.status, psycopg2.extensions.STATUS_READY)
self.assertEqual(self.conn.get_transaction_status(),
psycopg2.extensions.TRANSACTION_STATUS_IDLE)
- cur.execute("SHOW default_transaction_isolation;")
+ cur.execute("SHOW transaction_isolation;")
self.assertEqual(cur.fetchone()[0], 'serializable')
- cur.execute("SHOW default_transaction_read_only;")
+ cur.execute("SHOW transaction_read_only;")
self.assertEqual(cur.fetchone()[0], 'on')