Opened 10 years ago

Last modified 6 months ago

#23577 assigned Bug

Rename operations should rename indexes, constraints, sequences and triggers named after their former value — at Version 28

Reported by: Chris Woytowitz Owned by:
Component: Migrations Version: dev
Severity: Normal Keywords:
Cc: bugs@…, aksheshdoshi@…, emorley@…, Ryan Hiebert, Vadym Moshynskyi, Thomas Riccardi, Sardorbek Imomaliev, Friedrich Delgado, Daniel Hahler, Bhuvnesh Triage Stage: Accepted
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description (last modified by Thomas Riccardi)

This one's a bit of an edge case, but I ran into it trying to refactor some models on my moderately large work project.

Let's say I have a Django 1.7 app called sample:

  1. Create a model Bar.
  2. Create a model Foo with bar = models.ForeignKey(Bar, blank=True, null=True)
  3. makemigrations (migration 0001)
  4. Rename Foo to OldFoo.
  5. makemigrations, say yes to the rename prompt (migration 0002)
  6. Create new model Foo, which also has bar = models.ForeignKey(Bar, blank=True, null=True)
  7. makemigrations (migration 0003)
  8. migrate

When migrate hits 0003, it throws this error:
django.db.utils.OperationalError: index sample_foo_4350f7d0 already exists

You may notice that sqlmigrate sample 0001 and sqlmigrate sample 0003 have the same line in both of them:
CREATE INDEX sample_foo_4350f7d0 ON "sample_foo" ("bar_id");

In my production case, I actually had no problems on servers, because South had created the index with a different name. When I renamed the models and added a field, the new index did not collide with the old one. However, our test suite started failing, because it would run the migrations from the ground up, exposing the above bug.

I haven't decided on a workaround yet, but I thought I'd bring this to your attention. I might have to inject a migration that renames the index created in 0001, or something to that effect.

The attached test case has a project dj17test in the state after having performed all of the above steps.

Change History (29)

by Chris Woytowitz, 10 years ago

Attachment: dj17test.zip added

comment:1 by Chris Woytowitz, 10 years ago

It is worth noting that if you use postgres instead, the error is django.db.utils.ProgrammingError: relation "sample_foo_4350f7d0" already exists.

comment:2 by Simon Charette, 10 years ago

Triage Stage: UnreviewedAccepted

Haven't reproduced but the issue seems legit from the report.

I guess the RenameModel operation should also rename indexes generated from the original model name.

comment:3 by Tom V, 9 years ago

Owner: changed from nobody to Tom V
Status: newassigned

comment:4 by Tom V, 9 years ago

I've found an associated case where a field with db_index=True, is renamed, and another db_indexed field created with the original's name.

Just discussed this with @andrewgodwin, I've agreed to write a test case for the field rename case, and then post about a solution on django-dev, with his preference being to make index names randomised.

comment:5 by Tom V, 9 years ago

I've added a schema test case: https://github.com/tomviner/django/compare/ticket_23577_with_tests

