Commit e0449316 authored by Aymeric Augustin's avatar Aymeric Augustin
Browse files

Fixed #18130 -- Made the isolation level configurable on PostgreSQL.

Thanks limscoder for the report and niwi for the draft patch.
parent d63e5503
Loading
Loading
Loading
Loading
+7 −2
Original line number Diff line number Diff line
@@ -83,7 +83,8 @@ class DatabaseWrapper(BaseDatabaseWrapper):
        if autocommit:
            level = psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT
        else:
            level = psycopg2.extensions.ISOLATION_LEVEL_READ_COMMITTED
            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)
@@ -104,6 +105,8 @@ class DatabaseWrapper(BaseDatabaseWrapper):
        conn_params.update(settings_dict['OPTIONS'])
        if 'autocommit' in conn_params:
            del conn_params['autocommit']
        if 'isolation_level' in conn_params:
            del conn_params['isolation_level']
        if settings_dict['USER']:
            conn_params['user'] = settings_dict['USER']
        if settings_dict['PASSWORD']:
@@ -170,7 +173,9 @@ class DatabaseWrapper(BaseDatabaseWrapper):
        the same transaction is visible across all the queries.
        """
        if self.features.uses_autocommit and managed and not self.isolation_level:
            self._set_isolation_level(psycopg2.extensions.ISOLATION_LEVEL_READ_COMMITTED)
            level = self.settings_dict["OPTIONS"].get('isolation_level',
                psycopg2.extensions.ISOLATION_LEVEL_READ_COMMITTED)
            self._set_isolation_level(level)

    def _leave_transaction_management(self, managed):
        """
+33 −2
Original line number Diff line number Diff line
@@ -143,8 +143,11 @@ autocommit behavior is enabled by setting the ``autocommit`` key in
the :setting:`OPTIONS` part of your database configuration in
:setting:`DATABASES`::

    DATABASES = {
        # ...
        'OPTIONS': {
            'autocommit': True,
        },
    }

In this configuration, Django still ensures that :ref:`delete()
@@ -168,6 +171,34 @@ You should also audit your existing code for any instances of this behavior
before enabling this feature. It's faster, but it provides less automatic
protection for multi-call operations.

Isolation level
~~~~~~~~~~~~~~~

.. versionadded:: 1.6

Like PostgreSQL itself, Django defaults to the ``READ COMMITTED`` `isolation
level <postgresql-isolation-levels>`_. If you need a higher isolation level
such as ``REPEATABLE READ`` or ``SERIALIZABLE``, set it in the
:setting:`OPTIONS` part of your database configuration in
:setting:`DATABASES`::

    import psycopg2.extensions

    DATABASES = {
        # ...
        'OPTIONS': {
            'isolation_level': psycopg2.extensions.ISOLATION_LEVEL_SERIALIZABLE,
        },
    }

.. note::

    Under higher isolation levels, your application should be prepared to
    handle exceptions raised on serialization failures. This option is
    designed for advanced uses.

.. _postgresql-isolation-levels: http://www.postgresql.org/docs/devel/static/transaction-iso.html

Indexes for ``varchar`` and ``text`` columns
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

+2 −0
Original line number Diff line number Diff line
@@ -125,6 +125,8 @@ Minor features
* The admin list columns have a ``column-<field_name>`` class in the HTML
  so the columns header can be styled with CSS, e.g. to set a column width.

* The isolation level can be customized under PostgreSQL.

Backwards incompatible changes in 1.6
=====================================

+13 −11
Original line number Diff line number Diff line
@@ -242,17 +242,18 @@ class TestNewConnection(TransactionTestCase):

@skipUnless(connection.vendor == 'postgresql',
            "This test only valid for PostgreSQL")
class TestPostgresAutocommit(TransactionTestCase):
class TestPostgresAutocommitAndIsolation(TransactionTestCase):
    """
    Tests to make sure psycopg2's autocommit mode is restored after entering
    and leaving transaction management. Refs #16047.
    Tests to make sure psycopg2's autocommit mode and isolation level
    is restored after entering and leaving transaction management.
    Refs #16047, #18130.
    """
    def setUp(self):
        from psycopg2.extensions import (ISOLATION_LEVEL_AUTOCOMMIT,
                                         ISOLATION_LEVEL_READ_COMMITTED,
                                         ISOLATION_LEVEL_SERIALIZABLE,
                                         TRANSACTION_STATUS_IDLE)
        self._autocommit = ISOLATION_LEVEL_AUTOCOMMIT
        self._read_committed = ISOLATION_LEVEL_READ_COMMITTED
        self._serializable = ISOLATION_LEVEL_SERIALIZABLE
        self._idle = TRANSACTION_STATUS_IDLE

        # We want a clean backend with autocommit = True, so
@@ -261,6 +262,7 @@ class TestPostgresAutocommit(TransactionTestCase):
        settings = self._old_backend.settings_dict.copy()
        opts = settings['OPTIONS'].copy()
        opts['autocommit'] = True
        opts['isolation_level'] = ISOLATION_LEVEL_SERIALIZABLE
        settings['OPTIONS'] = opts
        new_backend = self._old_backend.__class__(settings, DEFAULT_DB_ALIAS)
        connections[DEFAULT_DB_ALIAS] = new_backend
@@ -279,7 +281,7 @@ class TestPostgresAutocommit(TransactionTestCase):
    def test_transaction_management(self):
        transaction.enter_transaction_management()
        transaction.managed(True)
        self.assertEqual(connection.isolation_level, self._read_committed)
        self.assertEqual(connection.isolation_level, self._serializable)

        transaction.leave_transaction_management()
        self.assertEqual(connection.isolation_level, self._autocommit)
@@ -287,13 +289,13 @@ class TestPostgresAutocommit(TransactionTestCase):
    def test_transaction_stacking(self):
        transaction.enter_transaction_management()
        transaction.managed(True)
        self.assertEqual(connection.isolation_level, self._read_committed)
        self.assertEqual(connection.isolation_level, self._serializable)

        transaction.enter_transaction_management()
        self.assertEqual(connection.isolation_level, self._read_committed)
        self.assertEqual(connection.isolation_level, self._serializable)

        transaction.leave_transaction_management()
        self.assertEqual(connection.isolation_level, self._read_committed)
        self.assertEqual(connection.isolation_level, self._serializable)

        transaction.leave_transaction_management()
        self.assertEqual(connection.isolation_level, self._autocommit)
@@ -301,7 +303,7 @@ class TestPostgresAutocommit(TransactionTestCase):
    def test_enter_autocommit(self):
        transaction.enter_transaction_management()
        transaction.managed(True)
        self.assertEqual(connection.isolation_level, self._read_committed)
        self.assertEqual(connection.isolation_level, self._serializable)
        list(Mod.objects.all())
        self.assertTrue(transaction.is_dirty())
        # Enter autocommit mode again.
@@ -314,7 +316,7 @@ class TestPostgresAutocommit(TransactionTestCase):
        list(Mod.objects.all())
        self.assertFalse(transaction.is_dirty())
        transaction.leave_transaction_management()
        self.assertEqual(connection.isolation_level, self._read_committed)
        self.assertEqual(connection.isolation_level, self._serializable)
        transaction.leave_transaction_management()
        self.assertEqual(connection.isolation_level, self._autocommit)