New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fixed #34529, Refs #34525 -- Reduced index operations with Meta.indexes/index_together when optimizing migrations. #16820
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
Reading this made me realise there's also some more opportunities for reducing constraints into createmodel - this can be done along with those other potential changes we discussed in the new ticket.
(May also warrant checking if anything could be included)
Yes, but it's not related with |
buildbot, test on oracle. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried to run this locally on a project that had a 6 migratios:
0001 - Initial migration using the deprecated index_together
0002 - The migrations.RenameIndex
operation following the deprecation update path from the 4.2 release notes
0003 up to 0006 various index operations following the report from ticket #34525
When squashing migrations, I get:
(djangodev) [nessita@dali projectfromrepo]$ python manage.py squashmigrations reduceindexops 0006 --noinput
Will squash the following migrations:
- 0001_initial
- 0002_rename_mymodel_field_2_field_4_reduceindex_field_2_92cb62_idx
- 0003_mymodel_reduceindex_field_3_d18611_idx
- 0004_mymodel_reduceindex_field_3_fb791e_idx
- 0005_remove_mymodel_reduceindex_field_2_92cb62_idx_and_more
- 0006_rename_reduceindex_field_1_098efd_idx_custom_name_idx
Optimizing...
Traceback (most recent call last):
File "/home/nessita/fellowship/projectfromrepo/manage.py", line 22, in <module>
main()
File "/home/nessita/fellowship/projectfromrepo/manage.py", line 18, in main
execute_from_command_line(sys.argv)
File "/home/nessita/fellowship/django/django/core/management/__init__.py", line 442, in execute_from_command_line
utility.execute()
File "/home/nessita/fellowship/django/django/core/management/__init__.py", line 436, in execute
self.fetch_command(subcommand).run_from_argv(self.argv)
File "/home/nessita/fellowship/django/django/core/management/base.py", line 412, in run_from_argv
self.execute(*args, **cmd_options)
File "/home/nessita/fellowship/django/django/core/management/base.py", line 458, in execute
output = self.handle(*args, **options)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/nessita/fellowship/django/django/core/management/commands/squashmigrations.py", line 172, in handle
new_operations = optimizer.optimize(operations, migration.app_label)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/nessita/fellowship/django/django/db/migrations/optimizer.py", line 34, in optimize
result = self.optimize_inner(operations, app_label)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/nessita/fellowship/django/django/db/migrations/optimizer.py", line 47, in optimize_inner
result = operation.reduce(other, app_label)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/nessita/fellowship/django/django/db/migrations/operations/models.py", line 345, in reduce
options_index_together = {
^
TypeError: 'NoneType' object is not iterable
Will debug further but wanted to let you know before this gets merged.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changes are looking good, it should make for a better index_together
deprecation experience.
Something I noticed while looking the at the code is that RenameIndex.references_field
is not specialized to consider self.old_fields
. A similar picture can be drawn for AddIndex
and RemoveIndex
wrt/ to self.index.fields
(and even conditions, expressions, ) which could cause issues with regards to optimizations of complex migration as the optimizer won't be able to break some cycles
django/django/db/migrations/operations/base.py
Lines 108 to 115 in c61219a
def references_field(self, model_name, name, app_label): | |
""" | |
Return True if there is a chance this operation references the given | |
field name, with an app label for accuracy. | |
Used for optimization. If in doubt, return True. | |
""" | |
return self.references_model(model_name, app_label) |
In short the code proposed here is correct but it might fail to optimize complex scenarios and require manual interventions.
Thanks for reviews 👍 I pushed small edits. |
Good catch 🎯 Fixed. |
It took me a while but I managed to reproduce the 0001_initial.py # Generated by Django 3.2.18 on 2023-05-02 13:44
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='MyModel',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('field_1', models.IntegerField()),
('field_2', models.TextField()),
('field_3', models.IntegerField()),
],
options={
'index_together': {('field_3',), ('field_1', 'field_2')},
},
),
] 0002_rename_mymodel_field_1_field_2_reduceindex_field_1_098efd_idx_and_more.py # Generated by Django 4.2 on 2023-05-02 13:47
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('reduceindexops', '0001_initial'),
]
operations = [
migrations.RenameIndex(
model_name='mymodel',
new_name='reduceindex_field_1_098efd_idx',
old_fields=('field_1', 'field_2'),
),
migrations.RenameIndex(
model_name='mymodel',
new_name='reduceindex_field_3_d18611_idx',
old_fields=('field_3',),
),
] 0003_remove_mymodel_reduceindex_field_1_098efd_idx_and_more.py # Generated by Django 4.2 on 2023-05-02 13:49
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
(
'reduceindexops',
'0002_rename_mymodel_field_1_field_2_reduceindex_field_1_098efd_idx_and_more',
),
]
operations = [
migrations.RemoveIndex(
model_name='mymodel',
name='reduceindex_field_1_098efd_idx',
),
migrations.AddField(
model_name='mymodel',
name='field_4',
field=models.IntegerField(null=True),
),
migrations.AddIndex(
model_name='mymodel',
index=models.Index(
fields=['field_2', 'field_4'],
name='reduceindex_field_2_92cb62_idx',
),
),
migrations.AddIndex(
model_name='mymodel',
index=models.Index(
fields=['field_3', 'field_4'],
name='reduceindex_field_3_fb791e_idx',
),
),
] 0004_rename_reduceindex_field_2_92cb62_idx_custom_name_idx.py # Generated by Django 4.2 on 2023-05-02 13:56
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
(
'reduceindexops',
'0003_remove_mymodel_reduceindex_field_1_098efd_idx_and_more',
),
]
operations = [
migrations.RenameIndex(
model_name='mymodel',
new_name='custom_name_idx',
old_name='reduceindex_field_2_92cb62_idx',
),
] |
Thanks. Were you able to reproduce the issue before submitting the fix? --- a/django/db/migrations/operations/models.py
+++ b/django/db/migrations/operations/models.py
@@ -344,7 +344,7 @@ class CreateModel(ModelOperation):
elif isinstance(operation, RenameIndex):
options_index_together = {
fields
- for fields in self.options.get("index_together")
+ for fields in self.options.get("index_together", [])
if fields != operation.old_fields
}
if options_index_together: yet this branch with the above change results in another exception:
|
The |
Fixed, I added a test. |
Thanks @felixxm, the latest push did fix errors and I was able to squash migrations. Otherwise this looks good! 👍 |
…es/index_together when optimizing migrations. This makes squashing migrations an available path for changing Meta.index_together, which is deprecated, to Meta.indexes. Follow up to f810325.
This should be covered by |
By --- a/django/db/migrations/operations/models.py
+++ b/django/db/migrations/operations/models.py
@@ -344,7 +344,7 @@ class CreateModel(ModelOperation):
elif isinstance(operation, RenameIndex) and operation.old_fields:
options_index_together = {
fields
- for fields in self.options.get("index_together", [])
+ for fields in self.options.get("index_together")
if fields != operation.old_fields
}
if options_index_together: no test fails:
This is where I think the code would benefit by having a test ensuring that no crash occurs if |
As I said, this is covered by diff --git a/django/db/migrations/operations/models.py b/django/db/migrations/operations/models.py
index 9c5bd5fbcb..39b16f4902 100644
--- a/django/db/migrations/operations/models.py
+++ b/django/db/migrations/operations/models.py
@@ -341,10 +341,10 @@ class CreateModel(ModelOperation):
managers=self.managers,
),
]
- elif isinstance(operation, RenameIndex) and operation.old_fields:
+ elif isinstance(operation, RenameIndex):
options_index_together = {
fields
- for fields in self.options.get("index_together", [])
+ for fields in self.options.get("index_together")
if fields != operation.old_fields
}
if options_index_together:
IMO, this is enough. |
This makes squashing migrations an available path for changing
Meta.index_together
, which is deprecated, toMeta.indexes
.ticket-34529
ticket-34525