This should currently fail on all backends except sqlite (which doesn't rename fields - it drop/creates the whole table). When this bug is fixed, it should pass for all backends, for that reason I haven't added any unittest.skip decorator.

Edit: link to tests updated.

Last edited 9 years ago by Tom V (previous) (diff)

comment:6 by Tom V, 9 years ago

Owner: Tom V removed
Status: assignednew

After a discussion on django-dev it makes sense to leave the resolution of this issue to Marc Tamlyn's Advanced indexes DEP, which he says will involve user specified/reproducible index names, as well as index renaming.

comment:7 by ris, 9 years ago

Cc: bugs@… added

in reply to:  4 ; comment:8 by ris, 9 years ago

Replying to tomviner:

I've found an associated case where a field with db_index=True, is renamed, and another db_indexed field created with the original's name.

FWIW I have worked around this in my migrations by doing two AlterFields, dropping & re-creating the index by doing db_index=False then db_index=True.

comment:9 by skyjur, 8 years ago

This also happens when renaming a ManyToManyField and then adding a new ManyToManyField with the same name. I've opened ticket #25752 then @timgraham pointed out that it's duplicate of this one.

Last edited 8 years ago by Tim Graham (previous) (diff)

comment:10 by Ian Clark, 8 years ago

I tried to migrate to a new model and bumped into this one. To fix, I made sure that I renamed my existing model to "old" first (e.g. Model->ModelOld), create the new model with the previous name (e.g. Model), migrate the data and then remove the old model. My previous strategy of creating the new table migrating the data and then renaming it broke due to the index issues.

in reply to:  8 comment:11 by Luke Murphy, 8 years ago

Replying to ris:

Replying to tomviner:

I've found an associated case where a field with db_index=True, is renamed, and another db_indexed field created with the original's name.

FWIW I have worked around this in my migrations by doing two AlterFields, dropping & re-creating the index by doing db_index=False then db_index=True.

Confirmed. This worked for me also. I can use manage.py sqlmigrate <app> <migration> to see which indexes are giving me conflicts (should be the same as the error you get when you try to run migrate) and then used the db_index=False/True trick with AlterField.

comment:12 by Akshesh Doshi, 8 years ago

Cc: aksheshdoshi@… added

comment:13 by Simon Charette, 8 years ago

Summary: CREATE INDEX-related OperationalError on migrating renamed models with colliding field namesRenameModel should rename indexes and constraints named after its old table name
Version: 1.7master

#24528 was a duplicate.

comment:14 by Simon Charette, 8 years ago

Summary: RenameModel should rename indexes and constraints named after its old table nameRenameModel should rename indexes, constraints, sequences and triggers named after its old table name

#24715 was duplicate with a patch for the Oracle case where the sequence and triggers created for an AutoField are not renamed.

Last edited 8 years ago by Simon Charette (previous) (diff)

comment:15 by Simon Charette, 8 years ago

Summary: RenameModel should rename indexes, constraints, sequences and triggers named after its old table nameRename operations should rename indexes, constraints, sequences and triggers named after their former value

This should also be the case for AlterField operations on both sides (e.g. foreign key constraint names are generated from their referenced table and column names).

comment:16 by Ed Morley, 8 years ago

Cc: emorley@… added

comment:17 by Martin Koistinen, 7 years ago

Until this is resolved, one can work-around this issue by doing something like:

        migrations.RunSQL(
            'ALTER INDEX myapp_mymodel_myfield_othermodel_id_0f4cfc54 rename TO myapp_mymodel_myfield_othermodel_legacy_id_0f4cfc54',
            'ALTER INDEX myapp_mymodel_myfield_othermodel_legacy_id_0f4cfc54 rename TO myapp_mymodel_myfield_othermodel_id_0f4cfc54',
        )

This sort of approach should work well for both FKs and M2Ms (example below), but it may make your migrations DB Backend specific =/

comment:18 by Tim Graham, 6 years ago

I closed #28803 as a duplicate.

comment:19 by Ryan Hiebert, 6 years ago

Cc: Ryan Hiebert added

comment:20 by jonathan-golorry, 6 years ago

I just ran into this issue with RenameField on ManyToMany relations. The (automatically generated) through table changes name, but the id_seq for it doesn't.

comment:21 by Vadym Moshynskyi, 5 years ago

Cc: Vadym Moshynskyi added

comment:22 by Thomas Riccardi, 5 years ago

Cc: Thomas Riccardi added

comment:23 by Thomas Riccardi, 5 years ago

Some analysis (with postgresql backend):

  • when creating a new field with index (such as a ForeignKey), django creates the column and the index
  • when deleting a field, django only deletes the column: it lets postgresql delete all related index automatically
  • when renaming a field, django only renames the column: it lets postgresql update all index references (and probably more), *but does not* rename the index

It seems django doesn't really track the index name, and it works great except for renames

=> when a new index has to be created for the old field name, the index name conflicts: this is this issue.

This reliance on field reference automatic update by the db seems to create a similar issue with index_together/unique_together:
field rename + index_together/unique_together alternation to update the field name results in no SQL except the RENAME COLUMN: the index/unique names are *not* updated.

The workaround from comment 17 (ticket:23577#comment:17) with a manual index rename may create issues when this ticket is fixed:
at that point django will probably assume the index name is the one it generates if it had to create the index, and the comment uses a different one (the hash_suffix_part is different)
=> for more rubustness/future compatibility I suggest to use the "proper" index name: manually create migrations to delete then re-create the field, and use ./manage.py sqlmigrate to get the created index name (it depends solely on table name and index fields name: see django/db/backends/base/schema.py:BaseDatabaseSchemaEditor._create_index_name)

comment:24 by Thomas Riccardi, 5 years ago

To get the proper new index name:

from django.db import connection
schema_editor = connection.schema_editor()

def get_index_name(field):
    return schema_editor._create_index_name(field.model._meta.db_table, [field.attname])

get_index_name(ModelFoo.field_bar.field)

comment:25 by Sardorbek Imomaliev, 5 years ago

Cc: Sardorbek Imomaliev added

comment:26 by Friedrich Delgado, 5 years ago

Cc: Friedrich Delgado added

comment:27 by clavay, 5 years ago

Description: modified (diff)

I tried this and it works :
In the migration file I replace this :

        migrations.AlterField(
            model_name='foo',
            name='variable',
            field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='app.Variable'),
        ),

with this :

        migrations.RenameModel('Foo', 'FooNew'),
        migrations.AlterField(
            model_name='foonew',
            name='variable',
            field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='app.Variable'),
        ),
        migrations.RenameModel('FooNew', 'Foo'),

Is it a good solution ?

comment:28 by Thomas Riccardi, 5 years ago

Description: modified (diff)

Restore ticket description

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