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

Prefetching nested fields with shared Type instances throws error #729

Open
cdomigan opened this issue Feb 14, 2025 · 0 comments
Open

Prefetching nested fields with shared Type instances throws error #729

cdomigan opened this issue Feb 14, 2025 · 0 comments

Comments

@cdomigan
Copy link

cdomigan commented Feb 14, 2025

Thank you for this wonderful library.

Until the change introduced by #702 by @sudevva I was able to "share" instances of Types in my prefetch logic.

The change to this line:

$prefetchBuffer->purgeResult($source);

means that the source is now purged from results. If I then want to return the same Type instance later, I will get an error:

UnexpectedValueException: Object not found
/workspaces/graphqlite/src/PrefetchBuffer.php:90

This error only shows up in a nested prefetch context, specifically where an intermediate type is being prefetched, instances of that type are being shared, and it has a prefetched child field also.

In the test suite on this repo, this demonstrates the issue:

// tests/Integration/EndToEndTest.php

// A NEW TEST TO DEMONSTRATE THE ISSUE
    public function testPrefetchingDeeplyNested(): void
    {
        $schema = $this->mainContainer->get(Schema::class);
        assert($schema instanceof Schema);

        $schema->assertValid();

        $queryString = '
        query {
            companies {
                contact {
                    name
                    supervisor {
                        name
                    }
                }
            }
        }
        ';

        $result = GraphQL::executeQuery(
            $schema,
            $queryString,
            null,
            new Context(),
        );

        $this->assertSame([
            'companies' => [
                [
                    'contact' => [
                        'name' => 'Kate',
                        'supervisor' => [
                            'name' => 'The Boss',
                        ],
                    ],
                ],
                [
                    'contact' => [
                        'name' => 'Kate',
                        'supervisor' => [
                            'name' => 'The Boss',
                        ],
                    ],
                ],
            ]
        ], $this->getSuccessResult($result));
    }
// tests/Fixtures/Integration/Controllers/CompanyController
/**
     * @return Company[]
     */
    #[Query]
    public function getCompanies(): array
    {
        return [new Company('Company1'), new Company('Company2')];
    }
// tests/Fixtures/Integration/Types/CompanyType.php
    /**
     * @param Company[] $companies
     *
     * @return Contact[]
     */
    public static function prefetchContacts(array $companies): array
    {
        $contacts = [];

        $kate = new Contact('Kate');

        foreach ($companies as $company) {
            // This works
            //$contacts[$company->name] = new Contact('Kate');

            // This does not work
            $contacts[$company->name] = $kate;
        }

        return $contacts;
    }
// tests/Fixtures/Integration/Models/Contact.php
    /** @param Contact[] $contacts */
    #[Field]
    public function getSupervisor(#[Prefetch('prefetchSupervisors')] array $supervisors): Contact|null
    {
        return $supervisors[$this->name] ?? null;
    }

    /**
     * @param Contact[] $contacts
     *
     * @return Contact[]
     */
    public static function prefetchSupervisors(array $contacts): array
    {
        $supervisors = [];

        $theBoss = new Contact('The Boss');

        foreach ($contacts as $contacts) {
            $supervisors[$contacts->name] = $theBoss;
        }

        return $supervisors;
    }

Notably, it is the sharing of the $kate Type instance in the prefetchContacts method on CompanyType.php that causes the error:

There was 1 error:

1) TheCodingMachine\GraphQLite\Integration\EndToEndTest::testPrefetchingDeeplyNested
UnexpectedValueException: Object not found

/workspaces/graphqlite/src/PrefetchBuffer.php:90
/workspaces/graphqlite/src/Parameters/PrefetchDataParameter.php:60
/workspaces/graphqlite/vendor/webonyx/graphql-php/src/Executor/Promise/Adapter/SyncPromise.php:63
/workspaces/graphqlite/vendor/webonyx/graphql-php/src/Executor/Promise/Adapter/SyncPromise.php:50
/workspaces/graphqlite/vendor/webonyx/graphql-php/src/Executor/Promise/Adapter/SyncPromiseAdapter.php:147
/workspaces/graphqlite/vendor/webonyx/graphql-php/src/GraphQL.php:109
/workspaces/graphqlite/tests/Integration/EndToEndTest.php:2477

If I revert to using a new Type instance then the error goes away (see comment in above code). But this means I can't cache/reuse my Type instances in deeply nested prefetch scenarios.

Note if I don't fetch the child field supervisor (itself prefetched) then the error goes away. It is only apparent with nested prefetches.

Commenting out this line appears to fix the issue, but I'm unsure if this has repercussions elsewhere:

$prefetchBuffer->purgeResult($source);

Is this expected behaviour? Am I meant to always return a new Type instance for each result, even if the result is identical and used elsewhere? This seems like a change if so?

cdomigan added a commit to cdomigan/graphqlite that referenced this issue Feb 17, 2025
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

No branches or pull requests

1 participant