strawberry-graphql / strawberry-django

Strawberry GraphQL Django extension
MIT License
391 stars 115 forks source link

prefetch_related and filtering in custom resolver #559

Open tasiotas opened 2 weeks ago

tasiotas commented 2 weeks ago

I need to filter related models. Query optimizer works fine, but I cannot get it working with filtering inside custom resolver (without using @strawberry.django.filter)

  1. When I define my own Prefetch, I am getting double prefetch queries. One is mine, the other is from optimizer.
    @strawberry.django.field(
        prefetch_related=[
            lambda info: Prefetch(
                "downloadables",
                queryset=Downloadable.objects.filter(is_published=True).all(),
                to_attr="downloadables_prefetched",
            )
        ],
    )
    def downloadables(self) -> List[Annotated["DownloadableType", strawberry.lazy("vfxtricks.common.schema")]]:
        return self.downloadables_prefetched
  1. When I dont define my own Prefetch with custom name, then optimizer does make a Prefetch query, but query in my resolver does not take advantage of it. Meaning, I end up with way too many queries.
    @strawberry.django.field()
    def downloadables(self) -> List[Annotated["DownloadableType", strawberry.lazy("vfxtricks.common.schema")]]:
        return self.downloadables.filter(is_published=True)

thank you

Upvote & Fund

Fund with Polar

bellini666 commented 1 week ago

Hey @tasiotas ,

Yeah, that's a current limitation, and I'll explain you why this case is not being optimized.

Suppose you have a query like this:

query {
  users {
    id
    emails {
      id
    }
  }
}

The prefetch for the emails will happen before even resolving users (we will generate the QuerySet for users and add the prefetch inside it), meaning that it is not totally safe yet to call the emails resolver itself.

The main reason is because the resolver might want to use the parent object (i.e. the user) for something. This can probably be worked around by checking if the resolver is "safe" (it doesn't depend on self and neither root or a Parent argument).

But even with that, I'm worried about some complex scenarios, where people might be using the context execution or even contextvars to set something in the parent to modify the behavior of the child's resolver, meaning running it before the actual parent resolution would change the field's behavior.

Right now I would say that the best way is to either use the custom prefetch like you did, or use filters, which since https://github.com/strawberry-graphql/strawberry-django/releases/tag/v0.44.0 will be optimized properly for nested relations. Or even define a DownloadableType.get_queryset() to modify any get_queryset for the type, although I usually advice against it and to instead do that on a per-field/resolver case.

Having said all of that, I'll try to think further into this.

@patrick91 any ideas about this?