Skip to content
Open
Show file tree
Hide file tree
Changes from 34 commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
79a1f03
fix import path in optimizer schema configuration example
benjaminderei Oct 26, 2025
9f2921a
add ProjectNote model and related tests for polymorphic queries
benjaminderei Oct 27, 2025
a2773ae
fix prefetch duplication for inherited models in optimizer
benjaminderei Oct 27, 2025
89f94f5
add ArtProjectNote model and related tests for polymorphic queries
benjaminderei Oct 27, 2025
1f2a210
include foreign key columns in `only` query to prevent per-row querie…
benjaminderei Oct 27, 2025
e413ce1
add test for related objects on subtype to validate query optimization
benjaminderei Oct 27, 2025
464554e
improve optimizer handling of subclass prefetch logic and inheritance…
benjaminderei Oct 27, 2025
0c80160
add test for related objects on polymorphic base types to validate qu…
benjaminderei Oct 28, 2025
c0ef0fb
fix optimizer to correctly handle sub-fields for GraphQL interfaces
benjaminderei Oct 28, 2025
7e11552
add tests for reverse relation polymorphic resolution and N+1 query v…
benjaminderei Oct 28, 2025
49f61d3
add tests for reverse relation polymorphic resolution and N+1 query v…
benjaminderei Oct 28, 2025
903317b
(missing optimisation) add test for nested polymorphic relation with …
benjaminderei Oct 29, 2025
13d8191
add tests for polymorphic relations and reverse relations, extend mod…
benjaminderei Oct 29, 2025
a540b09
fix optimizer to handle django-polymorphic subclass prefetch logic an…
benjaminderei Oct 29, 2025
fc5feac
update test to validate optimized query count for polymorphic relations
benjaminderei Oct 30, 2025
19a91e3
add postfetch optimization to handle django-polymorphic reverse relat…
benjaminderei Oct 30, 2025
7357a46
add tests for postfetch and prefetch behavior on polymorphic relation…
benjaminderei Oct 30, 2025
b93d5f6
update test to validate query optimization and stable count for polym…
benjaminderei Oct 30, 2025
fe72d8f
improve subclass prefetch handling to avoid cache misses and ensure r…
benjaminderei Oct 30, 2025
12ff37f
update test to validate stable query count after polymorphic relation…
benjaminderei Oct 30, 2025
8d23a67
add tests for polymorphic optimization and query stability on inherit…
benjaminderei Oct 30, 2025
fb153af
add postfetch handling to optimize queryset caching for nested relate…
benjaminderei Oct 30, 2025
d526a74
add ArtProjectNoteDetails model and schema with tests to validate que…
benjaminderei Oct 31, 2025
6ea1ed2
refactor optimizer to improve handling of nested prefetch paths and e…
benjaminderei Oct 31, 2025
4f2a031
add test to validate absence of N+1 queries and stable query count fo…
benjaminderei Nov 3, 2025
c125606
add parent-level postfetch optimization to enable query batching acro…
benjaminderei Nov 3, 2025
f334ffe
add tests for ArtProjectNoteDetails and optimize nested query handlin…
benjaminderei Nov 3, 2025
5f7aa18
improve optimizer to refine subclass prefetch handling, ensuring only…
benjaminderei Nov 3, 2025
67ef528
add test to validate N+1 query prevention and stable query count for …
benjaminderei Nov 3, 2025
cce87ca
update test documentation to replace French comments and docstrings w…
benjaminderei Nov 3, 2025
57f65eb
update optimizer guide to include recommendations for interface field…
benjaminderei Nov 3, 2025
908f0de
add `CompanyProjectLink` model, schema, and tests to validate query o…
benjaminderei Nov 3, 2025
d044790
refactor: extract postfetch utilities to standalone module and simpli…
benjaminderei Nov 3, 2025
041633d
add polymorphism relay test suite with models, schema, and extensive …
benjaminderei Nov 3, 2025
0f6bc35
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Nov 5, 2025
4d48ce5
add tests for polymorphic pagination queries with subtype filtering (…
benjaminderei Nov 6, 2025
44ecb19
add page-level postfetch handling to optimize prefetches for individu…
benjaminderei Nov 6, 2025
96e67da
Merge remote-tracking branch 'origin/optimize_optimizer' into optimiz…
benjaminderei Nov 6, 2025
766b424
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Nov 6, 2025
6197da3
refactor: simplify exception handling and imports across optimizer an…
benjaminderei Nov 6, 2025
f77ec4f
refactor: extract and centralize optimizer utilities for cleaner logi…
benjaminderei Nov 6, 2025
3ed6732
refactor: remove unused variable assignments in polymorphic relation …
benjaminderei Nov 6, 2025
2de8d59
refactor: use `cast` for type safety in polymorphic relation tests an…
benjaminderei Nov 6, 2025
4396c67
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Nov 6, 2025
841b926
refactor: replace `cast` with direct `project_id` assignment in polym…
benjaminderei Nov 7, 2025
71708ff
refactor: simplify parent-level postfetch branches initialization by …
benjaminderei Nov 7, 2025
0d9ce84
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Nov 7, 2025
a251726
refactor: replace `project_ptr` with `project_id` in polymorphic rela…
benjaminderei Nov 7, 2025
d7e2b28
Add `companies_paginated` field with pagination support in schema and…
benjaminderei Nov 9, 2025
c83dfeb
Add early SQL pagination for root connections and enhance total count…
benjaminderei Nov 9, 2025
f87eede
refactor: streamline exception handling, imports, and queryset compil…
benjaminderei Nov 9, 2025
bb4a65a
refactor: standardize variable naming, improve assertions, and clean …
benjaminderei Nov 9, 2025
4f4981c
support: enhance database alias handling in postfetch logic for multi…
benjaminderei Nov 9, 2025
a524124
refactor: simplify manager usage and database alias resolution in pos…
benjaminderei Nov 9, 2025
cc7c87d
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Nov 9, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions docs/guide/optimizer.md
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,31 @@ in your schema.
> Either change your base manager to also be an `InheritanceManager` or set Strawberry Django to use the default
> manager: `DjangoOptimizerExtension(prefetch_custom_queryset=True)`.

### Interface fields and polymorphism

When working with GraphQL interfaces backed by a polymorphic Django base model or using InheritanceManager, make sure that any fields defined on the base model and needed by your queries are also declared on the GraphQL interface. If a base-model field is only declared on concrete subtype GraphQL types (and omitted on the interface), the optimizer cannot see that field when resolving the interface and therefore cannot reliably optimize it. This can result in missing `only()` pruning or missing `select_related()`/`prefetch_related()` calls, leading to extra queries (N+1) and larger payloads.

Recommendations:

- Declare base-model fields on the interface itself so the optimizer can select them up front when resolving the interface type.
- If you intentionally omit a base field from the interface, add explicit optimizer hints where appropriate (e.g., `only=...`, `select_related=...`, `prefetch_related=...`) using `strawberry_django.field(...)`, or tailor your queries to avoid relying on automatic optimization for that field.

Example:

```python
@strawberry_django.interface(models.Project)
class ProjectType:
# Base-model field declared on the interface so it can be optimized
topic: strawberry.auto

@strawberry_django.type(models.ArtProject)
class ArtProjectType(ProjectType):
# Subtype-only fields remain on the subtype
artist: strawberry.auto
```



### Custom polymorphic solution

The optimizer also supports polymorphism even if your models are not polymorphic.
Expand Down
69 changes: 67 additions & 2 deletions strawberry_django/fields/field.py
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,30 @@
if "info" not in kwargs:
kwargs["info"] = info

# If we have a prefetched cache for this reverse accessor on the source instance,
# pass a hint so the queryset hook can short-circuit to the cached list without
# re-optimizing/requerying.
try:
attname = self.django_name or self.python_name
if (
source is not None
and hasattr(source, "_prefetched_objects_cache")
and isinstance(source._prefetched_objects_cache, dict)

Check failure on line 288 in strawberry_django/fields/field.py

View workflow job for this annotation

GitHub Actions / Typing

Cannot access attribute "_prefetched_objects_cache" for class "Model"   Attribute "_prefetched_objects_cache" is unknown (reportAttributeAccessIssue)
and attname in source._prefetched_objects_cache

Check failure on line 289 in strawberry_django/fields/field.py

View workflow job for this annotation

GitHub Actions / Typing

Cannot access attribute "_prefetched_objects_cache" for class "Model"   Attribute "_prefetched_objects_cache" is unknown (reportAttributeAccessIssue)
):
kwargs = dict(kwargs)
kwargs["__use_prefetched_cache__"] = attname
except Exception:
pass

# Provide source instance in kwargs so the queryset hook can use prefetched cache
if source is not None:
try:
kwargs = dict(kwargs)
kwargs["__source__"] = source
except Exception:
pass

result = django_resolver(
self.get_queryset_hook(**kwargs),
qs_hook=lambda qs: qs,
Expand All @@ -286,13 +310,54 @@

def get_queryset_hook(self, info: Info, **kwargs):
if self.is_connection or self.is_paginated:
# We don't want to fetch results yet, those will be done by the connection/pagination
# For connections/paginated fields, avoid DB hits when we already have
# a prefetched cache for this reverse accessor on the source instance.
# Otherwise, just return the queryset and let the connection handle it.
def qs_hook(qs: models.QuerySet): # type: ignore
return self.get_queryset(qs, info, **kwargs)
# If the resolver passed a hint to use the source's prefetched cache,
# return the cached list so the connection can operate on a Python list
# (preventing per-node LIMIT queries on reverse relations).
use_cache_key = kwargs.pop("__use_prefetched_cache__", None)
source_obj = kwargs.pop("__source__", None)
if use_cache_key and source_obj is not None:
cache = getattr(source_obj, "_prefetched_objects_cache", None)
if isinstance(cache, dict) and use_cache_key in cache:
return cache[use_cache_key]
qs2 = self.get_queryset(qs, info, **kwargs)
# If the connection queryset carries parent-level postfetch branches,
# we need to trigger evaluation now so the optimizer's postfetch hook
# can batch nested reverse relations across the page. This will not
# bypass pagination because the queryset already carries LIMIT/OFFSET.
try:
from strawberry_django.queryset import get_queryset_config as _get_qs_cfg
cfg = _get_qs_cfg(qs2)
if getattr(cfg, "parent_postfetch_branches", None):
from strawberry_django.resolvers import default_qs_hook as _dqsh
qs2 = _dqsh(qs2)
except Exception:
pass
return qs2

elif self.is_list:

def qs_hook(qs: models.QuerySet): # type: ignore
# If the source instance has a prefetched cache for this accessor, short-circuit
use_cache_key = kwargs.pop("__use_prefetched_cache__", None)
source_obj = kwargs.pop("__source__", None)
if use_cache_key and source_obj is not None:
cache = getattr(source_obj, "_prefetched_objects_cache", None)
if isinstance(cache, dict) and use_cache_key in cache:
# Only short-circuit to the cache if the queryset does NOT carry
# postfetch hints. If it does, we must run the default_qs_hook so
# nested postfetch can execute on this queryset.
try:
from strawberry_django.queryset import get_queryset_config as _get_qs_cfg
cfg = _get_qs_cfg(qs)
if not getattr(cfg, "postfetch_prefetch", None):
return cache[use_cache_key]
except Exception:
# If we cannot inspect the config, be conservative and do not short-circuit
pass
qs = self.get_queryset(qs, info, **kwargs)
if not self.disable_fetch_list_results:
qs = default_qs_hook(qs)
Expand Down
Loading
Loading