Skip to content
Closed
Show file tree
Hide file tree
Changes from 9 commits
Commits
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
5 changes: 5 additions & 0 deletions RELEASE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
release type: minor
---

Adjust the optimizer to handle if the same field with different arguments using aliases is selected and the field doesn't have a custom resolver.
Comment thread
rcybulski1122012 marked this conversation as resolved.
Outdated
15 changes: 14 additions & 1 deletion strawberry_django/fields/field.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,11 @@
from strawberry_django.descriptors import ModelProperty
from strawberry_django.fields.base import StrawberryDjangoFieldBase
from strawberry_django.filters import FILTERS_ARG, StrawberryDjangoFieldFilters
from strawberry_django.optimizer import OptimizerStore, is_optimized_by_prefetching
from strawberry_django.optimizer import (
ALIAS_PREFIX,
OptimizerStore,
is_optimized_by_prefetching,
)
from strawberry_django.ordering import (
ORDER_ARG,
ORDERING_ARG,
Expand Down Expand Up @@ -217,6 +221,15 @@ def get_result(
# sync_to_async context if the value is already cached, since it will not
# hit the db anymore
attname = self.django_name or self.python_name

# Check for to_attr-based prefetch from optimizer (aliased field with filters)
if info is not None:
response_key = info._raw_info.path.key
alias_attr = f"{ALIAS_PREFIX}{response_key}"
prefetched = getattr(source, alias_attr, None)
if prefetched is not None:
return prefetched
Comment thread
rcybulski1122012 marked this conversation as resolved.
Outdated
Comment thread
rcybulski1122012 marked this conversation as resolved.
Outdated

attr = getattr(source.__class__, attname, None)
try:
if isinstance(attr, ModelProperty):
Expand Down
39 changes: 32 additions & 7 deletions strawberry_django/optimizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@
_sentinel = object()
_annotate_placeholder = "__annotated_placeholder__"

ALIAS_PREFIX = "_strawberry_alias_"


@dataclasses.dataclass
class OptimizerConfig:
Expand Down Expand Up @@ -953,6 +955,7 @@ def _get_hints_from_django_relation(
path: str,
cache: dict[type[models.Model], list[tuple[int, OptimizerStore]]],
level: int = 0,
to_attr: str | None = None,
) -> OptimizerStore:
try:
from django.contrib.contenttypes.fields import GenericRelation
Expand Down Expand Up @@ -1056,7 +1059,15 @@ def _get_hints_from_django_relation(
if is_inheritance_qs(base_qs):
base_qs = base_qs.select_subclasses(*subclasses)
field_qs = field_store.apply(base_qs, info=field_info, config=config)
field_prefetch = Prefetch(path, queryset=field_qs)
# Don't use to_attr for connection/paginated fields - their resolvers
# expect data in _prefetched_objects_cache, not as a plain list attribute.
# If to_attr was requested but can't be used, skip optimization entirely
# to avoid merging conflicting prefetches for the same path.
if to_attr and (
getattr(field, "is_connection", False) or getattr(field, "is_paginated", False)
):
return store
field_prefetch = Prefetch(path, queryset=field_qs, to_attr=to_attr)
field_prefetch._optimizer_sentinel = _sentinel # type: ignore
store.prefetch_related.append(field_prefetch)
Comment thread
rcybulski1122012 marked this conversation as resolved.

Expand All @@ -1076,6 +1087,7 @@ def _get_hints_from_django_field(
prefix: str = "",
cache: dict[type[models.Model], list[tuple[int, OptimizerStore]]],
level: int = 0,
to_attr: str | None = None,
) -> OptimizerStore | None:
try:
from django.contrib.contenttypes.fields import (
Expand Down Expand Up @@ -1179,6 +1191,7 @@ def _get_hints_from_django_field(
path=path,
cache=cache,
level=level,
to_attr=to_attr,
)
else:
store = OptimizerStore.with_hints(only=[path])
Expand Down Expand Up @@ -1309,18 +1322,29 @@ def _get_model_hints(
field_name_groups.setdefault(name, []).append(field_nodes)

# Merge aliased selections with same arguments; skip those with different args
Comment thread
rcybulski1122012 marked this conversation as resolved.
Outdated
merged_node_lists: list[list[FieldNode]] = []
# list of (field_nodes, to_attr)
merged_node_lists: list[tuple[list[FieldNode], str | None]] = []
for groups in field_name_groups.values():
if len(groups) == 1:
merged_node_lists.append(groups[0])
merged_node_lists.append((groups[0], None))
else:
first_args = _get_field_arguments(groups[0][0])
if all(_get_field_arguments(g[0]) == first_args for g in groups[1:]):
merged_node_lists.append([node for group in groups for node in group])
# Same args across all aliases - merge into single entry, no to_attr
merged_node_lists.append((
[node for group in groups for node in group],
None,
))
else:
# Different args - each alias gets its own to_attr
for group in groups:
alias = group[0].alias
to_attr = f"{ALIAS_PREFIX}{alias.value}" if alias else None
merged_node_lists.append((group, to_attr))

selections = [
field_data
for f_nodes in merged_node_lists
(*field_data, to_attr)
for f_nodes, to_attr in merged_node_lists
if (
field_data := _get_field_data(
f_nodes,
Expand All @@ -1333,7 +1357,7 @@ def _get_model_hints(
is not None
]

for field, f_definition, f_selection, f_info in selections:
for field, f_definition, f_selection, f_info, to_attr in selections:
strawberry_info = schema.config.info_class(_raw_info=f_info, _field=field)

# Add annotations from the field if they exist
Expand Down Expand Up @@ -1363,6 +1387,7 @@ def _get_model_hints(
prefix=prefix,
cache=cache,
level=level,
to_attr=to_attr,
):
store |= model_field_store

Expand Down
Loading
Loading