[Django] #32634: AlterField drops contraints in the wrong order when performed as reverse migration

21 views
Skip to first unread message

Django

unread,
Apr 12, 2021, 5:22:04 AM4/12/21
to django-...@googlegroups.com
#32634: AlterField drops contraints in the wrong order when performed as reverse
migration
--------------------------------------------+------------------------
Reporter: Matthias Dellweg | Owner: nobody
Type: Bug | Status: new
Component: Migrations | Version: 2.2
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 |
--------------------------------------------+------------------------
The following migration step

{{{
migrations.AlterField(
model_name='filedistribution',
name='distribution_ptr',
field=models.OneToOneField(auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True, primary_key=True,
related_name='file_filedistribution', serialize=False,
to='core.Distribution'),
preserve_default=False,
),
}}}

in reverse mode produces

{{{
ALTER TABLE "file_filedistribution" ALTER COLUMN "distribution_ptr_id"
DROP NOT NULL;
ALTER TABLE "file_filedistribution" DROP CONSTRAINT
"file_filedistribution_distribution_ptr_id_5ce5564b_pk";
}}}

leading to an error

{{{
django.db.utils.ProgrammingError: column "distribution_ptr_id" is in a
primary key
}}}

Switching the order of the statements enabled me execute the SQL just
fine.

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

Django

unread,
Apr 12, 2021, 6:42:39 AM4/12/21
to django-...@googlegroups.com
#32634: AlterField drops contraints in the wrong order when performed as reverse
migration
----------------------------------+--------------------------------------

Reporter: Matthias Dellweg | Owner: nobody
Type: Bug | Status: closed
Component: Migrations | Version: 2.2
Severity: Normal | Resolution: needsinfo

Keywords: | Triage Stage: Unreviewed
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
----------------------------------+--------------------------------------
Changes (by Mariusz Felisiak):

* status: new => closed
* resolution: => needsinfo


Comment:

Thanks for this report. Can you share a database state before this
operation? or a sample project which reproduces this issue.

Also, Django 2.2 is in extended support so it receives only security
fixes. Can you reproduce this issue on the main branch?

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

Django

unread,
Apr 13, 2021, 7:01:23 AM4/13/21
to django-...@googlegroups.com
#32634: AlterField drops contraints in the wrong order when performed as reverse
migration
----------------------------------+--------------------------------------

Reporter: Matthias Dellweg | Owner: nobody
Type: Bug | Status: new
Component: Migrations | Version: 3.2
Severity: Normal | Resolution:

Keywords: | Triage Stage: Unreviewed
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
----------------------------------+--------------------------------------
Changes (by Matthias Dellweg):

* status: closed => new
* version: 2.2 => 3.2
* resolution: needsinfo =>


Comment:

I could reproduce with Django==3.2.

I created a sample project here:
https://github.com/mdellweg/migrationtest

If you configure this app to a postgres driver and call
`migrationtest/manage.py sqlmigrate --backwards pk_migration 0002`, you
will see:
{{{
BEGIN;
--
-- Alter field secondbase_ptr on child
--
SET CONSTRAINTS
"pk_migration_child_secondbase_ptr_id_9b785ce8_fk_pk_migrat" IMMEDIATE;
ALTER TABLE "pk_migration_child" DROP CONSTRAINT
"pk_migration_child_secondbase_ptr_id_9b785ce8_fk_pk_migrat";
ALTER TABLE "pk_migration_child" ALTER COLUMN "secondbase_ptr_id" DROP NOT
NULL;
ALTER TABLE "pk_migration_child" DROP CONSTRAINT
"pk_migration_child_secondbase_ptr_id_9b785ce8_pk";
ALTER TABLE "pk_migration_child" ADD CONSTRAINT
"pk_migration_child_secondbase_ptr_id_9b785ce8_uniq" UNIQUE
("secondbase_ptr_id");
ALTER TABLE "pk_migration_child" ADD CONSTRAINT
"pk_migration_child_secondbase_ptr_id_9b785ce8_fk_pk_migrat" FOREIGN KEY
("secondbase_ptr_id") REFERENCES "pk_migration_secondbase" ("id")
DEFERRABLE INITIALLY DEFERRED;
--
-- Remove field firstbase_ptr from child
--
ALTER TABLE "pk_migration_child" ADD COLUMN "firstbase_ptr_id" bigint NOT
NULL PRIMARY KEY CONSTRAINT
"pk_migration_child_firstbase_ptr_id_3ea33ffb_fk_pk_migrat" REFERENCES
"pk_migration_firstbase"("id") DEFERRABLE INITIALLY DEFERRED; SET
CONSTRAINTS "pk_migration_child_firstbase_ptr_id_3ea33ffb_fk_pk_migrat"
IMMEDIATE;
--
-- MIGRATION NOW PERFORMS OPERATION THAT CANNOT BE WRITTEN AS SQL:
-- Raw Python operation
--
--
-- Add field secondbase_ptr to child
--
SET CONSTRAINTS
"pk_migration_child_secondbase_ptr_id_9b785ce8_fk_pk_migrat" IMMEDIATE;
ALTER TABLE "pk_migration_child" DROP CONSTRAINT
"pk_migration_child_secondbase_ptr_id_9b785ce8_fk_pk_migrat";
ALTER TABLE "pk_migration_child" DROP COLUMN "secondbase_ptr_id" CASCADE;
COMMIT;
}}}
Which will fail to migrate with `column "secondbase_ptr_id" is in a
primary key` when trying to execute `ALTER TABLE "pk_migration_child"
ALTER COLUMN "secondbase_ptr_id" DROP NOT NULL;`.

