south.db.mysql: 162 total statements, 0.0% covered

Generated: Wed 2013-03-13 10:33 CET

Source file: /media/Envs/Envs/filer-gallery/lib/python2.7/site-packages/south/db/mysql.py

Stats: 0 executed, 159 missed, 3 excluded, 118 ignored

  1. # MySQL-specific implementations for south
  2. # Original author: Andrew Godwin
  3. # Patches by: F. Gabriel Gosselin <gabrielNOSPAM@evidens.ca>
  4. from south.db import generic
  5. from south.db.generic import DryRunError, INVALID
  6. from south.logger import get_logger
  7. def delete_column_constraints(func):
  8. """
  9. Decorates column operation functions for MySQL.
  10. Deletes the constraints from the database and clears local cache.
  11. """
  12. def _column_rm(self, table_name, column_name, *args, **opts):
  13. # Delete foreign key constraints
  14. try:
  15. self.delete_foreign_key(table_name, column_name)
  16. except ValueError:
  17. pass # If no foreign key on column, OK because it checks first
  18. # Delete constraints referring to this column
  19. try:
  20. reverse = self._lookup_reverse_constraint(table_name, column_name)
  21. for cname, rtable, rcolumn in reverse:
  22. self.delete_foreign_key(rtable, rcolumn)
  23. except DryRunError:
  24. pass
  25. return func(self, table_name, column_name, *args, **opts)
  26. return _column_rm
  27. def copy_column_constraints(func):
  28. """
  29. Decorates column operation functions for MySQL.
  30. Determines existing constraints and copies them to a new column
  31. """
  32. def _column_cp(self, table_name, column_old, column_new, *args, **opts):
  33. # Copy foreign key constraint
  34. try:
  35. constraint = self._find_foreign_constraints(table_name, column_old)[0]
  36. (ftable, fcolumn) = self._lookup_constraint_references(table_name, constraint)
  37. if ftable and fcolumn:
  38. fk_sql = self.foreign_key_sql(
  39. table_name, column_new, ftable, fcolumn)
  40. get_logger().debug("Foreign key SQL: " + fk_sql)
  41. self.add_deferred_sql(fk_sql)
  42. except IndexError:
  43. pass # No constraint exists so ignore
  44. except DryRunError:
  45. pass
  46. # Copy constraints referring to this column
  47. try:
  48. reverse = self._lookup_reverse_constraint(table_name, column_old)
  49. for cname, rtable, rcolumn in reverse:
  50. fk_sql = self.foreign_key_sql(
  51. rtable, rcolumn, table_name, column_new)
  52. self.add_deferred_sql(fk_sql)
  53. except DryRunError:
  54. pass
  55. return func(self, table_name, column_old, column_new, *args, **opts)
  56. return _column_cp
  57. def invalidate_table_constraints(func):
  58. """
  59. For MySQL we grab all table constraints simultaneously, so this is
  60. effective.
  61. It further solves the issues of invalidating referred table constraints.
  62. """
  63. def _cache_clear(self, table, *args, **opts):
  64. db_name = self._get_setting('NAME')
  65. if db_name in self._constraint_cache:
  66. del self._constraint_cache[db_name]
  67. if db_name in self._reverse_cache:
  68. del self._reverse_cache[db_name]
  69. if db_name in self._constraint_references:
  70. del self._constraint_references[db_name]
  71. return func(self, table, *args, **opts)
  72. return _cache_clear
  73. class DatabaseOperations(generic.DatabaseOperations):
  74. """
  75. MySQL implementation of database operations.
  76. MySQL has no DDL transaction support This can confuse people when they ask
  77. how to roll back - hence the dry runs, etc., found in the migration code.
  78. """
  79. backend_name = "mysql"
  80. alter_string_set_type = ''
  81. alter_string_set_null = 'MODIFY %(column)s %(type)s NULL;'
  82. alter_string_drop_null = 'MODIFY %(column)s %(type)s NOT NULL;'
  83. drop_index_string = 'DROP INDEX %(index_name)s ON %(table_name)s'
  84. delete_primary_key_sql = "ALTER TABLE %(table)s DROP PRIMARY KEY"
  85. delete_foreign_key_sql = "ALTER TABLE %(table)s DROP FOREIGN KEY %(constraint)s"
  86. delete_unique_sql = "ALTER TABLE %s DROP INDEX %s"
  87. rename_table_sql = "RENAME TABLE %s TO %s;"
  88. allows_combined_alters = False
  89. has_check_constraints = False
  90. geom_types = ['geometry', 'point', 'linestring', 'polygon']
  91. text_types = ['text', 'blob']
  92. def __init__(self, db_alias):
  93. self._constraint_references = {}
  94. self._reverse_cache = {}
  95. super(DatabaseOperations, self).__init__(db_alias)
  96. def _is_valid_cache(self, db_name, table_name):
  97. cache = self._constraint_cache
  98. # we cache the whole db so if there are any tables table_name is valid
  99. return db_name in cache and cache[db_name].get(table_name, None) is not INVALID
  100. def _fill_constraint_cache(self, db_name, table_name):
  101. # for MySQL grab all constraints for this database. It's just as cheap as a single column.
  102. self._constraint_cache[db_name] = {}
  103. self._constraint_cache[db_name][table_name] = {}
  104. self._reverse_cache[db_name] = {}
  105. self._constraint_references[db_name] = {}
  106. name_query = """
  107. SELECT kc.`constraint_name`, kc.`column_name`, kc.`table_name`,
  108. kc.`referenced_table_name`, kc.`referenced_column_name`
  109. FROM information_schema.key_column_usage AS kc
  110. WHERE
  111. kc.table_schema = %s
  112. """
  113. rows = self.execute(name_query, [db_name])
  114. if not rows:
  115. return
  116. cnames = {}
  117. for constraint, column, table, ref_table, ref_column in rows:
  118. key = (table, constraint)
  119. cnames.setdefault(key, set())
  120. cnames[key].add((column, ref_table, ref_column))
  121. type_query = """
  122. SELECT c.constraint_name, c.table_name, c.constraint_type
  123. FROM information_schema.table_constraints AS c
  124. WHERE
  125. c.table_schema = %s
  126. """
  127. rows = self.execute(type_query, [db_name])
  128. for constraint, table, kind in rows:
  129. key = (table, constraint)
  130. self._constraint_cache[db_name].setdefault(table, {})
  131. try:
  132. cols = cnames[key]
  133. except KeyError:
  134. cols = set()
  135. for column_set in cols:
  136. (column, ref_table, ref_column) = column_set
  137. self._constraint_cache[db_name][table].setdefault(column, set())
  138. if kind == 'FOREIGN KEY':
  139. self._constraint_cache[db_name][table][column].add((kind,
  140. constraint))
  141. # Create constraint lookup, see constraint_references
  142. self._constraint_references[db_name][(table,
  143. constraint)] = (ref_table, ref_column)
  144. # Create reverse table lookup, reverse_lookup
  145. self._reverse_cache[db_name].setdefault(ref_table, {})
  146. self._reverse_cache[db_name][ref_table].setdefault(ref_column,
  147. set())
  148. self._reverse_cache[db_name][ref_table][ref_column].add(
  149. (constraint, table, column))
  150. else:
  151. self._constraint_cache[db_name][table][column].add((kind,
  152. constraint))
  153. def connection_init(self):
  154. """
  155. Run before any SQL to let database-specific config be sent as a command,
  156. e.g. which storage engine (MySQL) or transaction serialisability level.
  157. """
  158. cursor = self._get_connection().cursor()
  159. if self._has_setting('STORAGE_ENGINE') and self._get_setting('STORAGE_ENGINE'):
  160. cursor.execute("SET storage_engine=%s;" % self._get_setting('STORAGE_ENGINE'))
  161. def start_transaction(self):
  162. super(DatabaseOperations, self).start_transaction()
  163. self.execute("SET FOREIGN_KEY_CHECKS=0;")
  164. @copy_column_constraints
  165. @delete_column_constraints
  166. @invalidate_table_constraints
  167. def rename_column(self, table_name, old, new):
  168. if old == new or self.dry_run:
  169. return []
  170. rows = [x for x in self.execute('DESCRIBE %s' % (self.quote_name(table_name),)) if x[0] == old]
  171. if not rows:
  172. raise ValueError("No column '%s' in '%s'." % (old, table_name))
  173. params = (
  174. self.quote_name(table_name),
  175. self.quote_name(old),
  176. self.quote_name(new),
  177. rows[0][1],
  178. rows[0][2] == "YES" and "NULL" or "NOT NULL",
  179. rows[0][4] and "DEFAULT " or "",
  180. rows[0][4] and "%s" or "",
  181. rows[0][5] or "",
  182. )
  183. sql = 'ALTER TABLE %s CHANGE COLUMN %s %s %s %s %s %s %s;' % params
  184. if rows[0][4]:
  185. self.execute(sql, (rows[0][4],))
  186. else:
  187. self.execute(sql)
  188. @delete_column_constraints
  189. def delete_column(self, table_name, name):
  190. super(DatabaseOperations, self).delete_column(table_name, name)
  191. @invalidate_table_constraints
  192. def rename_table(self, old_table_name, table_name):
  193. super(DatabaseOperations, self).rename_table(old_table_name,
  194. table_name)
  195. @invalidate_table_constraints
  196. def delete_table(self, table_name):
  197. super(DatabaseOperations, self).delete_table(table_name)
  198. def _lookup_constraint_references(self, table_name, cname):
  199. """
  200. Provided an existing table and constraint, returns tuple of (foreign
  201. table, column)
  202. """
  203. db_name = self._get_setting('NAME')
  204. try:
  205. return self._constraint_references[db_name][(table_name, cname)]
  206. except KeyError:
  207. return None
  208. def _lookup_reverse_constraint(self, table_name, column_name=None):
  209. """Look for the column referenced by a foreign constraint"""
  210. db_name = self._get_setting('NAME')
  211. if self.dry_run:
  212. raise DryRunError("Cannot get constraints for columns.")
  213. if not self._is_valid_cache(db_name, table_name):
  214. # Piggy-back on lookup_constraint, ensures cache exists
  215. self.lookup_constraint(db_name, table_name)
  216. try:
  217. table = self._reverse_cache[db_name][table_name]
  218. if column_name == None:
  219. return [(y, tuple(y)) for x, y in table.items()]
  220. else:
  221. return tuple(table[column_name])
  222. except KeyError:
  223. return []
  224. def _field_sanity(self, field):
  225. """
  226. This particular override stops us sending DEFAULTs for BLOB/TEXT columns.
  227. """
  228. # MySQL does not support defaults for geometry columns also
  229. type = self._db_type_for_alter_column(field).lower()
  230. is_geom = True in [type.find(t) > -1 for t in self.geom_types]
  231. is_text = True in [type.find(t) > -1 for t in self.text_types]
  232. if is_geom or is_text:
  233. field._suppress_default = True
  234. return field
  235. def _alter_set_defaults(self, field, name, params, sqls):
  236. """
  237. MySQL does not support defaults on text or blob columns.
  238. """
  239. type = params['type']
  240. # MySQL does not support defaults for geometry columns also
  241. is_geom = True in [type.find(t) > -1 for t in self.geom_types]
  242. is_text = True in [type.find(t) > -1 for t in self.text_types]
  243. if not is_geom and not is_text:
  244. super(DatabaseOperations, self)._alter_set_defaults(field, name, params, sqls)