strawberry-graphql / strawberry-django

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

Query optimizer doesn't work with relay connection #337

Closed jlgonzalez-martinez closed 1 month ago

jlgonzalez-martinez commented 11 months ago

Describe the Bug

Example project could be found here.

The problem is that uncomment the students that use ListConnectionWithTotalCount and comment the other one seems to not optimize automatically. I try to put a specific prefetch in the connection but then don't optimize inner fields.

System Information

Additional Context

types.py

from typing import List

import strawberry_django as gql
from strawberry import relay, auto
from strawberry_django.relay import ListConnectionWithTotalCount

from school.models import Career, Student, School

@gql.type(Career)
class CareerType(relay.Node):
    title: auto

@gql.type(Student)
class StudentType(relay.Node):
    name: auto
    nia: auto
    career: "CareerType"

    @gql.field
    def upper_name(self) -> str:
        """Return the name in uppercase."""
        return self.name.upper()

@gql.type(School)
class SchoolType(relay.Node):
    name: auto
    # students: List[StudentType]
    students: ListConnectionWithTotalCount[StudentType] = gql.connection(
         prefetch_related="students"
    )

Upvote & Fund

Fund with Polar

bellini666 commented 11 months ago

This issue is the same as https://github.com/strawberry-graphql/strawberry-graphql-django/issues/340 .

This is happening because the optimizer currently is skipping any nested connections: https://github.com/strawberry-graphql/strawberry-graphql-django/blob/main/strawberry_django/optimizer.py#L320C29-L320C29

I'll try to create a meta issue about this to discuss how we can properly optimize nested connections. Because right now, since the nested query will be sliced, trying to optimize it will do the opposite of what we want the optimizer to do, which is do an extra prefetch without limit/offset.

bellini666 commented 11 months ago

@jlgonzalez-martinez as I mentioned in this comment, I just noticed that you are also doing a prefetch_related in the connection.

For the reasons I mentioned there, can you test without it to see if it does work correctly?

patrick91 commented 10 months ago

@bellini666 suggested this while on a call :D

WITH numbered_rows AS (
  SELECT 
     "_FilmToSpecies"."A" AS "_prefetch_related_val_a_id",
     "Species"."id",
     "Species"."name",
     ROW_NUMBER() OVER (
       PARTITION BY "_FilmToSpecies"."A"
       ORDER BY "Species"."id"
     ) row_num
   FROM "Species"
   INNER JOIN "_FilmToSpecies"
     ON ("Species"."id" = "_FilmToSpecies"."B")
   WHERE "_FilmToSpecies"."A" IN ('1', '2', '3')
)
SELECT "_prefetch_related_val_a_id", "id", "name" FROM numbered_rows WHERE row_num <= 10;
bellini666 commented 9 months ago

@bellini666 suggested this while on a call :D

WITH numbered_rows AS (
  SELECT 
     "_FilmToSpecies"."A" AS "_prefetch_related_val_a_id",
     "Species"."id",
     "Species"."name",
     ROW_NUMBER() OVER (
       PARTITION BY "_FilmToSpecies"."A"
       ORDER BY "Species"."id"
     ) row_num
   FROM "Species"
   INNER JOIN "_FilmToSpecies"
     ON ("Species"."id" = "_FilmToSpecies"."B")
   WHERE "_FilmToSpecies"."A" IN ('1', '2', '3')
)
SELECT "_prefetch_related_val_a_id", "id", "name" FROM numbered_rows WHERE row_num <= 10;

@patrick91 wow, was testing here and just noticed that Django 4.2+ actually does this window function filtering when you slice a prefetch. e.g.

SomeModel.objects.prefetch_related(
    Prefetch(
        "some_relation",
        queryset=SomeRelation.objects.all()[:10],
    )
)

It will use the ROW_NUMBER() window function and filter by it

catgirlinspace commented 3 months ago

hiya! am running into this same issue :( the 11 similar queries are always queries like SELECT "battles_battle"."id", "battles_battle"."uploader_id" FROM "battles_battle" WHERE "battles_battle"."id" = (some int) LIMIT 21 image image

stygmate commented 3 months ago

Hi, any progress on this one ?

patrick91 commented 1 month ago

@stygmate Thiago is working on adding support for this, hopefully we get it soon 😊