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 #29697 -- Changed Query.trim_start for avoid removing the aux tables #10498
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.
Please try to reuse or modify existing models. Adding more models slows the tests.
tests/queries/tests.py
Outdated
~Q(company__employees__permissions__contains=Concat(Value('records.'), 'key', Value('.denied'))), | ||
company__employees__pk=4).order_by('id') | ||
|
||
self.assertQuerysetEqual( |
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.
prefer assertSequenceEqual
tests/queries/tests.py
Outdated
@@ -3918,3 +3920,31 @@ def test_ticket_23622(self): | |||
set(Ticket23605A.objects.filter(qy).values_list('pk', flat=True)) | |||
) | |||
self.assertSequenceEqual(Ticket23605A.objects.filter(qx), [a2]) | |||
|
|||
|
|||
class Ticket29697Tests(TestCase): |
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.
Better to use a descriptive name than "Ticket29697" (or at least add a docstring that explains). It doesn't seem like a separate class is needed if there's only 1 test.
tests/queries/models.py
Outdated
class Ticket29697Status(models.Model): | ||
company = models.ForeignKey( | ||
Ticket29697Company, | ||
on_delete=models.CASCADE) |
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.
Pass on_delete
as a positional argument.
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'd also appreciate if you could describe why this is needed in the issue description. A brief summary of your investigation would be appreciated. Altering trim_start
can have a lot of ramifications and cause unnecessary JOINs that the suite might not be catching.
django/db/models/sql/query.py
Outdated
from_opts_tables = [ | ||
path.from_opts.db_table for path in all_paths | ||
if path.from_opts.db_table != self.base_table | ||
] |
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.
Use a set since it's only used for containment checks.
tests/queries/tests.py
Outdated
status = Ticket29697Status.objects.filter( | ||
Q(company__employees__is_superuser=True) | | ||
~Q(company__employees__permissions__contains=Concat(Value('records.'), 'key', Value('.denied'))), | ||
company__employees__pk=4).order_by('id') |
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'd be great if you could make the test case more concise somehow. I doubt all of these criterias are required to hit the issue and they add a lot of noise which makes future debugging harder.
I tried to apply your recommendations. Could you check again? |
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.
Hey @cansarigol I don't have time to go through an extensive review now but I just wanted to say that the extra time you took to refine your patch and reduce it to the minimal changes required to address the issue is greatly appreciated.
django/db/models/sql/query.py
Outdated
if path.from_opts.db_table in alias_joined_list and | ||
path.from_opts.db_table != path.to_opts.db_table | ||
] | ||
]) |
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.
Use set literals, {...}
here and above.
django/db/models/sql/query.py
Outdated
alias_refcount might be changed hence need alias_joined_list | ||
to check available from join | ||
""" | ||
alias_joined_list = set([k for k, v in self.alias_refcount.items() if v > 0]) |
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.
Might want to rename to something else since it's not a list?
django/db/models/sql/query.py
Outdated
@@ -2076,13 +2094,17 @@ def trim_start(self, names_with_path): | |||
t for t in self.alias_map | |||
if t in self._lookup_joins or t == self.base_table | |||
] | |||
# from_opts_tables for manage to define unref_alias table. | |||
# before unref, make sure the table wasn't added | |||
# in another from_opts as reference. |
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 guess this comment can be removed now?
tests/queries/tests.py
Outdated
@@ -1199,6 +1199,16 @@ def test_common_mixed_case_foreign_keys(self): | |||
) | |||
self.assertTrue(qs.first()) | |||
|
|||
def test_ticket_29697_complex_query_crashes(self): |
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.
test_excluded_intermediary_m2m_table_joined
?
@charettes thanks a lot. sorry about these shortcomings and thank again for your help. |
django/db/models/sql/query.py
Outdated
@@ -2082,7 +2100,8 @@ def trim_start(self, names_with_path): | |||
if self.alias_map[lookup_tables[trimmed_paths + 1]].join_type == LOUTER: | |||
contains_louter = True | |||
alias = lookup_tables[trimmed_paths] | |||
self.unref_alias(alias) | |||
if self.is_alias_available_join(all_paths, alias): |
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 don't see a test failure with this change reverted. If it's needed, could you add a test for it?
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'm not sure whether is_alias_available_join is useful. the tables I am interested at will not be there. so I removed it
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.
The changes require a few adjustments before being merged. They might happen to work for the tested case and pass on the suite it's not dealing with aliases appropriately and will be really inefficient when dealing with large querysets.
django/db/models/sql/query.py
Outdated
If so shouldn't be removed | ||
alias_refcount might be changed hence need joined_alias | ||
to check available from join | ||
""" |
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.
This docstring needs rewording. It should focus on what the function is doing and not on what the caller should do with the returned value.
django/db/models/sql/query.py
Outdated
path.from_opts.db_table != path.to_opts.db_table | ||
} | ||
} | ||
return alias not in from_to_tables |
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.
This seems overly complicated and probably also wrong with tables that are JOIN'ed more than once as the following JOINs will have aliases that are not db_table
.
> from django.contrib.auth.models import User
> User.objects.filter(groups__name='foo').query.alias_refcount
{'auth_group': 1, 'auth_user': 1, 'auth_user_groups': 1}
> User.objects.filter(groups__name='foo').filter(groups__name='bar').query.alias_refcount
{'T4': 1, 'T5': 1, 'auth_group': 1, 'auth_user': 2, 'auth_user_groups': 1}
Also you probably don't need to build the from_to_tables
set as it's really inneficent. You should use a loop and exit the function as soon as a matching alias is found.
thanks for your reviews. @charettes you're right about alias, I think so too but I couldn't find a better solution, it exceeded me. I changed is_alias_available_join |
This part of the ORM is definitely tricky, it's normal that you are feeling overwhelmed. I still think we should find a way to test the multi-alias case and address it before merging this change though. |
efd2dc6
to
6d0721a
Compare
6d0721a
to
0595465
Compare
hi @charettes, pushed a commit about maintaining is available check func before unref_alias and test for multi-alias queries. |
The added test passes without changes in |
that's a great solution. @charettes 👍 |
Ticket: #29697