Fixed #31335 -- Fixed removing composed composed Meta constraints/indexes on foreign keys on MySQL.

This commit is contained in:
Sergey Fursov 2021-12-29 00:18:17 +03:00 committed by Mariusz Felisiak
parent 1b08e9bf7d
commit b731e88415
2 changed files with 73 additions and 7 deletions

View File

@ -1,5 +1,6 @@
from django.db.backends.base.schema import BaseDatabaseSchemaEditor from django.db.backends.base.schema import BaseDatabaseSchemaEditor
from django.db.models import NOT_PROVIDED from django.db.models import NOT_PROVIDED, F, UniqueConstraint
from django.db.models.constants import LOOKUP_SEP
class DatabaseSchemaEditor(BaseDatabaseSchemaEditor): class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
@ -117,6 +118,23 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
[effective_default], [effective_default],
) )
def remove_constraint(self, model, constraint):
if isinstance(constraint, UniqueConstraint):
self._create_missing_fk_index(
model,
fields=constraint.fields,
expressions=constraint.expressions,
)
super().remove_constraint(model, constraint)
def remove_index(self, model, index):
self._create_missing_fk_index(
model,
fields=[field_name for field_name, _ in index.fields_orders],
expressions=index.expressions,
)
super().remove_index(model, index)
def _field_should_be_indexed(self, model, field): def _field_should_be_indexed(self, model, field):
if not super()._field_should_be_indexed(model, field): if not super()._field_should_be_indexed(model, field):
return False return False
@ -140,6 +158,7 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
model, model,
*, *,
fields, fields,
expressions=None,
): ):
""" """
MySQL can remove an implicit FK index on a field when that field is MySQL can remove an implicit FK index on a field when that field is
@ -150,14 +169,36 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
Manually create an implicit FK index to make it possible to remove the Manually create an implicit FK index to make it possible to remove the
composed index. composed index.
""" """
first_field = model._meta.get_field(fields[0]) first_field_name = None
if fields:
first_field_name = fields[0]
elif (
expressions
and self.connection.features.supports_expression_indexes
and isinstance(expressions[0], F)
and LOOKUP_SEP not in expressions[0].name
):
first_field_name = expressions[0].name
if not first_field_name:
return
first_field = model._meta.get_field(first_field_name)
if first_field.get_internal_type() == "ForeignKey": if first_field.get_internal_type() == "ForeignKey":
constraint_names = self._constraint_names( column = self.connection.introspection.identifier_converter(
model, first_field.column
[first_field.column],
index=True,
) )
if not constraint_names: with self.connection.cursor() as cursor:
constraint_names = [
name
for name, infodict in self.connection.introspection.get_constraints(
cursor, model._meta.db_table
).items()
if infodict["index"] and infodict["columns"][0] == column
]
# There are no other indexes that starts with the FK field, only
# the index that is expected to be deleted.
if len(constraint_names) == 1:
self.execute( self.execute(
self._create_index_sql(model, fields=[first_field], suffix="") self._create_index_sql(model, fields=[first_field], suffix="")
) )

View File

@ -2735,6 +2735,24 @@ class SchemaTests(TransactionTestCase):
editor.remove_index(Book, index) editor.remove_index(Book, index)
self.assertNotIn(index.name, self.get_constraints(table)) self.assertNotIn(index.name, self.get_constraints(table))
def test_composed_index_with_fk(self):
index = Index(fields=["author", "title"], name="book_author_title_idx")
self._test_composed_index_with_fk(index)
def test_composed_desc_index_with_fk(self):
index = Index(fields=["-author", "title"], name="book_author_title_idx")
self._test_composed_index_with_fk(index)
@skipUnlessDBFeature("supports_expression_indexes")
def test_composed_func_index_with_fk(self):
index = Index(F("author"), F("title"), name="book_author_title_idx")
self._test_composed_index_with_fk(index)
@skipUnlessDBFeature("supports_expression_indexes")
def test_composed_desc_func_index_with_fk(self):
index = Index(F("author").desc(), F("title"), name="book_author_title_idx")
self._test_composed_index_with_fk(index)
@skipUnlessDBFeature("supports_expression_indexes") @skipUnlessDBFeature("supports_expression_indexes")
def test_composed_func_transform_index_with_fk(self): def test_composed_func_transform_index_with_fk(self):
index = Index(F("title__lower"), name="book_title_lower_idx") index = Index(F("title__lower"), name="book_title_lower_idx")
@ -2756,6 +2774,13 @@ class SchemaTests(TransactionTestCase):
editor.remove_constraint(Book, constraint) editor.remove_constraint(Book, constraint)
self.assertNotIn(constraint.name, self.get_constraints(table)) self.assertNotIn(constraint.name, self.get_constraints(table))
def test_composed_constraint_with_fk(self):
constraint = UniqueConstraint(
fields=["author", "title"],
name="book_author_title_uniq",
)
self._test_composed_constraint_with_fk(constraint)
@skipUnlessDBFeature( @skipUnlessDBFeature(
"supports_column_check_constraints", "can_introspect_check_constraints" "supports_column_check_constraints", "can_introspect_check_constraints"
) )