"Definitions" are the generic name for top-level statements in the document.
Examples of this include:
1) Operations (such as a query)
2) Fragments
"Operations" are a generic name for requests in the document.
Examples of this include:
1) query,
2) mutation
"Selections" are the statements that can appear legally and at
single level of the query. These include:
1) field references e.g "a"
2) fragment "spreads" e.g. "...c"
3) inline fragment "spreads" e.g. "...on Type { a }"
/** * @it isTypeOf used to resolve runtime type for Union */ public function testIsTypeOfUsedToResolveRuntimeTypeForUnion() { $dogType = new ObjectType(['name' => 'Dog', 'isTypeOf' => function ($obj) { return $obj instanceof Dog; }, 'fields' => ['name' => ['type' => Type::string()], 'woofs' => ['type' => Type::boolean()]]]); $catType = new ObjectType(['name' => 'Cat', 'isTypeOf' => function ($obj) { return $obj instanceof Cat; }, 'fields' => ['name' => ['type' => Type::string()], 'meows' => ['type' => Type::boolean()]]]); $petType = new UnionType(['name' => 'Pet', 'types' => [$dogType, $catType]]); $schema = new Schema(['query' => new ObjectType(['name' => 'Query', 'fields' => ['pets' => ['type' => Type::listOf($petType), 'resolve' => function () { return [new Dog('Odie', true), new Cat('Garfield', false)]; }]]])]); $query = '{ pets { name ... on Dog { woofs } ... on Cat { meows } } }'; $expected = new ExecutionResult(['pets' => [['name' => 'Odie', 'woofs' => true], ['name' => 'Garfield', 'meows' => false]]]); $this->assertEquals($expected, Executor::execute($schema, Parser::parse($query))); }
/** * @it evaluates mutations correctly in the presense of a failed mutation */ public function testEvaluatesMutationsCorrectlyInThePresenseOfAFailedMutation() { $doc = 'mutation M { first: immediatelyChangeTheNumber(newNumber: 1) { theNumber }, second: promiseToChangeTheNumber(newNumber: 2) { theNumber }, third: failToChangeTheNumber(newNumber: 3) { theNumber } fourth: promiseToChangeTheNumber(newNumber: 4) { theNumber }, fifth: immediatelyChangeTheNumber(newNumber: 5) { theNumber } sixth: promiseAndFailToChangeTheNumber(newNumber: 6) { theNumber } }'; $ast = Parser::parse($doc); $mutationResult = Executor::execute($this->schema(), $ast, new Root(6)); $expected = ['data' => ['first' => ['theNumber' => 1], 'second' => ['theNumber' => 2], 'third' => null, 'fourth' => ['theNumber' => 4], 'fifth' => ['theNumber' => 5], 'sixth' => null], 'errors' => [FormattedError::create('Cannot change the number', [new SourceLocation(8, 7)]), FormattedError::create('Cannot change the number', [new SourceLocation(17, 7)])]]; $this->assertArraySubset($expected, self::awaitPromise($mutationResult)); }
public function testExecutesUsingASchema() { $BlogArticle = null; $BlogImage = new ObjectType(['name' => 'Image', 'fields' => ['url' => ['type' => Type::string()], 'width' => ['type' => Type::int()], 'height' => ['type' => Type::int()]]]); $BlogAuthor = new ObjectType(['name' => 'Author', 'fields' => ['id' => ['type' => Type::string()], 'name' => ['type' => Type::string()], 'pic' => ['args' => ['width' => ['type' => Type::int()], 'height' => ['type' => Type::int()]], 'type' => $BlogImage, 'resolve' => function ($obj, $args) { return $obj['pic']($args['width'], $args['height']); }], 'recentArticle' => ['type' => function () use(&$BlogArticle) { return $BlogArticle; }]]]); $BlogArticle = new ObjectType(['name' => 'Article', 'fields' => ['id' => ['type' => Type::nonNull(Type::string())], 'isPublished' => ['type' => Type::boolean()], 'author' => ['type' => $BlogAuthor], 'title' => ['type' => Type::string()], 'body' => ['type' => Type::string()], 'keywords' => ['type' => Type::listOf(Type::string())]]]); $BlogQuery = new ObjectType(['name' => 'Query', 'fields' => ['article' => ['type' => $BlogArticle, 'args' => ['id' => ['type' => Type::id()]], 'resolve' => function ($_, $args) { return $this->article($args['id']); }], 'feed' => ['type' => Type::listOf($BlogArticle), 'resolve' => function () { return [$this->article(1), $this->article(2), $this->article(3), $this->article(4), $this->article(5), $this->article(6), $this->article(7), $this->article(8), $this->article(9), $this->article(10)]; }]]]); $BlogSchema = new Schema($BlogQuery); $request = ' { feed { id, title }, article(id: "1") { ...articleFields, author { id, name, pic(width: 640, height: 480) { url, width, height }, recentArticle { ...articleFields, keywords } } } } fragment articleFields on Article { id, isPublished, title, body, hidden, notdefined } '; $expected = ['data' => ['feed' => [['id' => '1', 'title' => 'My Article 1'], ['id' => '2', 'title' => 'My Article 2'], ['id' => '3', 'title' => 'My Article 3'], ['id' => '4', 'title' => 'My Article 4'], ['id' => '5', 'title' => 'My Article 5'], ['id' => '6', 'title' => 'My Article 6'], ['id' => '7', 'title' => 'My Article 7'], ['id' => '8', 'title' => 'My Article 8'], ['id' => '9', 'title' => 'My Article 9'], ['id' => '10', 'title' => 'My Article 10']], 'article' => ['id' => '1', 'isPublished' => true, 'title' => 'My Article 1', 'body' => 'This is a post', 'author' => ['id' => '123', 'name' => 'John Smith', 'pic' => ['url' => 'cdn://123', 'width' => 640, 'height' => 480], 'recentArticle' => ['id' => '1', 'isPublished' => true, 'title' => 'My Article 1', 'body' => 'This is a post', 'keywords' => ['foo', 'bar', '1', 'true', null]]]]]]; $this->assertEquals($expected, Executor::execute($BlogSchema, Parser::parse($request))->toArray()); }
private function check($testType, $testData, $expected) { $data = ['test' => $testData]; $dataType = null; $dataType = new ObjectType(['name' => 'DataType', 'fields' => function () use(&$testType, &$dataType, $data) { return ['test' => ['type' => $testType], 'nest' => ['type' => $dataType, 'resolve' => function () use($data) { return $data; }]]; }]); $schema = new Schema(['query' => $dataType]); $ast = Parser::parse('{ nest { test } }'); $result = Executor::execute($schema, $ast, $data); $this->assertArraySubset($expected, $result->toArray()); }
/** * Handles execution of a lazily created interface */ public function testReturnsFragmentsWithLazyCreatedInterface() { $request = ' { lazyInterface { ... on TestObject { name } } } '; $expected = ['data' => ['lazyInterface' => ['name' => 'testname']]]; $this->assertEquals($expected, Executor::execute($this->schema, Parser::parse($request))->toArray()); }
/** * @param Schema $schema * @param $requestString * @param mixed $rootObject * @param array <string, string>|null $variableValues * @param string|null $operationName * @return array */ public static function execute(Schema $schema, $requestString, $rootObject = null, $variableValues = null, $operationName = null) { try { $source = new Source($requestString ?: '', 'GraphQL request'); $ast = Parser::parse($source); $validationResult = DocumentValidator::validate($schema, $ast); if (empty($validationResult['isValid'])) { return ['errors' => $validationResult['errors']]; } else { return Executor::execute($schema, $rootObject, $ast, $operationName, $variableValues); } } catch (\Exception $e) { return ['errors' => Error::formatError($e)]; } }
/** * @param Schema $schema * @param $requestString * @param null $rootValue * @param null $variableValues * @param null $operationName * @return array|ExecutionResult */ public static function executeAndReturnResult(Schema $schema, $requestString, $rootValue = null, $variableValues = null, $operationName = null) { try { $source = new Source($requestString ?: '', 'GraphQL request'); $documentAST = Parser::parse($source); $validationErrors = DocumentValidator::validate($schema, $documentAST); if (!empty($validationErrors)) { return new ExecutionResult(null, $validationErrors); } else { return Executor::execute($schema, $documentAST, $rootValue, $variableValues, $operationName); } } catch (Error $e) { return new ExecutionResult(null, [$e]); } }
/** * @param Schema $schema * @param $requestString * @param mixed $rootValue * @param array <string, string>|null $variableValues * @param string|null $operationName * @return array */ public static function execute(Schema $schema, $requestString, $rootValue = null, $variableValues = null, $operationName = null) { try { $source = new Source($requestString ?: '', 'GraphQL request'); $documentAST = Parser::parse($source); $validationErrors = DocumentValidator::validate($schema, $documentAST); if (!empty($validationErrors)) { return ['errors' => array_map(['GraphQL\\Error', 'formatError'], $validationErrors)]; } else { return Executor::execute($schema, $documentAST, $rootValue, $variableValues, $operationName)->toArray(); } } catch (Error $e) { return ['errors' => [Error::formatError($e)]]; } }
/** * @param Schema $schema * @param $requestString * @param null $rootValue * @param null $variableValues * @param null $operationName * @return array|ExecutionResult */ public static function executeAndReturnResult(Schema $schema, $requestString, $rootValue = null, $contextValue = null, $variableValues = null, $operationName = null) { try { if ($requestString instanceof Document) { $documentAST = $requestString; } else { $source = new Source($requestString ?: '', 'GraphQL request'); $documentAST = Parser::parse($source); } /** @var QueryComplexity $queryComplexity */ $queryComplexity = DocumentValidator::getRule('QueryComplexity'); $queryComplexity->setRawVariableValues($variableValues); $validationErrors = DocumentValidator::validate($schema, $documentAST); if (!empty($validationErrors)) { return new ExecutionResult(null, $validationErrors); } else { return Executor::execute($schema, $documentAST, $rootValue, $contextValue, $variableValues, $operationName); } } catch (Error $e) { return new ExecutionResult(null, [$e]); } }
/** * @see https://github.com/webonyx/graphql-php/issues/59 */ public function testSerializesToEmptyObjectVsEmptyArray() { $iface = null; $a = new ObjectType(['name' => 'A', 'fields' => ['id' => Type::id()], 'interfaces' => function () use(&$iface) { return [$iface]; }]); $b = new ObjectType(['name' => 'B', 'fields' => ['id' => Type::id()], 'interfaces' => function () use(&$iface) { return [$iface]; }]); $iface = new InterfaceType(['name' => 'Iface', 'fields' => ['id' => Type::id()], 'resolveType' => function ($v) use($a, $b) { return $v['type'] === 'A' ? $a : $b; }]); $schema = new Schema(['query' => new ObjectType(['name' => 'Query', 'fields' => ['ab' => Type::listOf($iface)]]), 'types' => [$a, $b]]); $data = ['ab' => [['id' => 1, 'type' => 'A'], ['id' => 2, 'type' => 'A'], ['id' => 3, 'type' => 'B'], ['id' => 4, 'type' => 'B']]]; $query = Parser::parse(' { ab { ... on A{ id } } } '); $result = Executor::execute($schema, $query, $data, null); $this->assertEquals(['data' => ['ab' => [['id' => '1'], ['id' => '2'], new \stdClass(), new \stdClass()]]], $result->toArray()); }
/** * @it when argument provided cannot be parsed */ public function testWhenArgumentProvidedCannotBeParsed() { $ast = Parser::parse('{ fieldWithDefaultArgumentValue(input: WRONG_TYPE) }'); $this->assertEquals(['data' => ['fieldWithDefaultArgumentValue' => '"Hello World"']], Executor::execute($this->schema(), $ast)->toArray()); }
public function testNullsTheTopLevelIfAsyncNonNullableFieldResolvesNull() { $doc = ' query Q { nonNullPromise } '; $ast = Parser::parse($doc); $expected = ['data' => null, 'errors' => [FormattedError::create('Cannot return null for non-nullable field DataType.nonNullPromise.', [new SourceLocation(2, 17)])]]; Executor::setPromiseAdapter(new ReactPromiseAdapter()); $this->assertArraySubsetPromise($expected, Executor::execute($this->schema, $ast, $this->nullingData, null, [], 'Q')); }
public function testDoesNotAllowNonNullListsOfNonNullsToContainNull() { $doc = ' query q($input:[String!]!) { nnListNN(input: $input) } '; $ast = Parser::parse($doc); $expected = ['data' => null, 'errors' => [new FormattedError('Variable $input expected value of type [String!]! but got: ["A",null,"B"].', [new SourceLocation(2, 17)])]]; $this->assertEquals($expected, Executor::execute($this->schema(), null, $ast, null, ['input' => ['A', null, 'B']])); }
public function testResolvedValueIsMemoized() { $doc = ' query Q { a { b { c d } } } '; $memoizedValue = new \ArrayObject(['b' => 'id1']); $A = null; $Test = new ObjectType(['name' => 'Test', 'fields' => ['a' => ['type' => function () use(&$A) { return Type::listOf($A); }, 'resolve' => function () use($memoizedValue) { return [$memoizedValue, new \ArrayObject(['b' => 'id2']), $memoizedValue, new \ArrayObject(['b' => 'id2'])]; }]]]); $callCounts = ['id1' => 0, 'id2' => 0]; $A = new ObjectType(['name' => 'A', 'fields' => ['b' => ['type' => new ObjectType(['name' => 'B', 'fields' => ['c' => ['type' => Type::string()], 'd' => ['type' => Type::string()]]]), 'resolve' => function ($value) use(&$callCounts) { $callCounts[$value['b']]++; switch ($value['b']) { case 'id1': return ['c' => 'c1', 'd' => 'd1']; case 'id2': return ['c' => 'c2', 'd' => 'd2']; } }]]]); // Test that value resolved once is memoized for same query field $schema = new Schema($Test); $query = Parser::parse($doc); $result = Executor::execute($schema, $query); $expected = ['data' => ['a' => [['b' => ['c' => 'c1', 'd' => 'd1']], ['b' => ['c' => 'c2', 'd' => 'd2']], ['b' => ['c' => 'c1', 'd' => 'd1']], ['b' => ['c' => 'c2', 'd' => 'd2']]]]]; $this->assertEquals($expected, $result->toArray()); $this->assertSame($callCounts['id1'], 1); // Result for id1 is expected to be memoized after first call $this->assertSame($callCounts['id2'], 2); }
/** * @param PromiseAdapter|null $promiseAdapter */ public static function setPromiseAdapter(PromiseAdapter $promiseAdapter = null) { Executor::setPromiseAdapter($promiseAdapter); }
function testResolveTypeOnInterfaceYieldsUsefulError() { $DogType = null; $CatType = null; $HumanType = null; $PetType = new InterfaceType(['name' => 'Pet', 'resolveType' => function ($obj) use(&$DogType, &$CatType, &$HumanType) { if ($obj instanceof Dog) { return $DogType; } if ($obj instanceof Cat) { return $CatType; } if ($obj instanceof Human) { return $HumanType; } return null; }, 'fields' => ['name' => ['type' => Type::string()]]]); $HumanType = new ObjectType(['name' => 'Human', 'fields' => ['name' => ['type' => Type::string()]]]); $DogType = new ObjectType(['name' => 'Dog', 'interfaces' => [$PetType], 'fields' => ['name' => ['type' => Type::string()], 'woofs' => ['type' => Type::boolean()]]]); $CatType = new ObjectType(['name' => 'Cat', 'interfaces' => [$PetType], 'fields' => ['name' => ['type' => Type::string()], 'meows' => ['type' => Type::boolean()]]]); $schema = new Schema(new ObjectType(['name' => 'Query', 'fields' => ['pets' => ['type' => Type::listOf($PetType), 'resolve' => function () { return [new Dog('Odie', true), new Cat('Garfield', false), new Human('Jon')]; }]]])); $query = '{ pets { name ... on Dog { woofs } ... on Cat { meows } } }'; $expected = ['data' => ['pets' => [['name' => 'Odie', 'woofs' => true], ['name' => 'Garfield', 'meows' => false], null]], 'errors' => [['message' => 'Runtime Object type "Human" is not a possible type for "Pet".']]]; $this->assertEquals($expected, Executor::execute($schema, Parser::parse($query))->toArray()); }
public function testDoesNotIncludeArgumentsThatWereNotSet() { $schema = new Schema(new ObjectType(['name' => 'Type', 'fields' => ['field' => ['type' => Type::string(), 'resolve' => function ($data, $args) { return $args ? json_encode($args) : ''; }, 'args' => ['a' => ['type' => Type::boolean()], 'b' => ['type' => Type::boolean()], 'c' => ['type' => Type::boolean()], 'd' => ['type' => Type::int()], 'e' => ['type' => Type::int()]]]]])); $query = Parser::parse('{ field(a: true, c: false, e: 0) }'); $result = Executor::execute($schema, $query); $expected = ['data' => ['field' => '{"a":true,"c":false,"e":0}']]; $this->assertEquals($expected, $result->toArray()); /* var query = parse('{ field(a: true, c: false, e: 0) }'); var result = await execute(schema, query); expect(result).to.deep.equal({ data: { field: '{"a":true,"c":false,"e":0}' } }); }); it('fails when an isTypeOf check is not met', async () => { class Special { constructor(value) { this.value = value; } } class NotSpecial { constructor(value) { this.value = value; } } var SpecialType = new GraphQLObjectType({ name: 'SpecialType', isTypeOf(obj) { return obj instanceof Special; }, fields: { value: { type: GraphQLString } } }); var schema = new GraphQLSchema({ query: new GraphQLObjectType({ name: 'Query', fields: { specials: { type: new GraphQLList(SpecialType), resolve: rootValue => rootValue.specials } } }) }); var query = parse('{ specials { value } }'); var value = { specials: [ new Special('foo'), new NotSpecial('bar') ] }; var result = await execute(schema, query, value); expect(result.data).to.deep.equal({ specials: [ { value: 'foo' }, null ] }); expect(result.errors).to.have.lengthOf(1); expect(result.errors).to.containSubset([ { message: 'Expected value of type "SpecialType" but got: [object Object].', locations: [ { line: 1, column: 3 } ] } ]); }); */ }
private function executeTestQuery($doc) { return Executor::execute(self::getSchema(), Parser::parse($doc), self::getData())->toArray(); }
public function testAllowsFragmentConditionsToBeAbstractTypes() { $ast = Parser::parse(' { __typename name pets { ...PetFields } friends { ...FriendFields } } fragment PetFields on Pet { __typename ... on Dog { name barks } ... on Cat { name meows } } fragment FriendFields on Named { __typename name ... on Dog { barks } ... on Cat { meows } } '); $expected = ['data' => ['__typename' => 'Person', 'name' => 'John', 'pets' => [['__typename' => 'Cat', 'name' => 'Garfield', 'meows' => false], ['__typename' => 'Dog', 'name' => 'Odie', 'barks' => true]], 'friends' => [['__typename' => 'Person', 'name' => 'Liz'], ['__typename' => 'Dog', 'name' => 'Odie', 'barks' => true]]]]; $this->assertEquals($expected, Executor::execute($this->schema, $this->john, $ast)); }
public function testDoesNotIncludeIllegalFieldsInOutput() { $doc = 'mutation M { thisIsIllegalDontIncludeMe }'; $ast = Parser::parse($doc); $schema = new Schema(new ObjectType(['name' => 'Q', 'fields' => ['a' => ['type' => Type::string()]]]), new ObjectType(['name' => 'M', 'fields' => ['c' => ['type' => Type::string()]]])); $mutationResult = Executor::execute($schema, null, $ast); $this->assertEquals(['data' => []], $mutationResult); }
public function testRespectsListsOfAbstractTypeWhenResolvingViaMap() { $type1 = null; $type2 = null; $type3 = null; $resolveType = function ($value) use(&$type1, &$type2, &$type3) { switch ($value['type']) { case 'Type1': return $type1; case 'Type2': return $type2; case 'Type3': default: return $type3; } }; $mapValues = function ($typeValues, $args) { return Utils::map($typeValues, function ($value) use($args) { if (array_key_exists('foo', $value)) { return json_encode(['value' => $value, 'args' => $args]); } else { return null; } }); }; $interface = new InterfaceType(['name' => 'SomeInterface', 'fields' => ['foo' => ['type' => Type::string()]], 'resolveType' => $resolveType]); $type1 = new ObjectType(['name' => 'Type1', 'fields' => ['foo' => ['type' => Type::string(), 'map' => $mapValues]], 'interfaces' => [$interface]]); $type2 = new ObjectType(['name' => 'Type2', 'fields' => ['foo' => ['type' => Type::string(), 'map' => $mapValues]], 'interfaces' => [$interface]]); $type3 = new ObjectType(['name' => 'Type3', 'fields' => ['bar' => ['type' => Type::listOf(Type::string()), 'map' => function ($type3Values, $args) { return Utils::map($type3Values, function ($value) use($args) { return [json_encode(['value' => $value, 'args' => $args])]; }); }]]]); $union = new UnionType(['name' => 'SomeUnion', 'types' => [$type1, $type3], 'resolveType' => $resolveType]); $complexType = new ObjectType(['name' => 'ComplexType', 'fields' => ['iface' => ['type' => $interface], 'ifaceList' => ['type' => Type::listOf($interface)], 'union' => ['type' => $union], 'unionList' => ['type' => Type::listOf($union)]]]); $type1values = [['type' => 'Type1', 'foo' => 'str1'], ['type' => 'Type1'], ['type' => 'Type1', 'foo' => null]]; $type2values = [['type' => 'Type2', 'foo' => 'str1'], ['type' => 'Type2', 'foo' => null], ['type' => 'Type2']]; $type3values = [['type' => 'Type3', 'bar' => ['str1', 'str2']], ['type' => 'Type3', 'bar' => null]]; $complexTypeValues = ['iface' => $type1values[0], 'ifaceList' => array_merge($type1values, $type2values), 'union' => $type3values[0], 'unionList' => array_merge($type1values, $type3values)]; $expected = ['data' => ['test' => ['iface' => ['foo' => json_encode(['value' => $type1values[0], 'args' => []])], 'ifaceList' => [['foo' => '{"value":{"type":"Type1","foo":"str1"},"args":[]}'], ['foo' => null], ['foo' => '{"value":{"type":"Type1","foo":null},"args":[]}'], ['foo' => '{"value":{"type":"Type2","foo":"str1"},"args":[]}'], ['foo' => '{"value":{"type":"Type2","foo":null},"args":[]}'], ['foo' => null]], 'union' => ['bar' => ['{"value":{"type":"Type3","bar":["str1","str2"]},"args":[]}']], 'unionList' => [['foo' => '{"value":{"type":"Type1","foo":"str1"},"args":[]}'], ['foo' => null], ['foo' => '{"value":{"type":"Type1","foo":null},"args":[]}'], ['bar' => ['{"value":{"type":"Type3","bar":["str1","str2"]},"args":[]}']], ['bar' => ['{"value":{"type":"Type3","bar":null},"args":[]}']]]]]]; $schema = new Schema(new ObjectType(['name' => 'Query', 'fields' => ['test' => ['type' => $complexType, 'resolve' => function () use($complexTypeValues) { return $complexTypeValues; }]]])); $query = '{ test { iface{foo}, ifaceList{foo} union { ... on Type1 { foo } ... on Type3 { bar } } unionList { ... on Type1 { foo } ... on Type3 { bar } } } }'; $query = Parser::parse($query); $result = Executor::execute($schema, $query); $this->assertEquals($expected, $result->toArray()); }
public function testHandlesNonNullListsOfNonNullsWhenTheyReturnNull() { $doc = ' query Q { nest { nonNullListOfNonNullReturnsNull, } } '; $ast = Parser::parse($doc); $expected = ['data' => ['nest' => null], 'errors' => [FormattedError::create('Cannot return null for non-nullable type.', [new SourceLocation(4, 11)])]]; $this->assertEquals($expected, Executor::execute($this->schema(), $ast, $this->data(), [], 'Q')->toArray()); }
public function testNullsTheTopLevelIfSyncNonNullableFieldReturnsNull() { // nulls the top level if sync non-nullable field returns null $doc = ' query Q { nonNullSync } '; $expected = ['data' => null, 'errors' => [FormattedError::create('Cannot return null for non-nullable type.', [new SourceLocation(2, 17)])]]; $this->assertEquals($expected, Executor::execute($this->schema, Parser::parse($doc), $this->nullingData)->toArray()); }
/** * @it not when argument cannot be coerced */ public function testNotWhenArgumentCannotBeCoerced() { $ast = Parser::parse('{ fieldWithDefaultArgumentValue(input: WRONG_TYPE) }'); $expected = ['data' => ['fieldWithDefaultArgumentValue' => null], 'errors' => [['message' => 'Argument "input" got invalid value WRONG_TYPE.' . "\n" . 'Expected type "String", found WRONG_TYPE.', 'locations' => [['line' => 2, 'column' => 50]], 'path' => ['fieldWithDefaultArgumentValue']]]]; $this->assertEquals($expected, Executor::execute($this->schema(), $ast)->toArray()); }