Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Newer
Older
100644 234 lines (201 sloc) 9.253 kb
f69cf70 @adrianholovaty MERGED MAGIC-REMOVAL BRANCH TO TRUNK. This change is highly backwards…
adrianholovaty authored
1 """
2 MySQL database backend for Django.
3
4 Requires MySQLdb: http://sourceforge.net/projects/mysql-python
5 """
6
ca71eac @malcolmt Convert binary-matched VARCHAR fields to unicode objects in the MySQL…
malcolmt authored
7 import re
8
9dc4ba8 @freakboy3742 Fixed #5461 -- Refactored the database backend code to use classes fo…
freakboy3742 authored
9 from django.db.backends import *
10 from django.db.backends.mysql.client import DatabaseClient
11 from django.db.backends.mysql.creation import DatabaseCreation
12 from django.db.backends.mysql.introspection import DatabaseIntrospection
13 from django.db.backends.mysql.validation import DatabaseValidation
14
8e9833f @adrianholovaty Fixed #1673 -- Every database backend now raises ImproperlyConfigured…
adrianholovaty authored
15 try:
16 import MySQLdb as Database
17 except ImportError, e:
18 from django.core.exceptions import ImproperlyConfigured
b367ec2 @adrianholovaty Made various negligible formatting cleanups to the database backends
adrianholovaty authored
19 raise ImproperlyConfigured("Error loading MySQLdb module: %s" % e)
cb624b1 @malcolmt Fixed #3747 -- Added a stricter MySQLdb version check so that (1, 2, 1,
malcolmt authored
20
21 # We want version (1, 2, 1, 'final', 2) or later. We can't just use
22 # lexicographic ordering in this check because then (1, 2, 1, 'gamma')
23 # inadvertently passes the version test.
24 version = Database.version_info
92c35a0 @malcolmt Fixed #2365, #3324 -- Renamed FloatField to DecimalField and changed …
malcolmt authored
25 if (version < (1,2,1) or (version[:3] == (1, 2, 1) and
cb624b1 @malcolmt Fixed #3747 -- Added a stricter MySQLdb version check so that (1, 2, 1,
malcolmt authored
26 (len(version) < 5 or version[3] != 'final' or version[4] < 2))):
89e7b67 @malcolmt Fixed #5531 -- Changes a while back meant we are handling import erro…
malcolmt authored
27 from django.core.exceptions import ImproperlyConfigured
28 raise ImproperlyConfigured("MySQLdb-1.2.1p2 or newer is required; you have %s" % Database.__version__)
f9c4ce5 @malcolmt Fixed #2635 -- Added improved MySQL backend support from Andy Dustman…
malcolmt authored
29
f69cf70 @adrianholovaty MERGED MAGIC-REMOVAL BRANCH TO TRUNK. This change is highly backwards…
adrianholovaty authored
30 from MySQLdb.converters import conversions
ca71eac @malcolmt Convert binary-matched VARCHAR fields to unicode objects in the MySQL…
malcolmt authored
31 from MySQLdb.constants import FIELD_TYPE, FLAG
f69cf70 @adrianholovaty MERGED MAGIC-REMOVAL BRANCH TO TRUNK. This change is highly backwards…
adrianholovaty authored
32
dfdbf9e @adrianholovaty Fixed #5161 -- Changed MySQL backend only to report warning once. Tha…
adrianholovaty authored
33 # Raise exceptions for database warnings if DEBUG is on
4c0ac53 @jacobian Fixed a missing import in mysql backend. Thanks for pointing it out, …
jacobian authored
34 from django.conf import settings
dfdbf9e @adrianholovaty Fixed #5161 -- Changed MySQL backend only to report warning once. Tha…
adrianholovaty authored
35 if settings.DEBUG:
36 from warnings import filterwarnings
37 filterwarnings("error", category=Database.Warning)
38
f69cf70 @adrianholovaty MERGED MAGIC-REMOVAL BRANCH TO TRUNK. This change is highly backwards…
adrianholovaty authored
39 DatabaseError = Database.DatabaseError
b3e0b59 @malcolmt Fixed #3450 -- Exposed IntegrityError in a backend-neutral fashion. T…
malcolmt authored
40 IntegrityError = Database.IntegrityError
f69cf70 @adrianholovaty MERGED MAGIC-REMOVAL BRANCH TO TRUNK. This change is highly backwards…
adrianholovaty authored
41
f9c4ce5 @malcolmt Fixed #2635 -- Added improved MySQL backend support from Andy Dustman…
malcolmt authored
42 # MySQLdb-1.2.1 supports the Python boolean type, and only uses datetime
43 # module for time-related columns; older versions could have used mx.DateTime
44 # or strings if there were no datetime module. However, MySQLdb still returns
45 # TIME columns as timedelta -- they are more like timedelta in terms of actual
46 # behavior as they are signed and include days -- and Django expects time, so
47 # we still need to override that.
f69cf70 @adrianholovaty MERGED MAGIC-REMOVAL BRANCH TO TRUNK. This change is highly backwards…
adrianholovaty authored
48 django_conversions = conversions.copy()
49 django_conversions.update({
50 FIELD_TYPE.TIME: util.typecast_time,
92c35a0 @malcolmt Fixed #2365, #3324 -- Renamed FloatField to DecimalField and changed …
malcolmt authored
51 FIELD_TYPE.DECIMAL: util.typecast_decimal,
52 FIELD_TYPE.NEWDECIMAL: util.typecast_decimal,
ca71eac @malcolmt Convert binary-matched VARCHAR fields to unicode objects in the MySQL…
malcolmt authored
53 # By default, mysqldb will return VARCHAR BINARY fields as type str.
54 # This is a bad idea, as BINARY doesn't indicate that it's arbitrary
55 # binary data, but that collation uses the binary representation.
56 # Replacing the list makes it return unicode. MySQLdb later adds
57 # another list entry for non-binary fields.
58 FIELD_TYPE.VARCHAR: [(FLAG.BINARY, lambda s: s.decode('utf-8'))],
f69cf70 @adrianholovaty MERGED MAGIC-REMOVAL BRANCH TO TRUNK. This change is highly backwards…
adrianholovaty authored
59 })
60
6068f3e @malcolmt Reintroduced the changes from [3855] with more flexible handling of v…
malcolmt authored
61 # This should match the numerical portion of the version numbers (we can treat
62 # versions like 5.0.24 and 5.0.24a as the same). Based on the list of version
63 # at http://dev.mysql.com/doc/refman/4.1/en/news.html and
64 # http://dev.mysql.com/doc/refman/5.0/en/news.html .
65 server_version_re = re.compile(r'(\d{1,2})\.(\d{1,2})\.(\d{1,2})')
66
f9c4ce5 @malcolmt Fixed #2635 -- Added improved MySQL backend support from Andy Dustman…
malcolmt authored
67 # MySQLdb-1.2.1 and newer automatically makes use of SHOW WARNINGS on
68 # MySQL-4.1 and newer, so the MysqlDebugWrapper is unnecessary. Since the
69 # point is to raise Warnings as exceptions, this can be done with the Python
70 # warning module, and this is setup when the connection is created, and the
71 # standard util.CursorDebugWrapper can be used. Also, using sql_mode
72 # TRADITIONAL will automatically cause most warnings to be treated as errors.
f69cf70 @adrianholovaty MERGED MAGIC-REMOVAL BRANCH TO TRUNK. This change is highly backwards…
adrianholovaty authored
73
1a8f9b2 @adrianholovaty Implemented BaseDatabaseFeatures and changed all code to access it --…
adrianholovaty authored
74 class DatabaseFeatures(BaseDatabaseFeatures):
9c52d56 @malcolmt Merged the queryset-refactor branch into trunk.
malcolmt authored
75 empty_fetchmany_value = ()
ba010ec @malcolmt Made some types of nested update queries very slightly more efficient…
malcolmt authored
76 update_can_self_select = False
1a8f9b2 @adrianholovaty Implemented BaseDatabaseFeatures and changed all code to access it --…
adrianholovaty authored
77
38b5d7f @adrianholovaty Began implementing BaseDatabaseOperations class for every database ba…
adrianholovaty authored
78 class DatabaseOperations(BaseDatabaseOperations):
aab04a4 @adrianholovaty Refactored get_date_extract_sql() to DatabaseOperations.date_extract_…
adrianholovaty authored
79 def date_extract_sql(self, lookup_type, field_name):
80 # http://dev.mysql.com/doc/mysql/en/date-and-time-functions.html
81 return "EXTRACT(%s FROM %s)" % (lookup_type.upper(), field_name)
38b5d7f @adrianholovaty Began implementing BaseDatabaseOperations class for every database ba…
adrianholovaty authored
82
5f51f05 @adrianholovaty Refactored get_date_trunc_sql() to DatabaseOperations.date_trunc_sql(…
adrianholovaty authored
83 def date_trunc_sql(self, lookup_type, field_name):
84 fields = ['year', 'month', 'day', 'hour', 'minute', 'second']
85 format = ('%%Y-', '%%m', '-%%d', ' %%H:', '%%i', ':%%s') # Use double percents to escape.
86 format_def = ('0000-', '01', '-01', ' 00:', '00', ':00')
87 try:
88 i = fields.index(lookup_type) + 1
89 except ValueError:
90 sql = field_name
91 else:
92 format_str = ''.join([f for f in format[:i]] + [f for f in format_def[i:]])
93 sql = "CAST(DATE_FORMAT(%s, '%s') AS DATETIME)" % (field_name, format_str)
94 return sql
95
23a736d @adrianholovaty Refactored get_drop_foreignkey_sql() to DatabaseOperations.drop_forei…
adrianholovaty authored
96 def drop_foreignkey_sql(self):
97 return "DROP FOREIGN KEY"
98
5a64264 @adrianholovaty Refactored get_fulltext_search_sql() to DatabaseOperations.fulltext_s…
adrianholovaty authored
99 def fulltext_search_sql(self, field_name):
100 return 'MATCH (%s) AGAINST (%%s IN BOOLEAN MODE)' % field_name
101
9c52d56 @malcolmt Merged the queryset-refactor branch into trunk.
malcolmt authored
102 def no_limit_value(self):
103 # 2**64 - 1, as recommended by the MySQL documentation
104 return 18446744073709551615L
105
221f99e @adrianholovaty Refactored quote_name() to DatabaseOperations.quote_name(). Refs #5106
adrianholovaty authored
106 def quote_name(self, name):
107 if name.startswith("`") and name.endswith("`"):
108 return name # Quoting once is enough.
109 return "`%s`" % name
110
c44fb66 @adrianholovaty Refactored get_random_function_sql() to DatabaseOperations.random_fun…
adrianholovaty authored
111 def random_function_sql(self):
112 return 'RAND()'
113
aaed6e0 @adrianholovaty Refactored get_sql_flush() to DatabaseOperations.sql_flush(). Refs #5106
adrianholovaty authored
114 def sql_flush(self, style, tables, sequences):
115 # NB: The generated SQL below is specific to MySQL
116 # 'TRUNCATE x;', 'TRUNCATE y;', 'TRUNCATE z;'... style SQL statements
117 # to clear all tables of all data
118 if tables:
119 sql = ['SET FOREIGN_KEY_CHECKS = 0;']
120 for table in tables:
221f99e @adrianholovaty Refactored quote_name() to DatabaseOperations.quote_name(). Refs #5106
adrianholovaty authored
121 sql.append('%s %s;' % (style.SQL_KEYWORD('TRUNCATE'), style.SQL_FIELD(self.quote_name(table))))
aaed6e0 @adrianholovaty Refactored get_sql_flush() to DatabaseOperations.sql_flush(). Refs #5106
adrianholovaty authored
122 sql.append('SET FOREIGN_KEY_CHECKS = 1;')
123
124 # 'ALTER TABLE table AUTO_INCREMENT = 1;'... style SQL statements
125 # to reset sequence indices
126 sql.extend(["%s %s %s %s %s;" % \
127 (style.SQL_KEYWORD('ALTER'),
128 style.SQL_KEYWORD('TABLE'),
221f99e @adrianholovaty Refactored quote_name() to DatabaseOperations.quote_name(). Refs #5106
adrianholovaty authored
129 style.SQL_TABLE(self.quote_name(sequence['table'])),
aaed6e0 @adrianholovaty Refactored get_sql_flush() to DatabaseOperations.sql_flush(). Refs #5106
adrianholovaty authored
130 style.SQL_KEYWORD('AUTO_INCREMENT'),
131 style.SQL_FIELD('= 1'),
132 ) for sequence in sequences])
133 return sql
134 else:
135 return []
136
b3b71a0 @malcolmt Fixed #7560 -- Moved a lot of the value conversion preparation for
malcolmt authored
137 def value_to_db_datetime(self, value):
138 # MySQL doesn't support microseconds
139 if value is None:
140 return None
141 return unicode(value.replace(microsecond=0))
142
143 def value_to_db_time(self, value):
144 # MySQL doesn't support microseconds
145 if value is None:
146 return None
147 return unicode(value.replace(microsecond=0))
148
149 def year_lookup_bounds(self, value):
150 # Again, no microseconds
151 first = '%s-01-01 00:00:00'
152 second = '%s-12-31 23:59:59.99'
153 return [first % value, second % value]
154
7c41b19 @adrianholovaty Refactored all database backends to inherit from a common base class …
adrianholovaty authored
155 class DatabaseWrapper(BaseDatabaseWrapper):
9dc4ba8 @freakboy3742 Fixed #5461 -- Refactored the database backend code to use classes fo…
freakboy3742 authored
156
14db373 @adrianholovaty Refactored OPERATOR_MAPPING so that it exists as django.db.connection…
adrianholovaty authored
157 operators = {
8e816c8 @malcolmt Fixed #2170 -- "exact" lookups in MySQL are now case-sensitive (the s…
malcolmt authored
158 'exact': '= BINARY %s',
14db373 @adrianholovaty Refactored OPERATOR_MAPPING so that it exists as django.db.connection…
adrianholovaty authored
159 'iexact': 'LIKE %s',
160 'contains': 'LIKE BINARY %s',
161 'icontains': 'LIKE %s',
162 'regex': 'REGEXP BINARY %s',
163 'iregex': 'REGEXP %s',
164 'gt': '> %s',
165 'gte': '>= %s',
166 'lt': '< %s',
167 'lte': '<= %s',
168 'startswith': 'LIKE BINARY %s',
169 'endswith': 'LIKE BINARY %s',
170 'istartswith': 'LIKE %s',
171 'iendswith': 'LIKE %s',
172 }
38b5d7f @adrianholovaty Began implementing BaseDatabaseOperations class for every database ba…
adrianholovaty authored
173
fef89a0 @jacobian Fixed #2866: Added DATABASE_OPTIONS setting which gets passed as extr…
jacobian authored
174 def __init__(self, **kwargs):
7c41b19 @adrianholovaty Refactored all database backends to inherit from a common base class …
adrianholovaty authored
175 super(DatabaseWrapper, self).__init__(**kwargs)
6068f3e @malcolmt Reintroduced the changes from [3855] with more flexible handling of v…
malcolmt authored
176 self.server_version = None
ca71eac @malcolmt Convert binary-matched VARCHAR fields to unicode objects in the MySQL…
malcolmt authored
177
9dc4ba8 @freakboy3742 Fixed #5461 -- Refactored the database backend code to use classes fo…
freakboy3742 authored
178 self.features = DatabaseFeatures()
179 self.ops = DatabaseOperations()
180 self.client = DatabaseClient()
181 self.creation = DatabaseCreation(self)
182 self.introspection = DatabaseIntrospection(self)
183 self.validation = DatabaseValidation()
f69cf70 @adrianholovaty MERGED MAGIC-REMOVAL BRANCH TO TRUNK. This change is highly backwards…
adrianholovaty authored
184
185 def _valid_connection(self):
186 if self.connection is not None:
187 try:
188 self.connection.ping()
189 return True
190 except DatabaseError:
191 self.connection.close()
192 self.connection = None
193 return False
194
7c41b19 @adrianholovaty Refactored all database backends to inherit from a common base class …
adrianholovaty authored
195 def _cursor(self, settings):
f69cf70 @adrianholovaty MERGED MAGIC-REMOVAL BRANCH TO TRUNK. This change is highly backwards…
adrianholovaty authored
196 if not self._valid_connection():
197 kwargs = {
198 'conv': django_conversions,
1f9711f @malcolmt Fixed #3754 -- Re-introduced utf-8 as default encoding for interactio…
malcolmt authored
199 'charset': 'utf8',
953badb @malcolmt Merged Unicode branch into trunk (r4952:5608). This should be fully
malcolmt authored
200 'use_unicode': True,
f69cf70 @adrianholovaty MERGED MAGIC-REMOVAL BRANCH TO TRUNK. This change is highly backwards…
adrianholovaty authored
201 }
f9c4ce5 @malcolmt Fixed #2635 -- Added improved MySQL backend support from Andy Dustman…
malcolmt authored
202 if settings.DATABASE_USER:
203 kwargs['user'] = settings.DATABASE_USER
204 if settings.DATABASE_NAME:
205 kwargs['db'] = settings.DATABASE_NAME
206 if settings.DATABASE_PASSWORD:
207 kwargs['passwd'] = settings.DATABASE_PASSWORD
f69cf70 @adrianholovaty MERGED MAGIC-REMOVAL BRANCH TO TRUNK. This change is highly backwards…
adrianholovaty authored
208 if settings.DATABASE_HOST.startswith('/'):
209 kwargs['unix_socket'] = settings.DATABASE_HOST
f9c4ce5 @malcolmt Fixed #2635 -- Added improved MySQL backend support from Andy Dustman…
malcolmt authored
210 elif settings.DATABASE_HOST:
f69cf70 @adrianholovaty MERGED MAGIC-REMOVAL BRANCH TO TRUNK. This change is highly backwards…
adrianholovaty authored
211 kwargs['host'] = settings.DATABASE_HOST
212 if settings.DATABASE_PORT:
213 kwargs['port'] = int(settings.DATABASE_PORT)
fef89a0 @jacobian Fixed #2866: Added DATABASE_OPTIONS setting which gets passed as extr…
jacobian authored
214 kwargs.update(self.options)
f69cf70 @adrianholovaty MERGED MAGIC-REMOVAL BRANCH TO TRUNK. This change is highly backwards…
adrianholovaty authored
215 self.connection = Database.connect(**kwargs)
7c41b19 @adrianholovaty Refactored all database backends to inherit from a common base class …
adrianholovaty authored
216 cursor = self.connection.cursor()
f69cf70 @adrianholovaty MERGED MAGIC-REMOVAL BRANCH TO TRUNK. This change is highly backwards…
adrianholovaty authored
217 return cursor
218
219 def _rollback(self):
7c41b19 @adrianholovaty Refactored all database backends to inherit from a common base class …
adrianholovaty authored
220 try:
221 BaseDatabaseWrapper._rollback(self)
222 except Database.NotSupportedError:
223 pass
f69cf70 @adrianholovaty MERGED MAGIC-REMOVAL BRANCH TO TRUNK. This change is highly backwards…
adrianholovaty authored
224
6068f3e @malcolmt Reintroduced the changes from [3855] with more flexible handling of v…
malcolmt authored
225 def get_server_version(self):
226 if not self.server_version:
227 if not self._valid_connection():
228 self.cursor()
229 m = server_version_re.match(self.connection.get_server_info())
230 if not m:
231 raise Exception('Unable to determine MySQL version from version string %r' % self.connection.get_server_info())
a834f21 @malcolmt Fixed omission in [3872].
malcolmt authored
232 self.server_version = tuple([int(x) for x in m.groups()])
6068f3e @malcolmt Reintroduced the changes from [3855] with more flexible handling of v…
malcolmt authored
233 return self.server_version
Something went wrong with that request. Please try again.