File tree Expand file tree Collapse file tree
Expand file tree Collapse file tree Original file line number Diff line number Diff line change 1+ ---
2+ release type : patch
3+ ---
4+
5+ Fix nullable connections (e.g. when guarded by a permission extension) eagerly
6+ fetching the entire table before the connection applied its pagination. The
7+ return type is now unwrapped from ` StrawberryOptional ` /other containers so the
8+ queryset evaluation stays deferred and the connection can apply a ` LIMIT ` .
Original file line number Diff line number Diff line change 1313from django .db import models
1414from strawberry import relay
1515from strawberry .relay .exceptions import NodeIDAnnotationError
16+ from strawberry .types .base import StrawberryContainer
1617from strawberry .types .info import Info
1718from strawberry .utils .await_maybe import AwaitableOrValue
1819
@@ -161,6 +162,9 @@ def resolve_model_nodes(
161162 # Connection will filter the results when its is being resolved.
162163 # We don't want to fetch everything before it does that
163164 return_type = info .return_type
165+ # Unwrap containers (e.g. `StrawberryOptional` for a nullable connection)
166+ while isinstance (return_type , StrawberryContainer ):
167+ return_type = return_type .of_type
164168 if isinstance (return_type , type ) and issubclass (return_type , relay .Connection ):
165169 extra_args ["qs_hook" ] = lambda qs : qs
166170
Original file line number Diff line number Diff line change @@ -58,6 +58,8 @@ class Query:
5858 node_optional : relay .Node | None = strawberry_django .node ()
5959 nodes_optional : list [relay .Node | None ] = strawberry_django .node ()
6060 fruits : DjangoListConnection [Fruit ] = strawberry_django .connection ()
61+ # Nullable connection (e.g. as produced by a permission extension)
62+ fruits_optional : DjangoListConnection [Fruit ] | None = strawberry_django .connection ()
6163 fruits_lazy : DjangoListConnection [
6264 Annotated ["Fruit" , strawberry .lazy ("tests.relay.schema" )]
6365 ] = strawberry_django .connection ()
Original file line number Diff line number Diff line change @@ -104,6 +104,19 @@ type Query {
104104 """ Returns the items in the list that come after the specified cursor."""
105105 last : Int = null
106106 ): FruitConnection !
107+ fruitsOptional (
108+ """Returns the items in the list that come before the specified cursor."""
109+ before : String = null
110+
111+ """ Returns the items in the list that come after the specified cursor."""
112+ after : String = null
113+
114+ """ Returns the first n items from the list."""
115+ first : Int = null
116+
117+ """ Returns the items in the list that come after the specified cursor."""
118+ last : Int = null
119+ ): FruitConnection
107120 fruitsLazy (
108121 """Returns the items in the list that come before the specified cursor."""
109122 before : String = null
Original file line number Diff line number Diff line change 11import pytest
2+ from django .db import connection
3+ from django .test .utils import CaptureQueriesContext
24from pytest_django import DjangoAssertNumQueries
35from strawberry .relay .utils import to_base64
46
@@ -1177,3 +1179,35 @@ def test_query_connection_total_count_sql_queries(
11771179 assert result .data == {
11781180 query_attr : {"totalCount" : 5 },
11791181 }
1182+
1183+
1184+ def test_nullable_connection_does_not_fetch_whole_table ():
1185+ # Regression test: a nullable connection (e.g. wrapped in `StrawberryOptional`
1186+ # by a permission extension) used to fetch the entire table before the
1187+ # connection sliced the results, because the return type was not unwrapped
1188+ # and the queryset was evaluated eagerly instead of being deferred with a
1189+ # LIMIT applied by the connection.
1190+ with CaptureQueriesContext (connection ) as ctx :
1191+ result = schema .execute_sync (
1192+ """
1193+ query {
1194+ fruitsOptional (first: 2) {
1195+ edges {
1196+ node { id name }
1197+ }
1198+ }
1199+ }
1200+ """ ,
1201+ )
1202+
1203+ assert result .errors is None
1204+ assert result .data is not None
1205+ edges = result .data ["fruitsOptional" ]["edges" ]
1206+ assert len (edges ) == 2
1207+
1208+ # The query that fetches the fruit rows must use a LIMIT so it doesn't
1209+ # pull the entire table into memory.
1210+ fetch_queries = [
1211+ q ["sql" ] for q in ctx .captured_queries if "fruit" in q ["sql" ].lower ()
1212+ ]
1213+ assert any ("LIMIT 3" in sql for sql in fetch_queries ), fetch_queries
You can’t perform that action at this time.
0 commit comments