From 62dc2e4829fe0b947b88f14fb9098935d05c5475 Mon Sep 17 00:00:00 2001
From: Tay Ray Chuan <rctay89@gmail.com>
Date: Sat, 12 Jun 2010 00:00:34 +0800
Subject: [PATCH] implement savepoints for sqlite3

---
 django/db/backends/sqlite3/base.py |   39 ++++++++++++++++++++++++++++++++++++
 1 files changed, 39 insertions(+), 0 deletions(-)

diff --git a/django/db/backends/sqlite3/base.py b/django/db/backends/sqlite3/base.py
index bc97f5c..c57df14 100644
--- a/django/db/backends/sqlite3/base.py
+++ b/django/db/backends/sqlite3/base.py
@@ -54,12 +54,17 @@ if Database.version_info >= (2,4,1):
     Database.register_adapter(str, lambda s:s.decode('utf-8'))
     Database.register_adapter(SafeString, lambda s:s.decode('utf-8'))
 
+uses_savepoints = False
+if Database.sqlite_version_info >= (3,6,8):
+    uses_savepoints = True
+
 class DatabaseFeatures(BaseDatabaseFeatures):
     # SQLite cannot handle us only partially reading from a cursor's result set
     # and then writing the same rows to the database in another cursor. This
     # setting ensures we always read result sets fully into memory all in one
     # go.
     can_use_chunked_reads = False
+    uses_savepoints = uses_savepoints
 
 class DatabaseOperations(BaseDatabaseOperations):
     def date_extract_sql(self, lookup_type, field_name):
@@ -90,6 +95,15 @@ class DatabaseOperations(BaseDatabaseOperations):
     def no_limit_value(self):
         return -1
 
+    def savepoint_create_sql(self, sid):
+        return "SAVEPOINT " + self.quote_name(sid)
+
+    def savepoint_rollback_sql(self, sid):
+        return "ROLLBACK TO SAVEPOINT " + self.quote_name(sid)
+
+    def savepoint_commit_sql(self, sid):
+        return "RELEASE SAVEPOINT " + self.quote_name(sid)
+
     def sql_flush(self, style, tables, sequences):
         # NB: The generated SQL below is specific to SQLite
         # Note: The DELETE FROM... SQL generated below works for SQLite databases
@@ -160,6 +174,10 @@ class DatabaseWrapper(BaseDatabaseWrapper):
         self.introspection = DatabaseIntrospection(self)
         self.validation = BaseDatabaseValidation(self)
 
+        if Database.sqlite_version_info >= (3,6,8):
+            self.features.uses_savepoints = True
+        self._default_isolation_level = None
+
     def _cursor(self):
         if self.connection is None:
             settings_dict = self.settings_dict
@@ -172,6 +190,7 @@ class DatabaseWrapper(BaseDatabaseWrapper):
             }
             kwargs.update(settings_dict['OPTIONS'])
             self.connection = Database.connect(**kwargs)
+            self._default_isolation_level = self.connection.isolation_level
             # Register extract, date_trunc, and regexp functions.
             self.connection.create_function("django_extract", 2, _sqlite_extract)
             self.connection.create_function("django_date_trunc", 2, _sqlite_date_trunc)
@@ -179,6 +198,26 @@ class DatabaseWrapper(BaseDatabaseWrapper):
             connection_created.send(sender=self.__class__)
         return self.connection.cursor(factory=SQLiteCursorWrapper)
 
+    def _enter_transaction_management(self, managed):
+        """
+        If savepoints are allowed, switch the isolation_level to None.
+
+        However, this implies autocommits, so begin a transaction explicitly -
+        django assumes non-autocommits.
+        """
+        if managed and self.features.uses_savepoints:
+            cursor = self._cursor()
+            cursor.connection.isolation_level = None
+            cursor.execute('BEGIN TRANSACTION')
+
+    def _leave_transaction_management(self, managed):
+        """
+        If savepoints are allowed, we're now in autocommit mode - switch the
+        isolation_level back to the default one.
+        """
+        if managed and self.features.uses_savepoints and self.connection.isolation_level is None:
+            self.connection.isolation_level = self._default_isolation_level
+
     def close(self):
         # If database is in memory, closing the connection destroys the
         # database. To prevent accidental data loss, ignore close requests on
-- 
1.7.1.352.g12d15

