Opened 5 years ago

Closed 5 years ago

#30276 closed Bug (fixed)

reverse DeleteModel migration with a primary key OneToOneField fails in MySQL

Reported by: Florian Zimmermann Owned by: nobody
Component: Migrations Version: 2.1
Severity: Normal Keywords: migrations, mysql
Cc: Triage Stage: Unreviewed
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

Preparations

In a new app create two models (without the dummy field this would trigger #27746):

from django.db import models

class One(models.Model):
    pass

class Two(models.Model):
    one = models.OneToOneField(One, primary_key=True, on_delete=models.CASCADE)
    dummy = models.IntegerField()

python manage.py makemigrations yields this migration:

from django.db import migrations, models
import django.db.models.deletion

class Migration(migrations.Migration):
    initial = True
    dependencies = []
    operations = [
        migrations.CreateModel(
            name='One',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
            ],
        ),
        migrations.CreateModel(
            name='Two',
            fields=[
                ('one', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, serialize=False, to='bug.One')),
                ('dummy', models.IntegerField()),
            ],
        ),
    ]

Then remove the Two model:

from django.db import models

class One(models.Model):
    pass

#class Two(models.Model):
#    one = models.OneToOneField(One, primary_key=True, on_delete=models.CASCADE)
#    dummy = models.IntegerField()

which yields the offending migration:

from django.db import migrations

class Migration(migrations.Migration):
    dependencies = [
        ('bug', '0001_initial'),
    ]
    operations = [
        migrations.RemoveField(
            model_name='two',
            name='one',
        ),
        migrations.DeleteModel(
            name='Two',
        ),
    ]

Triggering the bug

Apply both migrations and then unapply the second migration:

python manage.py migrate bug
python manage.py migrate bug 0001
Operations to perform:
  Target specific migration: 0001_initial, from bug
Running migrations:
  Rendering model states... DONE
  Unapplying bug.0002_auto_20190321_1219...Traceback (most recent call last):
  File "D:\temp\django-bug\venv\lib\site-packages\django\db\backends\utils.py", line 85, in _execute
    return self.cursor.execute(sql, params)
  File "D:\temp\django-bug\venv\lib\site-packages\django\db\backends\mysql\base.py", line 71, in execute
    return self.cursor.execute(query, args)
  File "D:\temp\django-bug\venv\lib\site-packages\MySQLdb\cursors.py", line 250, in execute
    self.errorhandler(self, exc, value)
  File "D:\temp\django-bug\venv\lib\site-packages\MySQLdb\connections.py", line 50, in defaulterrorhandler
    raise errorvalue
  File "D:\temp\django-bug\venv\lib\site-packages\MySQLdb\cursors.py", line 247, in execute
    res = self._query(query)
  File "D:\temp\django-bug\venv\lib\site-packages\MySQLdb\cursors.py", line 412, in _query
    rowcount = self._do_query(q)
  File "D:\temp\django-bug\venv\lib\site-packages\MySQLdb\cursors.py", line 375, in _do_query
    db.query(q)
  File "D:\temp\django-bug\venv\lib\site-packages\MySQLdb\connections.py", line 276, in query
    _mysql.connection.query(self, query)
