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`).
- 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

View File

@ -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

View File

@ -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::

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
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::

View File

@ -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."""

View File

@ -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);

View File

@ -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;
else {
return self->isolevel;
}
/* 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;
}
/* Change the state of the session */
RAISES_NEG int
conn_set_session(connectionObject *self,
const char *isolevel, const char *readonly, const char *deferrable,
int autocommit)
conn_set_session(connectionObject *self, int autocommit,
int isolevel, int readonly, int deferrable)
{
int rv = -1;
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 */
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 */
/* 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 (isolevel == ISOLATION_LEVEL_READ_UNCOMMITTED) {
isolevel = ISOLATION_LEVEL_READ_COMMITTED;
}
else if (isolevel == ISOLATION_LEVEL_REPEATABLE_READ) {
isolevel = 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;
}
/* 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;
}
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 (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;
}
}
if (!isolevel->name) {
ret = -1;
error = strdup("bad isolation level value");
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 (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;
}

View File

@ -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);

View File

@ -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;

View File

@ -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')