Skip to content

Commit 6afede8

Browse files
MarkusHtimgraham
authored andcommitted
[1.11.x] Fixed #28052 -- Prevented dropping Meta.indexes when changing db_index to False.
Thanks Marc Tamlyn for the report and Ian Foote/Tim Graham for review. Backport of 663e489 from master
1 parent 211d2bf commit 6afede8

File tree

4 files changed

+68
-6
lines changed

4 files changed

+68
-6
lines changed

django/db/backends/base/schema.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from datetime import datetime
44

55
from django.db.backends.utils import strip_quotes
6+
from django.db.models import Index
67
from django.db.transaction import TransactionManagementError, atomic
78
from django.utils import six, timezone
89
from django.utils.encoding import force_bytes
@@ -564,8 +565,16 @@ def _alter_field(self, model, old_field, new_field, old_type, new_type,
564565
# True | False | True | True
565566
if old_field.db_index and not old_field.unique and (not new_field.db_index or new_field.unique):
566567
# Find the index for this field
567-
index_names = self._constraint_names(model, [old_field.column], index=True)
568+
meta_index_names = {index.name for index in model._meta.indexes}
569+
# Retrieve only BTREE indexes since this is what's created with
570+
# db_index=True.
571+
index_names = self._constraint_names(model, [old_field.column], index=True, type_=Index.suffix)
568572
for index_name in index_names:
573+
if index_name in meta_index_names:
574+
# There only way to check if an index was created with
575+
# db_index=True or with Index(['field'], name='foo')
576+
# is to look at its name (refs #28053).
577+
continue
569578
self.execute(self._delete_constraint_sql(self.sql_delete_index, model, index_name))
570579
# Change check constraints?
571580
if old_db_params['check'] != new_db_params['check'] and old_db_params['check']:
@@ -966,7 +975,7 @@ def _delete_constraint_sql(self, template, model, name):
966975

967976
def _constraint_names(self, model, column_names=None, unique=None,
968977
primary_key=None, index=None, foreign_key=None,
969-
check=None):
978+
check=None, type_=None):
970979
"""
971980
Returns all constraint names matching the columns and conditions
972981
"""
@@ -990,5 +999,7 @@ def _constraint_names(self, model, column_names=None, unique=None,
990999
continue
9911000
if foreign_key is not None and not infodict['foreign_key']:
9921001
continue
1002+
if type_ is not None and infodict['type'] != type_:
1003+
continue
9931004
result.append(name)
9941005
return result

docs/releases/1.11.1.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,3 +72,6 @@ Bugfixes
7272

7373
* Prevented ``AddIndex`` and ``RemoveIndex`` from mutating model state
7474
(:ticket:`28043`).
75+
76+
* Prevented migrations from dropping database indexes from ``Meta.indexes``
77+
when changing ``Field.db_index`` to ``False`` (:ticket:`28052`).

tests/schema/models.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,13 @@ class Meta:
3434
apps = new_apps
3535

3636

37+
class AuthorWithIndexedName(models.Model):
38+
name = models.CharField(max_length=255, db_index=True)
39+
40+
class Meta:
41+
apps = new_apps
42+
43+
3744
class Book(models.Model):
3845
author = models.ForeignKey(Author, models.CASCADE)
3946
title = models.CharField(max_length=100, db_index=True)

tests/schema/tests.py

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,11 @@
2828
CustomManyToManyField, InheritedManyToManyField, MediumBlobField,
2929
)
3030
from .models import (
31-
Author, AuthorWithDefaultHeight, AuthorWithEvenLongerName, Book,
32-
BookForeignObj, BookWeak, BookWithLongName, BookWithO2O, BookWithoutAuthor,
33-
BookWithSlug, IntegerPK, Node, Note, NoteRename, Tag, TagIndexed,
34-
TagM2MTest, TagUniqueRename, Thing, UniqueTest, new_apps,
31+
Author, AuthorWithDefaultHeight, AuthorWithEvenLongerName,
32+
AuthorWithIndexedName, Book, BookForeignObj, BookWeak, BookWithLongName,
33+
BookWithO2O, BookWithoutAuthor, BookWithSlug, IntegerPK, Node, Note,
34+
NoteRename, Tag, TagIndexed, TagM2MTest, TagUniqueRename, Thing,
35+
UniqueTest, new_apps,
3536
)
3637

3738

@@ -1632,6 +1633,46 @@ def test_add_remove_index(self):
16321633
editor.remove_index(Author, index)
16331634
self.assertNotIn('name', self.get_indexes(Author._meta.db_table))
16341635

1636+
def test_remove_db_index_doesnt_remove_custom_indexes(self):
1637+
"""
1638+
Changing db_index to False doesn't remove indexes from Meta.indexes.
1639+
"""
1640+
with connection.schema_editor() as editor:
1641+
editor.create_model(AuthorWithIndexedName)
1642+
# Ensure the table has its index
1643+
self.assertIn('name', self.get_indexes(AuthorWithIndexedName._meta.db_table))
1644+
1645+
# Add the custom index
1646+
index = Index(fields=['-name'], name='author_name_idx')
1647+
author_index_name = index.name
1648+
with connection.schema_editor() as editor:
1649+
db_index_name = editor._create_index_name(
1650+
model=AuthorWithIndexedName,
1651+
column_names=('name',),
1652+
)
1653+
if connection.features.uppercases_column_names:
1654+
author_index_name = author_index_name.upper()
1655+
db_index_name = db_index_name.upper()
1656+
try:
1657+
AuthorWithIndexedName._meta.indexes = [index]
1658+
with connection.schema_editor() as editor:
1659+
editor.add_index(AuthorWithIndexedName, index)
1660+
old_constraints = self.get_constraints(AuthorWithIndexedName._meta.db_table)
1661+
self.assertIn(author_index_name, old_constraints)
1662+
self.assertIn(db_index_name, old_constraints)
1663+
# Change name field to db_index=False
1664+
old_field = AuthorWithIndexedName._meta.get_field('name')
1665+
new_field = CharField(max_length=255)
1666+
new_field.set_attributes_from_name('name')
1667+
with connection.schema_editor() as editor:
1668+
editor.alter_field(AuthorWithIndexedName, old_field, new_field, strict=True)
1669+
new_constraints = self.get_constraints(AuthorWithIndexedName._meta.db_table)
1670+
self.assertNotIn(db_index_name, new_constraints)
1671+
# The index from Meta.indexes is still in the database.
1672+
self.assertIn(author_index_name, new_constraints)
1673+
finally:
1674+
AuthorWithIndexedName._meta.indexes = []
1675+
16351676
def test_order_index(self):
16361677
"""
16371678
Indexes defined with ordering (ASC/DESC) defined on column

0 commit comments

Comments
 (0)