From 3702819227fd0cdd9b581cd99e11d1561d51cbeb Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Fri, 11 Feb 2022 22:21:58 +0100 Subject: [PATCH] Refs #32502 -- Avoided table rebuild when removing fields on SQLite 3.35.5+. ALTER TABLE ... DROP COLUMN was introduced in SQLite 3.35+ however a data corruption issue was fixed in SQLite 3.35.5. --- django/db/backends/sqlite3/features.py | 2 ++ django/db/backends/sqlite3/schema.py | 10 ++++++++++ tests/schema/tests.py | 18 ++++++++++++++++++ 3 files changed, 30 insertions(+) diff --git a/django/db/backends/sqlite3/features.py b/django/db/backends/sqlite3/features.py index d0c9dc5391..9161ae3133 100644 --- a/django/db/backends/sqlite3/features.py +++ b/django/db/backends/sqlite3/features.py @@ -28,6 +28,8 @@ class DatabaseFeatures(BaseDatabaseFeatures): has_case_insensitive_like = True # Is "ALTER TABLE ... RENAME COLUMN" supported? can_alter_table_rename_column = Database.sqlite_version_info >= (3, 25, 0) + # Is "ALTER TABLE ... DROP COLUMN" supported? + can_alter_table_drop_column = Database.sqlite_version_info >= (3, 35, 5) supports_parentheses_in_compound = False # Deferred constraint checks can be emulated on SQLite < 3.20 but not in a # reasonably performant way. diff --git a/django/db/backends/sqlite3/schema.py b/django/db/backends/sqlite3/schema.py index 18449026bd..b3a2f4282b 100644 --- a/django/db/backends/sqlite3/schema.py +++ b/django/db/backends/sqlite3/schema.py @@ -18,6 +18,7 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor): "REFERENCES %(to_table)s (%(to_column)s) DEFERRABLE INITIALLY DEFERRED" ) sql_create_column_inline_fk = sql_create_inline_fk + sql_delete_column = "ALTER TABLE %(table)s DROP COLUMN %(column)s" sql_create_unique = "CREATE UNIQUE INDEX %(name)s ON %(table)s (%(columns)s)" sql_delete_unique = "DROP INDEX %(name)s" @@ -400,6 +401,15 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor): if field.remote_field.through._meta.auto_created: self.delete_model(field.remote_field.through) # For explicit "through" M2M fields, do nothing + elif ( + self.connection.features.can_alter_table_drop_column + # Primary keys, unique fields, and foreign keys are not + # supported in ALTER TABLE DROP COLUMN. + and not field.primary_key + and not field.unique + and not (field.remote_field and field.db_constraint) + ): + super().remove_field(model, field) # For everything else, remake. else: # It might not actually have a column behind it diff --git a/tests/schema/tests.py b/tests/schema/tests.py index 208421eb64..37f2946006 100644 --- a/tests/schema/tests.py +++ b/tests/schema/tests.py @@ -789,6 +789,24 @@ class SchemaTests(TransactionTestCase): # Introspection treats BLOBs as TextFields self.assertEqual(columns["bits"][0], "TextField") + def test_remove_field(self): + with connection.schema_editor() as editor: + editor.create_model(Author) + with CaptureQueriesContext(connection) as ctx: + editor.remove_field(Author, Author._meta.get_field("name")) + columns = self.column_classes(Author) + self.assertNotIn("name", columns) + if getattr(connection.features, "can_alter_table_drop_column", True): + # Table is not rebuilt. + self.assertIs( + any("CREATE TABLE" in query["sql"] for query in ctx.captured_queries), + False, + ) + self.assertIs( + any("DROP TABLE" in query["sql"] for query in ctx.captured_queries), + False, + ) + def test_alter(self): """ Tests simple altering of fields