Opened 11 months ago
Closed 11 months ago
#35991 closed Bug (fixed)
Migrations crash on SQLite when renaming part of CompositePrimaryKey.
| Reported by: | Mariusz Felisiak | Owned by: | Mariusz Felisiak |
|---|---|---|---|
| Component: | Database layer (models, ORM) | Version: | dev |
| Severity: | Release blocker | Keywords: | |
| Cc: | Csirmaz Bendegúz, Simon Charette | Triage Stage: | Ready for checkin |
| Has patch: | yes | Needs documentation: | no |
| Needs tests: | no | Patch needs improvement: | no |
| Easy pickings: | no | UI/UX: | no |
Description
I've created a sample project that tries to rename a column included in CompositePrimaryKey. Unfortunately, it crashes:
$ python manage.py migrate test_one 0002
...
File "/django/db/backends/base/schema.py", line 509, in create_model
sql, params = self.table_sql(model)
^^^^^^^^^^^^^^^^^^^^^
File "/django/db/backends/base/schema.py", line 290, in table_sql
constraint_sqls.append(self._pk_constraint_sql(pk.columns))
^^^^^^^^^^
File "/django/utils/functional.py", line 47, in __get__
res = instance.__dict__[self.name] = self.func(instance)
^^^^^^^^^^^^^^^^^^^
File "/django/db/models/fields/composite.py", line 81, in columns
return tuple(field.column for field in self.fields)
^^^^^^^^^^^
File "/django/utils/functional.py", line 47, in __get__
res = instance.__dict__[self.name] = self.func(instance)
^^^^^^^^^^^^^^^^^^^
File "/django/db/models/fields/composite.py", line 77, in fields
return tuple(meta.get_field(field_name) for field_name in self.field_names)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/django/db/models/fields/composite.py", line 77, in <genexpr>
return tuple(meta.get_field(field_name) for field_name in self.field_names)
^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/django/db/models/options.py", line 685, in get_field
raise FieldDoesNotExist(
django.core.exceptions.FieldDoesNotExist: NewRelease has no field named 'name'
I've attached a sample project. I can prepare a patch in the coming days.
Attachments (1)
Change History (10)
by , 11 months ago
| Attachment: | ticket_373_rename_column.zip added |
|---|
follow-up: 5 comment:2 by , 11 months ago
| Triage Stage: | Unreviewed → Accepted |
|---|
Thanks for the report!
The issue likely lies in ProjectState.rename_field and in MigrationAutoDetector.create_renamed_fields which shouldn't generate the AlterField against the composite primary key in the first place.
comment:3 by , 11 months ago
| Owner: | set to |
|---|---|
| Status: | new → assigned |
comment:4 by , 11 months ago
| Cc: | added |
|---|
comment:5 by , 11 months ago
Replying to Simon Charette:
Thanks for the report!
The issue likely lies in
ProjectState.rename_fieldand inMigrationAutoDetector.create_renamed_fieldswhich shouldn't generate theAlterFieldagainst the composite primary key in the first place.
In my case the second migration has three operations:
migrations.RenameField(
model_name='release',
old_name='name',
new_name='name2',
),
migrations.AddField(
model_name='release',
name='rename',
field=models.CharField(default='x', max_length=20),
preserve_default=False,
),
migrations.AlterField(
model_name='release',
name='pk',
field=models.CompositePrimaryKey('version', 'rename', blank=True, editable=False, primary_key=True, serialize=False),
),
AddField crashes like in the ticket description, not AlterField, at least on SQLite where it tries to remake a table. I think we should update field references for CompositePrimaryKey before remaking a table. The following draft patch fixes this crash for me:
-
django/db/migrations/state.py
diff --git a/django/db/migrations/state.py b/django/db/migrations/state.py index 6cf675d0e4..9a4549f1f1 100644
a b from django.core.exceptions import FieldDoesNotExist 11 11 from django.db import models 12 12 from django.db.migrations.utils import field_is_referenced, get_references 13 13 from django.db.models import NOT_PROVIDED 14 from django.db.models.fields.composite import CompositePrimaryKey 14 15 from django.db.models.fields.related import RECURSIVE_RELATIONSHIP_CONSTANT 15 16 from django.db.models.options import DEFAULT_NAMES, normalize_together 16 17 from django.db.models.utils import make_model_tuple … … class ProjectState: 355 356 field = to_model[model_key].pop(old_name_lower) 356 357 field.name = new_name_lower 357 358 to_model[model_key][new_name_lower] = field 359 # Fix field references in a composite primary key. 360 for field_name, field in model_state.fields.items(): 361 if isinstance(field, CompositePrimaryKey): 362 if old_name in field.field_names: 363 field.field_names = tuple( 364 new_name if sub_field_name == old_name else sub_field_name 365 for sub_field_name in field.field_names 366 ) 367 358 368 self.reload_model(*model_key, delay=delay) 359 369 360 370 def _find_reload_model(self, app_label, model_name, delay=False):
What do you think?
comment:6 by , 11 months ago
| Cc: | added |
|---|
comment:8 by , 11 months ago
| Triage Stage: | Accepted → Ready for checkin |
|---|
I've found other migrations that crash in exactly the same way on SQLite.