schema.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412
  1. import copy
  2. from decimal import Decimal
  3. from django.apps.registry import Apps
  4. from django.db.backends.base.schema import BaseDatabaseSchemaEditor
  5. from django.db.backends.ddl_references import Statement
  6. from django.db.backends.utils import strip_quotes
  7. from django.db.models import UniqueConstraint
  8. from django.db.transaction import atomic
  9. from django.db.utils import NotSupportedError
  10. class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
  11. sql_delete_table = "DROP TABLE %(table)s"
  12. sql_create_fk = None
  13. sql_create_inline_fk = "REFERENCES %(to_table)s (%(to_column)s) DEFERRABLE INITIALLY DEFERRED"
  14. sql_create_unique = "CREATE UNIQUE INDEX %(name)s ON %(table)s (%(columns)s)"
  15. sql_delete_unique = "DROP INDEX %(name)s"
  16. def __enter__(self):
  17. # Some SQLite schema alterations need foreign key constraints to be
  18. # disabled. Enforce it here for the duration of the schema edition.
  19. if not self.connection.disable_constraint_checking():
  20. raise NotSupportedError(
  21. 'SQLite schema editor cannot be used while foreign key '
  22. 'constraint checks are enabled. Make sure to disable them '
  23. 'before entering a transaction.atomic() context because '
  24. 'SQLite does not support disabling them in the middle of '
  25. 'a multi-statement transaction.'
  26. )
  27. return super().__enter__()
  28. def __exit__(self, exc_type, exc_value, traceback):
  29. self.connection.check_constraints()
  30. super().__exit__(exc_type, exc_value, traceback)
  31. self.connection.enable_constraint_checking()
  32. def quote_value(self, value):
  33. # The backend "mostly works" without this function and there are use
  34. # cases for compiling Python without the sqlite3 libraries (e.g.
  35. # security hardening).
  36. try:
  37. import sqlite3
  38. value = sqlite3.adapt(value)
  39. except ImportError:
  40. pass
  41. except sqlite3.ProgrammingError:
  42. pass
  43. # Manual emulation of SQLite parameter quoting
  44. if isinstance(value, bool):
  45. return str(int(value))
  46. elif isinstance(value, (Decimal, float, int)):
  47. return str(value)
  48. elif isinstance(value, str):
  49. return "'%s'" % value.replace("\'", "\'\'")
  50. elif value is None:
  51. return "NULL"
  52. elif isinstance(value, (bytes, bytearray, memoryview)):
  53. # Bytes are only allowed for BLOB fields, encoded as string
  54. # literals containing hexadecimal data and preceded by a single "X"
  55. # character.
  56. return "X'%s'" % value.hex()
  57. else:
  58. raise ValueError("Cannot quote parameter value %r of type %s" % (value, type(value)))
  59. def _is_referenced_by_fk_constraint(self, table_name, column_name=None, ignore_self=False):
  60. """
  61. Return whether or not the provided table name is referenced by another
  62. one. If `column_name` is specified, only references pointing to that
  63. column are considered. If `ignore_self` is True, self-referential
  64. constraints are ignored.
  65. """
  66. with self.connection.cursor() as cursor:
  67. for other_table in self.connection.introspection.get_table_list(cursor):
  68. if ignore_self and other_table.name == table_name:
  69. continue
  70. constraints = self.connection.introspection._get_foreign_key_constraints(cursor, other_table.name)
  71. for constraint in constraints.values():
  72. constraint_table, constraint_column = constraint['foreign_key']
  73. if (constraint_table == table_name and
  74. (column_name is None or constraint_column == column_name)):
  75. return True
  76. return False
  77. def alter_db_table(self, model, old_db_table, new_db_table, disable_constraints=True):
  78. if (not self.connection.features.supports_atomic_references_rename and
  79. disable_constraints and self._is_referenced_by_fk_constraint(old_db_table)):
  80. if self.connection.in_atomic_block:
  81. raise NotSupportedError((
  82. 'Renaming the %r table while in a transaction is not '
  83. 'supported on SQLite < 3.26 because it would break referential '
  84. 'integrity. Try adding `atomic = False` to the Migration class.'
  85. ) % old_db_table)
  86. self.connection.enable_constraint_checking()
  87. super().alter_db_table(model, old_db_table, new_db_table)
  88. self.connection.disable_constraint_checking()
  89. else:
  90. super().alter_db_table(model, old_db_table, new_db_table)
  91. def alter_field(self, model, old_field, new_field, strict=False):
  92. old_field_name = old_field.name
  93. table_name = model._meta.db_table
  94. _, old_column_name = old_field.get_attname_column()
  95. if (new_field.name != old_field_name and
  96. not self.connection.features.supports_atomic_references_rename and
  97. self._is_referenced_by_fk_constraint(table_name, old_column_name, ignore_self=True)):
  98. if self.connection.in_atomic_block:
  99. raise NotSupportedError((
  100. 'Renaming the %r.%r column while in a transaction is not '
  101. 'supported on SQLite < 3.26 because it would break referential '
  102. 'integrity. Try adding `atomic = False` to the Migration class.'
  103. ) % (model._meta.db_table, old_field_name))
  104. with atomic(self.connection.alias):
  105. super().alter_field(model, old_field, new_field, strict=strict)
  106. # Follow SQLite's documented procedure for performing changes
  107. # that don't affect the on-disk content.
  108. # https://sqlite.org/lang_altertable.html#otheralter
  109. with self.connection.cursor() as cursor:
  110. schema_version = cursor.execute('PRAGMA schema_version').fetchone()[0]
  111. cursor.execute('PRAGMA writable_schema = 1')
  112. references_template = ' REFERENCES "%s" ("%%s") ' % table_name
  113. new_column_name = new_field.get_attname_column()[1]
  114. search = references_template % old_column_name
  115. replacement = references_template % new_column_name
  116. cursor.execute('UPDATE sqlite_master SET sql = replace(sql, %s, %s)', (search, replacement))
  117. cursor.execute('PRAGMA schema_version = %d' % (schema_version + 1))
  118. cursor.execute('PRAGMA writable_schema = 0')
  119. # The integrity check will raise an exception and rollback
  120. # the transaction if the sqlite_master updates corrupt the
  121. # database.
  122. cursor.execute('PRAGMA integrity_check')
  123. # Perform a VACUUM to refresh the database representation from
  124. # the sqlite_master table.
  125. with self.connection.cursor() as cursor:
  126. cursor.execute('VACUUM')
  127. else:
  128. super().alter_field(model, old_field, new_field, strict=strict)
  129. def _remake_table(self, model, create_field=None, delete_field=None, alter_field=None):
  130. """
  131. Shortcut to transform a model from old_model into new_model
  132. This follows the correct procedure to perform non-rename or column
  133. addition operations based on SQLite's documentation
  134. https://www.sqlite.org/lang_altertable.html#caution
  135. The essential steps are:
  136. 1. Create a table with the updated definition called "new__app_model"
  137. 2. Copy the data from the existing "app_model" table to the new table
  138. 3. Drop the "app_model" table
  139. 4. Rename the "new__app_model" table to "app_model"
  140. 5. Restore any index of the previous "app_model" table.
  141. """
  142. # Self-referential fields must be recreated rather than copied from
  143. # the old model to ensure their remote_field.field_name doesn't refer
  144. # to an altered field.
  145. def is_self_referential(f):
  146. return f.is_relation and f.remote_field.model is model
  147. # Work out the new fields dict / mapping
  148. body = {
  149. f.name: f.clone() if is_self_referential(f) else f
  150. for f in model._meta.local_concrete_fields
  151. }
  152. # Since mapping might mix column names and default values,
  153. # its values must be already quoted.
  154. mapping = {f.column: self.quote_name(f.column) for f in model._meta.local_concrete_fields}
  155. # This maps field names (not columns) for things like unique_together
  156. rename_mapping = {}
  157. # If any of the new or altered fields is introducing a new PK,
  158. # remove the old one
  159. restore_pk_field = None
  160. if getattr(create_field, 'primary_key', False) or (
  161. alter_field and getattr(alter_field[1], 'primary_key', False)):
  162. for name, field in list(body.items()):
  163. if field.primary_key:
  164. field.primary_key = False
  165. restore_pk_field = field
  166. if field.auto_created:
  167. del body[name]
  168. del mapping[field.column]
  169. # Add in any created fields
  170. if create_field:
  171. body[create_field.name] = create_field
  172. # Choose a default and insert it into the copy map
  173. if not create_field.many_to_many and create_field.concrete:
  174. mapping[create_field.column] = self.quote_value(
  175. self.effective_default(create_field)
  176. )
  177. # Add in any altered fields
  178. if alter_field:
  179. old_field, new_field = alter_field
  180. body.pop(old_field.name, None)
  181. mapping.pop(old_field.column, None)
  182. body[new_field.name] = new_field
  183. if old_field.null and not new_field.null:
  184. case_sql = "coalesce(%(col)s, %(default)s)" % {
  185. 'col': self.quote_name(old_field.column),
  186. 'default': self.quote_value(self.effective_default(new_field))
  187. }
  188. mapping[new_field.column] = case_sql
  189. else:
  190. mapping[new_field.column] = self.quote_name(old_field.column)
  191. rename_mapping[old_field.name] = new_field.name
  192. # Remove any deleted fields
  193. if delete_field:
  194. del body[delete_field.name]
  195. del mapping[delete_field.column]
  196. # Remove any implicit M2M tables
  197. if delete_field.many_to_many and delete_field.remote_field.through._meta.auto_created:
  198. return self.delete_model(delete_field.remote_field.through)
  199. # Work inside a new app registry
  200. apps = Apps()
  201. # Work out the new value of unique_together, taking renames into
  202. # account
  203. unique_together = [
  204. [rename_mapping.get(n, n) for n in unique]
  205. for unique in model._meta.unique_together
  206. ]
  207. # Work out the new value for index_together, taking renames into
  208. # account
  209. index_together = [
  210. [rename_mapping.get(n, n) for n in index]
  211. for index in model._meta.index_together
  212. ]
  213. indexes = model._meta.indexes
  214. if delete_field:
  215. indexes = [
  216. index for index in indexes
  217. if delete_field.name not in index.fields
  218. ]
  219. constraints = list(model._meta.constraints)
  220. # Provide isolated instances of the fields to the new model body so
  221. # that the existing model's internals aren't interfered with when
  222. # the dummy model is constructed.
  223. body_copy = copy.deepcopy(body)
  224. # Construct a new model with the new fields to allow self referential
  225. # primary key to resolve to. This model won't ever be materialized as a
  226. # table and solely exists for foreign key reference resolution purposes.
  227. # This wouldn't be required if the schema editor was operating on model
  228. # states instead of rendered models.
  229. meta_contents = {
  230. 'app_label': model._meta.app_label,
  231. 'db_table': model._meta.db_table,
  232. 'unique_together': unique_together,
  233. 'index_together': index_together,
  234. 'indexes': indexes,
  235. 'constraints': constraints,
  236. 'apps': apps,
  237. }
  238. meta = type("Meta", (), meta_contents)
  239. body_copy['Meta'] = meta
  240. body_copy['__module__'] = model.__module__
  241. type(model._meta.object_name, model.__bases__, body_copy)
  242. # Construct a model with a renamed table name.
  243. body_copy = copy.deepcopy(body)
  244. meta_contents = {
  245. 'app_label': model._meta.app_label,
  246. 'db_table': 'new__%s' % strip_quotes(model._meta.db_table),
  247. 'unique_together': unique_together,
  248. 'index_together': index_together,
  249. 'indexes': indexes,
  250. 'constraints': constraints,
  251. 'apps': apps,
  252. }
  253. meta = type("Meta", (), meta_contents)
  254. body_copy['Meta'] = meta
  255. body_copy['__module__'] = model.__module__
  256. new_model = type('New%s' % model._meta.object_name, model.__bases__, body_copy)
  257. # Create a new table with the updated schema.
  258. self.create_model(new_model)
  259. # Copy data from the old table into the new table
  260. self.execute("INSERT INTO %s (%s) SELECT %s FROM %s" % (
  261. self.quote_name(new_model._meta.db_table),
  262. ', '.join(self.quote_name(x) for x in mapping),
  263. ', '.join(mapping.values()),
  264. self.quote_name(model._meta.db_table),
  265. ))
  266. # Delete the old table to make way for the new
  267. self.delete_model(model, handle_autom2m=False)
  268. # Rename the new table to take way for the old
  269. self.alter_db_table(
  270. new_model, new_model._meta.db_table, model._meta.db_table,
  271. disable_constraints=False,
  272. )
  273. # Run deferred SQL on correct table
  274. for sql in self.deferred_sql:
  275. self.execute(sql)
  276. self.deferred_sql = []
  277. # Fix any PK-removed field
  278. if restore_pk_field:
  279. restore_pk_field.primary_key = True
  280. def delete_model(self, model, handle_autom2m=True):
  281. if handle_autom2m:
  282. super().delete_model(model)
  283. else:
  284. # Delete the table (and only that)
  285. self.execute(self.sql_delete_table % {
  286. "table": self.quote_name(model._meta.db_table),
  287. })
  288. # Remove all deferred statements referencing the deleted table.
  289. for sql in list(self.deferred_sql):
  290. if isinstance(sql, Statement) and sql.references_table(model._meta.db_table):
  291. self.deferred_sql.remove(sql)
  292. def add_field(self, model, field):
  293. """
  294. Create a field on a model. Usually involves adding a column, but may
  295. involve adding a table instead (for M2M fields).
  296. """
  297. # Special-case implicit M2M tables
  298. if field.many_to_many and field.remote_field.through._meta.auto_created:
  299. return self.create_model(field.remote_field.through)
  300. self._remake_table(model, create_field=field)
  301. def remove_field(self, model, field):
  302. """
  303. Remove a field from a model. Usually involves deleting a column,
  304. but for M2Ms may involve deleting a table.
  305. """
  306. # M2M fields are a special case
  307. if field.many_to_many:
  308. # For implicit M2M tables, delete the auto-created table
  309. if field.remote_field.through._meta.auto_created:
  310. self.delete_model(field.remote_field.through)
  311. # For explicit "through" M2M fields, do nothing
  312. # For everything else, remake.
  313. else:
  314. # It might not actually have a column behind it
  315. if field.db_parameters(connection=self.connection)['type'] is None:
  316. return
  317. self._remake_table(model, delete_field=field)
  318. def _alter_field(self, model, old_field, new_field, old_type, new_type,
  319. old_db_params, new_db_params, strict=False):
  320. """Perform a "physical" (non-ManyToMany) field update."""
  321. # Use "ALTER TABLE ... RENAME COLUMN" if only the column name
  322. # changed and there aren't any constraints.
  323. if (self.connection.features.can_alter_table_rename_column and
  324. old_field.column != new_field.column and
  325. self.column_sql(model, old_field) == self.column_sql(model, new_field) and
  326. not (old_field.remote_field and old_field.db_constraint or
  327. new_field.remote_field and new_field.db_constraint)):
  328. return self.execute(self._rename_field_sql(model._meta.db_table, old_field, new_field, new_type))
  329. # Alter by remaking table
  330. self._remake_table(model, alter_field=(old_field, new_field))
  331. # Rebuild tables with FKs pointing to this field if the PK type changed.
  332. if old_field.primary_key and new_field.primary_key and old_type != new_type:
  333. for rel in new_field.model._meta.related_objects:
  334. if not rel.many_to_many:
  335. self._remake_table(rel.related_model)
  336. def _alter_many_to_many(self, model, old_field, new_field, strict):
  337. """Alter M2Ms to repoint their to= endpoints."""
  338. if old_field.remote_field.through._meta.db_table == new_field.remote_field.through._meta.db_table:
  339. # The field name didn't change, but some options did; we have to propagate this altering.
  340. self._remake_table(
  341. old_field.remote_field.through,
  342. alter_field=(
  343. # We need the field that points to the target model, so we can tell alter_field to change it -
  344. # this is m2m_reverse_field_name() (as opposed to m2m_field_name, which points to our model)
  345. old_field.remote_field.through._meta.get_field(old_field.m2m_reverse_field_name()),
  346. new_field.remote_field.through._meta.get_field(new_field.m2m_reverse_field_name()),
  347. ),
  348. )
  349. return
  350. # Make a new through table
  351. self.create_model(new_field.remote_field.through)
  352. # Copy the data across
  353. self.execute("INSERT INTO %s (%s) SELECT %s FROM %s" % (
  354. self.quote_name(new_field.remote_field.through._meta.db_table),
  355. ', '.join([
  356. "id",
  357. new_field.m2m_column_name(),
  358. new_field.m2m_reverse_name(),
  359. ]),
  360. ', '.join([
  361. "id",
  362. old_field.m2m_column_name(),
  363. old_field.m2m_reverse_name(),
  364. ]),
  365. self.quote_name(old_field.remote_field.through._meta.db_table),
  366. ))
  367. # Delete the old through table
  368. self.delete_model(old_field.remote_field.through)
  369. def add_constraint(self, model, constraint):
  370. if isinstance(constraint, UniqueConstraint) and constraint.condition:
  371. super().add_constraint(model, constraint)
  372. else:
  373. self._remake_table(model)
  374. def remove_constraint(self, model, constraint):
  375. if isinstance(constraint, UniqueConstraint) and constraint.condition:
  376. super().remove_constraint(model, constraint)
  377. else:
  378. self._remake_table(model)