Fixed #33194 -- Fixed migrations when altering a field with functional indexes/unique constraints on SQLite.

This adjusts Expressions.rename_table_references() to only update alias
when needed.

Regression in 83fcfc9ec8.

Co-authored-by: Simon Charette <charettes@users.noreply.github.com>
This commit is contained in:
Hannes Ljungberg 2021-10-15 22:17:26 +02:00 committed by Mariusz Felisiak
parent 0d4e575c96
commit 86971c4090
4 changed files with 64 additions and 6 deletions

View File

@ -212,11 +212,7 @@ class Expressions(TableColumns):
def rename_table_references(self, old_table, new_table): def rename_table_references(self, old_table, new_table):
if self.table != old_table: if self.table != old_table:
return return
expressions = deepcopy(self.expressions) self.expressions = self.expressions.relabeled_clone({old_table: new_table})
self.columns = []
for col in self.compiler.query._gen_cols([expressions]):
col.alias = new_table
self.expressions = expressions
super().rename_table_references(old_table, new_table) super().rename_table_references(old_table, new_table)
def rename_column_references(self, table, old_column, new_column): def rename_column_references(self, table, old_column, new_column):

View File

@ -10,4 +10,5 @@ Django 3.2.9 fixes several bugs in 3.2.8 and adds compatibility with Python
Bugfixes 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`).

View File

@ -5,6 +5,7 @@ from django.db.backends.ddl_references import (
from django.db.models import ExpressionList, F from django.db.models import ExpressionList, F
from django.db.models.functions import Upper from django.db.models.functions import Upper
from django.db.models.indexes import IndexExpression from django.db.models.indexes import IndexExpression
from django.db.models.sql import Query
from django.test import SimpleTestCase, TransactionTestCase from django.test import SimpleTestCase, TransactionTestCase
from .models import Person from .models import Person
@ -229,6 +230,27 @@ class ExpressionsTests(TransactionTestCase):
str(self.expressions), 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): def test_rename_column_references(self):
table = Person._meta.db_table table = Person._meta.db_table
self.expressions.rename_column_references(table, 'first_name', 'other') self.expressions.rename_column_references(table, 'first_name', 'other')

View File

@ -2106,6 +2106,25 @@ class OperationTests(OperationTestBase):
self.assertEqual(definition[1], []) self.assertEqual(definition[1], [])
self.assertEqual(definition[2], {'model_name': 'Pony', 'name': index_name}) 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): def test_alter_field_with_index(self):
""" """
Test AlterField operation with an index to ensure indexes created via Test AlterField operation with an index to ensure indexes created via
@ -2664,6 +2683,26 @@ class OperationTests(OperationTestBase):
'name': 'covering_pink_constraint_rm', '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): def test_add_func_unique_constraint(self):
app_label = 'test_adfuncuc' app_label = 'test_adfuncuc'
constraint_name = f'{app_label}_pony_abs_uq' constraint_name = f'{app_label}_pony_abs_uq'