[Django] #30276: reverse DeleteModel migration with a primary key OneToOneField fails in MySQL

34 views
Skip to first unread message

Django

unread,
Mar 21, 2019, 7:52:35 AM3/21/19
to django-...@googlegroups.com
#30276: reverse DeleteModel migration with a primary key OneToOneField fails in
MySQL
-------------------------------------+-------------------------------------
Reporter: Florian | Owner: nobody
Zimmermann |
Type: | Status: new
Uncategorized |
Component: | Version: 2.1
Migrations |
Severity: Normal | Keywords: migrations, mysql
Triage Stage: | Has patch: 0
Unreviewed |
Needs documentation: 0 | Needs tests: 0
Patch needs improvement: 0 | Easy pickings: 0
UI/UX: 0 |
-------------------------------------+-------------------------------------
== Preparations ==

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

{{{#!python
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:

{{{#!python
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:
{{{#!python
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:
{{{#!python
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`):
{{{#!sql
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;
}}}

--
Ticket URL: <https://code.djangoproject.com/ticket/30276>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.

Django

unread,
Mar 21, 2019, 10:38:09 AM3/21/19
to django-...@googlegroups.com
#30276: reverse DeleteModel migration with a primary key OneToOneField fails in
MySQL
------------------------------------+--------------------------------------
Reporter: Florian Zimmermann | Owner: nobody
Type: Bug | Status: closed
Component: Migrations | Version: 2.1
Severity: Normal | Resolution: fixed
Keywords: migrations, mysql | Triage Stage: Unreviewed
Has patch: 0 | Needs documentation: 0

Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
------------------------------------+--------------------------------------
Changes (by Tim Graham):

* status: new => closed
* type: Uncategorized => Bug
* resolution: => fixed


Comment:

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

--
Ticket URL: <https://code.djangoproject.com/ticket/30276#comment:1>

Reply all
Reply to author
Forward
0 new messages