#31335: [mysql] Renaming a foreign key field that's also included in an index fails with "Cannot drop index 'foo_idx': needed in a foreign key constraint" --------------------------------------------+------------------------ Reporter: Stephen Finucane | Owner: nobody Type: Uncategorized | Status: new Component: Uncategorized | Version: 3.0 Severity: Normal | Keywords: Triage Stage: Unreviewed | Has patch: 0 Needs documentation: 0 | Needs tests: 0 Patch needs improvement: 0 | Easy pickings: 0 UI/UX: 0 | --------------------------------------------+------------------------ I have a foreign key field on a model that I wish to rename. Unfortunately that field is included in an index. Attempting to rename this generates the following error.
{{{ MySQLdb._exceptions.OperationalError: (1553, "Cannot drop index 'foo_idx': needed in a foreign key constraint") }}} I've included reproduction steps along with a workaround below. This is an issue on both 3.0.3 and 1.11.28. ---- Create a sample project and application: {{{ $ virtualenv venv $ pip install django mysqlclient $ django-admin startproject test $ cd test/ $ django-admin startapp core }}} Configure `test/settings.py` to use the `mysql` backend and enable the `core` app, then create some sample models: {{{ cat <<< EOF > core/models.py import datetime from django.db import models class Article(models.Model): name = models.CharField(max_length=255, blank=True, null=False) date = models.DateTimeField(default=datetime.datetime.utcnow) body = models.TextField(blank=True, null=False) class Comment(models.Model): article = models.ForeignKey( Article, related_name='comments', related_query_name='comment', on_delete=models.CASCADE, ) date = models.DateTimeField(default=datetime.datetime.utcnow) body = models.TextField(blank=True, null=False) class Meta: indexes = [ # This is a covering index for the /list/ query models.Index( fields=['article', 'date'], name='comment_list_covering_idx', ), ] EOF }}} Once done, create and apply the migrations, then create some entries: {{{ $ python manage.py makemigrations $ python manage.py migrate $ python manage.py shell >>> from core.models import Article, Comment >>> article_a = Article(name='Sample article', body='foo').save() >>> article_b = Article(name='Another article', body='bar').save() >>> comment_1 = Comment(article=article_a) >>> comment_2 = Comment(article=article_b, body='moar stuff') }}} Rename the foreign key field, for example from `article` to `foo`, updating the index in the process, and generate the migration: {{{ $ python manage.py makemigrations Did you rename comment.article to comment.foo (a ForeignKey)? [y/N] y Migrations for 'core': core/migrations/0002_auto_20200303_1424.py - Remove index comment_list_covering_idx from comment - Rename field article on comment to foo - Create index comment_list_covering_idx on field(s) foo, date of model comment }}} Attempt to run that migration. It will fail: {{{ $ python manage.py migrate Operations to perform: Apply all migrations: admin, auth, contenttypes, core, sessions Running migrations: Applying core.0002_auto_20200303_1424...Traceback (most recent call last): File "/tmp/django-bug/venv/lib/python3.7/site- packages/django/db/backends/utils.py", line 86, in _execute return self.cursor.execute(sql, params) File "/tmp/django-bug/venv/lib/python3.7/site- packages/django/db/backends/mysql/base.py", line 74, in execute return self.cursor.execute(query, args) File "/tmp/django-bug/venv/lib/python3.7/site- packages/MySQLdb/cursors.py", line 209, in execute res = self._query(query) File "/tmp/django-bug/venv/lib/python3.7/site- packages/MySQLdb/cursors.py", line 315, in _query db.query(q) File "/tmp/django-bug/venv/lib/python3.7/site- packages/MySQLdb/connections.py", line 239, in query _mysql.connection.query(self, query) MySQLdb._exceptions.OperationalError: (1553, "Cannot drop index 'comment_list_covering_idx': needed in a foreign key constraint") The above exception was the direct cause of the following exception: Traceback (most recent call last): File "manage.py", line 21, in <module> main() File "manage.py", line 17, in main execute_from_command_line(sys.argv) File "/tmp/django-bug/venv/lib/python3.7/site- packages/django/core/management/__init__.py", line 401, in execute_from_command_line utility.execute() File "/tmp/django-bug/venv/lib/python3.7/site- packages/django/core/management/__init__.py", line 395, in execute self.fetch_command(subcommand).run_from_argv(self.argv) File "/tmp/django-bug/venv/lib/python3.7/site- packages/django/core/management/base.py", line 328, in run_from_argv self.execute(*args, **cmd_options) File "/tmp/django-bug/venv/lib/python3.7/site- packages/django/core/management/base.py", line 369, in execute output = self.handle(*args, **options) File "/tmp/django-bug/venv/lib/python3.7/site- packages/django/core/management/base.py", line 83, in wrapped res = handle_func(*args, **kwargs) File "/tmp/django-bug/venv/lib/python3.7/site- packages/django/core/management/commands/migrate.py", line 233, in handle fake_initial=fake_initial, File "/tmp/django-bug/venv/lib/python3.7/site- packages/django/db/migrations/executor.py", line 117, in migrate state = self._migrate_all_forwards(state, plan, full_plan, fake=fake, fake_initial=fake_initial) File "/tmp/django-bug/venv/lib/python3.7/site- packages/django/db/migrations/executor.py", line 147, in _migrate_all_forwards state = self.apply_migration(state, migration, fake=fake, fake_initial=fake_initial) File "/tmp/django-bug/venv/lib/python3.7/site- packages/django/db/migrations/executor.py", line 245, in apply_migration state = migration.apply(state, schema_editor) File "/tmp/django-bug/venv/lib/python3.7/site- packages/django/db/migrations/migration.py", line 124, in apply operation.database_forwards(self.app_label, schema_editor, old_state, project_state) File "/tmp/django-bug/venv/lib/python3.7/site- packages/django/db/migrations/operations/models.py", line 783, in database_forwards schema_editor.remove_index(model, index) File "/tmp/django-bug/venv/lib/python3.7/site- packages/django/db/backends/base/schema.py", line 356, in remove_index self.execute(index.remove_sql(model, self)) File "/tmp/django-bug/venv/lib/python3.7/site- packages/django/db/backends/base/schema.py", line 142, in execute cursor.execute(sql, params) File "/tmp/django-bug/venv/lib/python3.7/site- packages/django/db/backends/utils.py", line 100, in execute return super().execute(sql, params) File "/tmp/django-bug/venv/lib/python3.7/site- packages/django/db/backends/utils.py", line 68, in execute return self._execute_with_wrappers(sql, params, many=False, executor=self._execute) File "/tmp/django-bug/venv/lib/python3.7/site- packages/django/db/backends/utils.py", line 77, in _execute_with_wrappers return executor(sql, params, many, context) File "/tmp/django-bug/venv/lib/python3.7/site- packages/django/db/backends/utils.py", line 86, in _execute return self.cursor.execute(sql, params) File "/tmp/django-bug/venv/lib/python3.7/site- packages/django/db/utils.py", line 90, in __exit__ raise dj_exc_value.with_traceback(traceback) from exc_value File "/tmp/django-bug/venv/lib/python3.7/site- packages/django/db/backends/utils.py", line 86, in _execute return self.cursor.execute(sql, params) File "/tmp/django-bug/venv/lib/python3.7/site- packages/django/db/backends/mysql/base.py", line 74, in execute return self.cursor.execute(query, args) File "/tmp/django-bug/venv/lib/python3.7/site- packages/MySQLdb/cursors.py", line 209, in execute res = self._query(query) File "/tmp/django-bug/venv/lib/python3.7/site- packages/MySQLdb/cursors.py", line 315, in _query db.query(q) File "/tmp/django-bug/venv/lib/python3.7/site- packages/MySQLdb/connections.py", line 239, in query _mysql.connection.query(self, query) django.db.utils.OperationalError: (1553, "Cannot drop index 'comment_list_covering_idx': needed in a foreign key constraint") }}} The only workaround I've seen is to use `AlterField` to remove the fk constraint on the `article` field before doing this work, then use `AlterField` again after to re-add it. Perhaps that needs to happen under the hood if MySQL can't do this? Output of pip freeze and the migrations below: {{{ $ pip freeze asgiref==3.2.3 Django==3.0.3 mysqlclient==1.4.6 pytz==2019.3 sqlparse==0.3.1 }}} {{{ $ cat core/migrations/0001_initial.py # Generated by Django 3.0.3 on 2020-03-03 14:22 import datetime from django.db import migrations, models import django.db.models.deletion class Migration(migrations.Migration): initial = True dependencies = [ ] operations = [ migrations.CreateModel( name='Article', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('name', models.CharField(blank=True, max_length=255)), ('date', models.DateTimeField(default=datetime.datetime.utcnow)), ('body', models.TextField(blank=True)), ], ), migrations.CreateModel( name='Comment', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('date', models.DateTimeField(default=datetime.datetime.utcnow)), ('body', models.TextField(blank=True)), ('article', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='comments', related_query_name='comment', to='core.Article')), ], ), migrations.AddIndex( model_name='comment', index=models.Index(fields=['article', 'date'], name='comment_list_covering_idx'), ), ] }}} {{{ $ cat core/migrations/0002_auto_20200303_1424.py # Generated by Django 3.0.3 on 2020-03-03 14:24 from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ ('core', '0001_initial'), ] operations = [ migrations.RemoveIndex( model_name='comment', name='comment_list_covering_idx', ), migrations.RenameField( model_name='comment', old_name='article', new_name='foo', ), migrations.AddIndex( model_name='comment', index=models.Index(fields=['foo', 'date'], name='comment_list_covering_idx'), ), ] }}} -- Ticket URL: <https://code.djangoproject.com/ticket/31335> Django <https://code.djangoproject.com/> The Web framework for perfectionists with deadlines. -- You received this message because you are subscribed to the Google Groups "Django updates" group. To unsubscribe from this group and stop receiving emails from it, send an email to django-updates+unsubscr...@googlegroups.com. To view this discussion on the web visit https://groups.google.com/d/msgid/django-updates/053.a8190492a52e72a0edafce5b488fb813%40djangoproject.com.