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
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
def supports_stddev(self):
"""Confirm support for STDDEV and related stats functions
@ -355,6 +359,12 @@ class DatabaseWrapper(BaseDatabaseWrapper):
if self.settings_dict['NAME'] != ":memory:":
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):
if autocommit:
level = None

View File

@ -424,8 +424,7 @@ Savepoints
Both the Django ORM and MySQL (when using the InnoDB :ref:`storage engine
<mysql-storage-engines>`) support database :ref:`savepoints
<topics-db-transactions-savepoints>`, but this feature wasn't available in
Django until version 1.4 when such support was added.
<topics-db-transactions-savepoints>`.
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

View File

@ -251,11 +251,11 @@ the transaction middleware, and only modify selected functions as needed.
Savepoints
==========
A savepoint is a marker within a transaction that enables you to roll back part
of a transaction, rather than the full transaction. Savepoints are available
with the PostgreSQL 8, Oracle and MySQL (when using the InnoDB storage engine)
backends. Other backends provide the savepoint functions, but they're empty
operations -- they don't actually do anything.
A savepoint is a marker within a transaction that enables you to roll back
part of a transaction, rather than the full transaction. Savepoints are
available with the SQLite (≥ 3.6.8), PostgreSQL, Oracle and MySQL (when using
the InnoDB storage engine) backends. Other backends provide the savepoint
functions, but they're empty operations -- they don't actually do anything.
Savepoints aren't especially useful if you are using the default
``autocommit`` behavior of Django. However, if you are using
@ -314,6 +314,21 @@ The following example demonstrates the use of savepoints::
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
---------------------
@ -363,11 +378,11 @@ itself.
Savepoint rollback
~~~~~~~~~~~~~~~~~~
If you are using PostgreSQL 8 or later, you can use :ref:`savepoints
<topics-db-transactions-savepoints>` to control the extent of a rollback.
Before performing a database operation that could fail, you can set or update
the savepoint; that way, if the operation fails, you can roll back the single
offending operation, rather than the entire transaction. For example::
You can use :ref:`savepoints <topics-db-transactions-savepoints>` to control
the extent of a rollback. Before performing a database operation that could
fail, you can set or update the savepoint; that way, if the operation fails,
you can roll back the single offending operation, rather than the entire
transaction. For example::
a.save() # Succeeds, and never undone by savepoint rollback
try:

View File

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