Skip to content

Commit f27093e

Browse files
committed
BUG19972427: Fix creating of redundant connections in Django
The Connector/Python Django backend was creating too many database connections since as soon as an object of DatabaseWrapper was instanciated, a connection to the MySQL server was set up. This is required to enable some version specific MySQL features. We fix this by adding the cached_property decorator to the mysql_version()-method. Unit tests are updated and also fixes BUG#19954882.
1 parent 142dba9 commit f27093e

File tree

4 files changed

+41
-33
lines changed

4 files changed

+41
-33
lines changed

lib/mysql/connector/conversion.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,11 @@ def set_unicode(self, value=True):
6666

6767
def to_mysql(self, value):
6868
"""Convert Python data type to MySQL"""
69-
return value
69+
type_name = value.__class__.__name__.lower()
70+
try:
71+
return getattr(self, "_{0}_to_mysql".format(type_name))(value)
72+
except AttributeError:
73+
return value
7074

7175
def to_python(self, vtype, value):
7276
"""Convert MySQL data type to Python"""

lib/mysql/connector/django/base.py

Lines changed: 15 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ def __init__(self, connection):
192192
self.supports_microsecond_precision = self._microseconds_precision()
193193

194194
def _microseconds_precision(self):
195-
if self.connection.server_version >= (5, 6, 3):
195+
if self.connection.mysql_version >= (5, 6, 3):
196196
return True
197197
return False
198198

@@ -212,7 +212,7 @@ def _mysql_storage_engine(self):
212212
cursor.execute(droptable)
213213
cursor.execute('CREATE TABLE {table} (X INT)'.format(table=tblname))
214214

