Skip to content
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

Closed
wants to merge 1 commit into from

Conversation

cansarigol
Copy link
Contributor

@cansarigol cansarigol commented Oct 10, 2018

Ticket: #29697

Copy link
Member

@timgraham timgraham left a 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.

~Q(company__employees__permissions__contains=Concat(Value('records.'), 'key', Value('.denied'))),
company__employees__pk=4).order_by('id')

self.assertQuerysetEqual(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

prefer assertSequenceEqual

@@ -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):
Copy link
Member

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.

class Ticket29697Status(models.Model):
company = models.ForeignKey(
Ticket29697Company,
on_delete=models.CASCADE)
Copy link
Member

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.

Copy link
Member

@charettes charettes left a 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.

from_opts_tables = [
path.from_opts.db_table for path in all_paths
if path.from_opts.db_table != self.base_table
]
Copy link
Member

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.

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')
Copy link
Member

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.

@cansarigol
Copy link
Contributor Author

I tried to apply your recommendations. Could you check again?

Copy link
Member

@charettes charettes left a 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.

if path.from_opts.db_table in alias_joined_list and
path.from_opts.db_table != path.to_opts.db_table
]
])
Copy link
Member

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.

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])
Copy link
Member

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?

@@ -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.
Copy link
Member

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?

@@ -1199,6 +1199,16 @@ def test_common_mixed_case_foreign_keys(self):
)
self.assertTrue(qs.first())

def test_ticket_29697_complex_query_crashes(self):
Copy link
Member

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?

@cansarigol
Copy link
Contributor Author

@charettes thanks a lot. sorry about these shortcomings and thank again for your help.

@@ -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):
Copy link
Member

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?

Copy link
Contributor Author

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

Copy link
Member

@charettes charettes left a 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.

If so shouldn't be removed
alias_refcount might be changed hence need joined_alias
to check available from join
"""
Copy link
Member

@charettes charettes Oct 31, 2018

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.

path.from_opts.db_table != path.to_opts.db_table
}
}
return alias not in from_to_tables
Copy link
Member

@charettes charettes Oct 31, 2018

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.

@cansarigol
Copy link
Contributor Author

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

@charettes
Copy link
Member

you're right about alias, I think so too but I couldn't find a better solution, it exceeded me.

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.

@cansarigol
Copy link
Contributor Author

hi @charettes, pushed a commit about maintaining is available check func before unref_alias and test for multi-alias queries.

@felixxm
Copy link
Member

felixxm commented Mar 29, 2019

The added test passes without changes in Query. Bug was probably fixed by Simon refactorization or test doesn't cover this issue. I believe in Simon 😄 , so I will check which commit has fixed this.

@felixxm felixxm self-assigned this Mar 29, 2019
@felixxm
Copy link
Member

felixxm commented Mar 29, 2019

Bug was fixed by f19a494.

Tests updated in #11142.

@felixxm felixxm closed this Mar 29, 2019
@felixxm felixxm removed their assignment Mar 29, 2019
@cansarigol
Copy link
Contributor Author

that's a great solution. @charettes 👍

@cansarigol cansarigol deleted the ticket_29697 branch March 29, 2019 22:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
4 participants