Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Separated autocommit and isolation level handling for PostgreSQL.

Autocommit cannot be manipulated independently from an open connection.
This commit introduces a minor change in behavior: entering transaction
management forces opening a databasse connection. This shouldn't be
backwards incompatible in any practical use case.
  • Loading branch information...
commit 8717b0668caf00ec5e81ef5a1e31b4d7c64eee8a 1 parent f515619
@aaugustin aaugustin authored
View
78 django/db/backends/postgresql_psycopg2/base.py
@@ -77,21 +77,21 @@ class DatabaseWrapper(BaseDatabaseWrapper):
def __init__(self, *args, **kwargs):
super(DatabaseWrapper, self).__init__(*args, **kwargs)
+ opts = self.settings_dict["OPTIONS"]
+ RC = psycopg2.extensions.ISOLATION_LEVEL_READ_COMMITTED
+ self.isolation_level = opts.get('isolation_level', RC)
+
self.features = DatabaseFeatures(self)
- autocommit = self.settings_dict["OPTIONS"].get('autocommit', False)
- self.features.uses_autocommit = autocommit
- if autocommit:
- level = psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT
- else:
- level = self.settings_dict["OPTIONS"].get('isolation_level',
- psycopg2.extensions.ISOLATION_LEVEL_READ_COMMITTED)
- self._set_isolation_level(level)
self.ops = DatabaseOperations(self)
self.client = DatabaseClient(self)
self.creation = DatabaseCreation(self)
self.introspection = DatabaseIntrospection(self)
self.validation = BaseDatabaseValidation(self)
+ autocommit = opts.get('autocommit', False)
+ self.features.uses_autocommit = autocommit
+ self.features.uses_savepoints = not autocommit
+
def get_connection_params(self):
settings_dict = self.settings_dict
if not settings_dict['NAME']:
@@ -135,11 +135,12 @@ def init_connection_state(self):
if conn_tz != tz:
# Set the time zone in autocommit mode (see #17062)
- self.connection.set_isolation_level(
- psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT)
+ self.set_autocommit(True)
self.connection.cursor().execute(
self.ops.set_time_zone_sql(), [tz])
self.connection.set_isolation_level(self.isolation_level)
+ if self.features.uses_autocommit:
+ self.set_autocommit(True)
def create_cursor(self):
cursor = self.connection.cursor()
@@ -172,42 +173,40 @@ def _enter_transaction_management(self, managed):
Switch the isolation level when needing transaction support, so that
the same transaction is visible across all the queries.
"""
- if self.features.uses_autocommit and managed and not self.isolation_level:
- level = self.settings_dict["OPTIONS"].get('isolation_level',
- psycopg2.extensions.ISOLATION_LEVEL_READ_COMMITTED)
- self._set_isolation_level(level)
+ if self.connection is None: # Force creating a connection.
+ self.cursor().close()
+ if self.features.uses_autocommit and managed and self.autocommit:
+ self.set_autocommit(False)
+ self.features.uses_savepoints = True
def _leave_transaction_management(self, managed):
"""
If the normal operating mode is "autocommit", switch back to that when
leaving transaction management.
"""
- if self.features.uses_autocommit and not managed and self.isolation_level:
- self._set_isolation_level(psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT)
-
- def _set_isolation_level(self, level):
- """
- Do all the related feature configurations for changing isolation
- levels. This doesn't touch the uses_autocommit feature, since that
- controls the movement *between* isolation levels.
- """
- assert level in range(5)
- try:
- if self.connection is not None:
- self.connection.set_isolation_level(level)
- if level == psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT:
- self.set_clean()
- finally:
- self.isolation_level = level
- self.features.uses_savepoints = bool(level)
+ if self.connection is None: # Force creating a connection.
+ self.cursor().close()
+ if self.features.uses_autocommit and not managed and not self.autocommit:
+ self.rollback() # Must terminate transaction first.
+ self.set_autocommit(True)
+ self.features.uses_savepoints = False
+
+ def _set_isolation_level(self, isolation_level):
+ assert isolation_level in range(1, 5) # Use set_autocommit for level = 0
+ if self.psycopg2_version >= (2, 4, 2):
+ self.connection.set_session(isolation_level=isolation_level)
+ else:
+ self.connection.set_isolation_level(isolation_level)
def _set_autocommit(self, autocommit):
- if autocommit:
- level = psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT
+ if self.psycopg2_version >= (2, 4, 2):
+ self.connection.autocommit = autocommit
else:
- level = self.settings_dict["OPTIONS"].get('isolation_level',
- psycopg2.extensions.ISOLATION_LEVEL_READ_COMMITTED)
- self._set_isolation_level(level)
+ if autocommit:
+ level = psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT
+ else:
+ level = self.isolation_level
+ self.connection.set_isolation_level(level)
def set_dirty(self):
if ((self.transaction_state and self.transaction_state[-1]) or
@@ -232,6 +231,11 @@ def is_usable(self):
return True
@cached_property
+ def psycopg2_version(self):
+ version = psycopg2.__version__.split(' ', 1)[0]
+ return tuple(int(v) for v in version.split('.'))
+
+ @cached_property
def pg_version(self):
with self.temporary_connection():
return get_version(self.connection)
View
17 tests/transactions_regress/tests.py
@@ -274,31 +274,39 @@ def tearDown(self):
connections[DEFAULT_DB_ALIAS] = self._old_backend
def test_initial_autocommit_state(self):
+ # Autocommit is activated when the connection is created.
+ connection.cursor().close()
+
self.assertTrue(connection.features.uses_autocommit)
- self.assertEqual(connection.isolation_level, self._autocommit)
+ self.assertTrue(connection.autocommit)
def test_transaction_management(self):
transaction.enter_transaction_management()
+ self.assertFalse(connection.autocommit)
self.assertEqual(connection.isolation_level, self._serializable)
transaction.leave_transaction_management()
- self.assertEqual(connection.isolation_level, self._autocommit)
+ self.assertTrue(connection.autocommit)
def test_transaction_stacking(self):
transaction.enter_transaction_management()
+ self.assertFalse(connection.autocommit)
self.assertEqual(connection.isolation_level, self._serializable)
transaction.enter_transaction_management()
+ self.assertFalse(connection.autocommit)
self.assertEqual(connection.isolation_level, self._serializable)
transaction.leave_transaction_management()
+ self.assertFalse(connection.autocommit)
self.assertEqual(connection.isolation_level, self._serializable)
transaction.leave_transaction_management()
- self.assertEqual(connection.isolation_level, self._autocommit)
+ self.assertTrue(connection.autocommit)
def test_enter_autocommit(self):
transaction.enter_transaction_management()
+ self.assertFalse(connection.autocommit)
self.assertEqual(connection.isolation_level, self._serializable)
list(Mod.objects.all())
self.assertTrue(transaction.is_dirty())
@@ -311,9 +319,10 @@ def test_enter_autocommit(self):
list(Mod.objects.all())
self.assertFalse(transaction.is_dirty())
transaction.leave_transaction_management()
+ self.assertFalse(connection.autocommit)
self.assertEqual(connection.isolation_level, self._serializable)
transaction.leave_transaction_management()
- self.assertEqual(connection.isolation_level, self._autocommit)
+ self.assertTrue(connection.autocommit)
class TestManyToManyAddTransaction(TransactionTestCase):
Please sign in to comment.
Something went wrong with that request. Please try again.