_mysql_exceptions.OperationalError: (1068, 'Multiple primary key defined')

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "manage.py", line 15, in <module>
    execute_from_command_line(sys.argv)
  File "D:\temp\django-bug\venv\lib\site-packages\django\core\management\__init__.py", line 381, in execute_from_command_line
    utility.execute()
  File "D:\temp\django-bug\venv\lib\site-packages\django\core\management\__init__.py", line 375, in execute
    self.fetch_command(subcommand).run_from_argv(self.argv)
  File "D:\temp\django-bug\venv\lib\site-packages\django\core\management\base.py", line 316, in run_from_argv
    self.execute(*args, **cmd_options)
  File "D:\temp\django-bug\venv\lib\site-packages\django\core\management\base.py", line 353, in execute
    output = self.handle(*args, **options)
  File "D:\temp\django-bug\venv\lib\site-packages\django\core\management\base.py", line 83, in wrapped
    res = handle_func(*args, **kwargs)
  File "D:\temp\django-bug\venv\lib\site-packages\django\core\management\commands\migrate.py", line 203, in handle
    fake_initial=fake_initial,
  File "D:\temp\django-bug\venv\lib\site-packages\django\db\migrations\executor.py", line 121, in migrate
    state = self._migrate_all_backwards(plan, full_plan, fake=fake)
  File "D:\temp\django-bug\venv\lib\site-packages\django\db\migrations\executor.py", line 196, in _migrate_all_backwards
    self.unapply_migration(states[migration], migration, fake=fake)
  File "D:\temp\django-bug\venv\lib\site-packages\django\db\migrations\executor.py", line 262, in unapply_migration
    state = migration.unapply(state, schema_editor)
  File "D:\temp\django-bug\venv\lib\site-packages\django\db\migrations\migration.py", line 175, in unapply
    operation.database_backwards(self.app_label, schema_editor, from_state, to_state)
  File "D:\temp\django-bug\venv\lib\site-packages\django\db\migrations\operations\fields.py", line 156, in database_backwards
    schema_editor.add_field(from_model, to_model._meta.get_field(self.name))
  File "D:\temp\django-bug\venv\lib\site-packages\django\db\backends\mysql\schema.py", line 42, in add_field
    super().add_field(model, field)
  File "D:\temp\django-bug\venv\lib\site-packages\django\db\backends\base\schema.py", line 435, in add_field
    self.execute(sql, params)
  File "D:\temp\django-bug\venv\lib\site-packages\django\db\backends\base\schema.py", line 133, in execute
    cursor.execute(sql, params)
  File "D:\temp\django-bug\venv\lib\site-packages\django\db\backends\utils.py", line 100, in execute
    return super().execute(sql, params)
  File "D:\temp\django-bug\venv\lib\site-packages\django\db\backends\utils.py", line 68, in execute
    return self._execute_with_wrappers(sql, params, many=False, executor=self._execute)
  File "D:\temp\django-bug\venv\lib\site-packages\django\db\backends\utils.py", line 77, in _execute_with_wrappers
    return executor(sql, params, many, context)
  File "D:\temp\django-bug\venv\lib\site-packages\django\db\backends\utils.py", line 85, in _execute
    return self.cursor.execute(sql, params)
  File "D:\temp\django-bug\venv\lib\site-packages\django\db\utils.py", line 89, in __exit__
    raise dj_exc_value.with_traceback(traceback) from exc_value
  File "D:\temp\django-bug\venv\lib\site-packages\django\db\backends\utils.py", line 85, in _execute
    return self.cursor.execute(sql, params)
  File "D:\temp\django-bug\venv\lib\site-packages\django\db\backends\mysql\base.py", line 71, in execute
    return self.cursor.execute(query, args)
  File "D:\temp\django-bug\venv\lib\site-packages\MySQLdb\cursors.py", line 250, in execute
    self.errorhandler(self, exc, value)
  File "D:\temp\django-bug\venv\lib\site-packages\MySQLdb\connections.py", line 50, in defaulterrorhandler
    raise errorvalue
  File "D:\temp\django-bug\venv\lib\site-packages\MySQLdb\cursors.py", line 247, in execute
    res = self._query(query)
  File "D:\temp\django-bug\venv\lib\site-packages\MySQLdb\cursors.py", line 412, in _query
    rowcount = self._do_query(q)
  File "D:\temp\django-bug\venv\lib\site-packages\MySQLdb\cursors.py", line 375, in _do_query
    db.query(q)
  File "D:\temp\django-bug\venv\lib\site-packages\MySQLdb\connections.py", line 276, in query
    _mysql.connection.query(self, query)
django.db.utils.OperationalError: (1068, 'Multiple primary key defined')

And sure enough the SQL for the backwards migration looks like this (python manage.py sqlmigrate --backwards bug 0002):

BEGIN;
--
-- Delete model Two
--
CREATE TABLE `bug_two` (`id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY, `foo` integer NOT NULL);
--
-- Remove field one from two
--
ALTER TABLE `bug_two` ADD COLUMN `one_id` integer NOT NULL PRIMARY KEY;
ALTER TABLE `bug_two` ADD CONSTRAINT `bug_two_one_id_2a65406f_fk_bug_one_id` FOREIGN KEY (`one_id`) REFERENCES `bug_one` (`id`);
COMMIT;

Change History (1)

comment:1 by Tim Graham, 5 years ago

Resolution: fixed
Status: newclosed
Type: UncategorizedBug

This is fixed in the upcoming Django 2.2 release by ad82900ad94ed4bbad050b9993373dafbe66b610. The second migration doesn't have a RemoveField operation.

Note: See TracTickets for help on using tickets.
Back to Top