18
18
from __future__ import unicode_literals
19
19
20
20
import sys
21
+
21
22
import django
23
+ from django .utils .functional import cached_property
22
24
23
25
try :
24
26
import mysql .connector
41
43
"is required; you have %s" % mysql .connector .__version__ )
42
44
43
45
from django .db import utils
44
- from django .db .backends import *
46
+ if django .VERSION < (1 , 7 ):
47
+ from django .db .backends import util
48
+ else :
49
+ from django .db .backends import utils as backend_utils
50
+ from django .db .backends import (BaseDatabaseFeatures , BaseDatabaseOperations ,
51
+ BaseDatabaseWrapper )
45
52
from django .db .backends .signals import connection_created
46
53
from django .utils import (six , timezone , dateparse )
47
54
from django .conf import settings
50
57
from mysql .connector .django .creation import DatabaseCreation
51
58
from mysql .connector .django .introspection import DatabaseIntrospection
52
59
from mysql .connector .django .validation import DatabaseValidation
60
+ if django .VERSION >= (1 , 7 ):
61
+ from mysql .connector .django .schema import DatabaseSchemaEditor
53
62
54
63
try :
55
64
import pytz
@@ -69,7 +78,7 @@ def _TIME_to_python(self, value, dsc=None):
69
78
70
79
Returns datetime.time()
71
80
"""
72
- return dateparse .parse_time (value )
81
+ return dateparse .parse_time (value . decode ( 'utf-8' ) )
73
82
74
83
def _DATETIME_to_python (self , value , dsc = None ):
75
84
"""Connector/Python always returns naive datetime.datetime
@@ -102,8 +111,10 @@ def _execute_wrapper(self, method, query, args):
102
111
"""Wrapper around execute() and executemany()"""
103
112
try :
104
113
return method (query , args )
105
- except (mysql .connector .ProgrammingError ,
106
- mysql .connector .IntegrityError ) as err :
114
+ except (mysql .connector .ProgrammingError ) as err :
115
+ six .reraise (utils .ProgrammingError ,
116
+ utils .ProgrammingError (err .msg ), sys .exc_info ()[2 ])
117
+ except (mysql .connector .IntegrityError ) as err :
107
118
six .reraise (utils .IntegrityError ,
108
119
utils .IntegrityError (err .msg ), sys .exc_info ()[2 ])
109
120
except mysql .connector .OperationalError as err :
@@ -127,7 +138,7 @@ def executemany(self, query, args):
127
138
This wrapper method around the executemany()-method of the cursor is
128
139
mainly needed to re-raise using different exceptions.
129
140
"""
130
- return self ._execute_wrapper (self .cursor .execute , query , args )
141
+ return self ._execute_wrapper (self .cursor .executemany , query , args )
131
142
132
143
def __getattr__ (self , attr ):
133
144
"""Return attribute of wrapped cursor"""
@@ -137,6 +148,12 @@ def __iter__(self):
137
148
"""Returns iterator over wrapped cursor"""
138
149
return iter (self .cursor )
139
150
151
+ def __enter__ (self ):
152
+ return self
153
+
154
+ def __exit__ (self , exc_type , exc_value , exc_traceback ):
155
+ self .close ()
156
+
140
157
141
158
class DatabaseFeatures (BaseDatabaseFeatures ):
142
159
"""Features specific to MySQL
@@ -154,14 +171,19 @@ class DatabaseFeatures(BaseDatabaseFeatures):
154
171
has_select_for_update_nowait = False
155
172
supports_forward_references = False
156
173
supports_long_model_names = False
174
+ supports_binary_field = six .PY2
157
175
supports_microsecond_precision = False # toggled in __init__()
158
176
supports_regex_backreferencing = False
159
177
supports_date_lookup_using_string = False
178
+ can_introspect_binary_field = False
179
+ can_introspect_boolean_field = False
160
180
supports_timezones = False
161
181
requires_explicit_null_ordering_when_grouping = True
182
+ allows_auto_pk_0 = False
162
183
allows_primary_key_0 = False
163
184
uses_savepoints = True
164
185
atomic_transactions = False
186
+ supports_column_check_constraints = False
165
187
166
188
def __init__ (self , connection ):
167
189
super (DatabaseFeatures , self ).__init__ (connection )
@@ -172,6 +194,7 @@ def _microseconds_precision(self):
172
194
return True
173
195
return False
174
196
197
+ @cached_property
175
198
def _mysql_storage_engine (self ):
176
199
"""Get default storage engine of MySQL
177
200
@@ -180,28 +203,30 @@ def _mysql_storage_engine(self):
180
203
181
204
Used by Django tests.
182
205
"""
183
- cursor = self .connection .cursor ()
184
206
tblname = 'INTROSPECT_TEST'
185
207
186
208
droptable = 'DROP TABLE IF EXISTS {table}' .format (table = tblname )
209
+ with self .connection .cursor () as cursor :
210
+ cursor .execute (droptable )
211
+ cursor .execute ('CREATE TABLE {table} (X INT)' .format (table = tblname ))
212
+
213
+ if self .connection .server_version >= (5 , 0 , 0 ):
214
+ cursor .execute (
215
+ "SELECT ENGINE FROM INFORMATION_SCHEMA.TABLES "
216
+ "WHERE TABLE_SCHEMA = %s AND TABLE_NAME = %s" ,
217
+ (self .connection .settings_dict ['NAME' ], tblname ))
218
+ engine = cursor .fetchone ()[0 ]
219
+ else :
220
+ # Very old MySQL servers..
221
+ cursor .execute ("SHOW TABLE STATUS WHERE Name='{table}'" .format (
222
+ table = tblname ))
223
+ engine = cursor .fetchone ()[1 ]
224
+ cursor .execute (droptable )
187
225
188
- cursor .execute (droptable )
189
- cursor .execute ('CREATE TABLE {table} (X INT)' .format (table = tblname ))
190
-
191
- if self .connection .server_version >= (5 , 0 , 0 ):
192
- cursor .execute (
193
- "SELECT ENGINE FROM INFORMATION_SCHEMA.TABLES "
194
- "WHERE TABLE_SCHEMA = %s AND TABLE_NAME = %s" ,
195
- (self .connection .settings_dict ['NAME' ], tblname ))
196
- engine = cursor .fetchone ()[0 ]
197
- else :
198
- # Very old MySQL servers..
199
- cursor .execute ("SHOW TABLE STATUS WHERE Name='{table}'" .format (
200
- table = tblname ))
201
- engine = cursor .fetchone ()[1 ]
202
- cursor .execute (droptable )
226
+ self ._cached_storage_engine = engine
203
227
return engine
204
228
229
+ @cached_property
205
230
def can_introspect_foreign_keys (self ):
206
231
"""Confirm support for introspected foreign keys
207
232
@@ -210,6 +235,7 @@ def can_introspect_foreign_keys(self):
210
235
"""
211
236
return self ._mysql_storage_engine == 'InnoDB'
212
237
238
+ @cached_property
213
239
def has_zoneinfo_database (self ):
214
240
"""Tests if the time zone definitions are installed
215
241
@@ -223,14 +249,21 @@ def has_zoneinfo_database(self):
223
249
if not HAVE_PYTZ :
224
250
return False
225
251
226
- cursor = self .connection .cursor ()
227
- cursor .execute ("SELECT 1 FROM mysql.time_zone LIMIT 1" )
228
- return cursor .fetchall () != []
252
+ with self .connection .cursor () as cursor :
253
+ cursor .execute ("SELECT 1 FROM mysql.time_zone LIMIT 1" )
254
+ return cursor .fetchall () != []
229
255
230
256
231
257
class DatabaseOperations (BaseDatabaseOperations ):
232
258
compiler_module = "mysql.connector.django.compiler"
233
259
260
+ # MySQL stores positive fields as UNSIGNED ints.
261
+ if django .VERSION >= (1 , 7 ):
262
+ integer_field_ranges = dict (BaseDatabaseOperations .integer_field_ranges ,
263
+ PositiveSmallIntegerField = (0 , 4294967295 ),
264
+ PositiveIntegerField = (
265
+ 0 , 18446744073709551615 ),)
266
+
234
267
def date_extract_sql (self , lookup_type , field_name ):
235
268
# http://dev.mysql.com/doc/mysql/en/date-and-time-functions.html
236
269
if lookup_type == 'week_day' :
@@ -250,7 +283,7 @@ def date_trunc_sql(self, lookup_type, field_name):
250
283
The field_name is returned when lookup_type is not supported.
251
284
"""
252
285
fields = ['year' , 'month' , 'day' , 'hour' , 'minute' , 'second' ]
253
- format = ('%% Y-' , '%% m' , '-%% d' , ' %% H:' , '%% i' , ':% %s' )
286
+ format = ('%Y-' , '%m' , '-%d' , ' %H:' , '%i' , ':%s' )
254
287
format_def = ('0000-' , '01' , '-01' , ' 00:' , '00' , ':00' )
255
288
try :
256
289
i = fields .index (lookup_type ) + 1
@@ -290,7 +323,7 @@ def datetime_trunc_sql(self, lookup_type, field_name, tzname):
290
323
else :
291
324
params = []
292
325
fields = ['year' , 'month' , 'day' , 'hour' , 'minute' , 'second' ]
293
- format_ = ('%% Y-' , '%% m' , '-%% d' , ' %% H:' , '%% i' , ':% %s' )
326
+ format_ = ('%Y-' , '%m' , '-%d' , ' %H:' , '%i' , ':%s' )
294
327
format_def = ('0000-' , '01' , '-01' , ' 00:' , '00' , ':00' )
295
328
try :
296
329
i = fields .index (lookup_type ) + 1
@@ -379,7 +412,7 @@ def sequence_reset_by_name_sql(self, style, sequences):
379
412
380
413
def validate_autopk_value (self , value ):
381
414
# MySQLism: zero in AUTO_INCREMENT field does not work. Refs #17653.
382
- if not value :
415
+ if value == 0 :
383
416
raise ValueError ('The database backend does not accept 0 as a '
384
417
'value for AutoField.' )
385
418
return value
@@ -454,6 +487,15 @@ def savepoint_commit_sql(self, sid):
454
487
def savepoint_rollback_sql (self , sid ):
455
488
return "ROLLBACK TO SAVEPOINT {0}" .format (sid )
456
489
490
+ def combine_expression (self , connector , sub_expressions ):
491
+ """
492
+ MySQL requires special cases for ^ operators in query expressions
493
+ """
494
+ if connector == '^' :
495
+ return 'POW(%s)' % ',' .join (sub_expressions )
496
+ return super (DatabaseOperations , self ).combine_expression (
497
+ connector , sub_expressions )
498
+
457
499
458
500
class DatabaseWrapper (BaseDatabaseWrapper ):
459
501
vendor = 'mysql'
@@ -681,8 +723,18 @@ def _rollback(self):
681
723
682
724
def _set_autocommit (self , autocommit ):
683
725
# Django 1.6
684
- self .connection .autocommit = autocommit
726
+ with self .wrap_database_errors :
727
+ self .connection .autocommit = autocommit
728
+
729
+ def schema_editor (self , * args , ** kwargs ):
730
+ """Returns a new instance of this backend's SchemaEditor"""
731
+ # Django 1.7
732
+ return DatabaseSchemaEditor (self , * args , ** kwargs )
685
733
686
734
def is_usable (self ):
687
735
# Django 1.6
688
736
return self .connection .is_connected ()
737
+
738
+ @property
739
+ def mysql_version (self ):
740
+ return self .server_version
0 commit comments