summaryrefslogtreecommitdiff
path: root/django/db/backends/sqlite3/schema.py
diff options
context:
space:
mode:
authorSimon Charette <charette.s@gmail.com>2017-11-26 22:39:43 -0500
committerSimon Charette <charette.s@gmail.com>2017-12-01 22:12:24 -0500
commit095c1aaa898bed40568009db836aa8434f1b983d (patch)
treefe6002bd40b12bedb73a683120c7f2407024590d /django/db/backends/sqlite3/schema.py
parent474bd7a5d4b0b47eeedc03ad471ae9e630e95258 (diff)
downloaddjango-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.py60
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)