Skip to content

Commit 6344aaa

Browse files
committed
BUG25726671: Fix compatibility issues with the latest Django versions
This patch fixes some compatibility issues with the latest Django versions. The following bugs were also fixed: BUG25349918: Add proxy methods for Django 1.9+ operations - Methods for filtering time and datetime fields sql were changed in Django 1.9 from `value_to_db_datetime` to `adapt_datetimefield_value` and from `value_to_db_time` to `adapt_timefield_value`. Proxy methods were added to ensure compatibility. BUG25349912: Bug fix for incorrect Django introspection type - Removed extra encapsulation from get_constraints for foreign key parameter. BUG25349897: Fix Django 1.9 bulk insert compatibility - Added support for database backend API change introduced in Django 1.9 for `bulk_insert_sql` method. Connector/Python now only supports Django 1.8+. Test cases were added for regression.
1 parent 1a7360f commit 6344aaa

File tree

3 files changed

+100
-28
lines changed

3 files changed

+100
-28
lines changed

lib/mysql/connector/django/introspection.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -279,9 +279,8 @@ def get_constraints(self, cursor, table_name):
279279
'unique': False,
280280
'index': False,
281281
'check': False,
282-
'foreign_key': (
283-
(ref_table, ref_column) if ref_column else None,
284-
)
282+
'foreign_key': \
283+
(ref_table, ref_column) if ref_column else None
285284
}
286285
constraints[constraint]['columns'].add(column)
287286
# Now get the constraint types

lib/mysql/connector/django/operations.py

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,10 @@ def validate_autopk_value(self, value):
185185
'value for AutoField.')
186186
return value
187187

188+
if django.VERSION > (1, 8):
189+
def adapt_datetimefield_value(self, value):
190+
return self.value_to_db_datetime(value)
191+
188192
def value_to_db_datetime(self, value):
189193
if value is None:
190194
return None
@@ -202,6 +206,10 @@ def value_to_db_datetime(self, value):
202206
return datetime_to_mysql(value)
203207
return self.connection.converter.to_mysql(value)
204208

209+
if django.VERSION > (1, 8):
210+
def adapt_timefield_value(self, value):
211+
return self.value_to_db_time(value)
212+
205213
def value_to_db_time(self, value):
206214
if value is None:
207215
return None
@@ -218,9 +226,15 @@ def value_to_db_time(self, value):
218226
def max_name_length(self):
219227
return 64
220228

221-
def bulk_insert_sql(self, fields, num_values):
222-
items_sql = "({0})".format(", ".join(["%s"] * len(fields)))
223-
return "VALUES " + ", ".join([items_sql] * num_values)
229+
if django.VERSION < (1, 9):
230+
def bulk_insert_sql(self, fields, num_values):
231+
items_sql = "({0})".format(", ".join(["%s"] * len(fields)))
232+
return "VALUES " + ", ".join([items_sql] * num_values)
233+
else:
234+
def bulk_insert_sql(self, fields, placeholder_rows):
235+
placeholder_rows_sql = (", ".join(row) for row in placeholder_rows)
236+
values_sql = ", ".join("({0})".format(sql) for sql in placeholder_rows_sql)
237+
return "VALUES " + values_sql
224238

225239
if django.VERSION < (1, 8):
226240
def year_lookup_bounds(self, value):

tests/test_django.py

Lines changed: 81 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# MySQL Connector/Python - MySQL driver written in Python.
2-
# Copyright (c) 2014, 2015, Oracle and/or its affiliates. All rights reserved.
2+
# Copyright (c) 2014, 2017, Oracle and/or its affiliates. All rights reserved.
33

44
# MySQL Connector/Python is licensed under the terms of the GPLv2
55
# <http://www.gnu.org/licenses/old-licenses/gpl-2.0.html>, like most
@@ -60,6 +60,7 @@
6060
'TEST_COLLATION': 'utf8_general_ci',
6161
'CONN_MAX_AGE': 0,
6262
'AUTOCOMMIT': True,
63+
'TIME_ZONE': None,
6364
},
6465
}
6566
settings.SECRET_KEY = "django_tests_secret_key"
@@ -92,15 +93,11 @@
9293

