Opened 7 years ago
Closed 7 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 , 7 years ago
| Resolution: | → fixed | 
|---|---|
| Status: | new → closed | 
| Type: | Uncategorized → Bug | 
  Note:
 See   TracTickets
 for help on using tickets.
    
This is fixed in the upcoming Django 2.2 release by ad82900ad94ed4bbad050b9993373dafbe66b610. The second migration doesn't have a
RemoveFieldoperation.