diff options
| author | David Wobrock <david.wobrock@gmail.com> | 2022-05-02 17:22:54 +0200 |
|---|---|---|
| committer | Mariusz Felisiak <felisiak.mariusz@gmail.com> | 2022-05-12 20:44:03 +0200 |
| commit | eacd4977f6a4bb038e82796ba79a2f61bae330c6 (patch) | |
| tree | 8d9b1c7d18226a8922826257ed997cee6229af10 /django/db | |
| parent | 20e65a34aea0ace077033c84854dcf225e248f8c (diff) | |
Refs #27064 -- Added RenameIndex migration operation.
Diffstat (limited to 'django/db')
| -rw-r--r-- | django/db/backends/base/features.py | 3 | ||||
| -rw-r--r-- | django/db/backends/base/schema.py | 19 | ||||
| -rw-r--r-- | django/db/backends/mysql/features.py | 6 | ||||
| -rw-r--r-- | django/db/backends/mysql/schema.py | 1 | ||||
| -rw-r--r-- | django/db/backends/oracle/features.py | 1 | ||||
| -rw-r--r-- | django/db/backends/postgresql/features.py | 1 | ||||
| -rw-r--r-- | django/db/migrations/operations/__init__.py | 2 | ||||
| -rw-r--r-- | django/db/migrations/operations/models.py | 146 | ||||
| -rw-r--r-- | django/db/migrations/state.py | 22 |
9 files changed, 201 insertions, 0 deletions
diff --git a/django/db/backends/base/features.py b/django/db/backends/base/features.py index a96dbe1b08..68cad9fef2 100644 --- a/django/db/backends/base/features.py +++ b/django/db/backends/base/features.py @@ -176,6 +176,9 @@ class BaseDatabaseFeatures: # Can it create foreign key constraints inline when adding columns? can_create_inline_fk = True + # Can an index be renamed? + can_rename_index = False + # Does it automatically index foreign keys? indexes_foreign_keys = True diff --git a/django/db/backends/base/schema.py b/django/db/backends/base/schema.py index 468ab42490..f2ca8c8df9 100644 --- a/django/db/backends/base/schema.py +++ b/django/db/backends/base/schema.py @@ -131,6 +131,7 @@ class BaseDatabaseSchemaEditor: "CREATE UNIQUE INDEX %(name)s ON %(table)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_create_pk = ( @@ -492,6 +493,16 @@ class BaseDatabaseSchemaEditor: return None 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): """Add a constraint to a model.""" sql = constraint.create_sql(model, self) @@ -1361,6 +1372,14 @@ class BaseDatabaseSchemaEditor: 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): return Columns(table, columns, self.quote_name, col_suffixes=col_suffixes) diff --git a/django/db/backends/mysql/features.py b/django/db/backends/mysql/features.py index 7b9a90ab06..1261d9e9a6 100644 --- a/django/db/backends/mysql/features.py +++ b/django/db/backends/mysql/features.py @@ -344,3 +344,9 @@ class DatabaseFeatures(BaseDatabaseFeatures): and self._mysql_storage_engine != "MyISAM" 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 diff --git a/django/db/backends/mysql/schema.py b/django/db/backends/mysql/schema.py index 562b209eef..d6d303f0f0 100644 --- a/django/db/backends/mysql/schema.py +++ b/django/db/backends/mysql/schema.py @@ -23,6 +23,7 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor): sql_delete_fk = "ALTER TABLE %(table)s DROP FOREIGN KEY %(name)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 = ( "ALTER TABLE %(table)s ADD CONSTRAINT %(name)s PRIMARY KEY (%(columns)s)" diff --git a/django/db/backends/oracle/features.py b/django/db/backends/oracle/features.py index e0db3daa88..defd0a0ff8 100644 --- a/django/db/backends/oracle/features.py +++ b/django/db/backends/oracle/features.py @@ -60,6 +60,7 @@ class DatabaseFeatures(BaseDatabaseFeatures): supports_ignore_conflicts = False max_query_params = 2**16 - 1 supports_partial_indexes = False + can_rename_index = True supports_slicing_ordering_in_compound = True allows_multiple_constraints_on_same_fields = False supports_boolean_expr_in_select_clause = False diff --git a/django/db/backends/postgresql/features.py b/django/db/backends/postgresql/features.py index 57688642eb..ce01e88603 100644 --- a/django/db/backends/postgresql/features.py +++ b/django/db/backends/postgresql/features.py @@ -60,6 +60,7 @@ class DatabaseFeatures(BaseDatabaseFeatures): supports_update_conflicts = True supports_update_conflicts_with_target = True supports_covering_indexes = True + can_rename_index = True test_collations = { "non_default": "sv-x-icu", "swedish_ci": "sv-x-icu", diff --git a/django/db/migrations/operations/__init__.py b/django/db/migrations/operations/__init__.py index 793969ed12..987c7c1fe6 100644 --- a/django/db/migrations/operations/__init__.py +++ b/django/db/migrations/operations/__init__.py @@ -12,6 +12,7 @@ from .models import ( DeleteModel, RemoveConstraint, RemoveIndex, + RenameIndex, RenameModel, ) from .special import RunPython, RunSQL, SeparateDatabaseAndState @@ -26,6 +27,7 @@ __all__ = [ "AlterModelOptions", "AddIndex", "RemoveIndex", + "RenameIndex", "AddField", "RemoveField", "AlterField", diff --git a/django/db/migrations/operations/models.py b/django/db/migrations/operations/models.py index 8a5c04393b..d17232e4ec 100644 --- a/django/db/migrations/operations/models.py +++ b/django/db/migrations/operations/models.py @@ -876,6 +876,152 @@ class RemoveIndex(IndexOperation): 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): option_name = "constraints" diff --git a/django/db/migrations/state.py b/django/db/migrations/state.py index 7d7a9174f0..ff5d0e93a9 100644 --- a/django/db/migrations/state.py +++ b/django/db/migrations/state.py @@ -187,6 +187,14 @@ class ProjectState: model_state.options.pop(key, False) 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): model_state = self.models[app_label, model_name] model_state.managers = list(managers) @@ -209,6 +217,20 @@ class ProjectState: def remove_index(self, app_label, model_name, 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): self._append_option(app_label, model_name, "constraints", constraint) |
