Skip to content

Commit 85a19c7

Browse files
committed
BUG18843153: Fix Django to check connection on each request
Each time a database request is made, the Django backend will make sure that the database connection is still valid. Before, a RuntimeError exception was raised when the application was inactive for a while.
1 parent 89128c5 commit 85a19c7

File tree

2 files changed

+76
-13
lines changed

2 files changed

+76
-13
lines changed

lib/mysql/connector/django/base.py

Lines changed: 46 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
from __future__ import unicode_literals
1919

2020
import sys
21-
import warnings
2221
import django
2322

2423
try:
@@ -52,6 +51,12 @@
5251
from mysql.connector.django.introspection import DatabaseIntrospection
5352
from mysql.connector.django.validation import DatabaseValidation
5453

54+
try:
55+
import pytz
56+
HAVE_PYTZ = True
57+
except ImportError:
58+
HAVE_PYTZ = False
59+
5560
DatabaseError = mysql.connector.DatabaseError
5661
IntegrityError = mysql.connector.IntegrityError
5762
NotSupportedError = mysql.connector.NotSupportedError
@@ -163,7 +168,7 @@ def __init__(self, connection):
163168
self.supports_microsecond_precision = self._microseconds_precision()
164169

165170
def _microseconds_precision(self):
166-
if self.connection.get_server_version() >= (5, 6, 3):
171+
if self.connection.server_version >= (5, 6, 3):
167172
return True
168173
return False
169174

@@ -183,7 +188,7 @@ def _mysql_storage_engine(self):
183188
cursor.execute(droptable)
184189
cursor.execute('CREATE TABLE {table} (X INT)'.format(table=tblname))
185190

