Fixed #24757 -- Recreated MySQL index when needed during combined index removal
Thanks Thomas Recouvreux for the report and Tim Graham for the tests and review.
This commit is contained in:
parent
0eaef8e527
commit
ae635cc365
|
@ -315,15 +315,7 @@ class BaseDatabaseSchemaEditor(object):
|
||||||
news = set(tuple(fields) for fields in new_unique_together)
|
news = set(tuple(fields) for fields in new_unique_together)
|
||||||
# Deleted uniques
|
# Deleted uniques
|
||||||
for fields in olds.difference(news):
|
for fields in olds.difference(news):
|
||||||
columns = [model._meta.get_field(field).column for field in fields]
|
self._delete_composed_index(model, fields, {'unique': True}, self.sql_delete_unique)
|
||||||
constraint_names = self._constraint_names(model, columns, unique=True)
|
|
||||||
if len(constraint_names) != 1:
|
|
||||||
raise ValueError("Found wrong number (%s) of constraints for %s(%s)" % (
|
|
||||||
len(constraint_names),
|
|
||||||
model._meta.db_table,
|
|
||||||
", ".join(columns),
|
|
||||||
))
|
|
||||||
self.execute(self._delete_constraint_sql(self.sql_delete_unique, model, constraint_names[0]))
|
|
||||||
# Created uniques
|
# Created uniques
|
||||||
for fields in news.difference(olds):
|
for fields in news.difference(olds):
|
||||||
columns = [model._meta.get_field(field).column for field in fields]
|
columns = [model._meta.get_field(field).column for field in fields]
|
||||||
|
@ -339,19 +331,22 @@ class BaseDatabaseSchemaEditor(object):
|
||||||
news = set(tuple(fields) for fields in new_index_together)
|
news = set(tuple(fields) for fields in new_index_together)
|
||||||
# Deleted indexes
|
# Deleted indexes
|
||||||
for fields in olds.difference(news):
|
for fields in olds.difference(news):
|
||||||
|
self._delete_composed_index(model, fields, {'index': True}, self.sql_delete_index)
|
||||||
|
# Created indexes
|
||||||
|
for field_names in news.difference(olds):
|
||||||
|
fields = [model._meta.get_field(field) for field in field_names]
|
||||||
|
self.execute(self._create_index_sql(model, fields, suffix="_idx"))
|
||||||
|
|
||||||
|
def _delete_composed_index(self, model, fields, constraint_kwargs, sql):
|
||||||
columns = [model._meta.get_field(field).column for field in fields]
|
columns = [model._meta.get_field(field).column for field in fields]
|
||||||
constraint_names = self._constraint_names(model, list(columns), index=True)
|
constraint_names = self._constraint_names(model, columns, **constraint_kwargs)
|
||||||
if len(constraint_names) != 1:
|
if len(constraint_names) != 1:
|
||||||
raise ValueError("Found wrong number (%s) of constraints for %s(%s)" % (
|
raise ValueError("Found wrong number (%s) of constraints for %s(%s)" % (
|
||||||
len(constraint_names),
|
len(constraint_names),
|
||||||
model._meta.db_table,
|
model._meta.db_table,
|
||||||
", ".join(columns),
|
", ".join(columns),
|
||||||
))
|
))
|
||||||
self.execute(self._delete_constraint_sql(self.sql_delete_index, model, constraint_names[0]))
|
self.execute(self._delete_constraint_sql(sql, model, constraint_names[0]))
|
||||||
# Created indexes
|
|
||||||
for field_names in news.difference(olds):
|
|
||||||
fields = [model._meta.get_field(field) for field in field_names]
|
|
||||||
self.execute(self._create_index_sql(model, fields, suffix="_idx"))
|
|
||||||
|
|
||||||
def alter_db_table(self, model, old_db_table, new_db_table):
|
def alter_db_table(self, model, old_db_table, new_db_table):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -62,6 +62,24 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
|
||||||
field.db_index = False
|
field.db_index = False
|
||||||
return super(DatabaseSchemaEditor, self)._model_indexes_sql(model)
|
return super(DatabaseSchemaEditor, self)._model_indexes_sql(model)
|
||||||
|
|
||||||
|
def _delete_composed_index(self, model, fields, *args):
|
||||||
|
"""
|
||||||
|
MySQL can remove an implicit FK index on a field when that field is
|
||||||
|
covered by another index like a unique_together. "covered" here means
|
||||||
|
that the more complex index starts like the simpler one.
|
||||||
|
http://bugs.mysql.com/bug.php?id=37910 / Django ticket #24757
|
||||||
|
We check here before removing the [unique|index]_together if we have to
|
||||||
|
recreate a FK index.
|
||||||
|
"""
|
||||||
|
first_field = model._meta.get_field(fields[0])
|
||||||
|
if first_field.get_internal_type() == 'ForeignKey':
|
||||||
|
constraint_names = self._constraint_names(model, fields[0], index=True)
|
||||||
|
if not constraint_names:
|
||||||
|
self.execute(
|
||||||
|
self._create_index_sql(model, [model._meta.get_field(fields[0])], suffix="")
|
||||||
|
)
|
||||||
|
return super(DatabaseSchemaEditor, self)._delete_composed_index(model, fields, *args)
|
||||||
|
|
||||||
def _alter_column_type_sql(self, table, old_field, new_field, new_type):
|
def _alter_column_type_sql(self, table, old_field, new_field, new_type):
|
||||||
# Keep null property of old field, if it has changed, it will be handled separately
|
# Keep null property of old field, if it has changed, it will be handled separately
|
||||||
if old_field.null:
|
if old_field.null:
|
||||||
|
|
|
@ -27,3 +27,6 @@ Bugfixes
|
||||||
:ticket:`24712`).
|
:ticket:`24712`).
|
||||||
|
|
||||||
* Fixed ``isnull`` lookup for ``HStoreField`` (:ticket:`24751`).
|
* Fixed ``isnull`` lookup for ``HStoreField`` (:ticket:`24751`).
|
||||||
|
|
||||||
|
* Fixed a MySQL crash when a migration removes a combined index (unique_together
|
||||||
|
or index_together) containing a foreign key (:ticket:`24757`).
|
||||||
|
|
|
@ -1084,6 +1084,24 @@ class SchemaTests(TransactionTestCase):
|
||||||
self.assertRaises(IntegrityError, UniqueTest.objects.create, year=2012, slug="foo")
|
self.assertRaises(IntegrityError, UniqueTest.objects.create, year=2012, slug="foo")
|
||||||
UniqueTest.objects.all().delete()
|
UniqueTest.objects.all().delete()
|
||||||
|
|
||||||
|
def test_unique_together_with_fk(self):
|
||||||
|
"""
|
||||||
|
Tests removing and adding unique_together constraints that include
|
||||||
|
a foreign key.
|
||||||
|
"""
|
||||||
|
# Create the table
|
||||||
|
with connection.schema_editor() as editor:
|
||||||
|
editor.create_model(Author)
|
||||||
|
editor.create_model(Book)
|
||||||
|
# Ensure the fields are unique to begin with
|
||||||
|
self.assertEqual(Book._meta.unique_together, ())
|
||||||
|
# Add the unique_together constraint
|
||||||
|
with connection.schema_editor() as editor:
|
||||||
|
editor.alter_unique_together(Book, [], [['author', 'title']])
|
||||||
|
# Alter it back
|
||||||
|
with connection.schema_editor() as editor:
|
||||||
|
editor.alter_unique_together(Book, [['author', 'title']], [])
|
||||||
|
|
||||||
def test_index_together(self):
|
def test_index_together(self):
|
||||||
"""
|
"""
|
||||||
Tests removing and adding index_together constraints on a model.
|
Tests removing and adding index_together constraints on a model.
|
||||||
|
@ -1127,6 +1145,24 @@ class SchemaTests(TransactionTestCase):
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_index_together_with_fk(self):
|
||||||
|
"""
|
||||||
|
Tests removing and adding index_together constraints that include
|
||||||
|
a foreign key.
|
||||||
|
"""
|
||||||
|
# Create the table
|
||||||
|
with connection.schema_editor() as editor:
|
||||||
|
editor.create_model(Author)
|
||||||
|
editor.create_model(Book)
|
||||||
|
# Ensure the fields are unique to begin with
|
||||||
|
self.assertEqual(Book._meta.index_together, ())
|
||||||
|
# Add the unique_together constraint
|
||||||
|
with connection.schema_editor() as editor:
|
||||||
|
editor.alter_index_together(Book, [], [['author', 'title']])
|
||||||
|
# Alter it back
|
||||||
|
with connection.schema_editor() as editor:
|
||||||
|
editor.alter_index_together(Book, [['author', 'title']], [])
|
||||||
|
|
||||||
def test_create_index_together(self):
|
def test_create_index_together(self):
|
||||||
"""
|
"""
|
||||||
Tests creating models with index_together already defined
|
Tests creating models with index_together already defined
|
||||||
|
|
Loading…
Reference in New Issue