2 MySQL database backend for Django. |
2 MySQL database backend for Django. |
3 |
3 |
4 Requires MySQLdb: http://sourceforge.net/projects/mysql-python |
4 Requires MySQLdb: http://sourceforge.net/projects/mysql-python |
5 """ |
5 """ |
6 |
6 |
7 from django.db.backends import BaseDatabaseWrapper, BaseDatabaseFeatures, BaseDatabaseOperations, util |
7 import re |
|
8 |
8 try: |
9 try: |
9 import MySQLdb as Database |
10 import MySQLdb as Database |
10 except ImportError, e: |
11 except ImportError, e: |
11 from django.core.exceptions import ImproperlyConfigured |
12 from django.core.exceptions import ImproperlyConfigured |
12 raise ImproperlyConfigured("Error loading MySQLdb module: %s" % e) |
13 raise ImproperlyConfigured("Error loading MySQLdb module: %s" % e) |
19 (len(version) < 5 or version[3] != 'final' or version[4] < 2))): |
20 (len(version) < 5 or version[3] != 'final' or version[4] < 2))): |
20 from django.core.exceptions import ImproperlyConfigured |
21 from django.core.exceptions import ImproperlyConfigured |
21 raise ImproperlyConfigured("MySQLdb-1.2.1p2 or newer is required; you have %s" % Database.__version__) |
22 raise ImproperlyConfigured("MySQLdb-1.2.1p2 or newer is required; you have %s" % Database.__version__) |
22 |
23 |
23 from MySQLdb.converters import conversions |
24 from MySQLdb.converters import conversions |
24 from MySQLdb.constants import FIELD_TYPE |
25 from MySQLdb.constants import FIELD_TYPE, FLAG |
25 import re |
26 |
|
27 from django.db.backends import * |
|
28 from django.db.backends.mysql.client import DatabaseClient |
|
29 from django.db.backends.mysql.creation import DatabaseCreation |
|
30 from django.db.backends.mysql.introspection import DatabaseIntrospection |
|
31 from django.db.backends.mysql.validation import DatabaseValidation |
26 |
32 |
27 # Raise exceptions for database warnings if DEBUG is on |
33 # Raise exceptions for database warnings if DEBUG is on |
28 from django.conf import settings |
34 from django.conf import settings |
29 if settings.DEBUG: |
35 if settings.DEBUG: |
30 from warnings import filterwarnings |
36 from warnings import filterwarnings |
57 # point is to raise Warnings as exceptions, this can be done with the Python |
63 # point is to raise Warnings as exceptions, this can be done with the Python |
58 # warning module, and this is setup when the connection is created, and the |
64 # warning module, and this is setup when the connection is created, and the |
59 # standard util.CursorDebugWrapper can be used. Also, using sql_mode |
65 # standard util.CursorDebugWrapper can be used. Also, using sql_mode |
60 # TRADITIONAL will automatically cause most warnings to be treated as errors. |
66 # TRADITIONAL will automatically cause most warnings to be treated as errors. |
61 |
67 |
|
68 class CursorWrapper(object): |
|
69 """ |
|
70 A thin wrapper around MySQLdb's normal cursor class so that we can catch |
|
71 particular exception instances and reraise them with the right types. |
|
72 |
|
73 Implemented as a wrapper, rather than a subclass, so that we aren't stuck |
|
74 to the particular underlying representation returned by Connection.cursor(). |
|
75 """ |
|
76 codes_for_integrityerror = (1048,) |
|
77 |
|
78 def __init__(self, cursor): |
|
79 self.cursor = cursor |
|
80 |
|
81 def execute(self, query, args=None): |
|
82 try: |
|
83 return self.cursor.execute(query, args) |
|
84 except Database.OperationalError, e: |
|
85 # Map some error codes to IntegrityError, since they seem to be |
|
86 # misclassified and Django would prefer the more logical place. |
|
87 if e[0] in self.codes_for_integrityerror: |
|
88 raise Database.IntegrityError(tuple(e)) |
|
89 raise |
|
90 |
|
91 def executemany(self, query, args): |
|
92 try: |
|
93 return self.cursor.executemany(query, args) |
|
94 except Database.OperationalError, e: |
|
95 # Map some error codes to IntegrityError, since they seem to be |
|
96 # misclassified and Django would prefer the more logical place. |
|
97 if e[0] in self.codes_for_integrityerror: |
|
98 raise Database.IntegrityError(tuple(e)) |
|
99 raise |
|
100 |
|
101 def __getattr__(self, attr): |
|
102 if attr in self.__dict__: |
|
103 return self.__dict__[attr] |
|
104 else: |
|
105 return getattr(self.cursor, attr) |
|
106 |
|
107 def __iter__(self): |
|
108 return iter(self.cursor) |
|
109 |
62 class DatabaseFeatures(BaseDatabaseFeatures): |
110 class DatabaseFeatures(BaseDatabaseFeatures): |
63 autoindexes_primary_keys = False |
|
64 inline_fk_references = False |
|
65 empty_fetchmany_value = () |
111 empty_fetchmany_value = () |
66 update_can_self_select = False |
112 update_can_self_select = False |
|
113 related_fields_match_type = True |
67 |
114 |
68 class DatabaseOperations(BaseDatabaseOperations): |
115 class DatabaseOperations(BaseDatabaseOperations): |
69 def date_extract_sql(self, lookup_type, field_name): |
116 def date_extract_sql(self, lookup_type, field_name): |
70 # http://dev.mysql.com/doc/mysql/en/date-and-time-functions.html |
117 # http://dev.mysql.com/doc/mysql/en/date-and-time-functions.html |
71 return "EXTRACT(%s FROM %s)" % (lookup_type.upper(), field_name) |
118 return "EXTRACT(%s FROM %s)" % (lookup_type.upper(), field_name) |
86 def drop_foreignkey_sql(self): |
133 def drop_foreignkey_sql(self): |
87 return "DROP FOREIGN KEY" |
134 return "DROP FOREIGN KEY" |
88 |
135 |
89 def fulltext_search_sql(self, field_name): |
136 def fulltext_search_sql(self, field_name): |
90 return 'MATCH (%s) AGAINST (%%s IN BOOLEAN MODE)' % field_name |
137 return 'MATCH (%s) AGAINST (%%s IN BOOLEAN MODE)' % field_name |
91 |
|
92 def limit_offset_sql(self, limit, offset=None): |
|
93 # 'LIMIT 20,40' |
|
94 sql = "LIMIT " |
|
95 if offset and offset != 0: |
|
96 sql += "%s," % offset |
|
97 return sql + str(limit) |
|
98 |
138 |
99 def no_limit_value(self): |
139 def no_limit_value(self): |
100 # 2**64 - 1, as recommended by the MySQL documentation |
140 # 2**64 - 1, as recommended by the MySQL documentation |
101 return 18446744073709551615L |
141 return 18446744073709551615L |
102 |
142 |
129 ) for sequence in sequences]) |
169 ) for sequence in sequences]) |
130 return sql |
170 return sql |
131 else: |
171 else: |
132 return [] |
172 return [] |
133 |
173 |
|
174 def value_to_db_datetime(self, value): |
|
175 if value is None: |
|
176 return None |
|
177 |
|
178 # MySQL doesn't support tz-aware datetimes |
|
179 if value.tzinfo is not None: |
|
180 raise ValueError("MySQL backend does not support timezone-aware datetimes.") |
|
181 |
|
182 # MySQL doesn't support microseconds |
|
183 return unicode(value.replace(microsecond=0)) |
|
184 |
|
185 def value_to_db_time(self, value): |
|
186 if value is None: |
|
187 return None |
|
188 |
|
189 # MySQL doesn't support tz-aware datetimes |
|
190 if value.tzinfo is not None: |
|
191 raise ValueError("MySQL backend does not support timezone-aware datetimes.") |
|
192 |
|
193 # MySQL doesn't support microseconds |
|
194 return unicode(value.replace(microsecond=0)) |
|
195 |
|
196 def year_lookup_bounds(self, value): |
|
197 # Again, no microseconds |
|
198 first = '%s-01-01 00:00:00' |
|
199 second = '%s-12-31 23:59:59.99' |
|
200 return [first % value, second % value] |
|
201 |
134 class DatabaseWrapper(BaseDatabaseWrapper): |
202 class DatabaseWrapper(BaseDatabaseWrapper): |
135 features = DatabaseFeatures() |
203 |
136 ops = DatabaseOperations() |
|
137 operators = { |
204 operators = { |
138 'exact': '= %s', |
205 'exact': '= %s', |
139 'iexact': 'LIKE %s', |
206 'iexact': 'LIKE %s', |
140 'contains': 'LIKE BINARY %s', |
207 'contains': 'LIKE BINARY %s', |
141 'icontains': 'LIKE %s', |
208 'icontains': 'LIKE %s', |
152 } |
219 } |
153 |
220 |
154 def __init__(self, **kwargs): |
221 def __init__(self, **kwargs): |
155 super(DatabaseWrapper, self).__init__(**kwargs) |
222 super(DatabaseWrapper, self).__init__(**kwargs) |
156 self.server_version = None |
223 self.server_version = None |
|
224 |
|
225 self.features = DatabaseFeatures() |
|
226 self.ops = DatabaseOperations() |
|
227 self.client = DatabaseClient() |
|
228 self.creation = DatabaseCreation(self) |
|
229 self.introspection = DatabaseIntrospection(self) |
|
230 self.validation = DatabaseValidation() |
157 |
231 |
158 def _valid_connection(self): |
232 def _valid_connection(self): |
159 if self.connection is not None: |
233 if self.connection is not None: |
160 try: |
234 try: |
161 self.connection.ping() |
235 self.connection.ping() |
184 kwargs['host'] = settings.DATABASE_HOST |
258 kwargs['host'] = settings.DATABASE_HOST |
185 if settings.DATABASE_PORT: |
259 if settings.DATABASE_PORT: |
186 kwargs['port'] = int(settings.DATABASE_PORT) |
260 kwargs['port'] = int(settings.DATABASE_PORT) |
187 kwargs.update(self.options) |
261 kwargs.update(self.options) |
188 self.connection = Database.connect(**kwargs) |
262 self.connection = Database.connect(**kwargs) |
189 cursor = self.connection.cursor() |
263 cursor = CursorWrapper(self.connection.cursor()) |
190 return cursor |
264 return cursor |
191 |
265 |
192 def _rollback(self): |
266 def _rollback(self): |
193 try: |
267 try: |
194 BaseDatabaseWrapper._rollback(self) |
268 BaseDatabaseWrapper._rollback(self) |