Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Added support for savepoints to the MySQL DB backend.

MySQL provides the savepoint functionality starting with version 5.0.3
when using the MyISAM storage engine.

Thanks lamby for the report and patch.

Fixes #15507.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@17341 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit 8312b85c979c8ddd314abe0a7edb6cb37fe3e3ac 1 parent bc63ba7
Ramiro Morales authored January 05, 2012
41  django/db/backends/mysql/base.py
@@ -150,18 +150,28 @@ class DatabaseFeatures(BaseDatabaseFeatures):
150 150
     requires_explicit_null_ordering_when_grouping = True
151 151
     allows_primary_key_0 = False
152 152
 
  153
+    def __init__(self, connection):
  154
+        super(DatabaseFeatures, self).__init__(connection)
  155
+        self._storage_engine = None
  156
+
  157
+    def _mysql_storage_engine(self):
  158
+        "Internal method used in Django tests. Don't rely on this from your code"
  159
+        if self._storage_engine is None:
  160
+            cursor = self.connection.cursor()
  161
+            cursor.execute('CREATE TABLE INTROSPECT_TEST (X INT)')
  162
+            # This command is MySQL specific; the second column
  163
+            # will tell you the default table type of the created
  164
+            # table. Since all Django's test tables will have the same
  165
+            # table type, that's enough to evaluate the feature.
  166
+            cursor.execute("SHOW TABLE STATUS WHERE Name='INTROSPECT_TEST'")
  167
+            result = cursor.fetchone()
  168
+            cursor.execute('DROP TABLE INTROSPECT_TEST')
  169
+            self._storage_engine = result[1]
  170
+        return self._storage_engine
  171
+
153 172
     def _can_introspect_foreign_keys(self):
154 173
         "Confirm support for introspected foreign keys"
155  
-        cursor = self.connection.cursor()
156  
-        cursor.execute('CREATE TABLE INTROSPECT_TEST (X INT)')
157  
-        # This command is MySQL specific; the second column
158  
-        # will tell you the default table type of the created
159  
-        # table. Since all Django's test tables will have the same
160  
-        # table type, that's enough to evaluate the feature.
161  
-        cursor.execute("SHOW TABLE STATUS WHERE Name='INTROSPECT_TEST'")
162  
-        result = cursor.fetchone()
163  
-        cursor.execute('DROP TABLE INTROSPECT_TEST')
164  
-        return result[1] != 'MyISAM'
  174
+        return self._mysql_storage_engine() != 'MyISAM'
165 175
 
166 176
 class DatabaseOperations(BaseDatabaseOperations):
167 177
     compiler_module = "django.db.backends.mysql.compiler"
@@ -285,6 +295,15 @@ def bulk_insert_sql(self, fields, num_values):
285 295
         items_sql = "(%s)" % ", ".join(["%s"] * len(fields))
286 296
         return "VALUES " + ", ".join([items_sql] * num_values)
287 297
 
  298
+    def savepoint_create_sql(self, sid):
  299
+        return "SAVEPOINT %s" % sid
  300
+
  301
+    def savepoint_commit_sql(self, sid):
  302
+        return "RELEASE SAVEPOINT %s" % sid
  303
+
  304
+    def savepoint_rollback_sql(self, sid):
  305
+        return "ROLLBACK TO SAVEPOINT %s" % sid
  306
+
288 307
 class DatabaseWrapper(BaseDatabaseWrapper):
289 308
     vendor = 'mysql'