If you move `ALTER TABLE "pk_migration_child" DROP CONSTRAINT
"pk_migration_child_secondbase_ptr_id_9b785ce8_pk";` one place up in the
command chain, the migration will succeed.

--
Ticket URL: <https://code.djangoproject.com/ticket/32634#comment:2>

Django

unread,
Apr 13, 2021, 7:32:03 AM4/13/21
to django-...@googlegroups.com
#32634: AlterField drops contraints in the wrong order when performed as reverse
migration
----------------------------------+--------------------------------------

Reporter: Matthias Dellweg | Owner: nobody
Type: Bug | Status: closed
Component: Migrations | Version: 3.2
Severity: Normal | Resolution: invalid

Keywords: | Triage Stage: Unreviewed
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
----------------------------------+--------------------------------------
Changes (by Mariusz Felisiak):

* status: new => closed

* resolution: => invalid


Comment:

Matthias, thanks for details, however this is not a migration created by
Django. Everything works when I will remove `0002_auto_20210413_0915.py`
and use a generated migration:
{{{
$ python manage.py migrate
Operations to perform:
Apply all migrations: admin, auth, contenttypes, pk_migration, sessions
Running migrations:
Applying pk_migration.0001_initial... OK
Applying pk_migration.0002_auto_20210413_1116... OK

$ python manage.py migrate pk_migration zero
Operations to perform:
Unapply all migrations: pk_migration
Running migrations:
Rendering model states... DONE
Unapplying pk_migration.0002_auto_20210413_1116... OK
Unapplying pk_migration.0001_initial... OK
}}}

You could probably make it works with `SeparateDatabaseAndState()`.

--
Ticket URL: <https://code.djangoproject.com/ticket/32634#comment:3>

Django

unread,
Apr 14, 2021, 3:46:40 AM4/14/21
to django-...@googlegroups.com
#32634: AlterField drops contraints in the wrong order when performed as reverse
migration
----------------------------------+--------------------------------------

Reporter: Matthias Dellweg | Owner: nobody
Type: Bug | Status: closed
Component: Migrations | Version: 3.2
Severity: Normal | Resolution: invalid
Keywords: | Triage Stage: Unreviewed
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
----------------------------------+--------------------------------------

Comment (by Matthias Dellweg):

Thanks for you answer, Mariusz.

I do not think that this solves the problem. Yes the migration was
handcrafted (well heavily modified), but autogenerating it was impossible
without providing a default value for what is supposed to be the new
primary key of the model.
So with actual ojects of that Child model, what you need to do is:
1. Add the new secondbase_ptr (as null and not and index)
2. Migrate all associated objects from FirstBase to SecondBase and make
secondbase_ptr reference them (This is where RunPython comes to the play)
3. Remove the firstbase_ptr (to make place for another primary key)
4. Alter secondbase_ptr to not-null and being the primary key.

My best way to resolve this results in the question: Can i call AlterField
in a way to turn the primary key into a common field? Or is there another
way to pivot the primary key?

--
Ticket URL: <https://code.djangoproject.com/ticket/32634#comment:4>

Reply all
Reply to author
Forward
0 new messages