From 45e8ad1ed27e07e664e609e4ac1855e03032075c Mon Sep 17 00:00:00 2001 From: Mohamed Seleem <3057546+mselee@users.noreply.github.com> Date: Sun, 18 Feb 2024 13:34:10 +0000 Subject: [PATCH 1/4] feat: Avoid joins when only fetching an ID --- strawberry_django/fields/field.py | 14 +++++++++++++- strawberry_django/optimizer.py | 4 ++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/strawberry_django/fields/field.py b/strawberry_django/fields/field.py index fd1caa79..6e1e4344 100644 --- a/strawberry_django/fields/field.py +++ b/strawberry_django/fields/field.py @@ -183,7 +183,19 @@ def get_result( result = getattr(source, attr.field.attname) elif isinstance(attr, ForwardManyToOneDescriptor): # This will raise KeyError if it is not cached - result = attr.field.get_cached_value(source) # type: ignore + try: + result = attr.field.get_cached_value(source) # type: ignore + except KeyError: + selected_field = next(f for f in info.selected_fields if f.name == info.field_name) + target_attname = attr.field.target_field.attname + if len(selected_field.selections) == 1 and selected_field.selections[0].name == target_attname: + # If we are only retrieving the referenced `id` of a related foreignkey with no additional columns + # then we can optimize away this retrieval by reusing the existing value from the `source` object. + value = source.__dict__[attr.field.get_attname()]) + target_model = attr.field.target_field.model + result = target_model(pk=value) + else: + raise elif isinstance(attr, ReverseOneToOneDescriptor): # This will raise KeyError if it is not cached result = attr.related.get_cached_value(source) diff --git a/strawberry_django/optimizer.py b/strawberry_django/optimizer.py index b4761d35..5019feae 100644 --- a/strawberry_django/optimizer.py +++ b/strawberry_django/optimizer.py @@ -492,7 +492,6 @@ def _get_model_hints( if isinstance(model_field, (models.ForeignKey, OneToOneRel)): store.only.append(path) - store.select_related.append(path) # If adding a reverse relation, make sure to select its pointer to us, # or else this might causa a refetch from the database @@ -514,7 +513,8 @@ def _get_model_hints( cache=cache, level=level + 1, ) - if f_store is not None: + if f_store is not None and set(f_store.only) != {model_field.target_field.attname}: + store.select_related.append(path) cache.setdefault(f_model, []).append((level, f_store)) store |= f_store.with_prefix(path, info=info) elif GenericForeignKey and isinstance(model_field, GenericForeignKey): From e998a67a5da1d8b0b44c5a8d64c2cb0bb4ed2bad Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 18 Feb 2024 13:45:11 +0000 Subject: [PATCH 2/4] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- strawberry_django/optimizer.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/strawberry_django/optimizer.py b/strawberry_django/optimizer.py index 5019feae..5036db1a 100644 --- a/strawberry_django/optimizer.py +++ b/strawberry_django/optimizer.py @@ -513,7 +513,9 @@ def _get_model_hints( cache=cache, level=level + 1, ) - if f_store is not None and set(f_store.only) != {model_field.target_field.attname}: + if f_store is not None and set(f_store.only) != { + model_field.target_field.attname + }: store.select_related.append(path) cache.setdefault(f_model, []).append((level, f_store)) store |= f_store.with_prefix(path, info=info) From e94f54de94a5fabc42925a4bb19b3065fb7ec479 Mon Sep 17 00:00:00 2001 From: Mohamed Seleem <3057546+mselee@users.noreply.github.com> Date: Sun, 18 Feb 2024 13:50:40 +0000 Subject: [PATCH 3/4] fix: typo --- strawberry_django/fields/field.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/strawberry_django/fields/field.py b/strawberry_django/fields/field.py index 6e1e4344..6b25d3b7 100644 --- a/strawberry_django/fields/field.py +++ b/strawberry_django/fields/field.py @@ -191,7 +191,7 @@ def get_result( if len(selected_field.selections) == 1 and selected_field.selections[0].name == target_attname: # If we are only retrieving the referenced `id` of a related foreignkey with no additional columns # then we can optimize away this retrieval by reusing the existing value from the `source` object. - value = source.__dict__[attr.field.get_attname()]) + value = source.__dict__[attr.field.get_attname()] target_model = attr.field.target_field.model result = target_model(pk=value) else: From 0804792f5b2928b1c7a92d15beda0a505fea4f97 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 18 Feb 2024 13:50:49 +0000 Subject: [PATCH 4/4] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- strawberry_django/fields/field.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/strawberry_django/fields/field.py b/strawberry_django/fields/field.py index 6b25d3b7..f873c143 100644 --- a/strawberry_django/fields/field.py +++ b/strawberry_django/fields/field.py @@ -186,9 +186,14 @@ def get_result( try: result = attr.field.get_cached_value(source) # type: ignore except KeyError: - selected_field = next(f for f in info.selected_fields if f.name == info.field_name) + selected_field = next( + f for f in info.selected_fields if f.name == info.field_name + ) target_attname = attr.field.target_field.attname - if len(selected_field.selections) == 1 and selected_field.selections[0].name == target_attname: + if ( + len(selected_field.selections) == 1 + and selected_field.selections[0].name == target_attname + ): # If we are only retrieving the referenced `id` of a related foreignkey with no additional columns # then we can optimize away this retrieval by reusing the existing value from the `source` object. value = source.__dict__[attr.field.get_attname()]