Skip to content

Commit af94348

Browse files
committed
BUG#34127959: Add isolation level support in Django backend
When running concurrent loads, database transactions from different sessions may interact with each other. These interactions are affected by each session's transaction isolation level. This patch allows setting in the Django settings.py the connection isolation level with an 'isolation_level' entry in the OPTIONS part of the database configuration in DATABASES. Change-Id: If582da767a95692658a82e2af79f3cc6e35936bd
1 parent 50a9a46 commit af94348

File tree

3 files changed

+57
-3
lines changed

3 files changed

+57
-3
lines changed

Diff for: CHANGES.txt

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ v8.0.30
1616
- WL#15035: Enforce PEP 7 and PEP 8 coding style
1717
- WL#14822: Refactor the authentication plugin mechanism
1818
- WL#14815: Support OpenSSL 3.0
19+
- BUG#34127959: Add isolation level support in Django backend
1920
- BUG#33923516: Allow tuple of dictionaries as "failover" argument
2021
- BUG#28821983: Fix rounding errors for decimal values
2122

Diff for: lib/mysql/connector/django/base.py

+23-3
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,7 @@ class DatabaseWrapper(BaseDatabaseWrapper):
266266
"iendswith": "LIKE CONCAT('%%', {})",
267267
}
268268

269+
isolation_level = None
269270
isolation_levels = {
270271
"read uncommitted",
271272
"read committed",
@@ -338,12 +339,25 @@ def get_connection_params(self):
338339
# Need potentially affected rows on UPDATE
339340
mysql.connector.constants.ClientFlag.FOUND_ROWS,
340341
]
342+
341343
try:
342-
kwargs.update(settings_dict["OPTIONS"])
344+
options = settings_dict["OPTIONS"].copy()
345+
isolation_level = options.pop("isolation_level")
346+
if isolation_level:
347+
isolation_level = isolation_level.lower()
348+
if isolation_level not in self.isolation_levels:
349+
valid_levels = ", ".join(
350+
f"'{level}'" for level in sorted(self.isolation_levels)
351+
)
352+
raise ImproperlyConfigured(
353+
f"Invalid transaction isolation level '{isolation_level}' "
354+
f"specified.\nUse one of {valid_levels}, or None."
355+
)
356+
self.isolation_level = isolation_level
357+
kwargs.update(options)
343358
except KeyError:
344359
# OPTIONS missing is OK
345360
pass
346-
347361
return kwargs
348362

349363
def get_new_connection(self, conn_params):
@@ -362,6 +376,12 @@ def init_connection_state(self):
362376
# with SQL standards.
363377
assignments.append("SET SQL_AUTO_IS_NULL = 0")
364378

379+
if self.isolation_level:
380+
assignments.append(
381+
"SET SESSION TRANSACTION ISOLATION LEVEL "
382+
f"{self.isolation_level.upper()}"
383+
)
384+
365385
if assignments:
366386
with self.cursor() as cursor:
367387
cursor.execute("; ".join(assignments))
@@ -565,7 +585,7 @@ def _datetime_to_python(value, dsc=None):
565585
if not value:
566586
return None
567587

568-
dt = super()._datetime_to_python(value)
588+
dt = MySQLConverter._datetime_to_python(value)
569589
if dt is None:
570590
return None
571591
if settings.USE_TZ and timezone.is_naive(dt):

Diff for: tests/test_django.py

+33
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,8 @@
103103
# Have to load django.db to make importing db backend work for Django < 1.6
104104
import django.db # pylint: disable=W0611
105105

106+
from django.core.exceptions import ImproperlyConfigured
107+
from django.db import connection
106108
from django.db.backends.signals import connection_created
107109
from django.db.utils import DEFAULT_DB_ALIAS, load_backend
108110
from django.utils.safestring import SafeText
@@ -122,6 +124,37 @@
122124
from mysql.connector.django.introspection import DatabaseIntrospection
123125

124126

127+
@unittest.skipIf(not DJANGO_AVAILABLE, "Django not available")
128+
class DjangoSettings(tests.MySQLConnectorTests):
129+
"""Test the Django settings."""
130+
131+
def test_get_connection_params(self):
132+
config = tests.get_mysql_config()
133+
settings_dict = connection.settings_dict.copy()
134+
135+
# The default isolation_level should be None
136+
database_wrapper = DatabaseWrapper(settings_dict)
137+
self.assertIsNone(database_wrapper.isolation_level)
138+
139+
# An invalid isolation_level should raise ImproperlyConfigured
140+
settings_dict["OPTIONS"]["isolation_level"] = "invalid_level"
141+
with self.assertRaises(ImproperlyConfigured):
142+
_ = DatabaseWrapper(settings_dict).get_connection_params()
143+
144+
# Test a valid isolation_level
145+
settings_dict["OPTIONS"]["isolation_level"] = "read committed"
146+
database_wrapper = DatabaseWrapper(settings_dict)
147+
connection_params = database_wrapper.get_connection_params()
148+
self.assertEqual(database_wrapper.isolation_level, "read committed")
149+
self.assertEqual(connection_params["database"], config["database"])
150+
151+
# Test session isolation level integration
152+
with database_wrapper.cursor() as cur:
153+
cur.execute("SELECT @@transaction_isolation")
154+
res = cur.fetchall()
155+
self.assertEqual(res[0][0], "READ-COMMITTED")
156+
157+
125158
@unittest.skipIf(not DJANGO_AVAILABLE, "Django not available")
126159
class DjangoIntrospection(tests.MySQLConnectorTests):
127160
"""Test the Django introspection module"""

0 commit comments

Comments
 (0)