base.py 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. #!/usr/bin/env python
  2. '''
  3. MySQL database backend for Django using MySQLdb and Eventlet
  4. '''
  5. import eventlet.db_pool
  6. from django.db.backends import BaseDatabaseWrapper, BaseDatabaseFeatures, BaseDatabaseOperations, util
  7. try:
  8. import MySQLdb
  9. except ImportError, e:
  10. from django.core.exceptions import ImproperlyConfigured
  11. raise ImproperlyConfigured("Error loading MySQLdb module: %s" % e)
  12. import re
  13. from MySQLdb.converters import conversions
  14. from MySQLdb.constants import FIELD_TYPE, FLAG, CLIENT
  15. from django.db.backends import *
  16. from django.db.backends.mysql import base as mysqldb_base
  17. from django.db.backends.mysql.client import DatabaseClient
  18. from django.db.backends.mysql.creation import DatabaseCreation
  19. from django.db.backends.mysql.introspection import DatabaseIntrospection
  20. from django.db.backends.mysql.validation import DatabaseValidation
  21. from django.db.backends.signals import connection_created
  22. from django.utils.safestring import SafeString, SafeUnicode
  23. # Raise exceptions for database warnings if DEBUG is on
  24. from django.conf import settings
  25. DatabaseError = MySQLdb.DatabaseError
  26. IntegrityError = MySQLdb.IntegrityError
  27. # MySQLdb-1.2.1 returns TIME columns as timedelta -- they are more like
  28. # timedelta in terms of actual behavior as they are signed and include days --
  29. # and Django expects time, so we still need to override that. We also need to
  30. # add special handling for SafeUnicode and SafeString as MySQLdb's type
  31. # checking is too tight to catch those (see Django ticket #6052).
  32. django_conversions = conversions.copy()
  33. django_conversions.update({
  34. FIELD_TYPE.TIME: util.typecast_time,
  35. FIELD_TYPE.DECIMAL: util.typecast_decimal,
  36. FIELD_TYPE.NEWDECIMAL: util.typecast_decimal,
  37. })
  38. # This should match the numerical portion of the version numbers (we can treat
  39. # versions like 5.0.24 and 5.0.24a as the same). Based on the list of version
  40. # at http://dev.mysql.com/doc/refman/4.1/en/news.html and
  41. # http://dev.mysql.com/doc/refman/5.0/en/news.html .
  42. server_version_re = re.compile(r'(\d{1,2})\.(\d{1,2})\.(\d{1,2})')
  43. # MySQLdb-1.2.1 and newer automatically makes use of SHOW WARNINGS on
  44. # MySQL-4.1 and newer, so the MysqlDebugWrapper is unnecessary. Since the
  45. # point is to raise Warnings as exceptions, this can be done with the Python
  46. # warning module, and this is setup when the connection is created, and the
  47. # standard util.CursorDebugWrapper can be used. Also, using sql_mode
  48. # TRADITIONAL will automatically cause most warnings to be treated as errors.
  49. class DatabaseFeatures(mysqldb_base.DatabaseFeatures):
  50. pass
  51. class DatabaseWrapper(BaseDatabaseWrapper):
  52. operators = {
  53. 'exact': '= %s',
  54. 'iexact': 'LIKE %s',
  55. 'contains': 'LIKE BINARY %s',
  56. 'icontains': 'LIKE %s',
  57. 'regex': 'REGEXP BINARY %s',
  58. 'iregex': 'REGEXP %s',
  59. 'gt': '> %s',
  60. 'gte': '>= %s',
  61. 'lt': '< %s',
  62. 'lte': '<= %s',
  63. 'startswith': 'LIKE BINARY %s',
  64. 'endswith': 'LIKE BINARY %s',
  65. 'istartswith': 'LIKE %s',
  66. 'iendswith': 'LIKE %s',
  67. }
  68. def __init__(self, *args, **kwargs):
  69. super(DatabaseWrapper, self).__init__(*args, **kwargs)
  70. self.server_version = None
  71. self.features = DatabaseFeatures()
  72. self.ops = mysqldb_base.DatabaseOperations()
  73. self.client = DatabaseClient(self)
  74. self.creation = DatabaseCreation(self)
  75. self.introspection = DatabaseIntrospection(self)
  76. self.validation = DatabaseValidation(self)
  77. self.pool = None
  78. def _valid_connection(self):
  79. if self.connection is not None:
  80. try:
  81. self.connection.ping()
  82. return True
  83. except DatabaseError:
  84. self.put(self.connection)
  85. self.connection = None
  86. return False
  87. def _cursor(self):
  88. if not self.pool:
  89. kwargs = {
  90. 'conv': django_conversions,
  91. 'charset': 'utf8',
  92. 'use_unicode': True,
  93. }
  94. settings_dict = self.settings_dict
  95. if settings_dict['USER']:
  96. kwargs['user'] = settings_dict['USER']
  97. if settings_dict['NAME']:
  98. kwargs['db'] = settings_dict['NAME']
  99. if settings_dict['PASSWORD']:
  100. kwargs['passwd'] = settings_dict['PASSWORD']
  101. if settings_dict['HOST'].startswith('/'):
  102. kwargs['unix_socket'] = settings_dict['HOST']
  103. elif settings_dict['HOST']:
  104. kwargs['host'] = settings_dict['HOST']
  105. if settings_dict['PORT']:
  106. kwargs['port'] = int(settings_dict['PORT'])
  107. kwargs['client_flag'] = CLIENT.FOUND_ROWS
  108. kwargs.update(settings_dict['OPTIONS'])
  109. self.pool = eventlet.db_pool.TpooledConnectionPool(MySQLdb, min_size=1, max_size=16, **kwargs)
  110. if not self._valid_connection():
  111. self.connection = self.pool.get()
  112. connection_created.send(sender=self.__class__)
  113. cursor = mysqldb_base.CursorWrapper(self.connection.cursor())
  114. return cursor
  115. def _rollback(self):
  116. try:
  117. BaseDatabaseWrapper._rollback(self)
  118. except Database.NotSupportedError:
  119. pass
  120. def get_server_version(self):
  121. if not self.server_version:
  122. if not self._valid_connection():
  123. self.cursor()
  124. m = server_version_re.match(self.connection._base.get_server_info())
  125. if not m:
  126. raise Exception('Unable to determine MySQL version from version string %r' % self.connection.get_server_info())
  127. self.server_version = tuple([int(x) for x in m.groups()])
  128. return self.server_version