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
This commit is contained in:
parent
bc63ba700a
commit
8312b85c97
|
@ -150,18 +150,28 @@ class DatabaseFeatures(BaseDatabaseFeatures):
|
||||||
requires_explicit_null_ordering_when_grouping = True
|
requires_explicit_null_ordering_when_grouping = True
|
||||||
allows_primary_key_0 = False
|
allows_primary_key_0 = False
|
||||||
|
|
||||||
|
def __init__(self, connection):
|
||||||
|
super(DatabaseFeatures, self).__init__(connection)
|
||||||
|
self._storage_engine = None
|
||||||
|
|
||||||
|
def _mysql_storage_engine(self):
|
||||||
|
"Internal method used in Django tests. Don't rely on this from your code"
|
||||||
|
if self._storage_engine is None:
|
||||||
|
cursor = self.connection.cursor()
|
||||||
|
cursor.execute('CREATE TABLE INTROSPECT_TEST (X INT)')
|
||||||
|
# This command is MySQL specific; the second column
|
||||||
|
# will tell you the default table type of the created
|
||||||
|
# table. Since all Django's test tables will have the same
|
||||||
|
# table type, that's enough to evaluate the feature.
|
||||||
|
cursor.execute("SHOW TABLE STATUS WHERE Name='INTROSPECT_TEST'")
|
||||||
|
result = cursor.fetchone()
|
||||||
|
cursor.execute('DROP TABLE INTROSPECT_TEST')
|
||||||
|
self._storage_engine = result[1]
|
||||||
|
return self._storage_engine
|
||||||
|
|
||||||
def _can_introspect_foreign_keys(self):
|
def _can_introspect_foreign_keys(self):
|
||||||
"Confirm support for introspected foreign keys"
|
"Confirm support for introspected foreign keys"
|
||||||
cursor = self.connection.cursor()
|
return self._mysql_storage_engine() != 'MyISAM'
|
||||||
cursor.execute('CREATE TABLE INTROSPECT_TEST (X INT)')
|
|
||||||
# This command is MySQL specific; the second column
|
|
||||||
# will tell you the default table type of the created
|
|
||||||
# table. Since all Django's test tables will have the same
|
|
||||||
# table type, that's enough to evaluate the feature.
|
|
||||||
cursor.execute("SHOW TABLE STATUS WHERE Name='INTROSPECT_TEST'")
|
|
||||||
result = cursor.fetchone()
|
|
||||||
cursor.execute('DROP TABLE INTROSPECT_TEST')
|
|
||||||
return result[1] != 'MyISAM'
|
|
||||||
|
|
||||||
class DatabaseOperations(BaseDatabaseOperations):
|
class DatabaseOperations(BaseDatabaseOperations):
|
||||||
compiler_module = "django.db.backends.mysql.compiler"
|
compiler_module = "django.db.backends.mysql.compiler"
|
||||||
|
@ -285,6 +295,15 @@ class DatabaseOperations(BaseDatabaseOperations):
|
||||||
items_sql = "(%s)" % ", ".join(["%s"] * len(fields))
|
items_sql = "(%s)" % ", ".join(["%s"] * len(fields))
|
||||||
return "VALUES " + ", ".join([items_sql] * num_values)
|
return "VALUES " + ", ".join([items_sql] * num_values)
|
||||||
|
|
||||||
|
def savepoint_create_sql(self, sid):
|
||||||
|
return "SAVEPOINT %s" % sid
|
||||||
|
|
||||||
|
def savepoint_commit_sql(self, sid):
|
||||||
|
return "RELEASE SAVEPOINT %s" % sid
|
||||||
|
|
||||||
|
def savepoint_rollback_sql(self, sid):
|
||||||
|
return "ROLLBACK TO SAVEPOINT %s" % sid
|
||||||
|
|
||||||
class DatabaseWrapper(BaseDatabaseWrapper):
|
class DatabaseWrapper(BaseDatabaseWrapper):
|
||||||
vendor = 'mysql'
|
vendor = 'mysql'
|
||||||
operators = {
|
operators = {
|
||||||
|
@ -354,6 +373,8 @@ class DatabaseWrapper(BaseDatabaseWrapper):
|
||||||
self.connection = Database.connect(**kwargs)
|
self.connection = Database.connect(**kwargs)
|
||||||
self.connection.encoders[SafeUnicode] = self.connection.encoders[unicode]
|
self.connection.encoders[SafeUnicode] = self.connection.encoders[unicode]
|
||||||
self.connection.encoders[SafeString] = self.connection.encoders[str]
|
self.connection.encoders[SafeString] = self.connection.encoders[str]
|
||||||
|
self.features.uses_savepoints = \
|
||||||
|
self.get_server_version() >= (5, 0, 3)
|
||||||
connection_created.send(sender=self.__class__, connection=self)
|
connection_created.send(sender=self.__class__, connection=self)
|
||||||
cursor = self.connection.cursor()
|
cursor = self.connection.cursor()
|
||||||
if new_connection:
|
if new_connection:
|
||||||
|
|
|
@ -553,6 +553,9 @@ Django 1.4 also includes several smaller improvements worth noting:
|
||||||
password reset mechanism and making it available is now much easier. For
|
password reset mechanism and making it available is now much easier. For
|
||||||
details, see :ref:`auth_password_reset`.
|
details, see :ref:`auth_password_reset`.
|
||||||
|
|
||||||
|
* The MySQL database backend can now make use of the savepoint feature
|
||||||
|
implemented by MySQL version 5.0.3 or newer with the InnoDB storage engine.
|
||||||
|
|
||||||
Backwards incompatible changes in 1.4
|
Backwards incompatible changes in 1.4
|
||||||
=====================================
|
=====================================
|
||||||
|
|
||||||
|
|
|
@ -225,11 +225,14 @@ 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
|
A savepoint is a marker within a transaction that enables you to roll back part
|
||||||
part of a transaction, rather than the full transaction. Savepoints are
|
of a transaction, rather than the full transaction. Savepoints are available to
|
||||||
available to the PostgreSQL 8 and Oracle backends. Other backends will
|
the PostgreSQL 8, Oracle and MySQL (version 5.0.3 and newer, when using the
|
||||||
provide the savepoint functions, but they are empty operations - they won't
|
InnoDB storage engine) backends. Other backends will provide the savepoint
|
||||||
actually do anything.
|
functions, but they are empty operations - they won't actually do anything.
|
||||||
|
|
||||||
|
.. versionchanged:: 1.4
|
||||||
|
Savepoint support when using the MySQL backend was added in Django 1.4
|
||||||
|
|
||||||
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
|
||||||
|
|
|
@ -4,6 +4,7 @@ from django.core.exceptions import ImproperlyConfigured
|
||||||
from django.db import connection, transaction
|
from django.db import connection, transaction
|
||||||
from django.db.transaction import commit_on_success, commit_manually, TransactionManagementError
|
from django.db.transaction import commit_on_success, commit_manually, TransactionManagementError
|
||||||
from django.test import TransactionTestCase, skipUnlessDBFeature
|
from django.test import TransactionTestCase, skipUnlessDBFeature
|
||||||
|
from django.utils.unittest import skipIf
|
||||||
|
|
||||||
from .models import Mod, M2mA, M2mB
|
from .models import Mod, M2mA, M2mB
|
||||||
|
|
||||||
|
@ -165,6 +166,7 @@ class TestTransactionClosing(TransactionTestCase):
|
||||||
except:
|
except:
|
||||||
self.fail("A transaction consisting of a failed operation was not closed.")
|
self.fail("A transaction consisting of a failed operation was not closed.")
|
||||||
|
|
||||||
|
|
||||||
class TestManyToManyAddTransaction(TransactionTestCase):
|
class TestManyToManyAddTransaction(TransactionTestCase):
|
||||||
def test_manyrelated_add_commit(self):
|
def test_manyrelated_add_commit(self):
|
||||||
"Test for https://code.djangoproject.com/ticket/16818"
|
"Test for https://code.djangoproject.com/ticket/16818"
|
||||||
|
@ -178,3 +180,39 @@ class TestManyToManyAddTransaction(TransactionTestCase):
|
||||||
# that the bulk insert was not auto-committed.
|
# that the bulk insert was not auto-committed.
|
||||||
transaction.rollback()
|
transaction.rollback()
|
||||||
self.assertEqual(a.others.count(), 1)
|
self.assertEqual(a.others.count(), 1)
|
||||||
|
|
||||||
|
|
||||||
|
class SavepointTest(TransactionTestCase):
|
||||||
|
|
||||||
|
@skipUnlessDBFeature('uses_savepoints')
|
||||||
|
def test_savepoint_commit(self):
|
||||||
|
@commit_manually
|
||||||
|
def work():
|
||||||
|
mod = Mod.objects.create(fld=1)
|
||||||
|
pk = mod.pk
|
||||||
|
sid = transaction.savepoint()
|
||||||
|
mod1 = Mod.objects.filter(pk=pk).update(fld=10)
|
||||||
|
transaction.savepoint_commit(sid)
|
||||||
|
mod2 = Mod.objects.get(pk=pk)
|
||||||
|
transaction.commit()
|
||||||
|
self.assertEqual(mod2.fld, 10)
|
||||||
|
|
||||||
|
work()
|
||||||
|
|
||||||
|
@skipIf(connection.vendor == 'mysql' and \
|
||||||
|
connection.features._mysql_storage_engine() == 'MyISAM',
|
||||||
|
"MyISAM MySQL storage engine doesn't support savepoints")
|
||||||
|
@skipUnlessDBFeature('uses_savepoints')
|
||||||
|
def test_savepoint_rollback(self):
|
||||||
|
@commit_manually
|
||||||
|
def work():
|
||||||
|
mod = Mod.objects.create(fld=1)
|
||||||
|
pk = mod.pk
|
||||||
|
sid = transaction.savepoint()
|
||||||
|
mod1 = Mod.objects.filter(pk=pk).update(fld=20)
|
||||||
|
transaction.savepoint_rollback(sid)
|
||||||
|
mod2 = Mod.objects.get(pk=pk)
|
||||||
|
transaction.commit()
|
||||||
|
self.assertEqual(mod2.fld, 1)
|
||||||
|
|
||||||
|
work()
|
||||||
|
|
Loading…
Reference in New Issue