PageRenderTime 47ms CodeModel.GetById 21ms RepoModel.GetById 1ms app.codeStats 0ms

/django/db/backends/mysql/base.py

https://github.com/andnils/django
Python | 567 lines | 518 code | 19 blank | 30 comment | 18 complexity | 9d8fa2de16d1eb525fe65bea6e6c1d09 MD5 | raw file
Possible License(s): BSD-3-Clause
  1. """
  2. MySQL database backend for Django.
  3. Requires MySQLdb: http://sourceforge.net/projects/mysql-python
  4. """
  5. from __future__ import unicode_literals
  6. import datetime
  7. import re
  8. import sys
  9. import warnings
  10. try:
  11. import MySQLdb as Database
  12. except ImportError as e:
  13. from django.core.exceptions import ImproperlyConfigured
  14. raise ImproperlyConfigured("Error loading MySQLdb module: %s" % e)
  15. # We want version (1, 2, 1, 'final', 2) or later. We can't just use
  16. # lexicographic ordering in this check because then (1, 2, 1, 'gamma')
  17. # inadvertently passes the version test.
  18. version = Database.version_info
  19. if (version < (1, 2, 1) or (version[:3] == (1, 2, 1) and
  20. (len(version) < 5 or version[3] != 'final' or version[4] < 2))):
  21. from django.core.exceptions import ImproperlyConfigured
  22. raise ImproperlyConfigured("MySQLdb-1.2.1p2 or newer is required; you have %s" % Database.__version__)
  23. from MySQLdb.converters import conversions, Thing2Literal
  24. from MySQLdb.constants import FIELD_TYPE, CLIENT
  25. try:
  26. import pytz
  27. except ImportError:
  28. pytz = None
  29. from django.conf import settings
  30. from django.db import utils
  31. from django.db.backends import (utils as backend_utils, BaseDatabaseFeatures,
  32. BaseDatabaseOperations, BaseDatabaseWrapper)
  33. from django.db.backends.mysql.client import DatabaseClient
  34. from django.db.backends.mysql.creation import DatabaseCreation
  35. from django.db.backends.mysql.introspection import DatabaseIntrospection
  36. from django.db.backends.mysql.validation import DatabaseValidation
  37. from django.utils.encoding import force_str, force_text
  38. from django.db.backends.mysql.schema import DatabaseSchemaEditor
  39. from django.utils.functional import cached_property
  40. from django.utils.safestring import SafeBytes, SafeText
  41. from django.utils import six
  42. from django.utils import timezone
  43. # Raise exceptions for database warnings if DEBUG is on
  44. if settings.DEBUG:
  45. warnings.filterwarnings("error", category=Database.Warning)
  46. DatabaseError = Database.DatabaseError
  47. IntegrityError = Database.IntegrityError
  48. # It's impossible to import datetime_or_None directly from MySQLdb.times
  49. parse_datetime = conversions[FIELD_TYPE.DATETIME]
  50. def parse_datetime_with_timezone_support(value):
  51. dt = parse_datetime(value)
  52. # Confirm that dt is naive before overwriting its tzinfo.
  53. if dt is not None and settings.USE_TZ and timezone.is_naive(dt):
  54. dt = dt.replace(tzinfo=timezone.utc)
  55. return dt
  56. def adapt_datetime_with_timezone_support(value, conv):
  57. # Equivalent to DateTimeField.get_db_prep_value. Used only by raw SQL.
  58. if settings.USE_TZ:
  59. if timezone.is_naive(value):
  60. warnings.warn("MySQL received a naive datetime (%s)"
  61. " while time zone support is active." % value,
  62. RuntimeWarning)
  63. default_timezone = timezone.get_default_timezone()
  64. value = timezone.make_aware(value, default_timezone)
  65. value = value.astimezone(timezone.utc).replace(tzinfo=None)
  66. return Thing2Literal(value.strftime("%Y-%m-%d %H:%M:%S"), conv)
  67. # MySQLdb-1.2.1 returns TIME columns as timedelta -- they are more like
  68. # timedelta in terms of actual behavior as they are signed and include days --
  69. # and Django expects time, so we still need to override that. We also need to
  70. # add special handling for SafeText and SafeBytes as MySQLdb's type
  71. # checking is too tight to catch those (see Django ticket #6052).
  72. # Finally, MySQLdb always returns naive datetime objects. However, when
  73. # timezone support is active, Django expects timezone-aware datetime objects.
  74. django_conversions = conversions.copy()
  75. django_conversions.update({
  76. FIELD_TYPE.TIME: backend_utils.typecast_time,
  77. FIELD_TYPE.DECIMAL: backend_utils.typecast_decimal,
  78. FIELD_TYPE.NEWDECIMAL: backend_utils.typecast_decimal,
  79. FIELD_TYPE.DATETIME: parse_datetime_with_timezone_support,
  80. datetime.datetime: adapt_datetime_with_timezone_support,
  81. })
  82. # This should match the numerical portion of the version numbers (we can treat
  83. # versions like 5.0.24 and 5.0.24a as the same). Based on the list of version
  84. # at http://dev.mysql.com/doc/refman/4.1/en/news.html and
  85. # http://dev.mysql.com/doc/refman/5.0/en/news.html .
  86. server_version_re = re.compile(r'(\d{1,2})\.(\d{1,2})\.(\d{1,2})')
  87. # MySQLdb-1.2.1 and newer automatically makes use of SHOW WARNINGS on
  88. # MySQL-4.1 and newer, so the MysqlDebugWrapper is unnecessary. Since the
  89. # point is to raise Warnings as exceptions, this can be done with the Python
  90. # warning module, and this is setup when the connection is created, and the
  91. # standard backend_utils.CursorDebugWrapper can be used. Also, using sql_mode
  92. # TRADITIONAL will automatically cause most warnings to be treated as errors.
  93. class CursorWrapper(object):
  94. """
  95. A thin wrapper around MySQLdb's normal cursor class so that we can catch
  96. particular exception instances and reraise them with the right types.
  97. Implemented as a wrapper, rather than a subclass, so that we aren't stuck
  98. to the particular underlying representation returned by Connection.cursor().
  99. """
  100. codes_for_integrityerror = (1048,)
  101. def __init__(self, cursor):
  102. self.cursor = cursor
  103. def execute(self, query, args=None):
  104. try:
  105. # args is None means no string interpolation
  106. return self.cursor.execute(query, args)
  107. except Database.OperationalError as e:
  108. # Map some error codes to IntegrityError, since they seem to be
  109. # misclassified and Django would prefer the more logical place.
  110. if e.args[0] in self.codes_for_integrityerror:
  111. six.reraise(utils.IntegrityError, utils.IntegrityError(*tuple(e.args)), sys.exc_info()[2])
  112. raise
  113. def executemany(self, query, args):
  114. try:
  115. return self.cursor.executemany(query, args)
  116. except Database.OperationalError as e:
  117. # Map some error codes to IntegrityError, since they seem to be
  118. # misclassified and Django would prefer the more logical place.
  119. if e.args[0] in self.codes_for_integrityerror:
  120. six.reraise(utils.IntegrityError, utils.IntegrityError(*tuple(e.args)), sys.exc_info()[2])
  121. raise
  122. def __getattr__(self, attr):
  123. if attr in self.__dict__:
  124. return self.__dict__[attr]
  125. else:
  126. return getattr(self.cursor, attr)
  127. def __iter__(self):
  128. return iter(self.cursor)
  129. def __enter__(self):
  130. return self
  131. def __exit__(self, type, value, traceback):
  132. # Ticket #17671 - Close instead of passing thru to avoid backend
  133. # specific behavior.
  134. self.close()
  135. class DatabaseFeatures(BaseDatabaseFeatures):
  136. empty_fetchmany_value = ()
  137. update_can_self_select = False
  138. allows_group_by_pk = True
  139. related_fields_match_type = True
  140. allow_sliced_subqueries = False
  141. has_bulk_insert = True
  142. has_select_for_update = True
  143. has_select_for_update_nowait = False
  144. supports_forward_references = False
  145. supports_long_model_names = False
  146. supports_microsecond_precision = False
  147. supports_regex_backreferencing = False
  148. supports_date_lookup_using_string = False
  149. supports_timezones = False
  150. requires_explicit_null_ordering_when_grouping = True
  151. allows_auto_pk_0 = False
  152. uses_savepoints = True
  153. atomic_transactions = False
  154. supports_check_constraints = False
  155. def __init__(self, connection):
  156. super(DatabaseFeatures, self).__init__(connection)
  157. @cached_property
  158. def _mysql_storage_engine(self):
  159. "Internal method used in Django tests. Don't rely on this from your code"
  160. with self.connection.cursor() as cursor:
  161. cursor.execute('CREATE TABLE INTROSPECT_TEST (X INT)')
  162. # This command is MySQL specific; the second column
  163. # will tell you the default table type of the created
  164. # table. Since all Django's test tables will have the same
  165. # table type, that's enough to evaluate the feature.
  166. cursor.execute("SHOW TABLE STATUS WHERE Name='INTROSPECT_TEST'")
  167. result = cursor.fetchone()
  168. cursor.execute('DROP TABLE INTROSPECT_TEST')
  169. return result[1]
  170. @cached_property
  171. def can_introspect_foreign_keys(self):
  172. "Confirm support for introspected foreign keys"
  173. return self._mysql_storage_engine != 'MyISAM'
  174. @cached_property
  175. def has_zoneinfo_database(self):
  176. # MySQL accepts full time zones names (eg. Africa/Nairobi) but rejects
  177. # abbreviations (eg. EAT). When pytz isn't installed and the current
  178. # time zone is LocalTimezone (the only sensible value in this
  179. # context), the current time zone name will be an abbreviation. As a
  180. # consequence, MySQL cannot perform time zone conversions reliably.
  181. if pytz is None:
  182. return False
  183. # Test if the time zone definitions are installed.
  184. with self.connection.cursor() as cursor:
  185. cursor.execute("SELECT 1 FROM mysql.time_zone LIMIT 1")
  186. return cursor.fetchone() is not None
  187. class DatabaseOperations(BaseDatabaseOperations):
  188. compiler_module = "django.db.backends.mysql.compiler"
  189. # MySQL stores positive fields as UNSIGNED ints.
  190. integer_field_ranges = dict(BaseDatabaseOperations.integer_field_ranges,
  191. PositiveSmallIntegerField=(0, 4294967295),
  192. PositiveIntegerField=(0, 18446744073709551615),
  193. )
  194. def date_extract_sql(self, lookup_type, field_name):
  195. # http://dev.mysql.com/doc/mysql/en/date-and-time-functions.html
  196. if lookup_type == 'week_day':
  197. # DAYOFWEEK() returns an integer, 1-7, Sunday=1.
  198. # Note: WEEKDAY() returns 0-6, Monday=0.
  199. return "DAYOFWEEK(%s)" % field_name
  200. else:
  201. return "EXTRACT(%s FROM %s)" % (lookup_type.upper(), field_name)
  202. def date_trunc_sql(self, lookup_type, field_name):
  203. fields = ['year', 'month', 'day', 'hour', 'minute', 'second']
  204. format = ('%%Y-', '%%m', '-%%d', ' %%H:', '%%i', ':%%s') # Use double percents to escape.
  205. format_def = ('0000-', '01', '-01', ' 00:', '00', ':00')
  206. try:
  207. i = fields.index(lookup_type) + 1
  208. except ValueError:
  209. sql = field_name
  210. else:
  211. format_str = ''.join([f for f in format[:i]] + [f for f in format_def[i:]])
  212. sql = "CAST(DATE_FORMAT(%s, '%s') AS DATETIME)" % (field_name, format_str)
  213. return sql
  214. def datetime_extract_sql(self, lookup_type, field_name, tzname):
  215. if settings.USE_TZ:
  216. field_name = "CONVERT_TZ(%s, 'UTC', %%s)" % field_name
  217. params = [tzname]
  218. else:
  219. params = []
  220. # http://dev.mysql.com/doc/mysql/en/date-and-time-functions.html
  221. if lookup_type == 'week_day':
  222. # DAYOFWEEK() returns an integer, 1-7, Sunday=1.
  223. # Note: WEEKDAY() returns 0-6, Monday=0.
  224. sql = "DAYOFWEEK(%s)" % field_name
  225. else:
  226. sql = "EXTRACT(%s FROM %s)" % (lookup_type.upper(), field_name)
  227. return sql, params
  228. def datetime_trunc_sql(self, lookup_type, field_name, tzname):
  229. if settings.USE_TZ:
  230. field_name = "CONVERT_TZ(%s, 'UTC', %%s)" % field_name
  231. params = [tzname]
  232. else:
  233. params = []
  234. fields = ['year', 'month', 'day', 'hour', 'minute', 'second']
  235. format = ('%%Y-', '%%m', '-%%d', ' %%H:', '%%i', ':%%s') # Use double percents to escape.
  236. format_def = ('0000-', '01', '-01', ' 00:', '00', ':00')
  237. try:
  238. i = fields.index(lookup_type) + 1
  239. except ValueError:
  240. sql = field_name
  241. else:
  242. format_str = ''.join([f for f in format[:i]] + [f for f in format_def[i:]])
  243. sql = "CAST(DATE_FORMAT(%s, '%s') AS DATETIME)" % (field_name, format_str)
  244. return sql, params
  245. def date_interval_sql(self, sql, connector, timedelta):
  246. return "(%s %s INTERVAL '%d 0:0:%d:%d' DAY_MICROSECOND)" % (sql, connector,
  247. timedelta.days, timedelta.seconds, timedelta.microseconds)
  248. def drop_foreignkey_sql(self):
  249. return "DROP FOREIGN KEY"
  250. def force_no_ordering(self):
  251. """
  252. "ORDER BY NULL" prevents MySQL from implicitly ordering by grouped
  253. columns. If no ordering would otherwise be applied, we don't want any
  254. implicit sorting going on.
  255. """
  256. return ["NULL"]
  257. def fulltext_search_sql(self, field_name):
  258. return 'MATCH (%s) AGAINST (%%s IN BOOLEAN MODE)' % field_name
  259. def last_executed_query(self, cursor, sql, params):
  260. # With MySQLdb, cursor objects have an (undocumented) "_last_executed"
  261. # attribute where the exact query sent to the database is saved.
  262. # See MySQLdb/cursors.py in the source distribution.
  263. return force_text(getattr(cursor, '_last_executed', None), errors='replace')
  264. def no_limit_value(self):
  265. # 2**64 - 1, as recommended by the MySQL documentation
  266. return 18446744073709551615
  267. def quote_name(self, name):
  268. if name.startswith("`") and name.endswith("`"):
  269. return name # Quoting once is enough.
  270. return "`%s`" % name
  271. def random_function_sql(self):
  272. return 'RAND()'
  273. def sql_flush(self, style, tables, sequences, allow_cascade=False):
  274. # NB: The generated SQL below is specific to MySQL
  275. # 'TRUNCATE x;', 'TRUNCATE y;', 'TRUNCATE z;'... style SQL statements
  276. # to clear all tables of all data
  277. if tables:
  278. sql = ['SET FOREIGN_KEY_CHECKS = 0;']
  279. for table in tables:
  280. sql.append('%s %s;' % (
  281. style.SQL_KEYWORD('TRUNCATE'),
  282. style.SQL_FIELD(self.quote_name(table)),
  283. ))
  284. sql.append('SET FOREIGN_KEY_CHECKS = 1;')
  285. sql.extend(self.sequence_reset_by_name_sql(style, sequences))
  286. return sql
  287. else:
  288. return []
  289. def sequence_reset_by_name_sql(self, style, sequences):
  290. # Truncate already resets the AUTO_INCREMENT field from
  291. # MySQL version 5.0.13 onwards. Refs #16961.
  292. if self.connection.mysql_version < (5, 0, 13):
  293. return [
  294. "%s %s %s %s %s;" % (
  295. style.SQL_KEYWORD('ALTER'),
  296. style.SQL_KEYWORD('TABLE'),
  297. style.SQL_TABLE(self.quote_name(sequence['table'])),
  298. style.SQL_KEYWORD('AUTO_INCREMENT'),
  299. style.SQL_FIELD('= 1'),
  300. ) for sequence in sequences
  301. ]
  302. else:
  303. return []
  304. def validate_autopk_value(self, value):
  305. # MySQLism: zero in AUTO_INCREMENT field does not work. Refs #17653.
  306. if value == 0:
  307. raise ValueError('The database backend does not accept 0 as a '
  308. 'value for AutoField.')
  309. return value
  310. def value_to_db_datetime(self, value):
  311. if value is None:
  312. return None
  313. # MySQL doesn't support tz-aware datetimes
  314. if timezone.is_aware(value):
  315. if settings.USE_TZ:
  316. value = value.astimezone(timezone.utc).replace(tzinfo=None)
  317. else:
  318. raise ValueError("MySQL backend does not support timezone-aware datetimes when USE_TZ is False.")
  319. # MySQL doesn't support microseconds
  320. return six.text_type(value.replace(microsecond=0))
  321. def value_to_db_time(self, value):
  322. if value is None:
  323. return None
  324. # MySQL doesn't support tz-aware times
  325. if timezone.is_aware(value):
  326. raise ValueError("MySQL backend does not support timezone-aware times.")
  327. # MySQL doesn't support microseconds
  328. return six.text_type(value.replace(microsecond=0))
  329. def year_lookup_bounds_for_datetime_field(self, value):
  330. # Again, no microseconds
  331. first, second = super(DatabaseOperations, self).year_lookup_bounds_for_datetime_field(value)
  332. return [first.replace(microsecond=0), second.replace(microsecond=0)]
  333. def max_name_length(self):
  334. return 64
  335. def bulk_insert_sql(self, fields, num_values):
  336. items_sql = "(%s)" % ", ".join(["%s"] * len(fields))
  337. return "VALUES " + ", ".join([items_sql] * num_values)
  338. def combine_expression(self, connector, sub_expressions):
  339. """
  340. MySQL requires special cases for ^ operators in query expressions
  341. """
  342. if connector == '^':
  343. return 'POW(%s)' % ','.join(sub_expressions)
  344. return super(DatabaseOperations, self).combine_expression(connector, sub_expressions)
  345. class DatabaseWrapper(BaseDatabaseWrapper):
  346. vendor = 'mysql'
  347. operators = {
  348. 'exact': '= %s',
  349. 'iexact': 'LIKE %s',
  350. 'contains': 'LIKE BINARY %s',
  351. 'icontains': 'LIKE %s',
  352. 'regex': 'REGEXP BINARY %s',
  353. 'iregex': 'REGEXP %s',
  354. 'gt': '> %s',
  355. 'gte': '>= %s',
  356. 'lt': '< %s',
  357. 'lte': '<= %s',
  358. 'startswith': 'LIKE BINARY %s',
  359. 'endswith': 'LIKE BINARY %s',
  360. 'istartswith': 'LIKE %s',
  361. 'iendswith': 'LIKE %s',
  362. }
  363. Database = Database
  364. def __init__(self, *args, **kwargs):
  365. super(DatabaseWrapper, self).__init__(*args, **kwargs)
  366. self.features = DatabaseFeatures(self)
  367. self.ops = DatabaseOperations(self)
  368. self.client = DatabaseClient(self)
  369. self.creation = DatabaseCreation(self)
  370. self.introspection = DatabaseIntrospection(self)
  371. self.validation = DatabaseValidation(self)
  372. def get_connection_params(self):
  373. kwargs = {
  374. 'conv': django_conversions,
  375. 'charset': 'utf8',
  376. }
  377. if six.PY2:
  378. kwargs['use_unicode'] = True
  379. settings_dict = self.settings_dict
  380. if settings_dict['USER']:
  381. kwargs['user'] = settings_dict['USER']
  382. if settings_dict['NAME']:
  383. kwargs['db'] = settings_dict['NAME']
  384. if settings_dict['PASSWORD']:
  385. kwargs['passwd'] = force_str(settings_dict['PASSWORD'])
  386. if settings_dict['HOST'].startswith('/'):
  387. kwargs['unix_socket'] = settings_dict['HOST']
  388. elif settings_dict['HOST']:
  389. kwargs['host'] = settings_dict['HOST']
  390. if settings_dict['PORT']:
  391. kwargs['port'] = int(settings_dict['PORT'])
  392. # We need the number of potentially affected rows after an
  393. # "UPDATE", not the number of changed rows.
  394. kwargs['client_flag'] = CLIENT.FOUND_ROWS
  395. kwargs.update(settings_dict['OPTIONS'])
  396. return kwargs
  397. def get_new_connection(self, conn_params):
  398. conn = Database.connect(**conn_params)
  399. conn.encoders[SafeText] = conn.encoders[six.text_type]
  400. conn.encoders[SafeBytes] = conn.encoders[bytes]
  401. return conn
  402. def init_connection_state(self):
  403. with self.cursor() as cursor:
  404. # SQL_AUTO_IS_NULL in MySQL controls whether an AUTO_INCREMENT column
  405. # on a recently-inserted row will return when the field is tested for
  406. # NULL. Disabling this value brings this aspect of MySQL in line with
  407. # SQL standards.
  408. cursor.execute('SET SQL_AUTO_IS_NULL = 0')
  409. def create_cursor(self):
  410. cursor = self.connection.cursor()
  411. return CursorWrapper(cursor)
  412. def _rollback(self):
  413. try:
  414. BaseDatabaseWrapper._rollback(self)
  415. except Database.NotSupportedError:
  416. pass
  417. def _set_autocommit(self, autocommit):
  418. self.connection.autocommit(autocommit)
  419. def disable_constraint_checking(self):
  420. """
  421. Disables foreign key checks, primarily for use in adding rows with forward references. Always returns True,
  422. to indicate constraint checks need to be re-enabled.
  423. """
  424. self.cursor().execute('SET foreign_key_checks=0')
  425. return True
  426. def enable_constraint_checking(self):
  427. """
  428. Re-enable foreign key checks after they have been disabled.
  429. """
  430. # Override needs_rollback in case constraint_checks_disabled is
  431. # nested inside transaction.atomic.
  432. self.needs_rollback, needs_rollback = False, self.needs_rollback
  433. try:
  434. self.cursor().execute('SET foreign_key_checks=1')
  435. finally:
  436. self.needs_rollback = needs_rollback
  437. def check_constraints(self, table_names=None):
  438. """
  439. Checks each table name in `table_names` for rows with invalid foreign key references. This method is
  440. intended to be used in conjunction with `disable_constraint_checking()` and `enable_constraint_checking()`, to
  441. determine if rows with invalid references were entered while constraint checks were off.
  442. Raises an IntegrityError on the first invalid foreign key reference encountered (if any) and provides
  443. detailed information about the invalid reference in the error message.
  444. Backends can override this method if they can more directly apply constraint checking (e.g. via "SET CONSTRAINTS
  445. ALL IMMEDIATE")
  446. """
  447. cursor = self.cursor()
  448. if table_names is None:
  449. table_names = self.introspection.table_names(cursor)
  450. for table_name in table_names:
  451. primary_key_column_name = self.introspection.get_primary_key_column(cursor, table_name)
  452. if not primary_key_column_name:
  453. continue
  454. key_columns = self.introspection.get_key_columns(cursor, table_name)
  455. for column_name, referenced_table_name, referenced_column_name in key_columns:
  456. cursor.execute("""
  457. SELECT REFERRING.`%s`, REFERRING.`%s` FROM `%s` as REFERRING
  458. LEFT JOIN `%s` as REFERRED
  459. ON (REFERRING.`%s` = REFERRED.`%s`)
  460. WHERE REFERRING.`%s` IS NOT NULL AND REFERRED.`%s` IS NULL"""
  461. % (primary_key_column_name, column_name, table_name, referenced_table_name,
  462. column_name, referenced_column_name, column_name, referenced_column_name))
  463. for bad_row in cursor.fetchall():
  464. raise utils.IntegrityError("The row in table '%s' with primary key '%s' has an invalid "
  465. "foreign key: %s.%s contains a value '%s' that does not have a corresponding value in %s.%s."
  466. % (table_name, bad_row[0],
  467. table_name, column_name, bad_row[1],
  468. referenced_table_name, referenced_column_name))
  469. def schema_editor(self, *args, **kwargs):
  470. "Returns a new instance of this backend's SchemaEditor"
  471. return DatabaseSchemaEditor(self, *args, **kwargs)
  472. def is_usable(self):
  473. try:
  474. self.connection.ping()
  475. except DatabaseError:
  476. return False
  477. else:
  478. return True
  479. @cached_property
  480. def mysql_version(self):
  481. with self.temporary_connection():
  482. server_info = self.connection.get_server_info()
  483. match = server_version_re.match(server_info)
  484. if not match:
  485. raise Exception('Unable to determine MySQL version from version string %r' % server_info)
  486. return tuple(int(x) for x in match.groups())