From f5156194945661d217523d6648dfb9b48707ec95 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Sat, 2 Mar 2013 13:47:46 +0100 Subject: [PATCH] Added an API to control database-level autocommit. --- django/db/backends/__init__.py | 14 ++++++++++++++ django/db/backends/creation.py | 6 +++++- django/db/backends/dummy/base.py | 1 + django/db/backends/mysql/base.py | 3 +++ django/db/backends/oracle/base.py | 3 +++ django/db/backends/oracle/creation.py | 3 --- django/db/backends/postgresql_psycopg2/base.py | 8 ++++++++ .../db/backends/postgresql_psycopg2/creation.py | 3 --- django/db/backends/sqlite3/base.py | 11 +++++++++++ django/db/backends/sqlite3/creation.py | 3 --- django/db/transaction.py | 12 ++++++++++++ django/test/testcases.py | 3 +++ docs/internals/deprecation.txt | 7 ++++--- docs/topics/db/transactions.txt | 17 +++++++++++++++++ 14 files changed, 81 insertions(+), 13 deletions(-) diff --git a/django/db/backends/__init__.py b/django/db/backends/__init__.py index f11ee35260..379416fad7 100644 --- a/django/db/backends/__init__.py +++ b/django/db/backends/__init__.py @@ -44,6 +44,7 @@ class BaseDatabaseWrapper(object): self.savepoint_state = 0 # Transaction management related attributes + self.autocommit = False self.transaction_state = [] # Tracks if the connection is believed to be in transaction. This is # set somewhat aggressively, as the DBAPI doesn't make it easy to @@ -232,6 +233,12 @@ class BaseDatabaseWrapper(object): """ pass + def _set_autocommit(self, autocommit): + """ + Backend-specific implementation to enable or disable autocommit. + """ + raise NotImplementedError + ##### Generic transaction management methods ##### def enter_transaction_management(self, managed=True, forced=False): @@ -274,6 +281,13 @@ class BaseDatabaseWrapper(object): raise TransactionManagementError( "Transaction managed block ended with pending COMMIT/ROLLBACK") + def set_autocommit(self, autocommit=True): + """ + Enable or disable autocommit. + """ + self._set_autocommit(autocommit) + self.autocommit = autocommit + def abort(self): """ Roll back any ongoing transaction and clean the transaction state diff --git a/django/db/backends/creation.py b/django/db/backends/creation.py index 70c24bc820..aa4fb82b12 100644 --- a/django/db/backends/creation.py +++ b/django/db/backends/creation.py @@ -1,6 +1,7 @@ import hashlib import sys import time +import warnings from django.conf import settings from django.db.utils import load_backend @@ -466,7 +467,10 @@ class BaseDatabaseCreation(object): anymore by Django code. Kept for compatibility with user code that might use it. """ - pass + warnings.warn( + "set_autocommit was moved from BaseDatabaseCreation to " + "BaseDatabaseWrapper.", PendingDeprecationWarning, stacklevel=2) + return self.connection.set_autocommit() def _prepare_for_test_db_ddl(self): """ diff --git a/django/db/backends/dummy/base.py b/django/db/backends/dummy/base.py index c59f037a27..a8c2d5bada 100644 --- a/django/db/backends/dummy/base.py +++ b/django/db/backends/dummy/base.py @@ -57,6 +57,7 @@ class DatabaseWrapper(BaseDatabaseWrapper): _savepoint_rollback = ignore _enter_transaction_management = complain _leave_transaction_management = ignore + _set_autocommit = complain set_dirty = complain set_clean = complain commit_unless_managed = complain diff --git a/django/db/backends/mysql/base.py b/django/db/backends/mysql/base.py index 400fe6cdac..39fd3695b7 100644 --- a/django/db/backends/mysql/base.py +++ b/django/db/backends/mysql/base.py @@ -445,6 +445,9 @@ class DatabaseWrapper(BaseDatabaseWrapper): except Database.NotSupportedError: pass + def _set_autocommit(self, autocommit): + self.connection.autocommit(autocommit) + def disable_constraint_checking(self): """ Disables foreign key checks, primarily for use in adding rows with forward references. Always returns True, diff --git a/django/db/backends/oracle/base.py b/django/db/backends/oracle/base.py index 60ee1ba632..d895c1583a 100644 --- a/django/db/backends/oracle/base.py +++ b/django/db/backends/oracle/base.py @@ -612,6 +612,9 @@ class DatabaseWrapper(BaseDatabaseWrapper): def _savepoint_commit(self, sid): pass + def _set_autocommit(self, autocommit): + self.connection.autocommit = autocommit + def check_constraints(self, table_names=None): """ To check constraints, we set constraints to immediate. Then, when, we're done we must ensure they diff --git a/django/db/backends/oracle/creation.py b/django/db/backends/oracle/creation.py index aaca74e8d1..5485830bf5 100644 --- a/django/db/backends/oracle/creation.py +++ b/django/db/backends/oracle/creation.py @@ -273,6 +273,3 @@ class DatabaseCreation(BaseDatabaseCreation): settings_dict['NAME'], self._test_database_user(), ) - - def set_autocommit(self): - self.connection.connection.autocommit = True diff --git a/django/db/backends/postgresql_psycopg2/base.py b/django/db/backends/postgresql_psycopg2/base.py index f9af507311..a14844433e 100644 --- a/django/db/backends/postgresql_psycopg2/base.py +++ b/django/db/backends/postgresql_psycopg2/base.py @@ -201,6 +201,14 @@ class DatabaseWrapper(BaseDatabaseWrapper): self.isolation_level = level self.features.uses_savepoints = bool(level) + def _set_autocommit(self, 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) + def set_dirty(self): if ((self.transaction_state and self.transaction_state[-1]) or not self.features.uses_autocommit): diff --git a/django/db/backends/postgresql_psycopg2/creation.py b/django/db/backends/postgresql_psycopg2/creation.py index b19926b440..e6400d79a1 100644 --- a/django/db/backends/postgresql_psycopg2/creation.py +++ b/django/db/backends/postgresql_psycopg2/creation.py @@ -78,9 +78,6 @@ class DatabaseCreation(BaseDatabaseCreation): ' text_pattern_ops')) return output - def set_autocommit(self): - self._prepare_for_test_db_ddl() - def _prepare_for_test_db_ddl(self): """Rollback and close the active transaction.""" # Make sure there is an open connection. diff --git a/django/db/backends/sqlite3/base.py b/django/db/backends/sqlite3/base.py index 416a6293f5..9a37dd17fe 100644 --- a/django/db/backends/sqlite3/base.py +++ b/django/db/backends/sqlite3/base.py @@ -355,6 +355,17 @@ class DatabaseWrapper(BaseDatabaseWrapper): if self.settings_dict['NAME'] != ":memory:": BaseDatabaseWrapper.close(self) + def _set_autocommit(self, autocommit): + if autocommit: + level = None + else: + # sqlite3's internal default is ''. It's different from None. + # See Modules/_sqlite/connection.c. + level = '' + # 'isolation_level' is a misleading API. + # SQLite always runs at the SERIALIZABLE isolation level. + self.connection.isolation_level = level + def check_constraints(self, table_names=None): """ Checks each table name in `table_names` for rows with invalid foreign key references. This method is diff --git a/django/db/backends/sqlite3/creation.py b/django/db/backends/sqlite3/creation.py index c90a697e35..a9fb273f7a 100644 --- a/django/db/backends/sqlite3/creation.py +++ b/django/db/backends/sqlite3/creation.py @@ -72,9 +72,6 @@ class DatabaseCreation(BaseDatabaseCreation): # Remove the SQLite database file os.remove(test_database_name) - def set_autocommit(self): - self.connection.connection.isolation_level = None - def test_db_signature(self): """ Returns a tuple that uniquely identifies a test database. diff --git a/django/db/transaction.py b/django/db/transaction.py index 09ce2abbd2..dd48e14bf4 100644 --- a/django/db/transaction.py +++ b/django/db/transaction.py @@ -39,6 +39,18 @@ def get_connection(using=None): using = DEFAULT_DB_ALIAS return connections[using] +def get_autocommit(using=None): + """ + Get the autocommit status of the connection. + """ + return get_connection(using).autocommit + +def set_autocommit(using=None, autocommit=True): + """ + Set the autocommit status of the connection. + """ + return get_connection(using).set_autocommit(autocommit) + def abort(using=None): """ Roll back any ongoing transactions and clean the transaction management diff --git a/django/test/testcases.py b/django/test/testcases.py index 7f6b1a49ba..4b9116e3bc 100644 --- a/django/test/testcases.py +++ b/django/test/testcases.py @@ -63,6 +63,7 @@ def to_list(value): value = [value] return value +real_set_autocommit = transaction.set_autocommit real_commit = transaction.commit real_rollback = transaction.rollback real_enter_transaction_management = transaction.enter_transaction_management @@ -73,6 +74,7 @@ def nop(*args, **kwargs): return def disable_transaction_methods(): + transaction.set_autocommit = nop transaction.commit = nop transaction.rollback = nop transaction.enter_transaction_management = nop @@ -80,6 +82,7 @@ def disable_transaction_methods(): transaction.abort = nop def restore_transaction_methods(): + transaction.set_autocommit = real_set_autocommit transaction.commit = real_commit transaction.rollback = real_rollback transaction.enter_transaction_management = real_enter_transaction_management diff --git a/docs/internals/deprecation.txt b/docs/internals/deprecation.txt index 296f908a5b..74fbb563f0 100644 --- a/docs/internals/deprecation.txt +++ b/docs/internals/deprecation.txt @@ -348,9 +348,10 @@ these changes. * Remove the backward compatible shims introduced to rename the attributes ``ChangeList.root_query_set`` and ``ChangeList.query_set``. -* The private API ``django.db.close_connection`` will be removed. - -* The private API ``django.transaction.managed`` will be removed. +* The following private APIs will be removed: + - ``django.db.close_connection()`` + - ``django.db.backends.creation.BaseDatabaseCreation.set_autocommit()`` + - ``django.db.transaction.managed()`` 2.0 --- diff --git a/docs/topics/db/transactions.txt b/docs/topics/db/transactions.txt index 11755ff5c5..e145edf149 100644 --- a/docs/topics/db/transactions.txt +++ b/docs/topics/db/transactions.txt @@ -208,6 +208,23 @@ This applies to all database operations, not just write operations. Even if your transaction only reads from the database, the transaction must be committed or rolled back before you complete a request. +.. _managing-autocommit: + +Managing autocommit +=================== + +.. versionadded:: 1.6 + +Django provides a straightforward API to manage the autocommit state of each +database connection, if you need to. + +.. function:: get_autocommit(using=None) + +.. function:: set_autocommit(using=None, autocommit=True) + +These functions take a ``using`` argument which should be the name of a +database. If it isn't provided, Django uses the ``"default"`` database. + .. _deactivate-transaction-management: How to globally deactivate transaction management