diff options
Diffstat (limited to 'django/db/backends/sqlite3/schema.py')
| -rw-r--r-- | django/db/backends/sqlite3/schema.py | 60 |
1 files changed, 58 insertions, 2 deletions
diff --git a/django/db/backends/sqlite3/schema.py b/django/db/backends/sqlite3/schema.py index 93cf077dd2..8c427bee45 100644 --- a/django/db/backends/sqlite3/schema.py +++ b/django/db/backends/sqlite3/schema.py @@ -6,6 +6,8 @@ from decimal import Decimal from django.apps.registry import Apps from django.db.backends.base.schema import BaseDatabaseSchemaEditor from django.db.backends.ddl_references import Statement +from django.db.transaction import atomic +from django.db.utils import NotSupportedError class DatabaseSchemaEditor(BaseDatabaseSchemaEditor): @@ -59,6 +61,58 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor): else: raise ValueError("Cannot quote parameter value %r of type %s" % (value, type(value))) + def alter_db_table(self, model, old_db_table, new_db_table, disable_constraints=True): + if model._meta.related_objects and disable_constraints: + if self.connection.in_atomic_block: + raise NotSupportedError(( + 'Renaming the %r table while in a transaction is not ' + 'supported on SQLite because it would break referential ' + 'integrity. Try adding `atomic = False` to the Migration class.' + ) % old_db_table) + self.connection.enable_constraint_checking() + super().alter_db_table(model, old_db_table, new_db_table) + self.connection.disable_constraint_checking() + else: + super().alter_db_table(model, old_db_table, new_db_table) + + def alter_field(self, model, old_field, new_field, strict=False): + old_field_name = old_field.name + if (new_field.name != old_field_name and + any(r.field_name == old_field.name for r in model._meta.related_objects)): + if self.connection.in_atomic_block: + raise NotSupportedError(( + 'Renaming the %r.%r column while in a transaction is not ' + 'supported on SQLite because it would break referential ' + 'integrity. Try adding `atomic = False` to the Migration class.' + ) % (model._meta.db_table, old_field_name)) + with atomic(self.connection.alias): + super().alter_field(model, old_field, new_field, strict=strict) + # Follow SQLite's documented procedure for performing changes + # that don't affect the on-disk content. + # https://sqlite.org/lang_altertable.html#otheralter + with self.connection.cursor() as cursor: + schema_version = cursor.execute('PRAGMA schema_version').fetchone()[0] + cursor.execute('PRAGMA writable_schema = 1') + table_name = model._meta.db_table + references_template = ' REFERENCES "%s" ("%%s") ' % table_name + old_column_name = old_field.get_attname_column()[1] + new_column_name = new_field.get_attname_column()[1] + search = references_template % old_column_name + replacement = references_template % new_column_name + cursor.execute('UPDATE sqlite_master SET sql = replace(sql, %s, %s)', (search, replacement)) + cursor.execute('PRAGMA schema_version = %d' % (schema_version + 1)) + cursor.execute('PRAGMA writable_schema = 0') + # The integrity check will raise an exception and rollback + # the transaction if the sqlite_master updates corrupt the + # database. + cursor.execute('PRAGMA integrity_check') + # Perform a VACUUM to refresh the database representation from + # the sqlite_master table. + with self.connection.cursor() as cursor: + cursor.execute('VACUUM') + else: + super().alter_field(model, old_field, new_field, strict=strict) + def _remake_table(self, model, create_field=None, delete_field=None, alter_field=None): """ Shortcut to transform a model from old_model into new_model @@ -182,8 +236,10 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor): with altered_table_name(model, model._meta.db_table + "__old"): # Rename the old table to make way for the new - self.alter_db_table(model, temp_model._meta.db_table, model._meta.db_table) - + self.alter_db_table( + model, temp_model._meta.db_table, model._meta.db_table, + disable_constraints=False, + ) # Create a new table with the updated schema. self.create_model(temp_model) |
