PageRenderTime 53ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

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

https://github.com/niran/django-old
Python | 326 lines | 288 code | 12 blank | 26 comment | 9 complexity | 61e73d3e180bcc44ac0e3c06e6bfc766 MD5 | raw file
  1. """
  2. MySQL database backend for Django.
  3. Requires MySQLdb: http://sourceforge.net/projects/mysql-python
  4. """
  5. import re
  6. import sys
  7. try:
  8. import MySQLdb as Database
  9. except ImportError, e:
  10. from django.core.exceptions import ImproperlyConfigured
  11. raise ImproperlyConfigured("Error loading MySQLdb module: %s" % e)
  12. # We want version (1, 2, 1, 'final', 2) or later. We can't just use
  13. # lexicographic ordering in this check because then (1, 2, 1, 'gamma')
  14. # inadvertently passes the version test.
  15. version = Database.version_info
  16. if (version < (1,2,1) or (version[:3] == (1, 2, 1) and
  17. (len(version) < 5 or version[3] != 'final' or version[4] < 2))):
  18. from django.core.exceptions import ImproperlyConfigured
  19. raise ImproperlyConfigured("MySQLdb-1.2.1p2 or newer is required; you have %s" % Database.__version__)
  20. from MySQLdb.converters import conversions
  21. from MySQLdb.constants import FIELD_TYPE, FLAG, CLIENT
  22. from django.db import utils
  23. from django.db.backends import *
  24. from django.db.backends.signals import connection_created
  25. from django.db.backends.mysql.client import DatabaseClient
  26. from django.db.backends.mysql.creation import DatabaseCreation
  27. from django.db.backends.mysql.introspection import DatabaseIntrospection
  28. from django.db.backends.mysql.validation import DatabaseValidation
  29. from django.utils.safestring import SafeString, SafeUnicode
  30. # Raise exceptions for database warnings if DEBUG is on
  31. from django.conf import settings
  32. if settings.DEBUG:
  33. from warnings import filterwarnings
  34. filterwarnings("error", category=Database.Warning)
  35. DatabaseError = Database.DatabaseError
  36. IntegrityError = Database.IntegrityError
  37. # MySQLdb-1.2.1 returns TIME columns as timedelta -- they are more like
  38. # timedelta in terms of actual behavior as they are signed and include days --
  39. # and Django expects time, so we still need to override that. We also need to
  40. # add special handling for SafeUnicode and SafeString as MySQLdb's type
  41. # checking is too tight to catch those (see Django ticket #6052).
  42. django_conversions = conversions.copy()
  43. django_conversions.update({
  44. FIELD_TYPE.TIME: util.typecast_time,
  45. FIELD_TYPE.DECIMAL: util.typecast_decimal,
  46. FIELD_TYPE.NEWDECIMAL: util.typecast_decimal,
  47. })
  48. # This should match the numerical portion of the version numbers (we can treat
  49. # versions like 5.0.24 and 5.0.24a as the same). Based on the list of version
  50. # at http://dev.mysql.com/doc/refman/4.1/en/news.html and
  51. # http://dev.mysql.com/doc/refman/5.0/en/news.html .
  52. server_version_re = re.compile(r'(\d{1,2})\.(\d{1,2})\.(\d{1,2})')
  53. # MySQLdb-1.2.1 and newer automatically makes use of SHOW WARNINGS on
  54. # MySQL-4.1 and newer, so the MysqlDebugWrapper is unnecessary. Since the
  55. # point is to raise Warnings as exceptions, this can be done with the Python
  56. # warning module, and this is setup when the connection is created, and the
  57. # standard util.CursorDebugWrapper can be used. Also, using sql_mode
  58. # TRADITIONAL will automatically cause most warnings to be treated as errors.
  59. class CursorWrapper(object):
  60. """
  61. A thin wrapper around MySQLdb's normal cursor class so that we can catch
  62. particular exception instances and reraise them with the right types.
  63. Implemented as a wrapper, rather than a subclass, so that we aren't stuck
  64. to the particular underlying representation returned by Connection.cursor().
  65. """
  66. codes_for_integrityerror = (1048,)
  67. def __init__(self, cursor):
  68. self.cursor = cursor
  69. def execute(self, query, args=None):
  70. try:
  71. return self.cursor.execute(query, args)
  72. except Database.IntegrityError, e:
  73. raise utils.IntegrityError, utils.IntegrityError(*tuple(e)), sys.exc_info()[2]
  74. except Database.OperationalError, e:
  75. # Map some error codes to IntegrityError, since they seem to be
  76. # misclassified and Django would prefer the more logical place.
  77. if e[0] in self.codes_for_integrityerror:
  78. raise utils.IntegrityError, utils.IntegrityError(*tuple(e)), sys.exc_info()[2]
  79. raise
  80. except Database.DatabaseError, e:
  81. raise utils.DatabaseError, utils.DatabaseError(*tuple(e)), sys.exc_info()[2]
  82. def executemany(self, query, args):
  83. try:
  84. return self.cursor.executemany(query, args)
  85. except Database.IntegrityError, e:
  86. raise utils.IntegrityError, utils.IntegrityError(*tuple(e)), sys.exc_info()[2]
  87. except Database.OperationalError, e:
  88. # Map some error codes to IntegrityError, since they seem to be
  89. # misclassified and Django would prefer the more logical place.
  90. if e[0] in self.codes_for_integrityerror:
  91. raise utils.IntegrityError, utils.IntegrityError(*tuple(e)), sys.exc_info()[2]
  92. raise
  93. except Database.DatabaseError, e:
  94. raise utils.DatabaseError, utils.DatabaseError(*tuple(e)), sys.exc_info()[2]
  95. def __getattr__(self, attr):
  96. if attr in self.__dict__:
  97. return self.__dict__[attr]
  98. else:
  99. return getattr(self.cursor, attr)
  100. def __iter__(self):
  101. return iter(self.cursor)
  102. class DatabaseFeatures(BaseDatabaseFeatures):
  103. empty_fetchmany_value = ()
  104. update_can_self_select = False
  105. allows_group_by_pk = True
  106. related_fields_match_type = True
  107. allow_sliced_subqueries = False
  108. supports_forward_references = False
  109. supports_long_model_names = False
  110. supports_microsecond_precision = False
  111. supports_regex_backreferencing = False
  112. supports_date_lookup_using_string = False
  113. supports_timezones = False
  114. requires_explicit_null_ordering_when_grouping = True
  115. allows_primary_key_0 = False
  116. class DatabaseOperations(BaseDatabaseOperations):
  117. compiler_module = "django.db.backends.mysql.compiler"
  118. def date_extract_sql(self, lookup_type, field_name):
  119. # http://dev.mysql.com/doc/mysql/en/date-and-time-functions.html
  120. if lookup_type == 'week_day':
  121. # DAYOFWEEK() returns an integer, 1-7, Sunday=1.
  122. # Note: WEEKDAY() returns 0-6, Monday=0.
  123. return "DAYOFWEEK(%s)" % field_name
  124. else:
  125. return "EXTRACT(%s FROM %s)" % (lookup_type.upper(), field_name)
  126. def date_trunc_sql(self, lookup_type, field_name):
  127. fields = ['year', 'month', 'day', 'hour', 'minute', 'second']
  128. format = ('%%Y-', '%%m', '-%%d', ' %%H:', '%%i', ':%%s') # Use double percents to escape.
  129. format_def = ('0000-', '01', '-01', ' 00:', '00', ':00')
  130. try:
  131. i = fields.index(lookup_type) + 1
  132. except ValueError:
  133. sql = field_name
  134. else:
  135. format_str = ''.join([f for f in format[:i]] + [f for f in format_def[i:]])
  136. sql = "CAST(DATE_FORMAT(%s, '%s') AS DATETIME)" % (field_name, format_str)
  137. return sql
  138. def drop_foreignkey_sql(self):
  139. return "DROP FOREIGN KEY"
  140. def force_no_ordering(self):
  141. """
  142. "ORDER BY NULL" prevents MySQL from implicitly ordering by grouped
  143. columns. If no ordering would otherwise be applied, we don't want any
  144. implicit sorting going on.
  145. """
  146. return ["NULL"]
  147. def fulltext_search_sql(self, field_name):
  148. return 'MATCH (%s) AGAINST (%%s IN BOOLEAN MODE)' % field_name
  149. def no_limit_value(self):
  150. # 2**64 - 1, as recommended by the MySQL documentation
  151. return 18446744073709551615L
  152. def quote_name(self, name):
  153. if name.startswith("`") and name.endswith("`"):
  154. return name # Quoting once is enough.
  155. return "`%s`" % name
  156. def random_function_sql(self):
  157. return 'RAND()'
  158. def sql_flush(self, style, tables, sequences):
  159. # NB: The generated SQL below is specific to MySQL
  160. # 'TRUNCATE x;', 'TRUNCATE y;', 'TRUNCATE z;'... style SQL statements
  161. # to clear all tables of all data
  162. if tables:
  163. sql = ['SET FOREIGN_KEY_CHECKS = 0;']
  164. for table in tables:
  165. sql.append('%s %s;' % (style.SQL_KEYWORD('TRUNCATE'), style.SQL_FIELD(self.quote_name(table))))
  166. sql.append('SET FOREIGN_KEY_CHECKS = 1;')
  167. # 'ALTER TABLE table AUTO_INCREMENT = 1;'... style SQL statements
  168. # to reset sequence indices
  169. sql.extend(["%s %s %s %s %s;" % \
  170. (style.SQL_KEYWORD('ALTER'),
  171. style.SQL_KEYWORD('TABLE'),
  172. style.SQL_TABLE(self.quote_name(sequence['table'])),
  173. style.SQL_KEYWORD('AUTO_INCREMENT'),
  174. style.SQL_FIELD('= 1'),
  175. ) for sequence in sequences])
  176. return sql
  177. else:
  178. return []
  179. def value_to_db_datetime(self, value):
  180. if value is None:
  181. return None
  182. # MySQL doesn't support tz-aware datetimes
  183. if value.tzinfo is not None:
  184. raise ValueError("MySQL backend does not support timezone-aware datetimes.")
  185. # MySQL doesn't support microseconds
  186. return unicode(value.replace(microsecond=0))
  187. def value_to_db_time(self, value):
  188. if value is None:
  189. return None
  190. # MySQL doesn't support tz-aware datetimes
  191. if value.tzinfo is not None:
  192. raise ValueError("MySQL backend does not support timezone-aware datetimes.")
  193. # MySQL doesn't support microseconds
  194. return unicode(value.replace(microsecond=0))
  195. def year_lookup_bounds(self, value):
  196. # Again, no microseconds
  197. first = '%s-01-01 00:00:00'
  198. second = '%s-12-31 23:59:59.99'
  199. return [first % value, second % value]
  200. def max_name_length(self):
  201. return 64
  202. class DatabaseWrapper(BaseDatabaseWrapper):
  203. vendor = 'mysql'
  204. operators = {
  205. 'exact': '= %s',
  206. 'iexact': 'LIKE %s',
  207. 'contains': 'LIKE BINARY %s',
  208. 'icontains': 'LIKE %s',
  209. 'regex': 'REGEXP BINARY %s',
  210. 'iregex': 'REGEXP %s',
  211. 'gt': '> %s',
  212. 'gte': '>= %s',
  213. 'lt': '< %s',
  214. 'lte': '<= %s',
  215. 'startswith': 'LIKE BINARY %s',
  216. 'endswith': 'LIKE BINARY %s',
  217. 'istartswith': 'LIKE %s',
  218. 'iendswith': 'LIKE %s',
  219. }
  220. def __init__(self, *args, **kwargs):
  221. super(DatabaseWrapper, self).__init__(*args, **kwargs)
  222. self.server_version = None
  223. self.features = DatabaseFeatures(self)
  224. self.ops = DatabaseOperations()
  225. self.client = DatabaseClient(self)
  226. self.creation = DatabaseCreation(self)
  227. self.introspection = DatabaseIntrospection(self)
  228. self.validation = DatabaseValidation(self)
  229. def _valid_connection(self):
  230. if self.connection is not None:
  231. try:
  232. self.connection.ping()
  233. return True
  234. except DatabaseError:
  235. self.connection.close()
  236. self.connection = None
  237. return False
  238. def _cursor(self):
  239. if not self._valid_connection():
  240. kwargs = {
  241. 'conv': django_conversions,
  242. 'charset': 'utf8',
  243. 'use_unicode': True,
  244. }
  245. settings_dict = self.settings_dict
  246. if settings_dict['USER']:
  247. kwargs['user'] = settings_dict['USER']
  248. if settings_dict['NAME']:
  249. kwargs['db'] = settings_dict['NAME']
  250. if settings_dict['PASSWORD']:
  251. kwargs['passwd'] = settings_dict['PASSWORD']
  252. if settings_dict['HOST'].startswith('/'):
  253. kwargs['unix_socket'] = settings_dict['HOST']
  254. elif settings_dict['HOST']:
  255. kwargs['host'] = settings_dict['HOST']
  256. if settings_dict['PORT']:
  257. kwargs['port'] = int(settings_dict['PORT'])
  258. # We need the number of potentially affected rows after an
  259. # "UPDATE", not the number of changed rows.
  260. kwargs['client_flag'] = CLIENT.FOUND_ROWS
  261. kwargs.update(settings_dict['OPTIONS'])
  262. self.connection = Database.connect(**kwargs)
  263. self.connection.encoders[SafeUnicode] = self.connection.encoders[unicode]
  264. self.connection.encoders[SafeString] = self.connection.encoders[str]
  265. connection_created.send(sender=self.__class__, connection=self)
  266. cursor = CursorWrapper(self.connection.cursor())
  267. return cursor
  268. def _rollback(self):
  269. try:
  270. BaseDatabaseWrapper._rollback(self)
  271. except Database.NotSupportedError:
  272. pass
  273. def get_server_version(self):
  274. if not self.server_version:
  275. if not self._valid_connection():
  276. self.cursor()
  277. m = server_version_re.match(self.connection.get_server_info())
  278. if not m:
  279. raise Exception('Unable to determine MySQL version from version string %r' % self.connection.get_server_info())
  280. self.server_version = tuple([int(x) for x in m.groups()])
  281. return self.server_version