瀏覽代碼

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.
Mariusz Felisiak 3 年之前
父節點
當前提交
3702819227
共有 3 個文件被更改,包括 30 次插入0 次删除
  1. 2 0
      django/db/backends/sqlite3/features.py
  2. 10 0
      django/db/backends/sqlite3/schema.py
  3. 18 0
      tests/schema/tests.py

+ 2 - 0
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.

+ 10 - 0
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

+ 18 - 0
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