diff --git a/django/db/backends/ddl_references.py b/django/db/backends/ddl_references.py index c06386a2fa..f798fd648b 100644 --- a/django/db/backends/ddl_references.py +++ b/django/db/backends/ddl_references.py @@ -212,11 +212,7 @@ class Expressions(TableColumns): def rename_table_references(self, old_table, new_table): if self.table != old_table: return - expressions = deepcopy(self.expressions) - self.columns = [] - for col in self.compiler.query._gen_cols([expressions]): - col.alias = new_table - self.expressions = expressions + self.expressions = self.expressions.relabeled_clone({old_table: new_table}) super().rename_table_references(old_table, new_table) def rename_column_references(self, table, old_column, new_column): diff --git a/docs/releases/3.2.9.txt b/docs/releases/3.2.9.txt index 56cbd1d223..a03542c0b6 100644 --- a/docs/releases/3.2.9.txt +++ b/docs/releases/3.2.9.txt @@ -10,4 +10,5 @@ Django 3.2.9 fixes several bugs in 3.2.8 and adds compatibility with Python Bugfixes ======== -* ... +* Fixed a bug in Django 3.2 that caused a migration crash on SQLite when + altering a field with a functional index (:ticket:`33194`). diff --git a/tests/backends/test_ddl_references.py b/tests/backends/test_ddl_references.py index bd4036ee33..ab3ba6ccd2 100644 --- a/tests/backends/test_ddl_references.py +++ b/tests/backends/test_ddl_references.py @@ -5,6 +5,7 @@ from django.db.backends.ddl_references import ( from django.db.models import ExpressionList, F from django.db.models.functions import Upper from django.db.models.indexes import IndexExpression +from django.db.models.sql import Query from django.test import SimpleTestCase, TransactionTestCase from .models import Person @@ -229,6 +230,27 @@ class ExpressionsTests(TransactionTestCase): str(self.expressions), ) + def test_rename_table_references_without_alias(self): + compiler = Query(Person, alias_cols=False).get_compiler(connection=connection) + table = Person._meta.db_table + expressions = Expressions( + table=table, + expressions=ExpressionList( + IndexExpression(Upper('last_name')), + IndexExpression(F('first_name')), + ).resolve_expression(compiler.query), + compiler=compiler, + quote_value=self.editor.quote_value, + ) + expressions.rename_table_references(table, 'other') + self.assertIs(expressions.references_table(table), False) + self.assertIs(expressions.references_table('other'), True) + expected_str = '(UPPER(%s)), %s' % ( + self.editor.quote_name('last_name'), + self.editor.quote_name('first_name'), + ) + self.assertEqual(str(expressions), expected_str) + def test_rename_column_references(self): table = Person._meta.db_table self.expressions.rename_column_references(table, 'first_name', 'other') diff --git a/tests/migrations/test_operations.py b/tests/migrations/test_operations.py index 5ae75a477b..11961a1f40 100644 --- a/tests/migrations/test_operations.py +++ b/tests/migrations/test_operations.py @@ -2106,6 +2106,25 @@ class OperationTests(OperationTestBase): self.assertEqual(definition[1], []) self.assertEqual(definition[2], {'model_name': 'Pony', 'name': index_name}) + @skipUnlessDBFeature('supports_expression_indexes') + def test_alter_field_with_func_index(self): + app_label = 'test_alfuncin' + index_name = f'{app_label}_pony_idx' + table_name = f'{app_label}_pony' + project_state = self.set_up_test_model( + app_label, + indexes=[models.Index(Abs('pink'), name=index_name)], + ) + operation = migrations.AlterField('Pony', 'pink', models.IntegerField(null=True)) + new_state = project_state.clone() + operation.state_forwards(app_label, new_state) + with connection.schema_editor() as editor: + operation.database_forwards(app_label, editor, project_state, new_state) + self.assertIndexNameExists(table_name, index_name) + with connection.schema_editor() as editor: + operation.database_backwards(app_label, editor, new_state, project_state) + self.assertIndexNameExists(table_name, index_name) + def test_alter_field_with_index(self): """ Test AlterField operation with an index to ensure indexes created via @@ -2664,6 +2683,26 @@ class OperationTests(OperationTestBase): 'name': 'covering_pink_constraint_rm', }) + def test_alter_field_with_func_unique_constraint(self): + app_label = 'test_alfuncuc' + constraint_name = f'{app_label}_pony_uq' + table_name = f'{app_label}_pony' + project_state = self.set_up_test_model( + app_label, + constraints=[models.UniqueConstraint('pink', 'weight', name=constraint_name)] + ) + operation = migrations.AlterField('Pony', 'pink', models.IntegerField(null=True)) + new_state = project_state.clone() + operation.state_forwards(app_label, new_state) + with connection.schema_editor() as editor: + operation.database_forwards(app_label, editor, project_state, new_state) + if connection.features.supports_expression_indexes: + self.assertIndexNameExists(table_name, constraint_name) + with connection.schema_editor() as editor: + operation.database_backwards(app_label, editor, new_state, project_state) + if connection.features.supports_expression_indexes: + self.assertIndexNameExists(table_name, constraint_name) + def test_add_func_unique_constraint(self): app_label = 'test_adfuncuc' constraint_name = f'{app_label}_pony_abs_uq'