diff --git a/django/db/backends/postgresql/schema.py b/django/db/backends/postgresql/schema.py index f6be40f846..8d4db4ea1c 100644 --- a/django/db/backends/postgresql/schema.py +++ b/django/db/backends/postgresql/schema.py @@ -104,6 +104,15 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor): def _alter_field(self, model, old_field, new_field, old_type, new_type, old_db_params, new_db_params, strict=False): + # Drop indexes on varchar/text 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')) + ): + index_name = self._create_index_name(model, [old_field.column], suffix='_like') + self.execute(self._delete_constraint_sql(self.sql_delete_index, model, index_name)) + super()._alter_field( model, old_field, new_field, old_type, new_type, old_db_params, new_db_params, strict, diff --git a/tests/schema/models.py b/tests/schema/models.py index 0e04dbab87..4512d8bc01 100644 --- a/tests/schema/models.py +++ b/tests/schema/models.py @@ -17,6 +17,13 @@ class Author(models.Model): apps = new_apps +class AuthorCharFieldWithIndex(models.Model): + char_field = models.CharField(max_length=31, db_index=True) + + class Meta: + apps = new_apps + + class AuthorTextFieldWithIndex(models.Model): text_field = models.TextField(db_index=True) diff --git a/tests/schema/tests.py b/tests/schema/tests.py index ef4b192a36..293ae8e546 100644 --- a/tests/schema/tests.py +++ b/tests/schema/tests.py @@ -29,11 +29,11 @@ from .fields import ( CustomManyToManyField, InheritedManyToManyField, MediumBlobField, ) from .models import ( - Author, AuthorTextFieldWithIndex, AuthorWithDefaultHeight, - AuthorWithEvenLongerName, AuthorWithIndexedName, Book, BookForeignObj, - BookWeak, BookWithLongName, BookWithO2O, BookWithoutAuthor, BookWithSlug, - IntegerPK, Node, Note, NoteRename, Tag, TagIndexed, TagM2MTest, - TagUniqueRename, Thing, UniqueTest, new_apps, + Author, AuthorCharFieldWithIndex, AuthorTextFieldWithIndex, + AuthorWithDefaultHeight, AuthorWithEvenLongerName, AuthorWithIndexedName, + Book, BookForeignObj, BookWeak, BookWithLongName, BookWithO2O, + BookWithoutAuthor, BookWithSlug, IntegerPK, Node, Note, NoteRename, Tag, + TagIndexed, TagM2MTest, TagUniqueRename, Thing, UniqueTest, new_apps, ) @@ -49,9 +49,10 @@ class SchemaTests(TransactionTestCase): available_apps = [] models = [ - Author, AuthorWithDefaultHeight, AuthorWithEvenLongerName, Book, - BookWeak, BookWithLongName, BookWithO2O, BookWithSlug, IntegerPK, Node, - Note, Tag, TagIndexed, TagM2MTest, TagUniqueRename, Thing, UniqueTest, + Author, AuthorCharFieldWithIndex, AuthorTextFieldWithIndex, + AuthorWithDefaultHeight, AuthorWithEvenLongerName, Book, BookWeak, + BookWithLongName, BookWithO2O, BookWithSlug, IntegerPK, Node, Note, + Tag, TagIndexed, TagM2MTest, TagUniqueRename, Thing, UniqueTest, ] # Utility functions @@ -222,6 +223,49 @@ class SchemaTests(TransactionTestCase): else: self.fail("No FK constraint for author_id found") + @skipUnlessDBFeature('supports_foreign_keys') + def test_char_field_with_db_index_to_fk(self): + # Create the table + with connection.schema_editor() as editor: + editor.create_model(Author) + editor.create_model(AuthorCharFieldWithIndex) + # Change CharField to FK + old_field = AuthorCharFieldWithIndex._meta.get_field('char_field') + new_field = ForeignKey(Author, CASCADE, blank=True) + new_field.set_attributes_from_name('char_field') + with connection.schema_editor() as editor: + editor.alter_field(AuthorCharFieldWithIndex, old_field, new_field, strict=True) + # The new FK constraint is present + constraints = self.get_constraints(AuthorCharFieldWithIndex._meta.db_table) + constraint_fk = None + for name, details in constraints.items(): + if details['columns'] == ['char_field_id'] and details['foreign_key']: + constraint_fk = details['foreign_key'] + break + self.assertEqual(constraint_fk, ('schema_author', 'id')) + + @skipUnlessDBFeature('supports_foreign_keys') + @skipUnlessDBFeature('supports_index_on_text_field') + def test_text_field_with_db_index_to_fk(self): + # Create the table + with connection.schema_editor() as editor: + editor.create_model(Author) + editor.create_model(AuthorTextFieldWithIndex) + # Change TextField to FK + old_field = AuthorTextFieldWithIndex._meta.get_field('text_field') + new_field = ForeignKey(Author, CASCADE, blank=True) + new_field.set_attributes_from_name('text_field') + with connection.schema_editor() as editor: + editor.alter_field(AuthorTextFieldWithIndex, old_field, new_field, strict=True) + # The new FK constraint is present + constraints = self.get_constraints(AuthorTextFieldWithIndex._meta.db_table) + constraint_fk = None + for name, details in constraints.items(): + if details['columns'] == ['text_field_id'] and details['foreign_key']: + constraint_fk = details['foreign_key'] + break + self.assertEqual(constraint_fk, ('schema_author', 'id')) + @skipUnlessDBFeature('supports_foreign_keys') def test_fk_to_proxy(self): "Creating a FK to a proxy model creates database constraints."