215-
if self.connection.server_version >= (5, 0, 0):
215+
if self.connection.mysql_version >= (5, 0, 0):
216216
cursor.execute(
217217
"SELECT ENGINE FROM INFORMATION_SCHEMA.TABLES "
218218
"WHERE TABLE_SCHEMA = %s AND TABLE_NAME = %s",
@@ -399,7 +399,7 @@ def sequence_reset_by_name_sql(self, style, sequences):
399399
# Truncate already resets the AUTO_INCREMENT field from
400400
# MySQL version 5.0.13 onwards. Refs #16961.
401401
res = []
402-
if self.connection.server_version < (5, 0, 13):
402+
if self.connection.mysql_version < (5, 0, 13):
403403
fmt = "{alter} {table} {{tablename}} {auto_inc} {field};".format(
404404
alter=style.SQL_KEYWORD('ALTER'),
405405
table=style.SQL_KEYWORD('TABLE'),
@@ -431,13 +431,7 @@ def value_to_db_datetime(self, value):
431431
"MySQL backend does not support timezone-aware times."
432432
)
433433

434-
try:
435-
# Django 1.6
436-
self.connection.ensure_connection()
437-
except AttributeError:
438-
if not self.connection.connection:
439-
self.connection._connect()
440-
return self.connection.connection.converter._datetime_to_mysql(value)
434+
return self.connection.converter.to_mysql(value)
441435

442436
def value_to_db_time(self, value):
443437
if value is None:
@@ -448,13 +442,7 @@ def value_to_db_time(self, value):
448442
raise ValueError("MySQL backend does not support timezone-aware "
449443
"times.")
450444

451-
try:
452-
# Django 1.6
453-
self.connection.ensure_connection()
454-
except AttributeError:
455-
if not self.connection.connection:
456-
self.connection._connect()
457-
return self.connection.connection.converter._time_to_mysql(value)
445+
return self.connection.converter.to_mysql(value)
458446

459447
def year_lookup_bounds(self, value):
460448
# Again, no microseconds
@@ -467,7 +455,7 @@ def year_lookup_bounds_for_datetime_field(self, value):
467455
# Again, no microseconds
468456
first, second = super(DatabaseOperations,
469457
self).year_lookup_bounds_for_datetime_field(value)
470-
if self.connection.server_version >= (5, 6, 4):
458+
if self.connection.mysql_version >= (5, 6, 4):
471459
return [first.replace(microsecond=0), second]
472460
else:
473461
return [first.replace(microsecond=0),
@@ -522,15 +510,8 @@ class DatabaseWrapper(BaseDatabaseWrapper):
522510

523511
def __init__(self, *args, **kwargs):
524512
super(DatabaseWrapper, self).__init__(*args, **kwargs)
525-
self.server_version = None
526-
527-
# Since some features depend on the MySQL version, we need to connect
528-
try:
529-
# Django 1.6
530-
self.ensure_connection()
531-
except AttributeError:
532-
self._connect()
533513

514+
self.converter = DjangoMySQLConverter()
534515
self.ops = DatabaseOperations(self)
535516
self.features = DatabaseFeatures(self)
536517
self.client = DatabaseClient(self)
@@ -584,14 +565,13 @@ def get_connection_params(self):
584565
def get_new_connection(self, conn_params):
585566
# Django 1.6
586567
cnx = mysql.connector.connect(**conn_params)
587-
self.server_version = cnx.get_server_version()
588568
cnx.set_converter_class(DjangoMySQLConverter)
589569

590570
return cnx
591571

592572
def init_connection_state(self):
593573
# Django 1.6
594-
if self.server_version < (5, 5, 3):
574+
if self.mysql_version < (5, 5, 3):
595575
# See sysvar_sql_auto_is_null in MySQL Reference manual
596576
self.connection.cmd_query("SET SQL_AUTO_IS_NULL = 0")
597577

@@ -737,6 +717,11 @@ def is_usable(self):
737717
# Django 1.6
738718
return self.connection.is_connected()
739719

740-
@property
720+
@cached_property
741721
def mysql_version(self):
742-
return self.server_version
722+
config = self.get_connection_params()
723+
temp_conn = mysql.connector.connect(**config)
724+
server_version = temp_conn.get_server_version()
725+
temp_conn.close()
726+
727+
return server_version

lib/mysql/connector/django/creation.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ def __init__(self, connection):
3030
'IntegerField': 'integer',
3131
'BigIntegerField': 'bigint',
3232
'IPAddressField': 'char(15)',
33+
3334
'GenericIPAddressField': 'char(39)',
3435
'NullBooleanField': 'bool',
3536
'OneToOneField': 'integer',
@@ -42,7 +43,7 @@ def __init__(self, connection):
4243
}
4344

4445
# Support for microseconds
45-
if self.connection.get_server_version() >= (5, 6, 4):
46+
if self.connection.mysql_version >= (5, 6, 4):
4647
self.data_types.update({
4748
'DateTimeField': 'datetime(6)',
4849
'TimeField': 'time(6)',

tests/test_django.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ def setUp(self):
198198

199199
def test__init__(self):
200200
exp = self.conn.get_server_version()
201-
self.assertEqual(exp, self.cnx.server_version)
201+
self.assertEqual(exp, self.cnx.mysql_version)
202202

203203
value = datetime.time(2, 5, 7)
204204
exp = self.conn.converter._time_to_mysql(value)
@@ -214,7 +214,9 @@ def test_signal(self):
214214

215215
def conn_setup(*args, **kwargs):
216216
conn = kwargs['connection']
217+
settings.DEBUG = True
217218
cur = conn.cursor()
219+
settings.DEBUG = False
218220
cur.execute("SET @xyz=10")
219221
cur.close()
220222

@@ -226,6 +228,22 @@ def conn_setup(*args, **kwargs):
226228
cursor.close()
227229
self.cnx.close()
228230

231+
def count_conn(self, *args, **kwargs):
232+
try:
233+
self.connections += 1
234+
except AttributeError:
235+
self.connection = 1
236+
237+
def test_connections(self):
238+
connection_created.connect(self.count_conn)
239+
self.connections = 0
240+
241+
# Checking if DatabaseWrapper object creates a connection by default
242+
conn = DatabaseWrapper(settings.DATABASES['default'])
243+
dbo = DatabaseOperations(conn)
244+
dbo.value_to_db_time(datetime.time(3, 3, 3))
245+
self.assertEqual(self.connections, 0)
246+
229247

230248
class DjangoDatabaseOperations(tests.MySQLConnectorTests):
231249

0 commit comments

Comments
 (0)