mirror of https://github.com/django/django.git
Refs #27064 -- Added RenameIndex migration operation.
This commit is contained in:
parent
20e65a34ae
commit
eacd4977f6
|
@ -176,6 +176,9 @@ class BaseDatabaseFeatures:
|
||||||
# Can it create foreign key constraints inline when adding columns?
|
# Can it create foreign key constraints inline when adding columns?
|
||||||
can_create_inline_fk = True
|
can_create_inline_fk = True
|
||||||
|
|
||||||
|
# Can an index be renamed?
|
||||||
|
can_rename_index = False
|
||||||
|
|
||||||
# Does it automatically index foreign keys?
|
# Does it automatically index foreign keys?
|
||||||
indexes_foreign_keys = True
|
indexes_foreign_keys = True
|
||||||
|
|
||||||
|
|
|
@ -131,6 +131,7 @@ class BaseDatabaseSchemaEditor:
|
||||||
"CREATE UNIQUE INDEX %(name)s ON %(table)s "
|
"CREATE UNIQUE INDEX %(name)s ON %(table)s "
|
||||||
"(%(columns)s)%(include)s%(condition)s"
|
"(%(columns)s)%(include)s%(condition)s"
|
||||||
)
|
)
|
||||||
|
sql_rename_index = "ALTER INDEX %(old_name)s RENAME TO %(new_name)s"
|
||||||
sql_delete_index = "DROP INDEX %(name)s"
|
sql_delete_index = "DROP INDEX %(name)s"
|
||||||
|
|
||||||
sql_create_pk = (
|
sql_create_pk = (
|
||||||
|
@ -492,6 +493,16 @@ class BaseDatabaseSchemaEditor:
|
||||||
return None
|
return None
|
||||||
self.execute(index.remove_sql(model, self))
|
self.execute(index.remove_sql(model, self))
|
||||||
|
|
||||||
|
def rename_index(self, model, old_index, new_index):
|
||||||
|
if self.connection.features.can_rename_index:
|
||||||
|
self.execute(
|
||||||
|
self._rename_index_sql(model, old_index.name, new_index.name),
|
||||||
|
params=None,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self.remove_index(model, old_index)
|
||||||
|
self.add_index(model, new_index)
|
||||||
|
|
||||||
def add_constraint(self, model, constraint):
|
def add_constraint(self, model, constraint):
|
||||||
"""Add a constraint to a model."""
|
"""Add a constraint to a model."""
|
||||||
sql = constraint.create_sql(model, self)
|
sql = constraint.create_sql(model, self)
|
||||||
|
@ -1361,6 +1372,14 @@ class BaseDatabaseSchemaEditor:
|
||||||
name=self.quote_name(name),
|
name=self.quote_name(name),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _rename_index_sql(self, model, old_name, new_name):
|
||||||
|
return Statement(
|
||||||
|
self.sql_rename_index,
|
||||||
|
table=Table(model._meta.db_table, self.quote_name),
|
||||||
|
old_name=self.quote_name(old_name),
|
||||||
|
new_name=self.quote_name(new_name),
|
||||||
|
)
|
||||||
|
|
||||||
def _index_columns(self, table, columns, col_suffixes, opclasses):
|
def _index_columns(self, table, columns, col_suffixes, opclasses):
|
||||||
return Columns(table, columns, self.quote_name, col_suffixes=col_suffixes)
|
return Columns(table, columns, self.quote_name, col_suffixes=col_suffixes)
|
||||||
|
|
||||||
|
|
|
@ -344,3 +344,9 @@ class DatabaseFeatures(BaseDatabaseFeatures):
|
||||||
and self._mysql_storage_engine != "MyISAM"
|
and self._mysql_storage_engine != "MyISAM"
|
||||||
and self.connection.mysql_version >= (8, 0, 13)
|
and self.connection.mysql_version >= (8, 0, 13)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def can_rename_index(self):
|
||||||
|
if self.connection.mysql_is_mariadb:
|
||||||
|
return self.connection.mysql_version >= (10, 5, 2)
|
||||||
|
return True
|
||||||
|
|
|
@ -23,6 +23,7 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
|
||||||
sql_delete_fk = "ALTER TABLE %(table)s DROP FOREIGN KEY %(name)s"
|
sql_delete_fk = "ALTER TABLE %(table)s DROP FOREIGN KEY %(name)s"
|
||||||
|
|
||||||
sql_delete_index = "DROP INDEX %(name)s ON %(table)s"
|
sql_delete_index = "DROP INDEX %(name)s ON %(table)s"
|
||||||
|
sql_rename_index = "ALTER TABLE %(table)s RENAME INDEX %(old_name)s TO %(new_name)s"
|
||||||
|
|
||||||
sql_create_pk = (
|
sql_create_pk = (
|
||||||
"ALTER TABLE %(table)s ADD CONSTRAINT %(name)s PRIMARY KEY (%(columns)s)"
|
"ALTER TABLE %(table)s ADD CONSTRAINT %(name)s PRIMARY KEY (%(columns)s)"
|
||||||
|
|
|
@ -60,6 +60,7 @@ class DatabaseFeatures(BaseDatabaseFeatures):
|
||||||
supports_ignore_conflicts = False
|
supports_ignore_conflicts = False
|
||||||
max_query_params = 2**16 - 1
|
max_query_params = 2**16 - 1
|
||||||
supports_partial_indexes = False
|
supports_partial_indexes = False
|
||||||
|
can_rename_index = True
|
||||||
supports_slicing_ordering_in_compound = True
|
supports_slicing_ordering_in_compound = True
|
||||||
allows_multiple_constraints_on_same_fields = False
|
allows_multiple_constraints_on_same_fields = False
|
||||||
supports_boolean_expr_in_select_clause = False
|
supports_boolean_expr_in_select_clause = False
|
||||||
|
|
|
@ -60,6 +60,7 @@ class DatabaseFeatures(BaseDatabaseFeatures):
|
||||||
supports_update_conflicts = True
|
supports_update_conflicts = True
|
||||||
supports_update_conflicts_with_target = True
|
supports_update_conflicts_with_target = True
|
||||||
supports_covering_indexes = True
|
supports_covering_indexes = True
|
||||||
|
can_rename_index = True
|
||||||
test_collations = {
|
test_collations = {
|
||||||
"non_default": "sv-x-icu",
|
"non_default": "sv-x-icu",
|
||||||
"swedish_ci": "sv-x-icu",
|
"swedish_ci": "sv-x-icu",
|
||||||
|
|
|
@ -12,6 +12,7 @@ from .models import (
|
||||||
DeleteModel,
|
DeleteModel,
|
||||||
RemoveConstraint,
|
RemoveConstraint,
|
||||||
RemoveIndex,
|
RemoveIndex,
|
||||||
|
RenameIndex,
|
||||||
RenameModel,
|
RenameModel,
|
||||||
)
|
)
|
||||||
from .special import RunPython, RunSQL, SeparateDatabaseAndState
|
from .special import RunPython, RunSQL, SeparateDatabaseAndState
|
||||||
|
@ -26,6 +27,7 @@ __all__ = [
|
||||||
"AlterModelOptions",
|
"AlterModelOptions",
|
||||||
"AddIndex",
|
"AddIndex",
|
||||||
"RemoveIndex",
|
"RemoveIndex",
|
||||||
|
"RenameIndex",
|
||||||
"AddField",
|
"AddField",
|
||||||
"RemoveField",
|
"RemoveField",
|
||||||
"AlterField",
|
"AlterField",
|
||||||
|
|
|
@ -876,6 +876,152 @@ class RemoveIndex(IndexOperation):
|
||||||
return "remove_%s_%s" % (self.model_name_lower, self.name.lower())
|
return "remove_%s_%s" % (self.model_name_lower, self.name.lower())
|
||||||
|
|
||||||
|
|
||||||
|
class RenameIndex(IndexOperation):
|
||||||
|
"""Rename an index."""
|
||||||
|
|
||||||
|
def __init__(self, model_name, new_name, old_name=None, old_fields=None):
|
||||||
|
if not old_name and not old_fields:
|
||||||
|
raise ValueError(
|
||||||
|
"RenameIndex requires one of old_name and old_fields arguments to be "
|
||||||
|
"set."
|
||||||
|
)
|
||||||
|
if old_name and old_fields:
|
||||||
|
raise ValueError(
|
||||||
|
"RenameIndex.old_name and old_fields are mutually exclusive."
|
||||||
|
)
|
||||||
|
self.model_name = model_name
|
||||||
|
self.new_name = new_name
|
||||||
|
self.old_name = old_name
|
||||||
|
self.old_fields = old_fields
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def old_name_lower(self):
|
||||||
|
return self.old_name.lower()
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def new_name_lower(self):
|
||||||
|
return self.new_name.lower()
|
||||||
|
|
||||||
|
def deconstruct(self):
|
||||||
|
kwargs = {
|
||||||
|
"model_name": self.model_name,
|
||||||
|
"new_name": self.new_name,
|
||||||
|
}
|
||||||
|
if self.old_name:
|
||||||
|
kwargs["old_name"] = self.old_name
|
||||||
|
if self.old_fields:
|
||||||
|
kwargs["old_fields"] = self.old_fields
|
||||||
|
return (self.__class__.__qualname__, [], kwargs)
|
||||||
|
|
||||||
|
def state_forwards(self, app_label, state):
|
||||||
|
if self.old_fields:
|
||||||
|
state.add_index(
|
||||||
|
app_label,
|
||||||
|
self.model_name_lower,
|
||||||
|
models.Index(fields=self.old_fields, name=self.new_name),
|
||||||
|
)
|
||||||
|
state.remove_model_options(
|
||||||
|
app_label,
|
||||||
|
self.model_name_lower,
|
||||||
|
AlterIndexTogether.option_name,
|
||||||
|
self.old_fields,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
state.rename_index(
|
||||||
|
app_label, self.model_name_lower, self.old_name, self.new_name
|
||||||
|
)
|
||||||
|
|
||||||
|
def database_forwards(self, app_label, schema_editor, from_state, to_state):
|
||||||
|
model = to_state.apps.get_model(app_label, self.model_name)
|
||||||
|
if not self.allow_migrate_model(schema_editor.connection.alias, model):
|
||||||
|
return
|
||||||
|
|
||||||
|
if self.old_fields:
|
||||||
|
from_model = from_state.apps.get_model(app_label, self.model_name)
|
||||||
|
columns = [
|
||||||
|
from_model._meta.get_field(field).column for field in self.old_fields
|
||||||
|
]
|
||||||
|
matching_index_name = schema_editor._constraint_names(
|
||||||
|
from_model, column_names=columns, index=True
|
||||||
|
)
|
||||||
|
if len(matching_index_name) != 1:
|
||||||
|
raise ValueError(
|
||||||
|
"Found wrong number (%s) of indexes for %s(%s)."
|
||||||
|
% (
|
||||||
|
len(matching_index_name),
|
||||||
|
from_model._meta.db_table,
|
||||||
|
", ".join(columns),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
old_index = models.Index(
|
||||||
|
fields=self.old_fields,
|
||||||
|
name=matching_index_name[0],
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
from_model_state = from_state.models[app_label, self.model_name_lower]
|
||||||
|
old_index = from_model_state.get_index_by_name(self.old_name)
|
||||||
|
|
||||||
|
to_model_state = to_state.models[app_label, self.model_name_lower]
|
||||||
|
new_index = to_model_state.get_index_by_name(self.new_name)
|
||||||
|
schema_editor.rename_index(model, old_index, new_index)
|
||||||
|
|
||||||
|
def database_backwards(self, app_label, schema_editor, from_state, to_state):
|
||||||
|
if self.old_fields:
|
||||||
|
# Backward operation with unnamed index is a no-op.
|
||||||
|
return
|
||||||
|
|
||||||
|
self.new_name_lower, self.old_name_lower = (
|
||||||
|
self.old_name_lower,
|
||||||
|
self.new_name_lower,
|
||||||
|
)
|
||||||
|
self.new_name, self.old_name = self.old_name, self.new_name
|
||||||
|
|
||||||
|
self.database_forwards(app_label, schema_editor, from_state, to_state)
|
||||||
|
|
||||||
|
self.new_name_lower, self.old_name_lower = (
|
||||||
|
self.old_name_lower,
|
||||||
|
self.new_name_lower,
|
||||||
|
)
|
||||||
|
self.new_name, self.old_name = self.old_name, self.new_name
|
||||||
|
|
||||||
|
def describe(self):
|
||||||
|
if self.old_name:
|
||||||
|
return (
|
||||||
|
f"Rename index {self.old_name} on {self.model_name} to {self.new_name}"
|
||||||
|
)
|
||||||
|
return (
|
||||||
|
f"Rename unnamed index for {self.old_fields} on {self.model_name} to "
|
||||||
|
f"{self.new_name}"
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def migration_name_fragment(self):
|
||||||
|
if self.old_name:
|
||||||
|
return "rename_%s_%s" % (self.old_name_lower, self.new_name_lower)
|
||||||
|
return "rename_%s_%s_%s" % (
|
||||||
|
self.model_name_lower,
|
||||||
|
"_".join(self.old_fields),
|
||||||
|
self.new_name_lower,
|
||||||
|
)
|
||||||
|
|
||||||
|
def reduce(self, operation, app_label):
|
||||||
|
if (
|
||||||
|
isinstance(operation, RenameIndex)
|
||||||
|
and self.model_name_lower == operation.model_name_lower
|
||||||
|
and operation.old_name
|
||||||
|
and self.new_name_lower == operation.old_name_lower
|
||||||
|
):
|
||||||
|
return [
|
||||||
|
RenameIndex(
|
||||||
|
self.model_name,
|
||||||
|
new_name=operation.new_name,
|
||||||
|
old_name=self.old_name,
|
||||||
|
old_fields=self.old_fields,
|
||||||
|
)
|
||||||
|
]
|
||||||
|
return super().reduce(operation, app_label)
|
||||||
|
|
||||||
|
|
||||||
class AddConstraint(IndexOperation):
|
class AddConstraint(IndexOperation):
|
||||||
option_name = "constraints"
|
option_name = "constraints"
|
||||||
|
|
||||||
|
|
|
@ -187,6 +187,14 @@ class ProjectState:
|
||||||
model_state.options.pop(key, False)
|
model_state.options.pop(key, False)
|
||||||
self.reload_model(app_label, model_name, delay=True)
|
self.reload_model(app_label, model_name, delay=True)
|
||||||
|
|
||||||
|
def remove_model_options(self, app_label, model_name, option_name, value_to_remove):
|
||||||
|
model_state = self.models[app_label, model_name]
|
||||||
|
if objs := model_state.options.get(option_name):
|
||||||
|
model_state.options[option_name] = [
|
||||||
|
obj for obj in objs if tuple(obj) != tuple(value_to_remove)
|
||||||
|
]
|
||||||
|
self.reload_model(app_label, model_name, delay=True)
|
||||||
|
|
||||||
def alter_model_managers(self, app_label, model_name, managers):
|
def alter_model_managers(self, app_label, model_name, managers):
|
||||||
model_state = self.models[app_label, model_name]
|
model_state = self.models[app_label, model_name]
|
||||||
model_state.managers = list(managers)
|
model_state.managers = list(managers)
|
||||||
|
@ -209,6 +217,20 @@ class ProjectState:
|
||||||
def remove_index(self, app_label, model_name, index_name):
|
def remove_index(self, app_label, model_name, index_name):
|
||||||
self._remove_option(app_label, model_name, "indexes", index_name)
|
self._remove_option(app_label, model_name, "indexes", index_name)
|
||||||
|
|
||||||
|
def rename_index(self, app_label, model_name, old_index_name, new_index_name):
|
||||||
|
model_state = self.models[app_label, model_name]
|
||||||
|
objs = model_state.options["indexes"]
|
||||||
|
|
||||||
|
new_indexes = []
|
||||||
|
for obj in objs:
|
||||||
|
if obj.name == old_index_name:
|
||||||
|
obj = obj.clone()
|
||||||
|
obj.name = new_index_name
|
||||||
|
new_indexes.append(obj)
|
||||||
|
|
||||||
|
model_state.options["indexes"] = new_indexes
|
||||||
|
self.reload_model(app_label, model_name, delay=True)
|
||||||
|
|
||||||
def add_constraint(self, app_label, model_name, constraint):
|
def add_constraint(self, app_label, model_name, constraint):
|
||||||
self._append_option(app_label, model_name, "constraints", constraint)
|
self._append_option(app_label, model_name, "constraints", constraint)
|
||||||
|
|
||||||
|
|
|
@ -222,6 +222,22 @@ Creates an index in the database table for the model with ``model_name``.
|
||||||
|
|
||||||
Removes the index named ``name`` from the model with ``model_name``.
|
Removes the index named ``name`` from the model with ``model_name``.
|
||||||
|
|
||||||
|
``RenameIndex``
|
||||||
|
---------------
|
||||||
|
|
||||||
|
.. versionadded:: 4.1
|
||||||
|
|
||||||
|
.. class:: RenameIndex(model_name, new_name, old_name=None, old_fields=None)
|
||||||
|
|
||||||
|
Renames an index in the database table for the model with ``model_name``.
|
||||||
|
Exactly one of ``old_name`` and ``old_fields`` can be provided. ``old_fields``
|
||||||
|
is an iterable of the strings, often corresponding to fields of
|
||||||
|
:attr:`~django.db.models.Options.index_together`.
|
||||||
|
|
||||||
|
On databases that don't support an index renaming statement (SQLite and MariaDB
|
||||||
|
< 10.5.2), the operation will drop and recreate the index, which can be
|
||||||
|
expensive.
|
||||||
|
|
||||||
``AddConstraint``
|
``AddConstraint``
|
||||||
-----------------
|
-----------------
|
||||||
|
|
||||||
|
|
|
@ -79,6 +79,15 @@ Adds ``index`` to ``model``’s table.
|
||||||
|
|
||||||
Removes ``index`` from ``model``’s table.
|
Removes ``index`` from ``model``’s table.
|
||||||
|
|
||||||
|
``rename_index()``
|
||||||
|
------------------
|
||||||
|
|
||||||
|
.. versionadded:: 4.1
|
||||||
|
|
||||||
|
.. method:: BaseDatabaseSchemaEditor.rename_index(model, old_index, new_index)
|
||||||
|
|
||||||
|
Renames ``old_index`` from ``model``’s table to ``new_index``.
|
||||||
|
|
||||||
``add_constraint()``
|
``add_constraint()``
|
||||||
--------------------
|
--------------------
|
||||||
|
|
||||||
|
|
|
@ -342,7 +342,10 @@ Management Commands
|
||||||
Migrations
|
Migrations
|
||||||
~~~~~~~~~~
|
~~~~~~~~~~
|
||||||
|
|
||||||
* ...
|
* The new :class:`~django.db.migrations.operations.RenameIndex` operation
|
||||||
|
allows renaming indexes defined in the
|
||||||
|
:attr:`Meta.indexes <django.db.models.Options.indexes>` or
|
||||||
|
:attr:`~django.db.models.Options.index_together` options.
|
||||||
|
|
||||||
Models
|
Models
|
||||||
~~~~~~
|
~~~~~~
|
||||||
|
|
|
@ -2900,6 +2900,120 @@ class OperationTests(OperationTestBase):
|
||||||
self.unapply_operations("test_rmin", project_state, operations=operations)
|
self.unapply_operations("test_rmin", project_state, operations=operations)
|
||||||
self.assertIndexExists("test_rmin_pony", ["pink", "weight"])
|
self.assertIndexExists("test_rmin_pony", ["pink", "weight"])
|
||||||
|
|
||||||
|
def test_rename_index(self):
|
||||||
|
app_label = "test_rnin"
|
||||||
|
project_state = self.set_up_test_model(app_label, index=True)
|
||||||
|
table_name = app_label + "_pony"
|
||||||
|
self.assertIndexNameExists(table_name, "pony_pink_idx")
|
||||||
|
self.assertIndexNameNotExists(table_name, "new_pony_test_idx")
|
||||||
|
operation = migrations.RenameIndex(
|
||||||
|
"Pony", new_name="new_pony_test_idx", old_name="pony_pink_idx"
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
operation.describe(),
|
||||||
|
"Rename index pony_pink_idx on Pony to new_pony_test_idx",
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
operation.migration_name_fragment,
|
||||||
|
"rename_pony_pink_idx_new_pony_test_idx",
|
||||||
|
)
|
||||||
|
|
||||||
|
new_state = project_state.clone()
|
||||||
|
operation.state_forwards(app_label, new_state)
|
||||||
|
# Rename index.
|
||||||
|
expected_queries = 1 if connection.features.can_rename_index else 2
|
||||||
|
with connection.schema_editor() as editor, self.assertNumQueries(
|
||||||
|
expected_queries
|
||||||
|
):
|
||||||
|
operation.database_forwards(app_label, editor, project_state, new_state)
|
||||||
|
self.assertIndexNameNotExists(table_name, "pony_pink_idx")
|
||||||
|
self.assertIndexNameExists(table_name, "new_pony_test_idx")
|
||||||
|
# Reversal.
|
||||||
|
with connection.schema_editor() as editor, self.assertNumQueries(
|
||||||
|
expected_queries
|
||||||
|
):
|
||||||
|
operation.database_backwards(app_label, editor, new_state, project_state)
|
||||||
|
self.assertIndexNameExists(table_name, "pony_pink_idx")
|
||||||
|
self.assertIndexNameNotExists(table_name, "new_pony_test_idx")
|
||||||
|
# Deconstruction.
|
||||||
|
definition = operation.deconstruct()
|
||||||
|
self.assertEqual(definition[0], "RenameIndex")
|
||||||
|
self.assertEqual(definition[1], [])
|
||||||
|
self.assertEqual(
|
||||||
|
definition[2],
|
||||||
|
{
|
||||||
|
"model_name": "Pony",
|
||||||
|
"old_name": "pony_pink_idx",
|
||||||
|
"new_name": "new_pony_test_idx",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_rename_index_arguments(self):
|
||||||
|
msg = "RenameIndex.old_name and old_fields are mutually exclusive."
|
||||||
|
with self.assertRaisesMessage(ValueError, msg):
|
||||||
|
migrations.RenameIndex(
|
||||||
|
"Pony",
|
||||||
|
new_name="new_idx_name",
|
||||||
|
old_name="old_idx_name",
|
||||||
|
old_fields=("weight", "pink"),
|
||||||
|
)
|
||||||
|
msg = "RenameIndex requires one of old_name and old_fields arguments to be set."
|
||||||
|
with self.assertRaisesMessage(ValueError, msg):
|
||||||
|
migrations.RenameIndex("Pony", new_name="new_idx_name")
|
||||||
|
|
||||||
|
def test_rename_index_unnamed_index(self):
|
||||||
|
app_label = "test_rninui"
|
||||||
|
project_state = self.set_up_test_model(app_label, index_together=True)
|
||||||
|
table_name = app_label + "_pony"
|
||||||
|
self.assertIndexNameNotExists(table_name, "new_pony_test_idx")
|
||||||
|
operation = migrations.RenameIndex(
|
||||||
|
"Pony", new_name="new_pony_test_idx", old_fields=("weight", "pink")
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
operation.describe(),
|
||||||
|
"Rename unnamed index for ('weight', 'pink') on Pony to new_pony_test_idx",
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
operation.migration_name_fragment,
|
||||||
|
"rename_pony_weight_pink_new_pony_test_idx",
|
||||||
|
)
|
||||||
|
|
||||||
|
new_state = project_state.clone()
|
||||||
|
operation.state_forwards(app_label, new_state)
|
||||||
|
# Rename index.
|
||||||
|
with connection.schema_editor() as editor:
|
||||||
|
operation.database_forwards(app_label, editor, project_state, new_state)
|
||||||
|
self.assertIndexNameExists(table_name, "new_pony_test_idx")
|
||||||
|
# Reverse is a no-op.
|
||||||
|
with connection.schema_editor() as editor, self.assertNumQueries(0):
|
||||||
|
operation.database_backwards(app_label, editor, new_state, project_state)
|
||||||
|
self.assertIndexNameExists(table_name, "new_pony_test_idx")
|
||||||
|
# Deconstruction.
|
||||||
|
definition = operation.deconstruct()
|
||||||
|
self.assertEqual(definition[0], "RenameIndex")
|
||||||
|
self.assertEqual(definition[1], [])
|
||||||
|
self.assertEqual(
|
||||||
|
definition[2],
|
||||||
|
{
|
||||||
|
"model_name": "Pony",
|
||||||
|
"new_name": "new_pony_test_idx",
|
||||||
|
"old_fields": ("weight", "pink"),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_rename_index_unknown_unnamed_index(self):
|
||||||
|
app_label = "test_rninuui"
|
||||||
|
project_state = self.set_up_test_model(app_label)
|
||||||
|
operation = migrations.RenameIndex(
|
||||||
|
"Pony", new_name="new_pony_test_idx", old_fields=("weight", "pink")
|
||||||
|
)
|
||||||
|
new_state = project_state.clone()
|
||||||
|
operation.state_forwards(app_label, new_state)
|
||||||
|
msg = "Found wrong number (0) of indexes for test_rninuui_pony(weight, pink)."
|
||||||
|
with connection.schema_editor() as editor:
|
||||||
|
with self.assertRaisesMessage(ValueError, msg):
|
||||||
|
operation.database_forwards(app_label, editor, project_state, new_state)
|
||||||
|
|
||||||
def test_add_index_state_forwards(self):
|
def test_add_index_state_forwards(self):
|
||||||
project_state = self.set_up_test_model("test_adinsf")
|
project_state = self.set_up_test_model("test_adinsf")
|
||||||
index = models.Index(fields=["pink"], name="test_adinsf_pony_pink_idx")
|
index = models.Index(fields=["pink"], name="test_adinsf_pony_pink_idx")
|
||||||
|
@ -2923,6 +3037,35 @@ class OperationTests(OperationTestBase):
|
||||||
new_model = new_state.apps.get_model("test_rminsf", "Pony")
|
new_model = new_state.apps.get_model("test_rminsf", "Pony")
|
||||||
self.assertIsNot(old_model, new_model)
|
self.assertIsNot(old_model, new_model)
|
||||||
|
|
||||||
|
def test_rename_index_state_forwards(self):
|
||||||
|
app_label = "test_rnidsf"
|
||||||
|
project_state = self.set_up_test_model(app_label, index=True)
|
||||||
|
old_model = project_state.apps.get_model(app_label, "Pony")
|
||||||
|
new_state = project_state.clone()
|
||||||
|
|
||||||
|
operation = migrations.RenameIndex(
|
||||||
|
"Pony", new_name="new_pony_pink_idx", old_name="pony_pink_idx"
|
||||||
|
)
|
||||||
|
operation.state_forwards(app_label, new_state)
|
||||||
|
new_model = new_state.apps.get_model(app_label, "Pony")
|
||||||
|
self.assertIsNot(old_model, new_model)
|
||||||
|
self.assertEqual(new_model._meta.indexes[0].name, "new_pony_pink_idx")
|
||||||
|
|
||||||
|
def test_rename_index_state_forwards_unnamed_index(self):
|
||||||
|
app_label = "test_rnidsfui"
|
||||||
|
project_state = self.set_up_test_model(app_label, index_together=True)
|
||||||
|
old_model = project_state.apps.get_model(app_label, "Pony")
|
||||||
|
new_state = project_state.clone()
|
||||||
|
|
||||||
|
operation = migrations.RenameIndex(
|
||||||
|
"Pony", new_name="new_pony_pink_idx", old_fields=("weight", "pink")
|
||||||
|
)
|
||||||
|
operation.state_forwards(app_label, new_state)
|
||||||
|
new_model = new_state.apps.get_model(app_label, "Pony")
|
||||||
|
self.assertIsNot(old_model, new_model)
|
||||||
|
self.assertEqual(new_model._meta.index_together, tuple())
|
||||||
|
self.assertEqual(new_model._meta.indexes[0].name, "new_pony_pink_idx")
|
||||||
|
|
||||||
@skipUnlessDBFeature("supports_expression_indexes")
|
@skipUnlessDBFeature("supports_expression_indexes")
|
||||||
def test_add_func_index(self):
|
def test_add_func_index(self):
|
||||||
app_label = "test_addfuncin"
|
app_label = "test_addfuncin"
|
||||||
|
|
|
@ -1114,3 +1114,41 @@ class OptimizerTests(SimpleTestCase):
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_rename_index(self):
|
||||||
|
self.assertOptimizesTo(
|
||||||
|
[
|
||||||
|
migrations.RenameIndex(
|
||||||
|
"Pony", new_name="mid_name", old_fields=("weight", "pink")
|
||||||
|
),
|
||||||
|
migrations.RenameIndex(
|
||||||
|
"Pony", new_name="new_name", old_name="mid_name"
|
||||||
|
),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
migrations.RenameIndex(
|
||||||
|
"Pony", new_name="new_name", old_fields=("weight", "pink")
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
self.assertOptimizesTo(
|
||||||
|
[
|
||||||
|
migrations.RenameIndex(
|
||||||
|
"Pony", new_name="mid_name", old_name="old_name"
|
||||||
|
),
|
||||||
|
migrations.RenameIndex(
|
||||||
|
"Pony", new_name="new_name", old_name="mid_name"
|
||||||
|
),
|
||||||
|
],
|
||||||
|
[migrations.RenameIndex("Pony", new_name="new_name", old_name="old_name")],
|
||||||
|
)
|
||||||
|
self.assertDoesNotOptimize(
|
||||||
|
[
|
||||||
|
migrations.RenameIndex(
|
||||||
|
"Pony", new_name="mid_name", old_name="old_name"
|
||||||
|
),
|
||||||
|
migrations.RenameIndex(
|
||||||
|
"Pony", new_name="new_name", old_fields=("weight", "pink")
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
Loading…
Reference in New Issue