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

feat: Nested optimization for lists and connections #540

Merged
merged 1 commit into from
Jun 10, 2024

Conversation

bellini666
Copy link
Member

@bellini666 bellini666 commented Jun 2, 2024

This is going to enable optimization of nested relations even when using filters/ordering/pagination, which previously would mean throwing the results away and fetching new ones, causing more than n+1 issues.

Note: Although nested connections are being optimized as well, we are only doing that for those annotated with ListConnection and ListConnectionWithTotalCount, as custom connections may contain more complex code which are not safe to be optimized the way we are doing here.

We are also dropping support for Django versions older than 4.2 as we cannot filter over window functions in older versions. That is also a recommendation from Django itself when 5.0 was released: https://docs.djangoproject.com/en/5.0/releases/5.0

Fix #337
Fix #340
Fix #345
Fix #389
Fix #521

@bellini666 bellini666 self-assigned this Jun 2, 2024
@bellini666 bellini666 requested a review from patrick91 June 2, 2024 00:41
Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey @bellini666 - I've reviewed your changes and they look great!

Here's what I looked at during the review
  • 🟡 General issues: 7 issues found
  • 🟢 Security: all looks good
  • 🟡 Testing: 3 issues found
  • 🟢 Complexity: all looks good

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment to tell me if it was helpful.

strawberry_django/optimizer.py Outdated Show resolved Hide resolved
strawberry_django/optimizer.py Show resolved Hide resolved
strawberry_django/optimizer.py Show resolved Hide resolved
strawberry_django/relay.py Outdated Show resolved Hide resolved
strawberry_django/pagination.py Show resolved Hide resolved
strawberry_django/fields/types.py Show resolved Hide resolved
tests/test_optimizer.py Show resolved Hide resolved
tests/test_optimizer.py Show resolved Hide resolved
tests/filters/test_filters.py Show resolved Hide resolved
strawberry_django/optimizer.py Show resolved Hide resolved
@codecov-commenter
Copy link

codecov-commenter commented Jun 2, 2024

Codecov Report

Attention: Patch coverage is 88.98305% with 13 lines in your changes missing coverage. Please review.

Project coverage is 88.67%. Comparing base (eec919c) to head (305cacb).

Files Patch % Lines
strawberry_django/optimizer.py 83.67% 8 Missing ⚠️
strawberry_django/relay.py 89.65% 3 Missing ⚠️
strawberry_django/fields/field.py 80.00% 1 Missing ⚠️
strawberry_django/pagination.py 95.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #540      +/-   ##
==========================================
+ Coverage   88.60%   88.67%   +0.07%     
==========================================
  Files          41       41              
  Lines        3439     3524      +85     
==========================================
+ Hits         3047     3125      +78     
- Misses        392      399       +7     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

strawberry_django/fields/field.py Outdated Show resolved Hide resolved
strawberry_django/optimizer.py Outdated Show resolved Hide resolved
strawberry_django/pagination.py Show resolved Hide resolved
Comment on lines +82 to +109
if limit >= 0:
queryset = queryset.filter(_strawberry_row_number__lte=offset + limit)
Copy link
Member

Choose a reason for hiding this comment

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

should we enforce a limit?

Copy link
Member Author

Choose a reason for hiding this comment

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

This is just adding support to do the pagination from the optimizer.

I agree that we maybe should enforce a limit, but I'd do that in another PR focused on that


def get_queryset(
self,
queryset: _QS,
info: Info,
*,
pagination: Optional[object] = None,
_strawberry_related_field_id: Optional[str] = None,
Copy link
Member

Choose a reason for hiding this comment

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

why is this private? 😊

Copy link
Member Author

Choose a reason for hiding this comment

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

This is passed in **kwargs. Wanted to make sure this would not conflict at all with some user-given argument in the resolver.

But maybe the leading strawberry_ is enough? What do you think?

Copy link
Member

Choose a reason for hiding this comment

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

probably, but it is fine to keep the underscore

strawberry_django/relay.py Outdated Show resolved Hide resolved
strawberry_django/relay.py Outdated Show resolved Hide resolved
strawberry_django/fields/field.py Outdated Show resolved Hide resolved
strawberry_django/optimizer.py Outdated Show resolved Hide resolved

def get_queryset(
self,
queryset: _QS,
info: Info,
*,
pagination: Optional[object] = None,
_strawberry_related_field_id: Optional[str] = None,
Copy link
Member

Choose a reason for hiding this comment

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

probably, but it is fine to keep the underscore

@Eraldo
Copy link
Contributor

Eraldo commented Jun 4, 2024

Note: Although nested connections are being optimized as well, we are only doing that for those annotated with ListConnection and ListConnectionWithTotalCount, as custom connections may contain more complex code which are not safe to be optimized the way we are doing here.

Does this also apply to connections that inherit from ListConnection or ListConnectionWithTotalCount? 🤔

Eraldo added a commit to Eraldo/strawberry-django-lazy-connection-missing-filters-mre that referenced this pull request Jun 5, 2024
@Eraldo
Copy link
Contributor

Eraldo commented Jun 5, 2024

@bellini666 As you suggested, I tried using this PR.
I upgraded my minimal reproduction and it does not solve issue: #535

@bellini666
Copy link
Member Author

Note: Although nested connections are being optimized as well, we are only doing that for those annotated with ListConnection and ListConnectionWithTotalCount, as custom connections may contain more complex code which are not safe to be optimized the way we are doing here.

Does this also apply to connections that inherit from ListConnection or ListConnectionWithTotalCount? 🤔

Yes, because it is not safe if you are doing customizations, as the optimizer will actually change what nodes is

But if you are doing FruitConnection = ListConnectionWithTotalCount[Fruit] then you are safe.

But if you still need to optimize a connection subclass, we can think of a way of doing that... maybe by defining a ALLOW_UNSAFE_OPTIMIZATIONS = True in the class?

Copy link
Member

@patrick91 patrick91 left a comment

Choose a reason for hiding this comment

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

🔥

@bellini666 bellini666 merged commit eecec51 into main Jun 10, 2024
22 checks passed
@bellini666 bellini666 deleted the nested_optimizer branch June 10, 2024 16:49
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