2 MySQL database backend for Django.
4 Requires MySQLdb: http://sourceforge.net/projects/mysql-python
11 import MySQLdb
as Database
12 except ImportError, e
:
13 from django
.core
.exceptions
import ImproperlyConfigured
14 raise ImproperlyConfigured("Error loading MySQLdb module: %s" % e
)
16 # We want version (1, 2, 1, 'final', 2) or later. We can't just use
17 # lexicographic ordering in this check because then (1, 2, 1, 'gamma')
18 # inadvertently passes the version test.
19 version
= Database
.version_info
20 if (version
< (1,2,1) or (version
[:3] == (1, 2, 1) and
21 (len(version
) < 5 or version
[3] != 'final' or version
[4] < 2))):
22 from django
.core
.exceptions
import ImproperlyConfigured
23 raise ImproperlyConfigured("MySQLdb-1.2.1p2 or newer is required; you have %s" % Database
.__version
__)
25 from MySQLdb
.converters
import conversions
26 from MySQLdb
.constants
import FIELD_TYPE
, FLAG
, CLIENT
28 from django
.db
import utils
29 from django
.db
.backends
import *
30 from django
.db
.backends
.signals
import connection_created
31 from django
.db
.backends
.mysql
.client
import DatabaseClient
32 from django
.db
.backends
.mysql
.creation
import DatabaseCreation
33 from django
.db
.backends
.mysql
.introspection
import DatabaseIntrospection
34 from django
.db
.backends
.mysql
.validation
import DatabaseValidation
35 from django
.utils
.safestring
import SafeString
, SafeUnicode
37 # Raise exceptions for database warnings if DEBUG is on
38 from django
.conf
import settings
40 from warnings
import filterwarnings
41 filterwarnings("error", category
=Database
.Warning)
43 DatabaseError
= Database
.DatabaseError
44 IntegrityError
= Database
.IntegrityError
46 # MySQLdb-1.2.1 returns TIME columns as timedelta -- they are more like
47 # timedelta in terms of actual behavior as they are signed and include days --
48 # and Django expects time, so we still need to override that. We also need to
49 # add special handling for SafeUnicode and SafeString as MySQLdb's type
50 # checking is too tight to catch those (see Django ticket #6052).
51 django_conversions
= conversions
.copy()
52 django_conversions
.update({
53 FIELD_TYPE
.TIME
: util
.typecast_time
,
54 FIELD_TYPE
.DECIMAL
: util
.typecast_decimal
,
55 FIELD_TYPE
.NEWDECIMAL
: util
.typecast_decimal
,
58 # This should match the numerical portion of the version numbers (we can treat
59 # versions like 5.0.24 and 5.0.24a as the same). Based on the list of version
60 # at http://dev.mysql.com/doc/refman/4.1/en/news.html and
61 # http://dev.mysql.com/doc/refman/5.0/en/news.html .
62 server_version_re
= re
.compile(r
'(\d{1,2})\.(\d{1,2})\.(\d{1,2})')
64 # MySQLdb-1.2.1 and newer automatically makes use of SHOW WARNINGS on
65 # MySQL-4.1 and newer, so the MysqlDebugWrapper is unnecessary. Since the
66 # point is to raise Warnings as exceptions, this can be done with the Python
67 # warning module, and this is setup when the connection is created, and the
68 # standard util.CursorDebugWrapper can be used. Also, using sql_mode
69 # TRADITIONAL will automatically cause most warnings to be treated as errors.
71 class CursorWrapper(object):
73 A thin wrapper around MySQLdb's normal cursor class so that we can catch
74 particular exception instances and reraise them with the right types.
76 Implemented as a wrapper, rather than a subclass, so that we aren't stuck
77 to the particular underlying representation returned by Connection.cursor().
79 codes_for_integrityerror
= (1048,)
81 def __init__(self
, cursor
):
84 def execute(self
, query
, args
=None):
86 return self
.cursor
.execute(query
, args
)
87 except Database
.IntegrityError
, e
:
88 raise utils
.IntegrityError
, utils
.IntegrityError(*tuple(e
)), sys
.exc_info()[2]
89 except Database
.OperationalError
, e
:
90 # Map some error codes to IntegrityError, since they seem to be
91 # misclassified and Django would prefer the more logical place.
92 if e
[0] in self
.codes_for_integrityerror
:
93 raise utils
.IntegrityError
, utils
.IntegrityError(*tuple(e
)), sys
.exc_info()[2]
95 except Database
.DatabaseError
, e
:
96 raise utils
.DatabaseError
, utils
.DatabaseError(*tuple(e
)), sys
.exc_info()[2]
98 def executemany(self
, query
, args
):
100 return self
.cursor
.executemany(query
, args
)
101 except Database
.IntegrityError
, e
:
102 raise utils
.IntegrityError
, utils
.IntegrityError(*tuple(e
)), sys
.exc_info()[2]
103 except Database
.OperationalError
, e
:
104 # Map some error codes to IntegrityError, since they seem to be
105 # misclassified and Django would prefer the more logical place.
106 if e
[0] in self
.codes_for_integrityerror
:
107 raise utils
.IntegrityError
, utils
.IntegrityError(*tuple(e
)), sys
.exc_info()[2]
109 except Database
.DatabaseError
, e
:
110 raise utils
.DatabaseError
, utils
.DatabaseError(*tuple(e
)), sys
.exc_info()[2]
112 def __getattr__(self
, attr
):
113 if attr
in self
.__dict
__:
114 return self
.__dict
__[attr
]
116 return getattr(self
.cursor
, attr
)
119 return iter(self
.cursor
)
121 class DatabaseFeatures(BaseDatabaseFeatures
):
122 empty_fetchmany_value
= ()
123 update_can_self_select
= False
124 allows_group_by_pk
= True
125 related_fields_match_type
= True
126 allow_sliced_subqueries
= False
128 class DatabaseOperations(BaseDatabaseOperations
):
129 compiler_module
= "django.db.backends.mysql.compiler"
131 def date_extract_sql(self
, lookup_type
, field_name
):
132 # http://dev.mysql.com/doc/mysql/en/date-and-time-functions.html
133 if lookup_type
== 'week_day':
134 # DAYOFWEEK() returns an integer, 1-7, Sunday=1.
135 # Note: WEEKDAY() returns 0-6, Monday=0.
136 return "DAYOFWEEK(%s)" % field_name
138 return "EXTRACT(%s FROM %s)" % (lookup_type
.upper(), field_name
)
140 def date_trunc_sql(self
, lookup_type
, field_name
):
141 fields
= ['year', 'month', 'day', 'hour', 'minute', 'second']
142 format
= ('%%Y-', '%%m', '-%%d', ' %%H:', '%%i', ':%%s') # Use double percents to escape.
143 format_def
= ('0000-', '01', '-01', ' 00:', '00', ':00')
145 i
= fields
.index(lookup_type
) + 1
149 format_str
= ''.join([f
for f
in format
[:i
]] + [f
for f
in format_def
[i
:]])
150 sql
= "CAST(DATE_FORMAT(%s, '%s') AS DATETIME)" % (field_name
, format_str
)
153 def drop_foreignkey_sql(self
):
154 return "DROP FOREIGN KEY"
156 def force_no_ordering(self
):
158 "ORDER BY NULL" prevents MySQL from implicitly ordering by grouped
159 columns. If no ordering would otherwise be applied, we don't want any
160 implicit sorting going on.
164 def fulltext_search_sql(self
, field_name
):
165 return 'MATCH (%s) AGAINST (%%s IN BOOLEAN MODE)' % field_name
167 def no_limit_value(self
):
168 # 2**64 - 1, as recommended by the MySQL documentation
169 return 18446744073709551615L
171 def quote_name(self
, name
):
172 if name
.startswith("`") and name
.endswith("`"):
173 return name
# Quoting once is enough.
176 def random_function_sql(self
):
179 def sql_flush(self
, style
, tables
, sequences
):
180 # NB: The generated SQL below is specific to MySQL
181 # 'TRUNCATE x;', 'TRUNCATE y;', 'TRUNCATE z;'... style SQL statements
182 # to clear all tables of all data
184 sql
= ['SET FOREIGN_KEY_CHECKS = 0;']
186 sql
.append('%s %s;' % (style
.SQL_KEYWORD('TRUNCATE'), style
.SQL_FIELD(self
.quote_name(table
))))
187 sql
.append('SET FOREIGN_KEY_CHECKS = 1;')
189 # 'ALTER TABLE table AUTO_INCREMENT = 1;'... style SQL statements
190 # to reset sequence indices
191 sql
.extend(["%s %s %s %s %s;" % \
192 (style
.SQL_KEYWORD('ALTER'),
193 style
.SQL_KEYWORD('TABLE'),
194 style
.SQL_TABLE(self
.quote_name(sequence
['table'])),
195 style
.SQL_KEYWORD('AUTO_INCREMENT'),
196 style
.SQL_FIELD('= 1'),
197 ) for sequence
in sequences
])
202 def value_to_db_datetime(self
, value
):
206 # MySQL doesn't support tz-aware datetimes
207 if value
.tzinfo
is not None:
208 raise ValueError("MySQL backend does not support timezone-aware datetimes.")
210 # MySQL doesn't support microseconds
211 return unicode(value
.replace(microsecond
=0))
213 def value_to_db_time(self
, value
):
217 # MySQL doesn't support tz-aware datetimes
218 if value
.tzinfo
is not None:
219 raise ValueError("MySQL backend does not support timezone-aware datetimes.")
221 # MySQL doesn't support microseconds
222 return unicode(value
.replace(microsecond
=0))
224 def year_lookup_bounds(self
, value
):
225 # Again, no microseconds
226 first
= '%s-01-01 00:00:00'
227 second
= '%s-12-31 23:59:59.99'
228 return [first
% value
, second
% value
]
230 def max_name_length(self
):
233 class DatabaseWrapper(BaseDatabaseWrapper
):
238 'contains': 'LIKE BINARY %s',
239 'icontains': 'LIKE %s',
240 'regex': 'REGEXP BINARY %s',
241 'iregex': 'REGEXP %s',
246 'startswith': 'LIKE BINARY %s',
247 'endswith': 'LIKE BINARY %s',
248 'istartswith': 'LIKE %s',
249 'iendswith': 'LIKE %s',
252 def __init__(self
, *args
, **kwargs
):
253 super(DatabaseWrapper
, self
).__init
__(*args
, **kwargs
)
255 self
.server_version
= None
256 self
.features
= DatabaseFeatures()
257 self
.ops
= DatabaseOperations()
258 self
.client
= DatabaseClient(self
)
259 self
.creation
= DatabaseCreation(self
)
260 self
.introspection
= DatabaseIntrospection(self
)
261 self
.validation
= DatabaseValidation(self
)
263 def _valid_connection(self
):
264 if self
.connection
is not None:
266 self
.connection
.ping()
268 except DatabaseError
:
269 self
.connection
.close()
270 self
.connection
= None
274 if not self
._valid
_connection
():
276 'conv': django_conversions
,
280 settings_dict
= self
.settings_dict
281 if settings_dict
['USER']:
282 kwargs
['user'] = settings_dict
['USER']
283 if settings_dict
['NAME']:
284 kwargs
['db'] = settings_dict
['NAME']
285 if settings_dict
['PASSWORD']:
286 kwargs
['passwd'] = settings_dict
['PASSWORD']
287 if settings_dict
['HOST'].startswith('/'):
288 kwargs
['unix_socket'] = settings_dict
['HOST']
289 elif settings_dict
['HOST']:
290 kwargs
['host'] = settings_dict
['HOST']
291 if settings_dict
['PORT']:
292 kwargs
['port'] = int(settings_dict
['PORT'])
293 # We need the number of potentially affected rows after an
294 # "UPDATE", not the number of changed rows.
295 kwargs
['client_flag'] = CLIENT
.FOUND_ROWS
296 kwargs
.update(settings_dict
['OPTIONS'])
297 self
.connection
= Database
.connect(**kwargs
)
298 self
.connection
.encoders
[SafeUnicode
] = self
.connection
.encoders
[unicode]
299 self
.connection
.encoders
[SafeString
] = self
.connection
.encoders
[str]
300 connection_created
.send(sender
=self
.__class
__)
301 cursor
= CursorWrapper(self
.connection
.cursor())
306 BaseDatabaseWrapper
._rollback
(self
)
307 except Database
.NotSupportedError
:
310 def get_server_version(self
):
311 if not self
.server_version
:
312 if not self
._valid
_connection
():
314 m
= server_version_re
.match(self
.connection
.get_server_info())
316 raise Exception('Unable to determine MySQL version from version string %r' % self
.connection
.get_server_info())
317 self
.server_version
= tuple([int(x
) for x
in m
.groups()])
318 return self
.server_version