#32634 closed Bug (invalid)
AlterField drops contraints in the wrong order when performed as reverse migration
| Reported by: | Matthias Dellweg | Owned by: | nobody |
|---|---|---|---|
| Component: | Migrations | Version: | 3.2 |
| Severity: | Normal | Keywords: | |
| Cc: | Triage Stage: | Unreviewed | |
| Has patch: | no | Needs documentation: | no |
| Needs tests: | no | Patch needs improvement: | no |
| Easy pickings: | no | UI/UX: | no |
Description
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.
Change History (4)
comment:1 by , 5 years ago
| Resolution: | → needsinfo |
|---|---|
| Status: | new → closed |
comment:2 by , 5 years ago
| Resolution: | needsinfo |
|---|---|
| Status: | closed → new |
| Version: | 2.2 → 3.2 |
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.
comment:3 by , 5 years ago
| Resolution: | → invalid |
|---|---|
| Status: | new → closed |
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().
comment:4 by , 5 years ago
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:
- Add the new secondbase_ptr (as null and not and index)
- Migrate all associated objects from FirstBase to SecondBase and make secondbase_ptr reference them (This is where RunPython comes to the play)
- Remove the firstbase_ptr (to make place for another primary key)
- 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?
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?