Added support for savepoints in SQLite.

Technically speaking they aren't usable yet.
This commit is contained in:
Aymeric Augustin 2013-03-04 15:57:04 +01:00
parent e264f67174
commit 4b31a6a9e6
4 changed files with 40 additions and 12 deletions

View File

@ -100,6 +100,10 @@ class DatabaseFeatures(BaseDatabaseFeatures):
has_bulk_insert = True has_bulk_insert = True
can_combine_inserts_with_and_without_auto_increment_pk = False can_combine_inserts_with_and_without_auto_increment_pk = False
@cached_property
def uses_savepoints(self):
return Database.sqlite_version_info >= (3, 6, 8)
@cached_property @cached_property
def supports_stddev(self): def supports_stddev(self):
"""Confirm support for STDDEV and related stats functions """Confirm support for STDDEV and related stats functions
@ -355,6 +359,12 @@ class DatabaseWrapper(BaseDatabaseWrapper):
if self.settings_dict['NAME'] != ":memory:": if self.settings_dict['NAME'] != ":memory:":
BaseDatabaseWrapper.close(self) BaseDatabaseWrapper.close(self)
def _savepoint_allowed(self):
# When 'isolation_level' is None, Django doesn't provide a way to
# create a transaction (yet) so savepoints can't be created. When it
# isn't, sqlite3 commits before each savepoint -- it's a bug.
return False
def _set_autocommit(self, autocommit): def _set_autocommit(self, autocommit):
if autocommit: if autocommit:
level = None level = None

View File

@ -424,8 +424,7 @@ Savepoints
Both the Django ORM and MySQL (when using the InnoDB :ref:`storage engine Both the Django ORM and MySQL (when using the InnoDB :ref:`storage engine
<mysql-storage-engines>`) support database :ref:`savepoints <mysql-storage-engines>`) support database :ref:`savepoints
<topics-db-transactions-savepoints>`, but this feature wasn't available in <topics-db-transactions-savepoints>`.
Django until version 1.4 when such support was added.
If you use the MyISAM storage engine please be aware of the fact that you will If you use the MyISAM storage engine please be aware of the fact that you will
receive database-generated errors if you try to use the :ref:`savepoint-related receive database-generated errors if you try to use the :ref:`savepoint-related

View File

@ -251,11 +251,11 @@ the transaction middleware, and only modify selected functions as needed.
Savepoints Savepoints
========== ==========
A savepoint is a marker within a transaction that enables you to roll back part A savepoint is a marker within a transaction that enables you to roll back
of a transaction, rather than the full transaction. Savepoints are available part of a transaction, rather than the full transaction. Savepoints are
with the PostgreSQL 8, Oracle and MySQL (when using the InnoDB storage engine) available with the SQLite (≥ 3.6.8), PostgreSQL, Oracle and MySQL (when using
backends. Other backends provide the savepoint functions, but they're empty the InnoDB storage engine) backends. Other backends provide the savepoint
operations -- they don't actually do anything. functions, but they're empty operations -- they don't actually do anything.
Savepoints aren't especially useful if you are using the default Savepoints aren't especially useful if you are using the default
``autocommit`` behavior of Django. However, if you are using ``autocommit`` behavior of Django. However, if you are using
@ -314,6 +314,21 @@ The following example demonstrates the use of savepoints::
Database-specific notes Database-specific notes
======================= =======================
Savepoints in SQLite
--------------------
While SQLite ≥ 3.6.8 supports savepoints, a flaw in the design of the
:mod:`sqlite3` makes them hardly usable.
When autocommit is enabled, savepoints don't make sense. When it's disabled,
:mod:`sqlite3` commits implicitly before savepoint-related statement. (It
commits before any statement other than ``SELECT``, ``INSERT``, ``UPDATE``,
``DELETE`` and ``REPLACE``.)
As a consequence, savepoints are only usable if you start a transaction
manually while in autocommit mode, and Django doesn't provide an API to
achieve that.
Transactions in MySQL Transactions in MySQL
--------------------- ---------------------
@ -363,11 +378,11 @@ itself.
Savepoint rollback Savepoint rollback
~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~
If you are using PostgreSQL 8 or later, you can use :ref:`savepoints You can use :ref:`savepoints <topics-db-transactions-savepoints>` to control
<topics-db-transactions-savepoints>` to control the extent of a rollback. the extent of a rollback. Before performing a database operation that could
Before performing a database operation that could fail, you can set or update fail, you can set or update the savepoint; that way, if the operation fails,
the savepoint; that way, if the operation fails, you can roll back the single you can roll back the single offending operation, rather than the entire
offending operation, rather than the entire transaction. For example:: transaction. For example::
a.save() # Succeeds, and never undone by savepoint rollback a.save() # Succeeds, and never undone by savepoint rollback
try: try:

View File

@ -309,6 +309,8 @@ class TestManyToManyAddTransaction(TransactionTestCase):
class SavepointTest(TransactionTestCase): class SavepointTest(TransactionTestCase):
@skipIf(connection.vendor == 'sqlite',
"SQLite doesn't support savepoints in managed mode")
@skipUnlessDBFeature('uses_savepoints') @skipUnlessDBFeature('uses_savepoints')
def test_savepoint_commit(self): def test_savepoint_commit(self):
@commit_manually @commit_manually
@ -324,6 +326,8 @@ class SavepointTest(TransactionTestCase):
work() work()
@skipIf(connection.vendor == 'sqlite',
"SQLite doesn't support savepoints in managed mode")
@skipIf(connection.vendor == 'mysql' and @skipIf(connection.vendor == 'mysql' and
connection.features._mysql_storage_engine == 'MyISAM', connection.features._mysql_storage_engine == 'MyISAM',
"MyISAM MySQL storage engine doesn't support savepoints") "MyISAM MySQL storage engine doesn't support savepoints")