base.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376
  1. """
  2. MySQL database backend for Django.
  3. Requires mysqlclient: https://pypi.python.org/pypi/mysqlclient/
  4. MySQLdb is supported for Python 2 only: http://sourceforge.net/projects/mysql-python
  5. """
  6. from __future__ import unicode_literals
  7. import datetime
  8. import re
  9. import sys
  10. import warnings
  11. from django.conf import settings
  12. from django.db import utils
  13. from django.db.backends import utils as backend_utils
  14. from django.db.backends.base.base import BaseDatabaseWrapper
  15. from django.utils import six, timezone
  16. from django.utils.encoding import force_str
  17. from django.utils.functional import cached_property
  18. from django.utils.safestring import SafeBytes, SafeText
  19. try:
  20. import MySQLdb as Database
  21. except ImportError as e:
  22. from django.core.exceptions import ImproperlyConfigured
  23. raise ImproperlyConfigured("Error loading MySQLdb module: %s" % e)
  24. from MySQLdb.constants import CLIENT, FIELD_TYPE # isort:skip
  25. from MySQLdb.converters import Thing2Literal, conversions # isort:skip
  26. # Some of these import MySQLdb, so import them after checking if it's installed.
  27. from .client import DatabaseClient # isort:skip
  28. from .creation import DatabaseCreation # isort:skip
  29. from .features import DatabaseFeatures # isort:skip
  30. from .introspection import DatabaseIntrospection # isort:skip
  31. from .operations import DatabaseOperations # isort:skip
  32. from .schema import DatabaseSchemaEditor # isort:skip
  33. from .validation import DatabaseValidation # isort:skip
  34. # We want version (1, 2, 1, 'final', 2) or later. We can't just use
  35. # lexicographic ordering in this check because then (1, 2, 1, 'gamma')
  36. # inadvertently passes the version test.
  37. version = Database.version_info
  38. if (version < (1, 2, 1) or (version[:3] == (1, 2, 1) and
  39. (len(version) < 5 or version[3] != 'final' or version[4] < 2))):
  40. from django.core.exceptions import ImproperlyConfigured
  41. raise ImproperlyConfigured("MySQLdb-1.2.1p2 or newer is required; you have %s" % Database.__version__)
  42. DatabaseError = Database.DatabaseError
  43. IntegrityError = Database.IntegrityError
  44. # It's impossible to import datetime_or_None directly from MySQLdb.times
  45. parse_datetime = conversions[FIELD_TYPE.DATETIME]
  46. def parse_datetime_with_timezone_support(value):
  47. dt = parse_datetime(value)
  48. # Confirm that dt is naive before overwriting its tzinfo.
  49. if dt is not None and settings.USE_TZ and timezone.is_naive(dt):
  50. dt = dt.replace(tzinfo=timezone.utc)
  51. return dt
  52. def adapt_datetime_with_timezone_support(value, conv):
  53. # Equivalent to DateTimeField.get_db_prep_value. Used only by raw SQL.
  54. if settings.USE_TZ:
  55. if timezone.is_naive(value):
  56. warnings.warn("MySQL received a naive datetime (%s)"
  57. " while time zone support is active." % value,
  58. RuntimeWarning)
  59. default_timezone = timezone.get_default_timezone()
  60. value = timezone.make_aware(value, default_timezone)
  61. value = value.astimezone(timezone.utc).replace(tzinfo=None)
  62. return Thing2Literal(value.strftime("%Y-%m-%d %H:%M:%S.%f"), conv)
  63. # MySQLdb-1.2.1 returns TIME columns as timedelta -- they are more like
  64. # timedelta in terms of actual behavior as they are signed and include days --
  65. # and Django expects time, so we still need to override that. We also need to
  66. # add special handling for SafeText and SafeBytes as MySQLdb's type
  67. # checking is too tight to catch those (see Django ticket #6052).
  68. # Finally, MySQLdb always returns naive datetime objects. However, when
  69. # timezone support is active, Django expects timezone-aware datetime objects.
  70. django_conversions = conversions.copy()
  71. django_conversions.update({
  72. FIELD_TYPE.TIME: backend_utils.typecast_time,
  73. FIELD_TYPE.DECIMAL: backend_utils.typecast_decimal,
  74. FIELD_TYPE.NEWDECIMAL: backend_utils.typecast_decimal,
  75. FIELD_TYPE.DATETIME: parse_datetime_with_timezone_support,
  76. datetime.datetime: adapt_datetime_with_timezone_support,
  77. })
  78. # This should match the numerical portion of the version numbers (we can treat
  79. # versions like 5.0.24 and 5.0.24a as the same). Based on the list of version
  80. # at http://dev.mysql.com/doc/refman/4.1/en/news.html and
  81. # http://dev.mysql.com/doc/refman/5.0/en/news.html .
  82. server_version_re = re.compile(r'(\d{1,2})\.(\d{1,2})\.(\d{1,2})')
  83. # MySQLdb-1.2.1 and newer automatically makes use of SHOW WARNINGS on
  84. # MySQL-4.1 and newer, so the MysqlDebugWrapper is unnecessary. Since the
  85. # point is to raise Warnings as exceptions, this can be done with the Python
  86. # warning module, and this is setup when the connection is created, and the
  87. # standard backend_utils.CursorDebugWrapper can be used. Also, using sql_mode
  88. # TRADITIONAL will automatically cause most warnings to be treated as errors.
  89. class CursorWrapper(object):
  90. """
  91. A thin wrapper around MySQLdb's normal cursor class so that we can catch
  92. particular exception instances and reraise them with the right types.
  93. Implemented as a wrapper, rather than a subclass, so that we aren't stuck
  94. to the particular underlying representation returned by Connection.cursor().
  95. """
  96. codes_for_integrityerror = (1048,)
  97. def __init__(self, cursor):
  98. self.cursor = cursor
  99. def execute(self, query, args=None):
  100. try:
  101. # args is None means no string interpolation
  102. return self.cursor.execute(query, args)
  103. except Database.OperationalError as 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.args[0] in self.codes_for_integrityerror:
  107. six.reraise(utils.IntegrityError, utils.IntegrityError(*tuple(e.args)), sys.exc_info()[2])
  108. raise
  109. def executemany(self, query, args):
  110. try:
  111. return self.cursor.executemany(query, args)
  112. except Database.OperationalError as e:
  113. # Map some error codes to IntegrityError, since they seem to be
  114. # misclassified and Django would prefer the more logical place.
  115. if e.args[0] in self.codes_for_integrityerror:
  116. six.reraise(utils.IntegrityError, utils.IntegrityError(*tuple(e.args)), sys.exc_info()[2])
  117. raise
  118. def __getattr__(self, attr):
  119. if attr in self.__dict__:
  120. return self.__dict__[attr]
  121. else:
  122. return getattr(self.cursor, attr)
  123. def __iter__(self):
  124. return iter(self.cursor)
  125. def __enter__(self):
  126. return self
  127. def __exit__(self, type, value, traceback):
  128. # Ticket #17671 - Close instead of passing thru to avoid backend
  129. # specific behavior.
  130. self.close()
  131. class DatabaseWrapper(BaseDatabaseWrapper):
  132. vendor = 'mysql'
  133. # This dictionary maps Field objects to their associated MySQL column
  134. # types, as strings. Column-type strings can contain format strings; they'll
  135. # be interpolated against the values of Field.__dict__ before being output.
  136. # If a column type is set to None, it won't be included in the output.
  137. _data_types = {
  138. 'AutoField': 'integer AUTO_INCREMENT',
  139. 'BinaryField': 'longblob',
  140. 'BooleanField': 'bool',
  141. 'CharField': 'varchar(%(max_length)s)',
  142. 'CommaSeparatedIntegerField': 'varchar(%(max_length)s)',
  143. 'DateField': 'date',
  144. 'DateTimeField': 'datetime',
  145. 'DecimalField': 'numeric(%(max_digits)s, %(decimal_places)s)',
  146. 'DurationField': 'bigint',
  147. 'FileField': 'varchar(%(max_length)s)',
  148. 'FilePathField': 'varchar(%(max_length)s)',
  149. 'FloatField': 'double precision',
  150. 'IntegerField': 'integer',
  151. 'BigIntegerField': 'bigint',
  152. 'IPAddressField': 'char(15)',
  153. 'GenericIPAddressField': 'char(39)',
  154. 'NullBooleanField': 'bool',
  155. 'OneToOneField': 'integer',
  156. 'PositiveIntegerField': 'integer UNSIGNED',
  157. 'PositiveSmallIntegerField': 'smallint UNSIGNED',
  158. 'SlugField': 'varchar(%(max_length)s)',
  159. 'SmallIntegerField': 'smallint',
  160. 'TextField': 'longtext',
  161. 'TimeField': 'time',
  162. 'UUIDField': 'char(32)',
  163. }
  164. @cached_property
  165. def data_types(self):
  166. if self.features.supports_microsecond_precision:
  167. return dict(self._data_types, DateTimeField='datetime(6)', TimeField='time(6)')
  168. else:
  169. return self._data_types
  170. operators = {
  171. 'exact': '= %s',
  172. 'iexact': 'LIKE %s',
  173. 'contains': 'LIKE BINARY %s',
  174. 'icontains': 'LIKE %s',
  175. 'regex': 'REGEXP BINARY %s',
  176. 'iregex': 'REGEXP %s',
  177. 'gt': '> %s',
  178. 'gte': '>= %s',
  179. 'lt': '< %s',
  180. 'lte': '<= %s',
  181. 'startswith': 'LIKE BINARY %s',
  182. 'endswith': 'LIKE BINARY %s',
  183. 'istartswith': 'LIKE %s',
  184. 'iendswith': 'LIKE %s',
  185. }
  186. # The patterns below are used to generate SQL pattern lookup clauses when
  187. # the right-hand side of the lookup isn't a raw string (it might be an expression
  188. # or the result of a bilateral transformation).
  189. # In those cases, special characters for LIKE operators (e.g. \, *, _) should be
  190. # escaped on database side.
  191. #
  192. # Note: we use str.format() here for readability as '%' is used as a wildcard for
  193. # the LIKE operator.
  194. pattern_esc = r"REPLACE(REPLACE(REPLACE({}, '\\', '\\\\'), '%%', '\%%'), '_', '\_')"
  195. pattern_ops = {
  196. 'contains': "LIKE BINARY CONCAT('%%', {}, '%%')",
  197. 'icontains': "LIKE CONCAT('%%', {}, '%%')",
  198. 'startswith': "LIKE BINARY CONCAT({}, '%%')",
  199. 'istartswith': "LIKE CONCAT({}, '%%')",
  200. 'endswith': "LIKE BINARY CONCAT('%%', {})",
  201. 'iendswith': "LIKE CONCAT('%%', {})",
  202. }
  203. Database = Database
  204. SchemaEditorClass = DatabaseSchemaEditor
  205. def __init__(self, *args, **kwargs):
  206. super(DatabaseWrapper, self).__init__(*args, **kwargs)
  207. self.features = DatabaseFeatures(self)
  208. self.ops = DatabaseOperations(self)
  209. self.client = DatabaseClient(self)
  210. self.creation = DatabaseCreation(self)
  211. self.introspection = DatabaseIntrospection(self)
  212. self.validation = DatabaseValidation(self)
  213. def get_connection_params(self):
  214. kwargs = {
  215. 'conv': django_conversions,
  216. 'charset': 'utf8',
  217. }
  218. if six.PY2:
  219. kwargs['use_unicode'] = True
  220. settings_dict = self.settings_dict
  221. if settings_dict['USER']:
  222. kwargs['user'] = settings_dict['USER']
  223. if settings_dict['NAME']:
  224. kwargs['db'] = settings_dict['NAME']
  225. if settings_dict['PASSWORD']:
  226. kwargs['passwd'] = force_str(settings_dict['PASSWORD'])
  227. if settings_dict['HOST'].startswith('/'):
  228. kwargs['unix_socket'] = settings_dict['HOST']
  229. elif settings_dict['HOST']:
  230. kwargs['host'] = settings_dict['HOST']
  231. if settings_dict['PORT']:
  232. kwargs['port'] = int(settings_dict['PORT'])
  233. # We need the number of potentially affected rows after an
  234. # "UPDATE", not the number of changed rows.
  235. kwargs['client_flag'] = CLIENT.FOUND_ROWS
  236. kwargs.update(settings_dict['OPTIONS'])
  237. return kwargs
  238. def get_new_connection(self, conn_params):
  239. conn = Database.connect(**conn_params)
  240. conn.encoders[SafeText] = conn.encoders[six.text_type]
  241. conn.encoders[SafeBytes] = conn.encoders[bytes]
  242. return conn
  243. def init_connection_state(self):
  244. with self.cursor() as cursor:
  245. # SQL_AUTO_IS_NULL in MySQL controls whether an AUTO_INCREMENT column
  246. # on a recently-inserted row will return when the field is tested for
  247. # NULL. Disabling this value brings this aspect of MySQL in line with
  248. # SQL standards.
  249. cursor.execute('SET SQL_AUTO_IS_NULL = 0')
  250. def create_cursor(self):
  251. cursor = self.connection.cursor()
  252. return CursorWrapper(cursor)
  253. def _rollback(self):
  254. try:
  255. BaseDatabaseWrapper._rollback(self)
  256. except Database.NotSupportedError:
  257. pass
  258. def _set_autocommit(self, autocommit):
  259. with self.wrap_database_errors:
  260. self.connection.autocommit(autocommit)
  261. def disable_constraint_checking(self):
  262. """
  263. Disables foreign key checks, primarily for use in adding rows with forward references. Always returns True,
  264. to indicate constraint checks need to be re-enabled.
  265. """
  266. self.cursor().execute('SET foreign_key_checks=0')
  267. return True
  268. def enable_constraint_checking(self):
  269. """
  270. Re-enable foreign key checks after they have been disabled.
  271. """
  272. # Override needs_rollback in case constraint_checks_disabled is
  273. # nested inside transaction.atomic.
  274. self.needs_rollback, needs_rollback = False, self.needs_rollback
  275. try:
  276. self.cursor().execute('SET foreign_key_checks=1')
  277. finally:
  278. self.needs_rollback = needs_rollback
  279. def check_constraints(self, table_names=None):
  280. """
  281. Checks each table name in `table_names` for rows with invalid foreign
  282. key references. This method is intended to be used in conjunction with
  283. `disable_constraint_checking()` and `enable_constraint_checking()`, to
  284. determine if rows with invalid references were entered while constraint
  285. checks were off.
  286. Raises an IntegrityError on the first invalid foreign key reference
  287. encountered (if any) and provides detailed information about the
  288. invalid reference in the error message.
  289. Backends can override this method if they can more directly apply
  290. constraint checking (e.g. via "SET CONSTRAINTS ALL IMMEDIATE")
  291. """
  292. cursor = self.cursor()
  293. if table_names is None:
  294. table_names = self.introspection.table_names(cursor)
  295. for table_name in table_names:
  296. primary_key_column_name = self.introspection.get_primary_key_column(cursor, table_name)
  297. if not primary_key_column_name:
  298. continue
  299. key_columns = self.introspection.get_key_columns(cursor, table_name)
  300. for column_name, referenced_table_name, referenced_column_name in key_columns:
  301. cursor.execute("""
  302. SELECT REFERRING.`%s`, REFERRING.`%s` FROM `%s` as REFERRING
  303. LEFT JOIN `%s` as REFERRED
  304. ON (REFERRING.`%s` = REFERRED.`%s`)
  305. WHERE REFERRING.`%s` IS NOT NULL AND REFERRED.`%s` IS NULL"""
  306. % (primary_key_column_name, column_name, table_name, referenced_table_name,
  307. column_name, referenced_column_name, column_name, referenced_column_name))
  308. for bad_row in cursor.fetchall():
  309. raise utils.IntegrityError("The row in table '%s' with primary key '%s' has an invalid "
  310. "foreign key: %s.%s contains a value '%s' that does not have a corresponding value in %s.%s."
  311. % (table_name, bad_row[0],
  312. table_name, column_name, bad_row[1],
  313. referenced_table_name, referenced_column_name))
  314. def is_usable(self):
  315. try:
  316. self.connection.ping()
  317. except Database.Error:
  318. return False
  319. else:
  320. return True
  321. @cached_property
  322. def mysql_version(self):
  323. with self.temporary_connection():
  324. server_info = self.connection.get_server_info()
  325. match = server_version_re.match(server_info)
  326. if not match:
  327. raise Exception('Unable to determine MySQL version from version string %r' % server_info)
  328. return tuple(int(x) for x in match.groups())