Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix bug with prefetching on different nesting levels #702

Merged
merged 7 commits into from
Oct 13, 2024

Conversation

sudevva
Copy link
Contributor

@sudevva sudevva commented Sep 21, 2024

Fix the bug with prefetching on different nesting levels
As a side effect, it's now allowed a Promise to be returned from prefetchMethod with returnRequested: true, so Overblog Dataloader can be used.
Also, there's no need to map a prefetched result to return a value.

update version of related PR #519
also can potentially help with #187

@oojacoboo
Copy link
Collaborator

Thanks for this @sudevva. I'll review soon. Anyone else that's using prefetch, a review would be appreciated.

@oojacoboo
Copy link
Collaborator

@sudevva can I get a little more explanation on this PR? I'm having a hard time understanding why a boolean value is a good idea, and what returnRequested even means.

@sudevva
Copy link
Contributor Author

sudevva commented Sep 25, 2024

👋 So about returnRequested. Basically previous when prefetch is used, the function with Prefetch annotation receives an array of resolved values, that should be mapped to source(like $prefetchResult[$blog->getId()]).
This applies a limitation in a way, where you cannot use any Deffered/promise based code in prefetch method.
When returnRequested is true:

  • method will recieve a final(resolved) value already mapped to source
  • prefetch method can use promises now
  • when any of arguments still having a promise we wrap resolver in another Deffered so gql will resolve promises until all arguments becomes resolved value
  • you can enforce strict type checks on recieved value
    In the end method will recieve concrete and resolved value(it can be any type), instead of array.
    Now we store result of prefetch in PrefetchBuffer mapped to source, not to hash of arguments, and check is it exists, this resolves a nesting level problem

About bool value for annotation, maybe it will be better to create another type of annotation and mark prevous one as depricated.

Previous implementation where method recieves an array of values creates some confusion for me, why I recieve an array from which I should extract my value.

Thank you @oojacoboo, I hope this will clarify my intentions)

@oojacoboo
Copy link
Collaborator

oojacoboo commented Sep 26, 2024

Okay, so am I right in assuming that you added this boolean property as a way of achieving BC? Can we just merge the functionality without adding any additional properties to the Prefetch attribute? It's confusing, I think. I don't see the purpose of it. Why can't the prefetch method return a promise without this boolean value? What advantage does this additional property, returnRequested, provide?

@sudevva
Copy link
Contributor Author

sudevva commented Sep 26, 2024

Okay, so am I right in assuming that you added this boolean property as a way of achieving BC? Can we just merge the functionality without adding any additional properties to the Prefetch attribute? It's confusing, I think. I don't see the purpose of it. Why can't the prefetch method return a promise without this boolean value? What advantage does this additional property, returnRequested, provide?

Do you think I should leave a prefetch behavior as it was previously?
This is how I should map values each time Dataloader is used because I can return only one Promise from preload, that was my concern about default behavior


#[ExtendType(class: Video::class)]
class TagsExtension
{
    #[Field]
    /**
     * @return VideoTag[]
     * @param VideoTag[][] $videoTags     
     * @throws \Exception
     */
    public function getTags(
        Video $video,
        #[Prefetch('preload')] array $videoTags
    ): iterable {
        return $videoTags[$video->getId()]; // I can't return Promise here
    }

    /**
     * @param Video[] $videos
     * @return Promise<VideoTag[][]>
     */
    public static function preload(iterable $videos, #[Autowire] VideoTagResolver $videoTagResolver): Promise {
        return $videoTagResolver->getVideosTags(array_map(fn($video) => $video->getId(), $videos))
            ->then(function ($videoTags) use ($videos) {
                $return = [];
                foreach ($videos as $key => $video){
                    $return[$video->getId()] = $videoTags[$key] ?? [];
                }
                return $return;
            }
        );
    }
}

Btw, I can wrap \TheCodingMachine\GraphQLite\QueryField::__construct $callResolver in deferred as I have done for args until return type is resolved to call assertReturnType with proper value, then I will not use Prefetch anymore.
Take a look at ffbb68b

Andrii Sudiev added 3 commits September 27, 2024 02:43
@codecov-commenter
Copy link

codecov-commenter commented Sep 26, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 95.18%. Comparing base (53f9d49) to head (ca0823a).
Report is 91 commits behind head on master.

Additional details and impacted files
@@             Coverage Diff              @@
##             master     #702      +/-   ##
============================================
- Coverage     95.72%   95.18%   -0.55%     
- Complexity     1773     1824      +51     
============================================
  Files           154      174      +20     
  Lines          4586     4796     +210     
============================================
+ Hits           4390     4565     +175     
- Misses          196      231      +35     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@sudevva sudevva requested a review from oojacoboo September 27, 2024 15:59
@oojacoboo
Copy link
Collaborator

On ffbb68b, why is this being called unwrapReturnType and not something more fitting like resolveWithPromise?

The overall goal would be to create a more uniform implementation for resolving. Promises seem like a good idea to me for preload functionality. Wrapping non promise prefetch returns seems like a logical way of implementing things.

Please be sure to put line spaces between your control structures - easier for people to read. We're not worried about trying to optimize file sizes.

@sudevva
Copy link
Contributor Author

sudevva commented Oct 1, 2024

hey, I renamed a method, added a forgotten nesting levels test, and added some line spaces.

Is this PR more or less ok?

Copy link
Collaborator

@oojacoboo oojacoboo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is much better now. Just a couple remaining things. We also need updated docs to include details on using promises.

});
}

/** @param array<string, mixed> $args */
private function computePrefetch(array $args, mixed $context, ResolveInfo $info, PrefetchBuffer $prefetchBuffer): mixed
private function computePrefetch(array $args, mixed $context, ResolveInfo $info, PrefetchBuffer $prefetchBuffer): void
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is storing the results, maybe "compute" isn't the right term to use for this method name. Also "compute" isn't really a great term to be using here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method was not named by me)
Should I rename it?

doPrefetch | prefetchValues | prefetchResults | processPrefetch | ... ?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renaming a method where the logic contained within it has materially changed, is the right move. Otherwise, reading and understanding the code for the next developer becomes more confusing.

Being that it's return type is void, it's not really "prefetching results" - you'd expect a return value on that IMO. processPrefetch, cachePrefetchResults, bufferPrefetch; maybe something along these lines.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renamed method name to processPrefetch pushed to branch

@sudevva
Copy link
Contributor Author

sudevva commented Oct 8, 2024

If you want I can squash and rebase commits. There is no value in commits about CR fixes and reverting my initial changes.

@oojacoboo
Copy link
Collaborator

No that's fine. We do need the docs updated though, with details on using promises. That's the website/docs dir.

@sudevva
Copy link
Contributor Author

sudevva commented Oct 8, 2024

Promise mapping added

@oojacoboo oojacoboo merged commit 03499a3 into thecodingmachine:master Oct 13, 2024
9 checks passed
@oojacoboo
Copy link
Collaborator

Thanks @sudevva!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants