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

Add support for optimizing querysets with annotations #35

Open
wants to merge 7 commits into
base: master
Choose a base branch
from

Conversation

hilmar8
Copy link

@hilmar8 hilmar8 commented Oct 20, 2019

I've been using my own fork of this library for some time alongside my own fork of graphene-django and I'm ready to merge some of the changes back that I've been using in production successfully.

For this pull request I suggest annotations.

The biggest caveat, and why annotations should be listed as an advanced feature, is that they stop working if the optimization fails for other reasons. In my projects I always have tests that use django_assert_num_queries and always check that the optimization hasn't failed, like: assert "updated_at" not in connection.queries[0]["sql"].

Even though annotations could be easy to break, they make this library much more powerful. An example using GeoDjango features, only calculating distance if it's requested. Saving an extra PostGIS calculation when not needed.

class LocationType(DjangoObjectType):
    distance = graphene.Float(
        required=False,
        latitude=graphene.Float(required=True),
        longitude=graphene.Float(required=True),
    )
    ...
    @gql_optimizer.resolver_hints(
        annotate=lambda info, lat, lon: {
            "distance": Distance("coords", GEOSGeometry(Point(lat, lon), srid=4326))
        },
    )
    def resolve_distance(self: Location, info: ResolveInfo, **kwargs):
        if self.distance:
            return self.distance.m
        return None

Other useful examples can be seen in the tests and README. Most useful ones being Sum and Count.

@codecov-io
Copy link

codecov-io commented Oct 20, 2019

Codecov Report

Merging #35 into master will increase coverage by 0.27%.
The diff coverage is 100%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master      #35      +/-   ##
==========================================
+ Coverage   93.54%   93.82%   +0.27%     
==========================================
  Files           6        6              
  Lines         310      324      +14     
==========================================
+ Hits          290      304      +14     
  Misses         20       20
Impacted Files Coverage Δ
graphene_django_optimizer/hints.py 100% <100%> (ø) ⬆️
graphene_django_optimizer/query.py 92.48% <100%> (+0.38%) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update ee5e6ea...eac9fb7. Read the comment docs.

@NyanKiyoshi
Copy link
Collaborator

Hi! We are sorry, we didn't have time yet to fully test the changes. But we would like to let you know we didn't forget about your pull request and we really appreciate your suggestions! Great work!

@saschametz
Copy link

Great contribution. Just tested it and it works. One thing I've noticed is that if you use two fields in a GraphQL query that are using annotations with aggregations at the same time you will get wrong data. (Combining multiple aggregations)

It shouldn't be possible for the client to get wrong data by combining different fields. Maybe this could be avoided by throwing an exceptions if two fields with annotations and aggregations are used at the same time.

@sjdemartini
Copy link
Collaborator

I've just tested this on a project (with a forked version against latest master), and it's fantastic. Trying to support optimized annotations "by hand" without these additions is incredibly tough. Thank you hilmarh for opening this pull request!

Is there any chance it could be considered now and merged? This is an old PR but is highly useful still. The "combining multiple aggregations" issue mentioned above seems unavoidable per Django's notes, so throwing an error seems like a reasonable option, if desired.

@tfoxy
Copy link
Owner

tfoxy commented Jun 17, 2021

Hi @sjdemartini, can you provide an example of using this vs not having this feature?

@sjdemartini
Copy link
Collaborator

Thanks for following up, tfoxy! I don't have the code handy anymore when I'd attempted to build this myself outside of the gql_optimizer (without using this branch's code), but I never ended up getting it working as well as this. (e.g. if the model with the query-annotation wasn't the top-level field/list requested in the response, but instead was a child of the main field requested, getting the optimization to work properly and avoid N+1 queries proved really tricky.)

For what it's worth, I ended up personally moving away from this approach, and instead now make two separate queries, where the second query fetches the data that would've been included via annotation.

@felixmeziere
Copy link
Contributor

@hilmar8 you just made my week.

@felixmeziere
Copy link
Contributor

@tfoxy this is incredibly useful!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants