From 9f8c994851804863d556854f5231316eec478bd5 Mon Sep 17 00:00:00 2001 From: David Sanders Date: Sat, 24 Sep 2022 01:40:17 +1000 Subject: [PATCH] Fixed #34027 -- Fixed migrations crash when altering type of char/text fields referenced by foreign key on PostgreSQL. --- django/db/backends/postgresql/schema.py | 26 +++++++++-------- tests/migrations/test_operations.py | 39 +++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 12 deletions(-) diff --git a/django/db/backends/postgresql/schema.py b/django/db/backends/postgresql/schema.py index cfc30516e6..98af319570 100644 --- a/django/db/backends/postgresql/schema.py +++ b/django/db/backends/postgresql/schema.py @@ -131,6 +131,20 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor): return "" def _alter_column_type_sql(self, model, old_field, new_field, new_type): + # Drop indexes on varchar/text/citext columns that are changing to a + # different type. + old_db_params = old_field.db_parameters(connection=self.connection) + old_type = old_db_params["type"] + if (old_field.db_index or old_field.unique) and ( + (old_type.startswith("varchar") and not new_type.startswith("varchar")) + or (old_type.startswith("text") and not new_type.startswith("text")) + or (old_type.startswith("citext") and not new_type.startswith("citext")) + ): + index_name = self._create_index_name( + model._meta.db_table, [old_field.column], suffix="_like" + ) + self.execute(self._delete_index_sql(model, index_name)) + self.sql_alter_column_type = "ALTER COLUMN %(column)s TYPE %(type)s" # Cast when data type changed. if using_sql := self._using_sql(new_field, old_field): @@ -227,18 +241,6 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor): new_db_params, strict=False, ): - # Drop indexes on varchar/text/citext columns that are changing to a - # different type. - if (old_field.db_index or old_field.unique) and ( - (old_type.startswith("varchar") and not new_type.startswith("varchar")) - or (old_type.startswith("text") and not new_type.startswith("text")) - or (old_type.startswith("citext") and not new_type.startswith("citext")) - ): - index_name = self._create_index_name( - model._meta.db_table, [old_field.column], suffix="_like" - ) - self.execute(self._delete_index_sql(model, index_name)) - super()._alter_field( model, old_field, diff --git a/tests/migrations/test_operations.py b/tests/migrations/test_operations.py index 9d0eae7f01..e02dc9ef3f 100644 --- a/tests/migrations/test_operations.py +++ b/tests/migrations/test_operations.py @@ -2436,6 +2436,45 @@ class OperationTests(OperationTestBase): ], ) + def test_alter_field_pk_fk_char_to_int(self): + app_label = "alter_field_pk_fk_char_to_int" + project_state = self.apply_operations( + app_label, + ProjectState(), + operations=[ + migrations.CreateModel( + name="Parent", + fields=[ + ("id", models.CharField(max_length=255, primary_key=True)), + ], + ), + migrations.CreateModel( + name="Child", + fields=[ + ("id", models.BigAutoField(primary_key=True)), + ( + "parent", + models.ForeignKey( + f"{app_label}.Parent", + on_delete=models.CASCADE, + ), + ), + ], + ), + ], + ) + self.apply_operations( + app_label, + project_state, + operations=[ + migrations.AlterField( + model_name="parent", + name="id", + field=models.BigIntegerField(primary_key=True), + ), + ], + ) + def test_rename_field_reloads_state_on_fk_target_changes(self): """ If RenameField doesn't reload state appropriately, the AlterField