290 309
     operators = {
@@ -354,6 +373,8 @@ def _cursor(self):
354 373
             self.connection = Database.connect(**kwargs)
355 374
             self.connection.encoders[SafeUnicode] = self.connection.encoders[unicode]
356 375
             self.connection.encoders[SafeString] = self.connection.encoders[str]
  376
+            self.features.uses_savepoints = \
  377
+                self.get_server_version() >= (5, 0, 3)
357 378
             connection_created.send(sender=self.__class__, connection=self)
358 379
         cursor = self.connection.cursor()
359 380
         if new_connection:
3  docs/releases/1.4.txt
@@ -553,6 +553,9 @@ Django 1.4 also includes several smaller improvements worth noting:
553 553
   password reset mechanism and making it available is now much easier. For
554 554
   details, see :ref:`auth_password_reset`.
555 555
 
  556
+* The MySQL database backend can now make use of the savepoint feature
  557
+  implemented by MySQL version 5.0.3 or newer with the InnoDB storage engine.
  558
+
556 559
 Backwards incompatible changes in 1.4
557 560
 =====================================
558 561
 
13  docs/topics/db/transactions.txt
@@ -225,11 +225,14 @@ transaction middleware, and only modify selected functions as needed.
225 225
 Savepoints
226 226
 ==========
227 227
 
228  
-A savepoint is a marker within a transaction that enables you to roll back
229  
-part of a transaction, rather than the full transaction. Savepoints are
230  
-available to the PostgreSQL 8 and Oracle backends. Other backends will
231  
-provide the savepoint functions, but they are empty operations - they won't
232  
-actually do anything.
  228
+A savepoint is a marker within a transaction that enables you to roll back part
  229
+of a transaction, rather than the full transaction. Savepoints are available to
  230
+the PostgreSQL 8, Oracle and MySQL (version 5.0.3 and newer, when using the
  231
+InnoDB storage engine) backends. Other backends will provide the savepoint
  232
+functions, but they are empty operations - they won't actually do anything.
  233
+
  234
+.. versionchanged:: 1.4
  235
+   Savepoint support when using the MySQL backend was added in Django 1.4
233 236
 
234 237
 Savepoints aren't especially useful if you are using the default
235 238
 ``autocommit`` behavior of Django. However, if you are using
38  tests/regressiontests/transactions_regress/tests.py
@@ -4,6 +4,7 @@
4 4
 from django.db import connection, transaction
5 5
 from django.db.transaction import commit_on_success, commit_manually, TransactionManagementError
6 6
 from django.test import TransactionTestCase, skipUnlessDBFeature
  7
+from django.utils.unittest import skipIf
7 8
 
8 9
 from .models import Mod, M2mA, M2mB
9 10
 
@@ -165,6 +166,7 @@ def create_system_user():
165 166
         except:
166 167
             self.fail("A transaction consisting of a failed operation was not closed.")
167 168
 
  169
+
168 170
 class TestManyToManyAddTransaction(TransactionTestCase):
169 171
     def test_manyrelated_add_commit(self):
170 172
         "Test for https://code.djangoproject.com/ticket/16818"
@@ -178,3 +180,39 @@ def test_manyrelated_add_commit(self):
178 180
         # that the bulk insert was not auto-committed.
179 181
         transaction.rollback()
180 182
         self.assertEqual(a.others.count(), 1)
  183
+
  184
+
  185
+class SavepointTest(TransactionTestCase):
  186
+
  187
+    @skipUnlessDBFeature('uses_savepoints')
  188
+    def test_savepoint_commit(self):
  189
+        @commit_manually
  190
+        def work():
  191
+            mod = Mod.objects.create(fld=1)
  192
+            pk = mod.pk
  193
+            sid = transaction.savepoint()
  194
+            mod1 = Mod.objects.filter(pk=pk).update(fld=10)
  195
+            transaction.savepoint_commit(sid)
  196
+            mod2 = Mod.objects.get(pk=pk)
  197
+            transaction.commit()
  198
+            self.assertEqual(mod2.fld, 10)
  199
+
  200
+        work()
  201
+
  202
+    @skipIf(connection.vendor == 'mysql' and \
  203
+            connection.features._mysql_storage_engine() == 'MyISAM',
  204
+            "MyISAM MySQL storage engine doesn't support savepoints")
  205
+    @skipUnlessDBFeature('uses_savepoints')
  206
+    def test_savepoint_rollback(self):
  207
+        @commit_manually
  208
+        def work():
  209
+            mod = Mod.objects.create(fld=1)
  210
+            pk = mod.pk
  211
+            sid = transaction.savepoint()
  212
+            mod1 = Mod.objects.filter(pk=pk).update(fld=20)
  213
+            transaction.savepoint_rollback(sid)
  214
+            mod2 = Mod.objects.get(pk=pk)
  215
+            transaction.commit()
  216
+            self.assertEqual(mod2.fld, 1)
  217
+
  218
+        work()

0 notes on commit 8312b85

Please sign in to comment.
Something went wrong with that request. Please try again.