PageRenderTime 41ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/django/branches/attic/schema-evolution/django/db/backends/mysql_old/base.py

https://bitbucket.org/mirror/django/
Python | 321 lines | 277 code | 23 blank | 21 comment | 41 complexity | 91986fab7759fb7f7acc0fa4a1f317b2 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 django.db.backends import util
  6. from django.utils.encoding import force_unicode
  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. from MySQLdb.converters import conversions
  13. from MySQLdb.constants import FIELD_TYPE
  14. import types
  15. import re
  16. DatabaseError = Database.DatabaseError
  17. IntegrityError = Database.IntegrityError
  18. django_conversions = conversions.copy()
  19. django_conversions.update({
  20. types.BooleanType: util.rev_typecast_boolean,
  21. FIELD_TYPE.DATETIME: util.typecast_timestamp,
  22. FIELD_TYPE.DATE: util.typecast_date,
  23. FIELD_TYPE.TIME: util.typecast_time,
  24. FIELD_TYPE.DECIMAL: util.typecast_decimal,
  25. FIELD_TYPE.STRING: force_unicode,
  26. FIELD_TYPE.VAR_STRING: force_unicode,
  27. # Note: We don't add a convertor for BLOB here. Doesn't seem to be required.
  28. })
  29. # This should match the numerical portion of the version numbers (we can treat
  30. # versions like 5.0.24 and 5.0.24a as the same). Based on the list of version
  31. # at http://dev.mysql.com/doc/refman/4.1/en/news.html and
  32. # http://dev.mysql.com/doc/refman/5.0/en/news.html .
  33. server_version_re = re.compile(r'(\d{1,2})\.(\d{1,2})\.(\d{1,2})')
  34. # This is an extra debug layer over MySQL queries, to display warnings.
  35. # It's only used when DEBUG=True.
  36. class MysqlDebugWrapper:
  37. def __init__(self, cursor):
  38. self.cursor = cursor
  39. def execute(self, sql, params=()):
  40. try:
  41. return self.cursor.execute(sql, params)
  42. except Database.Warning, w:
  43. self.cursor.execute("SHOW WARNINGS")
  44. raise Database.Warning, "%s: %s" % (w, self.cursor.fetchall())
  45. def executemany(self, sql, param_list):
  46. try:
  47. return self.cursor.executemany(sql, param_list)
  48. except Database.Warning, w:
  49. self.cursor.execute("SHOW WARNINGS")
  50. raise Database.Warning, "%s: %s" % (w, self.cursor.fetchall())
  51. def __getattr__(self, attr):
  52. if attr in self.__dict__:
  53. return self.__dict__[attr]
  54. else:
  55. return getattr(self.cursor, attr)
  56. try:
  57. # Only exists in Python 2.4+
  58. from threading import local
  59. except ImportError:
  60. # Import copy of _thread_local.py from Python 2.4
  61. from django.utils._threading_local import local
  62. class DatabaseWrapper(local):
  63. def __init__(self, **kwargs):
  64. self.connection = None
  65. self.queries = []
  66. self.server_version = None
  67. self.options = kwargs
  68. def _valid_connection(self):
  69. if self.connection is not None:
  70. try:
  71. self.connection.ping()
  72. return True
  73. except DatabaseError:
  74. self.connection.close()
  75. self.connection = None
  76. return False
  77. def cursor(self):
  78. from django.conf import settings
  79. if not self._valid_connection():
  80. kwargs = {
  81. # Note: use_unicode intentonally not set to work around some
  82. # backwards-compat issues. We do it manually.
  83. 'user': settings.DATABASE_USER,
  84. 'db': settings.DATABASE_NAME,
  85. 'passwd': settings.DATABASE_PASSWORD,
  86. 'conv': django_conversions,
  87. }
  88. if settings.DATABASE_HOST.startswith('/'):
  89. kwargs['unix_socket'] = settings.DATABASE_HOST
  90. else:
  91. kwargs['host'] = settings.DATABASE_HOST
  92. if settings.DATABASE_PORT:
  93. kwargs['port'] = int(settings.DATABASE_PORT)
  94. kwargs.update(self.options)
  95. self.connection = Database.connect(**kwargs)
  96. cursor = self.connection.cursor()
  97. if self.connection.get_server_info() >= '4.1' and not self.connection.character_set_name().startswith('utf8'):
  98. if hasattr(self.connection, 'charset'):
  99. # MySQLdb < 1.2.1 backwards-compat hacks.
  100. conn = self.connection
  101. cursor.execute("SET NAMES 'utf8'")
  102. cursor.execute("SET CHARACTER SET 'utf8'")
  103. to_str = lambda u, dummy=None, c=conn: c.literal(u.encode('utf-8'))
  104. conn.converter[unicode] = to_str
  105. else:
  106. self.connection.set_character_set('utf8')
  107. else:
  108. cursor = self.connection.cursor()
  109. if settings.DEBUG:
  110. return util.CursorDebugWrapper(MysqlDebugWrapper(cursor), self)
  111. return cursor
  112. def _commit(self):
  113. if self.connection is not None:
  114. self.connection.commit()
  115. def _rollback(self):
  116. if self.connection is not None:
  117. try:
  118. self.connection.rollback()
  119. except Database.NotSupportedError:
  120. pass
  121. def close(self):
  122. if self.connection is not None:
  123. self.connection.close()
  124. self.connection = None
  125. def get_server_version(self):
  126. if not self.server_version:
  127. if not self._valid_connection():
  128. self.cursor()
  129. m = server_version_re.match(self.connection.get_server_info())
  130. if not m:
  131. raise Exception('Unable to determine MySQL version from version string %r' % self.connection.get_server_info())
  132. self.server_version = tuple([int(x) for x in m.groups()])
  133. return self.server_version
  134. allows_group_by_ordinal = True
  135. allows_unique_and_pk = True
  136. pk_requires_unique = False
  137. autoindexes_primary_keys = False
  138. needs_datetime_string_cast = True # MySQLdb requires a typecast for dates
  139. needs_upper_for_iops = False
  140. supports_constraints = True
  141. supports_tablespaces = False
  142. uses_case_insensitive_names = False
  143. def quote_name(name):
  144. if name.startswith("`") and name.endswith("`"):
  145. return name # Quoting once is enough.
  146. return "`%s`" % name
  147. dictfetchone = util.dictfetchone
  148. dictfetchmany = util.dictfetchmany
  149. dictfetchall = util.dictfetchall
  150. def get_last_insert_id(cursor, table_name, pk_name):
  151. return cursor.lastrowid
  152. def get_date_extract_sql(lookup_type, table_name):
  153. # lookup_type is 'year', 'month', 'day'
  154. # http://dev.mysql.com/doc/mysql/en/date-and-time-functions.html
  155. return "EXTRACT(%s FROM %s)" % (lookup_type.upper(), table_name)
  156. def get_date_trunc_sql(lookup_type, field_name):
  157. # lookup_type is 'year', 'month', 'day'
  158. fields = ['year', 'month', 'day', 'hour', 'minute', 'second']
  159. format = ('%%Y-', '%%m', '-%%d', ' %%H:', '%%i', ':%%s') # Use double percents to escape.
  160. format_def = ('0000-', '01', '-01', ' 00:', '00', ':00')
  161. try:
  162. i = fields.index(lookup_type) + 1
  163. except ValueError:
  164. sql = field_name
  165. else:
  166. format_str = ''.join([f for f in format[:i]] + [f for f in format_def[i:]])
  167. sql = "CAST(DATE_FORMAT(%s, '%s') AS DATETIME)" % (field_name, format_str)
  168. return sql
  169. def get_datetime_cast_sql():
  170. return None
  171. def get_limit_offset_sql(limit, offset=None):
  172. sql = "LIMIT "
  173. if offset and offset != 0:
  174. sql += "%s," % offset
  175. return sql + str(limit)
  176. def get_random_function_sql():
  177. return "RAND()"
  178. def get_deferrable_sql():
  179. return ""
  180. def get_fulltext_search_sql(field_name):
  181. return 'MATCH (%s) AGAINST (%%s IN BOOLEAN MODE)' % field_name
  182. def get_drop_foreignkey_sql():
  183. return "DROP FOREIGN KEY"
  184. def get_pk_default_value():
  185. return "DEFAULT"
  186. def get_max_name_length():
  187. return None;
  188. def get_start_transaction_sql():
  189. return "BEGIN;"
  190. def get_autoinc_sql(table):
  191. return None
  192. def get_sql_flush(style, tables, sequences):
  193. """Return a list of SQL statements required to remove all data from
  194. all tables in the database (without actually removing the tables
  195. themselves) and put the database in an empty 'initial' state
  196. """
  197. # NB: The generated SQL below is specific to MySQL
  198. # 'TRUNCATE x;', 'TRUNCATE y;', 'TRUNCATE z;'... style SQL statements
  199. # to clear all tables of all data
  200. if tables:
  201. sql = ['SET FOREIGN_KEY_CHECKS = 0;'] + \
  202. ['%s %s;' % \
  203. (style.SQL_KEYWORD('TRUNCATE'),
  204. style.SQL_FIELD(quote_name(table))
  205. ) for table in tables] + \
  206. ['SET FOREIGN_KEY_CHECKS = 1;']
  207. # 'ALTER TABLE table AUTO_INCREMENT = 1;'... style SQL statements
  208. # to reset sequence indices
  209. sql.extend(["%s %s %s %s %s;" % \
  210. (style.SQL_KEYWORD('ALTER'),
  211. style.SQL_KEYWORD('TABLE'),
  212. style.SQL_TABLE(quote_name(sequence['table'])),
  213. style.SQL_KEYWORD('AUTO_INCREMENT'),
  214. style.SQL_FIELD('= 1'),
  215. ) for sequence in sequences])
  216. return sql
  217. else:
  218. return []
  219. def get_sql_sequence_reset(style, model_list):
  220. "Returns a list of the SQL statements to reset sequences for the given models."
  221. # No sequence reset required
  222. return []
  223. def get_change_table_name_sql( table_name, old_table_name ):
  224. return ['ALTER TABLE '+ quote_name(old_table_name) +' RENAME TO '+ quote_name(table_name) + ';']
  225. def get_change_column_name_sql( table_name, indexes, old_col_name, new_col_name, col_def ):
  226. # mysql doesn't support column renames (AFAIK), so we fake it
  227. # TODO: only supports a single primary key so far
  228. pk_name = None
  229. for key in indexes.keys():
  230. if indexes[key]['primary_key']: pk_name = key
  231. output = []
  232. output.append( 'ALTER TABLE '+ quote_name(table_name) +' CHANGE COLUMN '+ quote_name(old_col_name) +' '+ quote_name(new_col_name) +' '+ col_def + ';' )
  233. return output
  234. def get_change_column_def_sql( table_name, col_name, col_type, null, unique, primary_key ):
  235. output = []
  236. col_def = col_type +' '+ ('%sNULL' % (not null and 'NOT ' or ''))
  237. if unique:
  238. col_def += ' '+ 'UNIQUE'
  239. if primary_key:
  240. col_def += ' '+ 'PRIMARY KEY'
  241. output.append( 'ALTER TABLE '+ quote_name(table_name) +' MODIFY COLUMN '+ quote_name(col_name) +' '+ col_def + ';' )
  242. return output
  243. def get_add_column_sql( table_name, col_name, col_type, null, unique, primary_key ):
  244. output = []
  245. field_output = []
  246. field_output.append('ALTER TABLE')
  247. field_output.append(quote_name(table_name))
  248. field_output.append('ADD COLUMN')
  249. field_output.append(quote_name(col_name))
  250. field_output.append(col_type)
  251. field_output.append(('%sNULL' % (not null and 'NOT ' or '')))
  252. if unique:
  253. field_output.append(('UNIQUE'))
  254. if primary_key:
  255. field_output.append(('PRIMARY KEY'))
  256. output.append(' '.join(field_output) + ';')
  257. return output
  258. def get_drop_column_sql( table_name, col_name ):
  259. output = []
  260. output.append( 'ALTER TABLE '+ quote_name(table_name) +' DROP COLUMN '+ quote_name(col_name) + ';' )
  261. return output
  262. OPERATOR_MAPPING = {
  263. 'exact': '= %s',
  264. 'iexact': 'LIKE %s',
  265. 'contains': 'LIKE BINARY %s',
  266. 'icontains': 'LIKE %s',
  267. 'regex': 'REGEXP BINARY %s',
  268. 'iregex': 'REGEXP %s',
  269. 'gt': '> %s',
  270. 'gte': '>= %s',
  271. 'lt': '< %s',
  272. 'lte': '<= %s',
  273. 'startswith': 'LIKE BINARY %s',
  274. 'endswith': 'LIKE BINARY %s',
  275. 'istartswith': 'LIKE %s',
  276. 'iendswith': 'LIKE %s',
  277. }