186-
if self.connection.get_server_version() >= (5, 0, 0):
191+
if self.connection.server_version >= (5, 0, 0):
187192
cursor.execute(
188193
"SELECT ENGINE FROM INFORMATION_SCHEMA.TABLES "
189194
"WHERE TABLE_SCHEMA = %s AND TABLE_NAME = %s",
@@ -215,11 +220,12 @@ def has_zoneinfo_database(self):
215220
MySQL cannot perform time zone conversions reliably.
216221
"""
217222
# Django 1.6
218-
if pytz is None:
223+
if not HAVE_PYTZ:
219224
return False
220225

221-
self.connection.cmd_query("SELECT 1 FROM mysql.time_zone LIMIT 1")
222-
return self.connection.get_rows()[0] is not []
226+
cursor = self.connection.cursor()
227+
cursor.execute("SELECT 1 FROM mysql.time_zone LIMIT 1")
228+
return cursor.fetchall() != []
223229

224230

225231
class DatabaseOperations(BaseDatabaseOperations):
@@ -358,7 +364,7 @@ def sequence_reset_by_name_sql(self, style, sequences):
358364
# Truncate already resets the AUTO_INCREMENT field from
359365
# MySQL version 5.0.13 onwards. Refs #16961.
360366
res = []
361-
if self.connection.get_server_version() < (5, 0, 13):
367+
if self.connection.server_version < (5, 0, 13):
362368
fmt = "{alter} {table} {{tablename}} {auto_inc} {field};".format(
363369
alter=style.SQL_KEYWORD('ALTER'),
364370
table=style.SQL_KEYWORD('TABLE'),
@@ -389,6 +395,13 @@ def value_to_db_datetime(self, value):
389395
raise ValueError(
390396
"MySQL backend does not support timezone-aware times."
391397
)
398+
399+
try:
400+
# Django 1.6
401+
self.connection.ensure_connection()
402+
except AttributeError:
403+
if not self.connection.connection:
404+
self.connection._connect()
392405
return self.connection.connection.converter._datetime_to_mysql(value)
393406

394407
def value_to_db_time(self, value):
@@ -400,6 +413,12 @@ def value_to_db_time(self, value):
400413
raise ValueError("MySQL backend does not support timezone-aware "
401414
"times.")
402415

416+
try:
417+
# Django 1.6
418+
self.connection.ensure_connection()
419+
except AttributeError:
420+
if not self.connection.connection:
421+
self.connection._connect()
403422
return self.connection.connection.converter._time_to_mysql(value)
404423

405424
def year_lookup_bounds(self, value):
@@ -413,7 +432,7 @@ def year_lookup_bounds_for_datetime_field(self, value):
413432
# Again, no microseconds
414433
first, second = super(DatabaseOperations,
415434
self).year_lookup_bounds_for_datetime_field(value)
416-
if self.connection.get_server_version() >= (5, 6, 4):
435+
if self.connection.server_version >= (5, 6, 4):
417436
return [first.replace(microsecond=0), second]
418437
else:
419438
return [first.replace(microsecond=0),
@@ -462,10 +481,14 @@ def __init__(self, *args, **kwargs):
462481
self.server_version = None
463482

464483
# Since some features depend on the MySQL version, we need to connect
465-
self._connect()
484+
try:
485+
# Django 1.6
486+
self.ensure_connection()
487+
except AttributeError:
488+
self._connect()
466489

467-
self.features = DatabaseFeatures(self)
468490
self.ops = DatabaseOperations(self)
491+
self.features = DatabaseFeatures(self)
469492
self.client = DatabaseClient(self)
470493
self.creation = DatabaseCreation(self)
471494
self.introspection = DatabaseIntrospection(self)
@@ -530,7 +553,11 @@ def init_connection_state(self):
530553
self.connection.cmd_query("SET SQL_AUTO_IS_NULL = 0")
531554

532555
if 'AUTOCOMMIT' in self.settings_dict:
533-
self.set_autocommit(self.settings_dict['AUTOCOMMIT'])
556+
try:
557+
# Django 1.6
558+
self.set_autocommit(self.settings_dict['AUTOCOMMIT'])
559+
except AttributeError:
560+
self._set_autocommit(self.settings_dict['AUTOCOMMIT'])
534561

535562
def create_cursor(self):
536563
# Django 1.6
@@ -560,7 +587,14 @@ def get_server_version(self):
560587
561588
Returns a tuple
562589
"""
563-
return self.server_version
590+
try:
591+
# Django 1.6
592+
self.ensure_connection()
593+
except AttributeError:
594+
if not self.connection:
595+
self._connect()
596+
597+
return self.connection.get_server_version()
564598

565599
def disable_constraint_checking(self):
566600
"""Disables foreign key checks

tests/test_django.py

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
"""Unittests for mysql.connector.django
2525
"""
2626

27+
import datetime
2728
import sys
2829

2930
import tests
@@ -45,6 +46,8 @@
4546
'PORT': DBCONFIG['port'],
4647
'TEST_CHARSET': 'utf8',
4748
'TEST_COLLATION': 'utf8_general_ci',
49+
'CONN_MAX_AGE': 0,
50+
'AUTOCOMMIT': True,
4851
},
4952
}
5053
settings.SECRET_KEY = "django_tests_secret_key"
@@ -80,7 +83,8 @@
8083
if tests.DJANGO_VERSION >= (1, 6):
8184
from django.db.backends import FieldInfo
8285

83-
from mysql.connector.django.base import DatabaseWrapper
86+
import mysql.connector
87+
from mysql.connector.django.base import DatabaseWrapper, DatabaseOperations
8488
from mysql.connector.django.introspection import DatabaseIntrospection
8589

8690

@@ -178,3 +182,28 @@ def test_get_primary_key_column(self):
178182
cur = self.cnx.cursor()
179183
res = self.introspect.get_primary_key_column(cur, 'django_t1')
180184
self.assertEqual('id', res)
185+
186+
187+
class DjangoDatabaseWrapper(tests.MySQLConnectorTests):
188+
189+
"""Test the Django base.DatabaseWrapper class"""
190+
191+
def setUp(self):
192+
dbconfig = tests.get_mysql_config()
193+
self.conn = mysql.connector.connect(**dbconfig)
194+
self.cnx = DatabaseWrapper(settings.DATABASES['default'])
195+
196+
def test__init__(self):
197+
exp = self.conn.get_server_version()
198+
self.assertEqual(exp, self.cnx.server_version)
199+
200+
value = datetime.time(2, 5, 7)
201+
exp = self.conn.converter._time_to_mysql(value)
202+
self.assertEqual(exp, self.cnx.ops.value_to_db_time(value))
203+
204+
self.cnx.connection = None
205+
value = datetime.time(2, 5, 7)
206+
exp = self.conn.converter._time_to_mysql(value)
207+
self.assertEqual(exp, self.cnx.ops.value_to_db_time(value))
208+
209+

0 commit comments

Comments
 (0)