Merge branch 'no-set-default-session'

This commit is contained in:
Daniele Varrazzo 2017-02-07 00:58:54 +00:00
commit 28c489f17e
10 changed files with 362 additions and 315 deletions

9
NEWS
View File

@ -35,6 +35,10 @@ New features:
(:ticket:`#491`). (:ticket:`#491`).
- Added ``async_`` as an alias for ``async`` to support Python 3.7 where - Added ``async_`` as an alias for ``async`` to support Python 3.7 where
``async`` will become a keyword (:ticket:`#495`). ``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: Bug fixes:
@ -46,6 +50,11 @@ Other changes:
- Dropped support for Python 2.5 and 3.1. - Dropped support for Python 2.5 and 3.1.
- Dropped support for client library older than PostgreSQL 9.1 (but older - Dropped support for client library older than PostgreSQL 9.1 (but older
server versions are still supported). 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 What's new in psycopg 2.6.3

View File

@ -400,6 +400,32 @@ The ``connection`` class
.. versionadded:: 2.4.2 .. 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 .. attribute:: autocommit
@ -428,32 +454,54 @@ The ``connection`` class
.. versionadded:: 2.4.2 .. versionadded:: 2.4.2
.. attribute:: isolation_level
.. method:: set_isolation_level(level) .. method:: set_isolation_level(level)
.. note:: .. 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. 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 The level defines the different phenomena that can happen in the
database between concurrent transactions. 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 the module `psycopg2.extensions`: see
:ref:`isolation-level-constants` for the available values. :ref:`isolation-level-constants` for the available values.
The default level is :sql:`READ COMMITTED`: at this level a The default level is `~psycopg2.extensions.ISOLATION_LEVEL_DEFAULT`:
transaction is automatically started the first time a database command at this level a transaction is automatically started the first time a
is executed. If you want an *autocommit* mode, switch to database command is executed. If you want an *autocommit* mode,
`~psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT` before switch to `~psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT` before
executing any command:: executing any command::
>>> conn.set_isolation_level(psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT) >>> conn.set_isolation_level(psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT)
See also :ref:`transactions-control`. 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:: .. index::
pair: Client; Encoding pair: Client; Encoding

View File

@ -567,15 +567,16 @@ Isolation level constants
------------------------- -------------------------
Psycopg2 `connection` objects hold informations about the PostgreSQL Psycopg2 `connection` objects hold informations about the PostgreSQL
`transaction isolation level`_. The current transaction level can be read `transaction isolation level`_. By default Psycopg doesn't change the default
from the `~connection.isolation_level` attribute. The default isolation configuration of the server (`ISOLATION_LEVEL_DEFAULT`); the default for
level is :sql:`READ COMMITTED`. A different isolation level con be set PostgreSQL servers is typically :sql:`READ COMMITTED`, but this may be changed
through the `~connection.set_isolation_level()` method. The level can be in the server configuration files. A different isolation level can be set
set to one of the following constants: 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 .. 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. `~connection.commit()` or `~connection.rollback()` is required.
Some PostgreSQL command such as :sql:`CREATE DATABASE` or :sql:`VACUUM` Some PostgreSQL command such as :sql:`CREATE DATABASE` or :sql:`VACUUM`
can't run into a transaction: to run such command use:: 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 .. __: 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:: .. index::

View File

@ -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 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 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 Psycopg, the connection must be in autocommit mode: you can use the
`~connection.autocommit` property (`~connection.set_isolation_level()` in `~connection.autocommit` property.
older versions).
.. warning:: .. warning::

View File

@ -72,6 +72,7 @@ ISOLATION_LEVEL_READ_UNCOMMITTED = 4
ISOLATION_LEVEL_READ_COMMITTED = 1 ISOLATION_LEVEL_READ_COMMITTED = 1
ISOLATION_LEVEL_REPEATABLE_READ = 2 ISOLATION_LEVEL_REPEATABLE_READ = 2
ISOLATION_LEVEL_SERIALIZABLE = 3 ISOLATION_LEVEL_SERIALIZABLE = 3
ISOLATION_LEVEL_DEFAULT = 5
"""psycopg connection status values.""" """psycopg connection status values."""

View File

@ -38,6 +38,12 @@ extern "C" {
#define ISOLATION_LEVEL_READ_COMMITTED 1 #define ISOLATION_LEVEL_READ_COMMITTED 1
#define ISOLATION_LEVEL_REPEATABLE_READ 2 #define ISOLATION_LEVEL_REPEATABLE_READ 2
#define ISOLATION_LEVEL_SERIALIZABLE 3 #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 */ /* connection status */
#define CONN_STATUS_SETUP 0 #define CONN_STATUS_SETUP 0
@ -129,6 +135,11 @@ struct connectionObject {
* codecs.getdecoder('utf8') */ * codecs.getdecoder('utf8') */
PyObject *pyencoder; /* python codec encoding function */ PyObject *pyencoder; /* python codec encoding function */
PyObject *pydecoder; /* python codec decoding 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 */ /* 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); HIDDEN void conn_close_locked(connectionObject *self);
RAISES_NEG HIDDEN int conn_commit(connectionObject *self); RAISES_NEG HIDDEN int conn_commit(connectionObject *self);
RAISES_NEG HIDDEN int conn_rollback(connectionObject *self); RAISES_NEG HIDDEN int conn_rollback(connectionObject *self);
RAISES_NEG HIDDEN int conn_set_session(connectionObject *self, const char *isolevel, RAISES_NEG HIDDEN int conn_set_session(connectionObject *self, int autocommit,
const char *readonly, const char *deferrable, int isolevel, int readonly, int 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_client_encoding(connectionObject *self, const char *enc); RAISES_NEG HIDDEN int conn_set_client_encoding(connectionObject *self, const char *enc);
HIDDEN int conn_poll(connectionObject *self); HIDDEN int conn_poll(connectionObject *self);
RAISES_NEG HIDDEN int conn_tpc_begin(connectionObject *self, xidObject *xid); RAISES_NEG HIDDEN int conn_tpc_begin(connectionObject *self, xidObject *xid);

View File

@ -34,18 +34,36 @@
#include <string.h> #include <string.h>
/* Mapping from isolation level name to value exposed by Python. /* String indexes match the ISOLATION_LEVEL_* consts */
* const char *srv_isolevels[] = {
* Note: ordering matters: to get a valid pre-PG 8 level from one not valid, NULL, /* autocommit */
* we increase a pointer in this list by one position. */ "READ COMMITTED",
const IsolationLevel conn_isolevels[] = { "REPEATABLE READ",
{"", ISOLATION_LEVEL_AUTOCOMMIT}, "SERIALIZABLE",
{"read uncommitted", ISOLATION_LEVEL_READ_UNCOMMITTED}, "READ UNCOMMITTED",
{"read committed", ISOLATION_LEVEL_READ_COMMITTED}, "default" /* only to set GUC, not for BEGIN */
{"repeatable read", ISOLATION_LEVEL_REPEATABLE_READ}, };
{"serializable", ISOLATION_LEVEL_SERIALIZABLE},
{"default", -1}, /* never to be found on the server */ /* Read only false, true */
{ NULL } 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 RAISES_NEG int
conn_get_isolation_level(connectionObject *self) 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 */ /* this may get called by async connections too: here's your result */
if (self->autocommit) { if (self->autocommit) {
return 0; return ISOLATION_LEVEL_AUTOCOMMIT;
} }
else {
Py_BEGIN_ALLOW_THREADS; return self->isolevel;
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);
}
return rv;
} }
@ -1208,156 +1189,98 @@ conn_rollback(connectionObject *self)
return res; return res;
} }
/* Change the state of the session */
RAISES_NEG int RAISES_NEG int
conn_set_session(connectionObject *self, conn_set_session(connectionObject *self, int autocommit,
const char *isolevel, const char *readonly, const char *deferrable, int isolevel, int readonly, int deferrable)
int autocommit)
{ {
int rv = -1;
PGresult *pgres = NULL; PGresult *pgres = NULL;
char *error = NULL; char *error = NULL;
int res = -1;
Py_BEGIN_ALLOW_THREADS; /* Promote an isolation level to one of the levels supported by the server */
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 */
RAISES_NEG int
conn_switch_isolation_level(connectionObject *self, int level)
{
PGresult *pgres = NULL;
char *error = NULL;
int curr_level;
int ret = -1;
/* use only supported levels on older PG versions */
if (self->server_version < 80000) { if (self->server_version < 80000) {
if (level == ISOLATION_LEVEL_READ_UNCOMMITTED) if (isolevel == ISOLATION_LEVEL_READ_UNCOMMITTED) {
level = ISOLATION_LEVEL_READ_COMMITTED; isolevel = ISOLATION_LEVEL_READ_COMMITTED;
else if (level == ISOLATION_LEVEL_REPEATABLE_READ)
level = ISOLATION_LEVEL_SERIALIZABLE;
} }
else if (isolevel == ISOLATION_LEVEL_REPEATABLE_READ) {
if (-1 == (curr_level = conn_get_isolation_level(self))) { isolevel = ISOLATION_LEVEL_SERIALIZABLE;
return -1;
} }
if (curr_level == level) {
/* no need to change level */
return 0;
} }
/* Emulate the previous semantic of set_isolation_level() using the
* functions currently available. */
Py_BEGIN_ALLOW_THREADS; Py_BEGIN_ALLOW_THREADS;
pthread_mutex_lock(&self->lock); pthread_mutex_lock(&self->lock);
/* terminate the current transaction if any */ if (autocommit) {
if ((ret = pq_abort_locked(self, &pgres, &error, &_save))) { /* 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; goto endlock;
} }
}
if (level == 0) { if (readonly != self->readonly) {
if ((ret = pq_set_guc_locked(self, if (0 > pq_set_guc_locked(self,
"default_transaction_read_only", srv_state_guc[readonly],
&pgres, &error, &_save)) {
goto endlock;
}
}
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", "default_transaction_isolation", "default",
&pgres, &error, &_save))) { &pgres, &error, &_save)) {
goto endlock; goto endlock;
} }
self->autocommit = 1; if (0 > pq_set_guc_locked(self,
} "default_transaction_read_only", "default",
else { &pgres, &error, &_save)) {
/* 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; goto endlock;
} }
if (self->server_version >= 90100) {
if (0 > pq_set_guc_locked(self,
"default_transaction_deferrable", "default",
&pgres, &error, &_save)) {
goto endlock;
}
}
}
if ((ret = pq_set_guc_locked(self, self->autocommit = autocommit;
"default_transaction_isolation", isolevel->name, self->isolevel = isolevel;
&pgres, &error, &_save))) { self->readonly = readonly;
goto endlock; self->deferrable = deferrable;
} rv = 0;
self->autocommit = 0;
}
Dprintf("conn_switch_isolation_level: switched to level %d", level);
endlock: endlock:
pthread_mutex_unlock(&self->lock); pthread_mutex_unlock(&self->lock);
Py_END_ALLOW_THREADS; Py_END_ALLOW_THREADS;
if (ret < 0) { if (rv < 0) {
pq_complete_error(self, &pgres, &error); 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;
} }

View File

@ -36,6 +36,9 @@
#include <string.h> #include <string.h>
#include <ctype.h> #include <ctype.h>
extern HIDDEN const char *srv_isolevels[];
extern HIDDEN const char *srv_readonly[];
extern HIDDEN const char *srv_deferrable[];
/** DBAPI methods **/ /** DBAPI methods **/
@ -444,18 +447,17 @@ exit:
/* parse a python object into one of the possible isolation level values */ /* parse a python object into one of the possible isolation level values */
extern const IsolationLevel conn_isolevels[]; RAISES_NEG static int
_psyco_conn_parse_isolevel(PyObject *pyval)
static const char *
_psyco_conn_parse_isolevel(connectionObject *self, PyObject *pyval)
{ {
const IsolationLevel *isolevel = NULL; int rv = -1;
long level;
Py_INCREF(pyval); /* for ensure_bytes */ Py_INCREF(pyval); /* for ensure_bytes */
/* parse from one of the level constants */ /* parse from one of the level constants */
if (PyInt_Check(pyval)) { if (PyInt_Check(pyval)) {
long level = PyInt_AsLong(pyval); level = PyInt_AsLong(pyval);
if (level == -1 && PyErr_Occurred()) { goto exit; } if (level == -1 && PyErr_Occurred()) { goto exit; }
if (level < 1 || level > 4) { if (level < 1 || level > 4) {
PyErr_SetString(PyExc_ValueError, PyErr_SetString(PyExc_ValueError,
@ -463,64 +465,79 @@ _psyco_conn_parse_isolevel(connectionObject *self, PyObject *pyval)
goto exit; goto exit;
} }
isolevel = conn_isolevels; rv = level;
while ((++isolevel)->value != level)
; /* continue */
} }
/* parse from the string -- this includes "default" */ /* parse from the string -- this includes "default" */
else { else {
isolevel = conn_isolevels;
while ((++isolevel)->name) {
if (!(pyval = psycopg_ensure_bytes(pyval))) { if (!(pyval = psycopg_ensure_bytes(pyval))) {
goto exit; goto exit;
} }
if (0 == strcasecmp(isolevel->name, Bytes_AS_STRING(pyval))) { for (level = 1; level <= 4; level++) {
if (0 == strcasecmp(srv_isolevels[level], Bytes_AS_STRING(pyval))) {
rv = level;
break; break;
} }
} }
if (!isolevel->name) { if (rv < 0 && 0 == strcasecmp("default", Bytes_AS_STRING(pyval))) {
char msg[256]; rv = ISOLATION_LEVEL_DEFAULT;
snprintf(msg, sizeof(msg), }
if (rv < 0) {
PyErr_Format(PyExc_ValueError,
"bad value for isolation_level: '%s'", Bytes_AS_STRING(pyval)); "bad value for isolation_level: '%s'", Bytes_AS_STRING(pyval));
PyErr_SetString(PyExc_ValueError, msg); goto exit;
}
}
/* 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: exit:
Py_XDECREF(pyval); 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) _psyco_conn_parse_onoff(PyObject *pyval)
{ {
int istrue = PyObject_IsTrue(pyval); int rv = -1;
if (-1 == istrue) { return NULL; }
if (istrue) { Py_INCREF(pyval); /* for ensure_bytes */
int cmp;
PyObject *pydef; if (PyUnicode_CheckExact(pyval) || Bytes_CheckExact(pyval)) {
if (!(pydef = Text_FromUTF8("default"))) { return NULL; } if (!(pyval = psycopg_ensure_bytes(pyval))) {
cmp = PyObject_RichCompareBool(pyval, pydef, Py_EQ); goto exit;
Py_DECREF(pydef); }
if (-1 == cmp) { return NULL; } if (0 == strcasecmp("default", Bytes_AS_STRING(pyval))) {
return cmp ? "default" : "on"; rv = STATE_DEFAULT;
} }
else { else {
return "off"; PyErr_Format(PyExc_ValueError,
"the only string accepted is 'default'; got %s",
Bytes_AS_STRING(pyval));
goto exit;
} }
} }
else {
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 */ /* set_session - set default transaction characteristics */
@ -536,17 +553,15 @@ psyco_conn_set_session(connectionObject *self, PyObject *args, PyObject *kwargs)
PyObject *deferrable = Py_None; PyObject *deferrable = Py_None;
PyObject *autocommit = Py_None; PyObject *autocommit = Py_None;
const char *c_isolevel = NULL; int c_isolevel = self->isolevel;
const char *c_readonly = NULL; int c_readonly = self->readonly;
const char *c_deferrable = NULL; int c_deferrable = self->deferrable;
int c_autocommit = self->autocommit; int c_autocommit = self->autocommit;
static char *kwlist[] = static char *kwlist[] =
{"isolation_level", "readonly", "deferrable", "autocommit", NULL}; {"isolation_level", "readonly", "deferrable", "autocommit", NULL};
EXC_IF_CONN_CLOSED(self); _set_session_checks(self, set_session);
EXC_IF_CONN_ASYNC(self, set_session);
EXC_IF_IN_TRANSACTION(self, set_session);
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|OOOO", kwlist, if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|OOOO", kwlist,
&isolevel, &readonly, &deferrable, &autocommit)) { &isolevel, &readonly, &deferrable, &autocommit)) {
@ -554,13 +569,13 @@ psyco_conn_set_session(connectionObject *self, PyObject *args, PyObject *kwargs)
} }
if (Py_None != isolevel) { if (Py_None != isolevel) {
if (!(c_isolevel = _psyco_conn_parse_isolevel(self, isolevel))) { if (0 > (c_isolevel = _psyco_conn_parse_isolevel(isolevel))) {
return NULL; return NULL;
} }
} }
if (Py_None != readonly) { if (Py_None != readonly) {
if (!(c_readonly = _psyco_conn_parse_onoff(readonly))) { if (0 > (c_readonly = _psyco_conn_parse_onoff(readonly))) {
return NULL; return NULL;
} }
} }
@ -571,17 +586,17 @@ psyco_conn_set_session(connectionObject *self, PyObject *args, PyObject *kwargs)
" from PostgreSQL 9.1"); " from PostgreSQL 9.1");
return NULL; return NULL;
} }
if (!(c_deferrable = _psyco_conn_parse_onoff(deferrable))) { if (0 > (c_deferrable = _psyco_conn_parse_onoff(readonly))) {
return NULL; return NULL;
} }
} }
if (Py_None != autocommit) { if (Py_None != autocommit) {
c_autocommit = PyObject_IsTrue(autocommit); if (-1 == (c_autocommit = PyObject_IsTrue(autocommit))) { return NULL; }
if (-1 == c_autocommit) { return NULL; }
} }
if (0 > conn_set_session(self, if (0 > conn_set_session(
c_isolevel, c_readonly, c_deferrable, c_autocommit)) { self, c_autocommit, c_isolevel, c_readonly, c_deferrable)) {
return NULL; return NULL;
} }
@ -606,9 +621,7 @@ _psyco_conn_autocommit_set_checks(connectionObject *self)
{ {
/* wrapper to use the EXC_IF macros. /* wrapper to use the EXC_IF macros.
* return NULL in case of error, else whatever */ * return NULL in case of error, else whatever */
EXC_IF_CONN_CLOSED(self); _set_session_checks(self, autocommit);
EXC_IF_CONN_ASYNC(self, autocommit);
EXC_IF_IN_TRANSACTION(self, autocommit);
return Py_None; /* borrowed */ 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 (!_psyco_conn_autocommit_set_checks(self)) { return -1; }
if (-1 == (value = PyObject_IsTrue(pyvalue))) { 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; return 0;
} }
@ -651,21 +667,28 @@ psyco_conn_set_isolation_level(connectionObject *self, PyObject *args)
{ {
int level = 1; int level = 1;
EXC_IF_CONN_CLOSED(self); _set_session_checks(self, set_isolation_level);
EXC_IF_CONN_ASYNC(self, set_isolation_level);
EXC_IF_TPC_PREPARED(self, set_isolation_level);
if (!PyArg_ParseTuple(args, "i", &level)) return NULL; if (!PyArg_ParseTuple(args, "i", &level)) return NULL;
if (level < 0 || level > 4) { if (level < 0 || level > 5) {
PyErr_SetString(PyExc_ValueError, PyErr_SetString(PyExc_ValueError,
"isolation level must be between 0 and 4"); "isolation level must be between 0 and 4");
return NULL; return NULL;
} }
if (conn_switch_isolation_level(self, level) < 0) { if (level == 0) {
if (0 > conn_set_session(self, 1,
ISOLATION_LEVEL_DEFAULT, self->readonly, self->deferrable)) {
return NULL; return NULL;
} }
}
else {
if (0 > conn_set_session(self, 0,
level, self->readonly, self->deferrable)) {
return NULL;
}
}
Py_RETURN_NONE; Py_RETURN_NONE;
} }
@ -1107,6 +1130,9 @@ connection_setup(connectionObject *self, const char *dsn, long int async)
self->async_status = ASYNC_DONE; self->async_status = ASYNC_DONE;
if (!(self->string_types = PyDict_New())) { goto exit; } if (!(self->string_types = PyDict_New())) { goto exit; }
if (!(self->binary_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 */ /* other fields have been zeroed by tp_alloc */
pthread_mutex_init(&(self->lock), NULL); pthread_mutex_init(&(self->lock), NULL);

View File

@ -53,7 +53,9 @@
#endif #endif
extern HIDDEN PyObject *psyco_DescriptionType; 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. */ /* Strip off the severity from a Postgres error message. */
static const char * static const char *
@ -479,6 +481,8 @@ int
pq_begin_locked(connectionObject *conn, PGresult **pgres, char **error, pq_begin_locked(connectionObject *conn, PGresult **pgres, char **error,
PyThreadState **tstate) PyThreadState **tstate)
{ {
const size_t bufsize = 256;
char buf[bufsize];
int result; int result;
Dprintf("pq_begin_locked: pgconn = %p, autocommit = %d, status = %d", 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; 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) if (result == 0)
conn->status = CONN_STATUS_BEGIN; conn->status = CONN_STATUS_BEGIN;

View File

@ -488,7 +488,7 @@ class IsolationLevelsTestCase(ConnectingTestCase):
conn = self.connect() conn = self.connect()
self.assertEqual( self.assertEqual(
conn.isolation_level, conn.isolation_level,
psycopg2.extensions.ISOLATION_LEVEL_READ_COMMITTED) psycopg2.extensions.ISOLATION_LEVEL_DEFAULT)
def test_encoding(self): def test_encoding(self):
conn = self.connect() conn = self.connect()
@ -522,14 +522,33 @@ class IsolationLevelsTestCase(ConnectingTestCase):
got_name = curs.fetchone()[0] got_name = curs.fetchone()[0]
if name is None: if name is None:
curs.execute('show default_transaction_isolation;') curs.execute('show transaction_isolation;')
name = curs.fetchone()[0] name = curs.fetchone()[0]
self.assertEqual(name, got_name) self.assertEqual(name, got_name)
conn.commit() conn.commit()
self.assertRaises(ValueError, conn.set_isolation_level, -1) 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): def test_set_isolation_level_abort(self):
conn = self.connect() conn = self.connect()
@ -541,32 +560,14 @@ class IsolationLevelsTestCase(ConnectingTestCase):
self.assertEqual(psycopg2.extensions.TRANSACTION_STATUS_INTRANS, self.assertEqual(psycopg2.extensions.TRANSACTION_STATUS_INTRANS,
conn.get_transaction_status()) 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) 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, self.assertEqual(psycopg2.extensions.TRANSACTION_STATUS_INTRANS,
conn.get_transaction_status()) conn.get_transaction_status())
conn.set_isolation_level( self.assertEqual(conn.isolation_level,
psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT) psycopg2.extensions.ISOLATION_LEVEL_DEFAULT)
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()
@ -1042,13 +1043,13 @@ class TransactionControlTests(ConnectingTestCase):
cur = self.conn.cursor() cur = self.conn.cursor()
self.conn.set_session( self.conn.set_session(
psycopg2.extensions.ISOLATION_LEVEL_SERIALIZABLE) psycopg2.extensions.ISOLATION_LEVEL_SERIALIZABLE)
cur.execute("SHOW default_transaction_isolation;") cur.execute("SHOW transaction_isolation;")
self.assertEqual(cur.fetchone()[0], 'serializable') self.assertEqual(cur.fetchone()[0], 'serializable')
self.conn.rollback() self.conn.rollback()
self.conn.set_session( self.conn.set_session(
psycopg2.extensions.ISOLATION_LEVEL_REPEATABLE_READ) psycopg2.extensions.ISOLATION_LEVEL_REPEATABLE_READ)
cur.execute("SHOW default_transaction_isolation;") cur.execute("SHOW transaction_isolation;")
if self.conn.server_version > 80000: if self.conn.server_version > 80000:
self.assertEqual(cur.fetchone()[0], 'repeatable read') self.assertEqual(cur.fetchone()[0], 'repeatable read')
else: else:
@ -1057,13 +1058,13 @@ class TransactionControlTests(ConnectingTestCase):
self.conn.set_session( self.conn.set_session(
isolation_level=psycopg2.extensions.ISOLATION_LEVEL_READ_COMMITTED) 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.assertEqual(cur.fetchone()[0], 'read committed')
self.conn.rollback() self.conn.rollback()
self.conn.set_session( self.conn.set_session(
isolation_level=psycopg2.extensions.ISOLATION_LEVEL_READ_UNCOMMITTED) 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: if self.conn.server_version > 80000:
self.assertEqual(cur.fetchone()[0], 'read uncommitted') self.assertEqual(cur.fetchone()[0], 'read uncommitted')
else: else:
@ -1073,12 +1074,12 @@ class TransactionControlTests(ConnectingTestCase):
def test_set_isolation_level_str(self): def test_set_isolation_level_str(self):
cur = self.conn.cursor() cur = self.conn.cursor()
self.conn.set_session("serializable") self.conn.set_session("serializable")
cur.execute("SHOW default_transaction_isolation;") cur.execute("SHOW transaction_isolation;")
self.assertEqual(cur.fetchone()[0], 'serializable') self.assertEqual(cur.fetchone()[0], 'serializable')
self.conn.rollback() self.conn.rollback()
self.conn.set_session("repeatable read") self.conn.set_session("repeatable read")
cur.execute("SHOW default_transaction_isolation;") cur.execute("SHOW transaction_isolation;")
if self.conn.server_version > 80000: if self.conn.server_version > 80000:
self.assertEqual(cur.fetchone()[0], 'repeatable read') self.assertEqual(cur.fetchone()[0], 'repeatable read')
else: else:
@ -1086,12 +1087,12 @@ class TransactionControlTests(ConnectingTestCase):
self.conn.rollback() self.conn.rollback()
self.conn.set_session("read committed") 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.assertEqual(cur.fetchone()[0], 'read committed')
self.conn.rollback() self.conn.rollback()
self.conn.set_session("read uncommitted") self.conn.set_session("read uncommitted")
cur.execute("SHOW default_transaction_isolation;") cur.execute("SHOW transaction_isolation;")
if self.conn.server_version > 80000: if self.conn.server_version > 80000:
self.assertEqual(cur.fetchone()[0], 'read uncommitted') self.assertEqual(cur.fetchone()[0], 'read uncommitted')
else: else:
@ -1106,57 +1107,57 @@ class TransactionControlTests(ConnectingTestCase):
def test_set_read_only(self): def test_set_read_only(self):
cur = self.conn.cursor() cur = self.conn.cursor()
self.conn.set_session(readonly=True) 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.assertEqual(cur.fetchone()[0], 'on')
self.conn.rollback() self.conn.rollback()
cur.execute("SHOW default_transaction_read_only;") cur.execute("SHOW transaction_read_only;")
self.assertEqual(cur.fetchone()[0], 'on') self.assertEqual(cur.fetchone()[0], 'on')
self.conn.rollback() self.conn.rollback()
cur = self.conn.cursor() cur = self.conn.cursor()
self.conn.set_session(readonly=None) 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.assertEqual(cur.fetchone()[0], 'on')
self.conn.rollback() self.conn.rollback()
self.conn.set_session(readonly=False) 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.assertEqual(cur.fetchone()[0], 'off')
self.conn.rollback() self.conn.rollback()
def test_set_default(self): def test_set_default(self):
cur = self.conn.cursor() cur = self.conn.cursor()
cur.execute("SHOW default_transaction_isolation;") cur.execute("SHOW transaction_isolation;")
default_isolevel = cur.fetchone()[0] isolevel = cur.fetchone()[0]
cur.execute("SHOW default_transaction_read_only;") cur.execute("SHOW transaction_read_only;")
default_readonly = cur.fetchone()[0] readonly = cur.fetchone()[0]
self.conn.rollback() self.conn.rollback()
self.conn.set_session(isolation_level='serializable', readonly=True) self.conn.set_session(isolation_level='serializable', readonly=True)
self.conn.set_session(isolation_level='default', readonly='default') self.conn.set_session(isolation_level='default', readonly='default')
cur.execute("SHOW default_transaction_isolation;") cur.execute("SHOW transaction_isolation;")
self.assertEqual(cur.fetchone()[0], default_isolevel) self.assertEqual(cur.fetchone()[0], isolevel)
cur.execute("SHOW default_transaction_read_only;") cur.execute("SHOW transaction_read_only;")
self.assertEqual(cur.fetchone()[0], default_readonly) self.assertEqual(cur.fetchone()[0], readonly)
@skip_before_postgres(9, 1) @skip_before_postgres(9, 1)
def test_set_deferrable(self): def test_set_deferrable(self):
cur = self.conn.cursor() cur = self.conn.cursor()
self.conn.set_session(readonly=True, deferrable=True) 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') self.assertEqual(cur.fetchone()[0], 'on')
cur.execute("SHOW default_transaction_deferrable;") cur.execute("SHOW transaction_deferrable;")
self.assertEqual(cur.fetchone()[0], 'on') self.assertEqual(cur.fetchone()[0], 'on')
self.conn.rollback() self.conn.rollback()
cur.execute("SHOW default_transaction_deferrable;") cur.execute("SHOW transaction_deferrable;")
self.assertEqual(cur.fetchone()[0], 'on') self.assertEqual(cur.fetchone()[0], 'on')
self.conn.rollback() self.conn.rollback()
self.conn.set_session(deferrable=False) 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') self.assertEqual(cur.fetchone()[0], 'on')
cur.execute("SHOW default_transaction_deferrable;") cur.execute("SHOW transaction_deferrable;")
self.assertEqual(cur.fetchone()[0], 'off') self.assertEqual(cur.fetchone()[0], 'off')
self.conn.rollback() self.conn.rollback()
@ -1258,9 +1259,9 @@ class AutocommitTests(ConnectingTestCase):
self.assertEqual(self.conn.status, psycopg2.extensions.STATUS_READY) self.assertEqual(self.conn.status, psycopg2.extensions.STATUS_READY)
self.assertEqual(self.conn.get_transaction_status(), self.assertEqual(self.conn.get_transaction_status(),
psycopg2.extensions.TRANSACTION_STATUS_IDLE) psycopg2.extensions.TRANSACTION_STATUS_IDLE)
cur.execute("SHOW default_transaction_isolation;") cur.execute("SHOW transaction_isolation;")
self.assertEqual(cur.fetchone()[0], 'serializable') 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') self.assertEqual(cur.fetchone()[0], 'on')