schema.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. import codecs
  2. import copy
  3. from decimal import Decimal
  4. from django.utils import six
  5. from django.apps.registry import Apps
  6. from django.db.backends.schema import BaseDatabaseSchemaEditor
  7. from django.db.models.fields.related import ManyToManyField
  8. class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
  9. sql_delete_table = "DROP TABLE %(table)s"
  10. sql_create_inline_fk = "REFERENCES %(to_table)s (%(to_column)s)"
  11. def quote_value(self, value):
  12. # Inner import to allow nice failure for backend if not present
  13. import _sqlite3
  14. try:
  15. value = _sqlite3.adapt(value)
  16. except _sqlite3.ProgrammingError:
  17. pass
  18. # Manual emulation of SQLite parameter quoting
  19. if isinstance(value, type(True)):
  20. return str(int(value))
  21. elif isinstance(value, (Decimal, float)):
  22. return str(value)
  23. elif isinstance(value, six.integer_types):
  24. return str(value)
  25. elif isinstance(value, six.string_types):
  26. return "'%s'" % six.text_type(value).replace("\'", "\'\'")
  27. elif value is None:
  28. return "NULL"
  29. elif isinstance(value, (bytes, bytearray, six.memoryview)):
  30. # Bytes are only allowed for BLOB fields, encoded as string
  31. # literals containing hexadecimal data and preceded by a single "X"
  32. # character:
  33. # value = b'\x01\x02' => value_hex = b'0102' => return X'0102'
  34. value = bytes(value)
  35. hex_encoder = codecs.getencoder('hex_codec')
  36. value_hex, _length = hex_encoder(value)
  37. # Use 'ascii' encoding for b'01' => '01', no need to use force_text here.
  38. return "X'%s'" % value_hex.decode('ascii')
  39. else:
  40. raise ValueError("Cannot quote parameter value %r of type %s" % (value, type(value)))
  41. def _remake_table(self, model, create_fields=[], delete_fields=[], alter_fields=[], override_uniques=None):
  42. """
  43. Shortcut to transform a model from old_model into new_model
  44. """
  45. # Work out the new fields dict / mapping
  46. body = dict((f.name, f) for f in model._meta.local_fields)
  47. # Since mapping might mix column names and default values,
  48. # its values must be already quoted.
  49. mapping = dict((f.column, self.quote_name(f.column)) for f in model._meta.local_fields)
  50. # This maps field names (not columns) for things like unique_together
  51. rename_mapping = {}
  52. # If any of the new or altered fields is introducing a new PK,
  53. # remove the old one
  54. restore_pk_field = None
  55. if any(f.primary_key for f in create_fields) or any(n.primary_key for o, n in alter_fields):
  56. for name, field in list(body.items()):
  57. if field.primary_key:
  58. field.primary_key = False
  59. restore_pk_field = field
  60. if field.auto_created:
  61. del body[name]
  62. del mapping[field.column]
  63. # Add in any created fields
  64. for field in create_fields:
  65. body[field.name] = field
  66. # If there's a default, insert it into the copy map
  67. if field.has_default():
  68. mapping[field.column] = self.quote_value(
  69. self.effective_default(field)
  70. )
  71. # Add in any altered fields
  72. for (old_field, new_field) in alter_fields:
  73. del body[old_field.name]
  74. del mapping[old_field.column]
  75. body[new_field.name] = new_field
  76. mapping[new_field.column] = self.quote_name(old_field.column)
  77. rename_mapping[old_field.name] = new_field.name
  78. # Remove any deleted fields
  79. for field in delete_fields:
  80. del body[field.name]
  81. del mapping[field.column]
  82. # Remove any implicit M2M tables
  83. if isinstance(field, ManyToManyField) and field.rel.through._meta.auto_created:
  84. return self.delete_model(field.rel.through)
  85. # Work inside a new app registry
  86. apps = Apps()
  87. # Provide isolated instances of the fields to the new model body
  88. # Instantiating the new model with an alternate db_table will alter
  89. # the internal references of some of the provided fields.
  90. body = copy.deepcopy(body)
  91. # Work out the new value of unique_together, taking renames into
  92. # account
  93. if override_uniques is None:
  94. override_uniques = [
  95. [rename_mapping.get(n, n) for n in unique]
  96. for unique in model._meta.unique_together
  97. ]
  98. # Construct a new model for the new state
  99. meta_contents = {
  100. 'app_label': model._meta.app_label,
  101. 'db_table': model._meta.db_table + "__new",
  102. 'unique_together': override_uniques,
  103. 'apps': apps,
  104. }
  105. meta = type("Meta", tuple(), meta_contents)
  106. body['Meta'] = meta
  107. body['__module__'] = model.__module__
  108. temp_model = type(model._meta.object_name, model.__bases__, body)
  109. # Create a new table with that format. We remove things from the
  110. # deferred SQL that match our table name, too
  111. self.deferred_sql = [x for x in self.deferred_sql if model._meta.db_table not in x]
  112. self.create_model(temp_model)
  113. # Copy data from the old table
  114. field_maps = list(mapping.items())
  115. self.execute("INSERT INTO %s (%s) SELECT %s FROM %s" % (
  116. self.quote_name(temp_model._meta.db_table),
  117. ', '.join(self.quote_name(x) for x, y in field_maps),
  118. ', '.join(y for x, y in field_maps),
  119. self.quote_name(model._meta.db_table),
  120. ))
  121. # Delete the old table
  122. self.delete_model(model, handle_autom2m=False)
  123. # Rename the new to the old
  124. self.alter_db_table(temp_model, temp_model._meta.db_table, model._meta.db_table)
  125. # Run deferred SQL on correct table
  126. for sql in self.deferred_sql:
  127. self.execute(sql.replace(temp_model._meta.db_table, model._meta.db_table))
  128. self.deferred_sql = []
  129. # Fix any PK-removed field
  130. if restore_pk_field:
  131. restore_pk_field.primary_key = True
  132. def delete_model(self, model, handle_autom2m=True):
  133. if handle_autom2m:
  134. super(DatabaseSchemaEditor, self).delete_model(model)
  135. else:
  136. # Delete the table (and only that)
  137. self.execute(self.sql_delete_table % {
  138. "table": self.quote_name(model._meta.db_table),
  139. })
  140. def add_field(self, model, field):
  141. """
  142. Creates a field on a model.
  143. Usually involves adding a column, but may involve adding a
  144. table instead (for M2M fields)
  145. """
  146. # Special-case implicit M2M tables
  147. if isinstance(field, ManyToManyField) and field.rel.through._meta.auto_created:
  148. return self.create_model(field.rel.through)
  149. self._remake_table(model, create_fields=[field])
  150. def remove_field(self, model, field):
  151. """
  152. Removes a field from a model. Usually involves deleting a column,
  153. but for M2Ms may involve deleting a table.
  154. """
  155. # M2M fields are a special case
  156. if isinstance(field, ManyToManyField):
  157. # For implicit M2M tables, delete the auto-created table
  158. if field.rel.through._meta.auto_created:
  159. self.delete_model(field.rel.through)
  160. # For explicit "through" M2M fields, do nothing
  161. # For everything else, remake.
  162. else:
  163. # It might not actually have a column behind it
  164. if field.db_parameters(connection=self.connection)['type'] is None:
  165. return
  166. self._remake_table(model, delete_fields=[field])
  167. def _alter_field(self, model, old_field, new_field, old_type, new_type, old_db_params, new_db_params, strict=False):
  168. """Actually perform a "physical" (non-ManyToMany) field update."""
  169. # Alter by remaking table
  170. self._remake_table(model, alter_fields=[(old_field, new_field)])
  171. def alter_unique_together(self, model, old_unique_together, new_unique_together):
  172. """
  173. Deals with a model changing its unique_together.
  174. Note: The input unique_togethers must be doubly-nested, not the single-
  175. nested ["foo", "bar"] format.
  176. """
  177. self._remake_table(model, override_uniques=new_unique_together)
  178. def _alter_many_to_many(self, model, old_field, new_field, strict):
  179. """
  180. Alters M2Ms to repoint their to= endpoints.
  181. """
  182. if old_field.rel.through._meta.db_table == new_field.rel.through._meta.db_table:
  183. # The field name didn't change, but some options did; we have to propagate this altering.
  184. self._remake_table(
  185. old_field.rel.through,
  186. alter_fields=[(
  187. # We need the field that points to the target model, so we can tell alter_field to change it -
  188. # this is m2m_reverse_field_name() (as opposed to m2m_field_name, which points to our model)
  189. old_field.rel.through._meta.get_field_by_name(old_field.m2m_reverse_field_name())[0],
  190. new_field.rel.through._meta.get_field_by_name(new_field.m2m_reverse_field_name())[0],
  191. )],
  192. override_uniques=(new_field.m2m_field_name(), new_field.m2m_reverse_field_name()),
  193. )
  194. return
  195. # Make a new through table
  196. self.create_model(new_field.rel.through)
  197. # Copy the data across
  198. self.execute("INSERT INTO %s (%s) SELECT %s FROM %s" % (
  199. self.quote_name(new_field.rel.through._meta.db_table),
  200. ', '.join([
  201. "id",
  202. new_field.m2m_column_name(),
  203. new_field.m2m_reverse_name(),
  204. ]),
  205. ', '.join([
  206. "id",
  207. old_field.m2m_column_name(),
  208. old_field.m2m_reverse_name(),
  209. ]),
  210. self.quote_name(old_field.rel.through._meta.db_table),
  211. ))
  212. # Delete the old through table
  213. self.delete_model(old_field.rel.through)