diff options
author | Simon Charette <charette.s@gmail.com> | 2017-11-26 22:39:43 -0500 |
---|---|---|
committer | Simon Charette <charette.s@gmail.com> | 2017-12-01 22:12:24 -0500 |
commit | 095c1aaa898bed40568009db836aa8434f1b983d (patch) | |
tree | fe6002bd40b12bedb73a683120c7f2407024590d /django/db/backends/sqlite3/schema.py | |
parent | 474bd7a5d4b0b47eeedc03ad471ae9e630e95258 (diff) | |
download | django-095c1aaa898bed40568009db836aa8434f1b983d.tar.gz |
Fixed #28849 -- Fixed referenced table and column rename on SQLite.
Thanks Ramiro for the input and Tim for the review.
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 71f6c1c303..c2950ffd2e 100644 --- a/django/db/backends/sqlite3/schema.py +++ b/django/db/backends/sqlite3/schema.py @@ -5,6 +5,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): @@ -53,6 +55,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 @@ -176,8 +230,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) |