9394
# Have to load django.db to make importing db backend work for Django < 1.6
9495
import django.db # pylint: disable=W0611
95-
if tests.DJANGO_VERSION >= (1, 6):
96-
if tests.DJANGO_VERSION >= (1, 8):
97-
from django.db.backends.base.introspection import FieldInfo
98-
else:
99-
from django.db.backends import FieldInfo
10096
from django.db.backends.signals import connection_created
10197
from django.utils.safestring import SafeBytes, SafeText
10298

10399
import mysql.connector
100+
from mysql.connector.django.introspection import FieldInfo
104101

105102
if DJANGO_AVAILABLE:
106103
from mysql.connector.django.base import (
@@ -147,14 +144,14 @@ def tearDownClass(cls):
147144

148145
def test_get_table_list(self):
149146
cur = self.cnx.cursor()
150-
exp = list(TABLES.keys())
151-
for exp in list(TABLES.keys()):
147+
for exp in TABLES.keys():
152148
if sys.version_info < (2, 7):
153149
self.assertTrue(exp in self.introspect.get_table_list(cur))
154150
else:
155-
self.assertIn(exp, self.introspect.get_table_list(cur),
156-
"Table {table_name} not in table list".format(
157-
table_name=exp))
151+
res = any(table.name == exp
152+
for table in self.introspect.get_table_list(cur))
153+
self.assertTrue(res, "Table {table_name} not in table list"
154+
"".format(table_name=exp))
158155

159156
def test_get_table_description(self):
160157
cur = self.cnx.cursor()
@@ -165,24 +162,39 @@ def test_get_table_description(self):
165162
('c1', 3, None, None, None, None, 1, 16392),
166163
('c2', 253, None, 20, None, None, 1, 16388)
167164
]
168-
else:
165+
elif tests.DJANGO_VERSION < (1, 8):
169166
exp = [
170-
FieldInfo(name='id', type_code=3, display_size=None,
167+
FieldInfo(name=u'id', type_code=3, display_size=None,
171168
internal_size=None, precision=None, scale=None,
172169
null_ok=0),
173-
FieldInfo(name='c1', type_code=3, display_size=None,
170+
FieldInfo(name=u'c1', type_code=3, display_size=None,
174171
internal_size=None, precision=None, scale=None,
175172
null_ok=1),
176-
FieldInfo(name='c2', type_code=253, display_size=None,
173+
FieldInfo(name=u'c2', type_code=253, display_size=None,
177174
internal_size=20, precision=None, scale=None,
178175
null_ok=1)
179176
]
177+
else:
178+
exp = [
179+
FieldInfo(name=u'id', type_code=3, display_size=None,
180+
internal_size=None, precision=10, scale=None,
181+
null_ok=0, extra=u'auto_increment'),
182+
FieldInfo(name=u'c1', type_code=3, display_size=None,
183+
internal_size=None, precision=10, scale=None,
184+
null_ok=1, extra=u''),
185+
FieldInfo(name=u'c2', type_code=253, display_size=None,
186+
internal_size=20, precision=None, scale=None,
187+
null_ok=1, extra=u'')
188+
]
180189
res = self.introspect.get_table_description(cur, 'django_t1')
181190
self.assertEqual(exp, res)
182191

183192
def test_get_relations(self):
184193
cur = self.cnx.cursor()
185-
exp = {1: (0, 'django_t1')}
194+
if tests.DJANGO_VERSION < (1, 8):
195+
exp = {1: (0, 'django_t1')}
196+
else:
197+
exp = {u'id_t1': (u'id', u'django_t1')}
186198
self.assertEqual(exp, self.introspect.get_relations(cur, 'django_t2'))
187199

188200
def test_get_key_columns(self):
@@ -204,6 +216,30 @@ def test_get_primary_key_column(self):
204216
res = self.introspect.get_primary_key_column(cur, 'django_t1')
205217
self.assertEqual('id', res)
206218

219+
def test_get_constraints(self):
220+
cur = self.cnx.cursor()
221+
exp = {
222+
'PRIMARY': {'check': False,
223+
'columns': ['id'],
224+
'foreign_key': None,
225+
'index': True,
226+
'primary_key': True,
227+
'unique': True},
228+
'django_t2_ibfk_1': {'check': False,
229+
'columns': ['id_t1'],
230+
'foreign_key': ('django_t1', 'id'),
231+
'index': False,
232+
'primary_key': False,
233+
'unique': False},
234+
'id_t1': {'check': False,
235+
'columns': ['id_t1'],
236+
'foreign_key': None,
237+
'index': True,
238+
'primary_key': False,
239+
'unique': False}
240+
}
241+
self.assertEqual(
242+
exp, self.introspect.get_constraints(cur, 'django_t2'))
207243

208244
@unittest.skipIf(not DJANGO_AVAILABLE, "Django not available")
209245
class DjangoDatabaseWrapper(tests.MySQLConnectorTests):
@@ -277,26 +313,49 @@ def setUp(self):
277313
self.dbo = DatabaseOperations(self.cnx)
278314

279315
def test_value_to_db_time(self):
280-
self.assertEqual(None, self.dbo.value_to_db_time(None))
316+
if tests.DJANGO_VERSION < (1, 9):
317+
value_to_db_time = self.dbo.value_to_db_time
318+
else:
319+
value_to_db_time = self.dbo.adapt_timefield_value
320+
321+
self.assertEqual(None, value_to_db_time(None))
281322

282323
value = datetime.time(0, 0, 0)
283324
exp = self.conn.converter._time_to_mysql(value)
284-
self.assertEqual(exp, self.dbo.value_to_db_time(value))
325+
self.assertEqual(exp, value_to_db_time(value))
285326

286327
value = datetime.time(2, 5, 7)
287328
exp = self.conn.converter._time_to_mysql(value)
288-
self.assertEqual(exp, self.dbo.value_to_db_time(value))
329+
self.assertEqual(exp, value_to_db_time(value))
289330

290331
def test_value_to_db_datetime(self):
291-
self.assertEqual(None, self.dbo.value_to_db_datetime(None))
332+
if tests.DJANGO_VERSION < (1, 9):
333+
value_to_db_datetime = self.dbo.value_to_db_datetime
334+
else:
335+
value_to_db_datetime = self.dbo.adapt_datetimefield_value
336+
337+
self.assertEqual(None, value_to_db_datetime(None))
292338

293339
value = datetime.datetime(1, 1, 1)
294340
exp = self.conn.converter._datetime_to_mysql(value)
295-
self.assertEqual(exp, self.dbo.value_to_db_datetime(value))
341+
self.assertEqual(exp, value_to_db_datetime(value))
296342

297343
value = datetime.datetime(2, 5, 7, 10, 10)
298344
exp = self.conn.converter._datetime_to_mysql(value)
299-
self.assertEqual(exp, self.dbo.value_to_db_datetime(value))
345+
self.assertEqual(exp, value_to_db_datetime(value))
346+
347+
def test_bulk_insert_sql(self):
348+
num_values = 5
349+
fields = ["col1", "col2", "col3"]
350+
placeholder_rows = [["%s"] * len(fields) for _ in range(num_values)]
351+
exp = "VALUES {0}".format(", ".join(
352+
["({0})".format(", ".join(["%s"] * len(fields)))] * num_values))
353+
if tests.DJANGO_VERSION < (1, 9):
354+
self.assertEqual(
355+
exp, self.dbo.bulk_insert_sql(fields, num_values))
356+
else:
357+
self.assertEqual(
358+
exp, self.dbo.bulk_insert_sql(fields, placeholder_rows))
300359

301360

302361
class DjangoMySQLConverterTests(tests.MySQLConnectorTests):

0 commit comments

Comments
 (0)