diff --git a/spec/Section 3 -- Type System.md b/spec/Section 3 -- Type System.md index 7a6c5acec..033f65287 100644 --- a/spec/Section 3 -- Type System.md +++ b/spec/Section 3 -- Type System.md @@ -779,8 +779,8 @@ type Person { } ``` -Valid operations must supply a nested field set for any field that returns an -object, so this operation is not valid: +Valid operations must supply a _selection set_ for every field of an object +type, so this operation is not valid: ```graphql counter-example { diff --git a/spec/Section 5 -- Validation.md b/spec/Section 5 -- Validation.md index ce06cb02c..e3085a85f 100644 --- a/spec/Section 5 -- Validation.md +++ b/spec/Section 5 -- Validation.md @@ -303,24 +303,25 @@ query getName { - Let {subscriptionType} be the root Subscription type in {schema}. - For each subscription operation definition {subscription} in the document: - Let {selectionSet} be the top level selection set on {subscription}. - - Let {groupedFieldSet} be the result of + - Let {collectedFieldsMap} be the result of {CollectSubscriptionFields(subscriptionType, selectionSet)}. - - {groupedFieldSet} must have exactly one entry, which must not be an + - {collectedFieldsMap} must have exactly one entry, which must not be an introspection field. CollectSubscriptionFields(objectType, selectionSet, visitedFragments): - If {visitedFragments} is not provided, initialize it to the empty set. -- Initialize {groupedFields} to an empty ordered map of lists. +- Initialize {collectedFieldsMap} to an empty ordered map of ordered sets. - For each {selection} in {selectionSet}: - {selection} must not provide the `@skip` directive. - {selection} must not provide the `@include` directive. - If {selection} is a {Field}: - - Let {responseKey} be the response key of {selection} (the alias if + - Let {responseName} be the _response name_ of {selection} (the alias if defined, otherwise the field name). - - Let {groupForResponseKey} be the list in {groupedFields} for - {responseKey}; if no such list exists, create it as an empty list. - - Append {selection} to the {groupForResponseKey}. + - Let {fieldsForResponseKey} be the _field set_ value in + {collectedFieldsMap} for the key {responseName}; otherwise create the + entry with an empty ordered set. + - Add {selection} to the {fieldsForResponseKey}. - If {selection} is a {FragmentSpread}: - Let {fragmentSpreadName} be the name of {selection}. - If {fragmentSpreadName} is in {visitedFragments}, continue with the next @@ -334,31 +335,31 @@ CollectSubscriptionFields(objectType, selectionSet, visitedFragments): - If {DoesFragmentTypeApply(objectType, fragmentType)} is {false}, continue with the next {selection} in {selectionSet}. - Let {fragmentSelectionSet} be the top-level selection set of {fragment}. - - Let {fragmentGroupedFieldSet} be the result of calling + - Let {fragmentCollectedFieldMap} be the result of calling {CollectSubscriptionFields(objectType, fragmentSelectionSet, visitedFragments)}. - - For each {fragmentGroup} in {fragmentGroupedFieldSet}: - - Let {responseKey} be the response key shared by all fields in - {fragmentGroup}. - - Let {groupForResponseKey} be the list in {groupedFields} for - {responseKey}; if no such list exists, create it as an empty list. - - Append all items in {fragmentGroup} to {groupForResponseKey}. + - For each {responseName} and {fragmentFields} in + {fragmentCollectedFieldMap}: + - Let {fieldsForResponseKey} be the _field set_ value in + {collectedFieldsMap} for the key {responseName}; otherwise create the + entry with an empty ordered set. + - Add each item from {fragmentFields} to {fieldsForResponseKey}. - If {selection} is an {InlineFragment}: - Let {fragmentType} be the type condition on {selection}. - If {fragmentType} is not {null} and {DoesFragmentTypeApply(objectType, fragmentType)} is {false}, continue with the next {selection} in {selectionSet}. - Let {fragmentSelectionSet} be the top-level selection set of {selection}. - - Let {fragmentGroupedFieldSet} be the result of calling + - Let {fragmentCollectedFieldMap} be the result of calling {CollectSubscriptionFields(objectType, fragmentSelectionSet, visitedFragments)}. - - For each {fragmentGroup} in {fragmentGroupedFieldSet}: - - Let {responseKey} be the response key shared by all fields in - {fragmentGroup}. - - Let {groupForResponseKey} be the list in {groupedFields} for - {responseKey}; if no such list exists, create it as an empty list. - - Append all items in {fragmentGroup} to {groupForResponseKey}. -- Return {groupedFields}. + - For each {responseName} and {fragmentFields} in + {fragmentCollectedFieldMap}: + - Let {fieldsForResponseKey} be the _field set_ value in + {collectedFieldsMap} for the key {responseName}; otherwise create the + entry with an empty ordered set. + - Add each item from {fragmentFields} to {fieldsForResponseKey}. +- Return {collectedFieldsMap}. Note: This algorithm is very similar to {CollectFields()}, it differs in that it does not have access to runtime variables and thus the `@skip` and `@include` @@ -584,7 +585,7 @@ should be unambiguous. Therefore any two field selections which might both be encountered for the same object are only valid if they are equivalent. During execution, the simultaneous execution of fields with the same response -name is accomplished by {MergeSelectionSets()} and {CollectFields()}. +name is accomplished by {CollectSubfields()} before execution. For simple hand-written GraphQL, this rule is obviously a clear developer error, however nested fragments can make this difficult to detect manually. diff --git a/spec/Section 6 -- Execution.md b/spec/Section 6 -- Execution.md index bf4f658b9..142193b8d 100644 --- a/spec/Section 6 -- Execution.md +++ b/spec/Section 6 -- Execution.md @@ -133,8 +133,8 @@ respectively. ### Query If the operation is a query, the result of the operation is the result of -executing the operation’s top level _selection set_ with the query root -operation type. +executing the operation’s _root selection set_ with the query root operation +type. An initial value may be provided when executing a query operation. @@ -142,19 +142,15 @@ ExecuteQuery(query, schema, variableValues, initialValue): - Let {queryType} be the root Query type in {schema}. - Assert: {queryType} is an Object type. -- Let {selectionSet} be the top level selection set in {query}. -- Let {data} be the result of running {ExecuteSelectionSet(selectionSet, - queryType, initialValue, variableValues)} _normally_ (allowing - parallelization). -- Let {errors} be the list of all _execution error_ raised while executing the - selection set. -- Return an unordered map containing {data} and {errors}. +- Let {rootSelectionSet} be the _root selection set_ in {query}. +- Return {ExecuteRootSelectionSet(variableValues, initialValue, queryType, + rootSelectionSet, "normal")}. ### Mutation If the operation is a mutation, the result of the operation is the result of -executing the operation’s top level _selection set_ on the mutation root object -type. This selection set should be executed serially. +executing the operation’s _root selection set_ on the mutation root object type. +This selection set should be executed serially. It is expected that the top level fields in a mutation operation perform side-effects on the underlying data system. Serial execution of the provided @@ -164,12 +160,9 @@ ExecuteMutation(mutation, schema, variableValues, initialValue): - Let {mutationType} be the root Mutation type in {schema}. - Assert: {mutationType} is an Object type. -- Let {selectionSet} be the top level selection set in {mutation}. -- Let {data} be the result of running {ExecuteSelectionSet(selectionSet, - mutationType, initialValue, variableValues)} _serially_. -- Let {errors} be the list of all _execution error_ raised while executing the - selection set. -- Return an unordered map containing {data} and {errors}. +- Let {rootSelectionSet} be the _root selection set_ in {mutation}. +- Return {ExecuteRootSelectionSet(variableValues, initialValue, mutationType, + rootSelectionSet, "serial")}. ### Subscription @@ -275,10 +268,11 @@ CreateSourceEventStream(subscription, schema, variableValues, initialValue): - Let {subscriptionType} be the root Subscription type in {schema}. - Assert: {subscriptionType} is an Object type. - Let {selectionSet} be the top level selection set in {subscription}. -- Let {groupedFieldSet} be the result of {CollectFields(subscriptionType, +- Let {collectedFieldsMap} be the result of {CollectFields(subscriptionType, selectionSet, variableValues)}. -- If {groupedFieldSet} does not have exactly one entry, raise a _request error_. -- Let {fields} be the value of the first entry in {groupedFieldSet}. +- If {collectedFieldsMap} does not have exactly one entry, raise a _request + error_. +- Let {fields} be the value of the first entry in {collectedFieldsMap}. - Let {fieldName} be the name of the first entry in {fields}. Note: This value is unaffected if an alias is used. - Let {field} be the first entry in {fields}. @@ -335,13 +329,9 @@ ExecuteSubscriptionEvent(subscription, schema, variableValues, initialValue): - Let {subscriptionType} be the root Subscription type in {schema}. - Assert: {subscriptionType} is an Object type. -- Let {selectionSet} be the top level selection set in {subscription}. -- Let {data} be the result of running {ExecuteSelectionSet(selectionSet, - subscriptionType, initialValue, variableValues)} _normally_ (allowing - parallelization). -- Let {errors} be the list of all _execution error_ raised while executing the - selection set. -- Return an unordered map containing {data} and {errors}. +- Let {rootSelectionSet} be the _root selection set_ in {subscription}. +- Return {ExecuteRootSelectionSet(variableValues, initialValue, + subscriptionType, rootSelectionSet, "normal")}. Note: The {ExecuteSubscriptionEvent()} algorithm is intentionally similar to {ExecuteQuery()} since this is how each event result is produced. @@ -359,19 +349,229 @@ Unsubscribe(responseStream): ## Executing Selection Sets -To execute a _selection set_, the object value being evaluated and the object -type need to be known, as well as whether it must be executed serially, or may -be executed in parallel. +Executing a GraphQL operation recursively collects and executes every selected +field in the operation. First all initially selected fields from the operation's +top most _root selection set_ are collected, then each executed. As each field +completes, all its subfields are collected, then each executed. This process +continues until there are no more subfields to collect and execute. + +### Executing the Root Selection Set + +:: A _root selection set_ is the top level _selection set_ provided by a GraphQL +operation. A root selection set always selects from a _root operation type_. + +To execute the root selection set, the initial value being evaluated and the +root type must be known, as well as whether each field must be executed +serially, or normally by executing all fields in parallel (see +[Normal and Serial Execution](#sec-Normal-and-Serial-Execution). + +Executing the root selection set works similarly for queries (parallel), +mutations (serial), and subscriptions (where it is executed for each event in +the underlying Source Stream). -First, the selection set is turned into a grouped field set; then, each -represented field in the grouped field set produces an entry into a result map. +First, the _selection set_ is collected into a _collected fields map_ which is +then executed, returning the resulting {data} and {errors}. -ExecuteSelectionSet(selectionSet, objectType, objectValue, variableValues): +ExecuteRootSelectionSet(variableValues, initialValue, objectType, selectionSet, +executionMode): -- Let {groupedFieldSet} be the result of {CollectFields(objectType, +- Let {collectedFieldsMap} be the result of {CollectFields(objectType, selectionSet, variableValues)}. +- Let {data} be the result of running + {ExecuteCollectedFields(collectedFieldsMap, objectType, initialValue, + variableValues)} _serially_ if {executionMode} is {"serial"}, otherwise + _normally_ (allowing parallelization)). +- Let {errors} be the list of all _execution error_ raised while executing the + selection set. +- Return an unordered map containing {data} and {errors}. + +### Field Collection + +Before execution, each _selection set_ is converted to a _collected fields map_ +by calling {CollectFields()} by collecting all fields with the same response +name, including those in referenced fragments, into an individual _field set_. +This ensures that multiple references to fields with the same response name will +only be executed once. + +:: A _collected fields map_ is an ordered map where each entry is a _response +name_ and its associated _field set_. A _collected fields map_ may be produced +from a selection set via {CollectFields()} or from the selection sets of all +entries of a _field set_ via {CollectSubfields()}. + +:: A _field set_ is an ordered set of selected fields that share the same +_response name_ (the field alias if defined, otherwise the field's name). +Validation ensures each field in the set has the same name and arguments, +however each may have different subfields (see: +[Field Selection Merging](#sec-Field-Selection-Merging)). + +Note: The order of field selections in both a _collected fields map_ and a +_field set_ are significant, hence the algorithms in this specification model +them as an ordered map and ordered set. + +As an example, collecting the fields of this query's selection set would result +in a collected fields map with two entries, `"a"` and `"b"`, with two instances +of the field `a` and one of field `b`: + +```graphql example +{ + a { + subfield1 + } + ...ExampleFragment +} + +fragment ExampleFragment on Query { + a { + subfield2 + } + b +} +``` + +The depth-first-search order of the _field set_ produced by {CollectFields()} is +maintained through execution, ensuring that fields appear in the executed +response in a stable and predictable order. + +CollectFields(objectType, selectionSet, variableValues, visitedFragments): + +- If {visitedFragments} is not provided, initialize it to the empty set. +- Initialize {collectedFieldsMap} to an empty ordered map of ordered sets. +- For each {selection} in {selectionSet}: + - If {selection} provides the directive `@skip`, let {skipDirective} be that + directive. + - If {skipDirective}'s {if} argument is {true} or is a variable in + {variableValues} with the value {true}, continue with the next {selection} + in {selectionSet}. + - If {selection} provides the directive `@include`, let {includeDirective} be + that directive. + - If {includeDirective}'s {if} argument is not {true} and is not a variable + in {variableValues} with the value {true}, continue with the next + {selection} in {selectionSet}. + - If {selection} is a {Field}: + - Let {responseName} be the _response name_ of {selection} (the alias if + defined, otherwise the field name). + - Let {fieldsForResponseName} be the _field set_ value in + {collectedFieldsMap} for the key {responseName}; otherwise create the + entry with an empty ordered set. + - Add {selection} to the {fieldsForResponseName}. + - If {selection} is a {FragmentSpread}: + - Let {fragmentSpreadName} be the name of {selection}. + - If {fragmentSpreadName} is in {visitedFragments}, continue with the next + {selection} in {selectionSet}. + - Add {fragmentSpreadName} to {visitedFragments}. + - Let {fragment} be the Fragment in the current Document whose name is + {fragmentSpreadName}. + - If no such {fragment} exists, continue with the next {selection} in + {selectionSet}. + - Let {fragmentType} be the type condition on {fragment}. + - If {DoesFragmentTypeApply(objectType, fragmentType)} is {false}, continue + with the next {selection} in {selectionSet}. + - Let {fragmentSelectionSet} be the top-level selection set of {fragment}. + - Let {fragmentCollectedFieldMap} be the result of calling + {CollectFields(objectType, fragmentSelectionSet, variableValues, + visitedFragments)}. + - For each {responseName} and {fragmentFields} in + {fragmentCollectedFieldMap}: + - Let {fieldsForResponseName} be the _field set_ value in + {collectedFieldsMap} for the key {responseName}; otherwise create the + entry with an empty ordered set. + - Add each item from {fragmentFields} to {fieldsForResponseName}. + - If {selection} is an {InlineFragment}: + - Let {fragmentType} be the type condition on {selection}. + - If {fragmentType} is not {null} and {DoesFragmentTypeApply(objectType, + fragmentType)} is {false}, continue with the next {selection} in + {selectionSet}. + - Let {fragmentSelectionSet} be the top-level selection set of {selection}. + - Let {fragmentCollectedFieldMap} be the result of calling + {CollectFields(objectType, fragmentSelectionSet, variableValues, + visitedFragments)}. + - For each {responseName} and {fragmentFields} in + {fragmentCollectedFieldMap}: + - Let {fieldsForResponseName} be the _field set_ value in + {collectedFieldsMap} for the key {responseName}; otherwise create the + entry with an empty ordered set. + - Append each item from {fragmentFields} to {fieldsForResponseName}. +- Return {collectedFieldsMap}. + +DoesFragmentTypeApply(objectType, fragmentType): + +- If {fragmentType} is an Object Type: + - If {objectType} and {fragmentType} are the same type, return {true}, + otherwise return {false}. +- If {fragmentType} is an Interface Type: + - If {objectType} is an implementation of {fragmentType}, return {true}, + otherwise return {false}. +- If {fragmentType} is a Union: + - If {objectType} is a possible type of {fragmentType}, return {true}, + otherwise return {false}. + +Note: The steps in {CollectFields()} evaluating the `@skip` and `@include` +directives may be applied in either order since they apply commutatively. + +**Merging Selection Sets** + +In order to execute the sub-selections of a object typed field, all _selection +sets_ of each field with the same response name of the parent _field set_ are +merged together into a single _collected fields map_ representing the subfields +to be executed next. + +An example operation illustrating parallel fields with the same name with +sub-selections. + +Continuing the example above, + +```graphql example +{ + a { + subfield1 + } + ...ExampleFragment +} + +fragment ExampleFragment on Query { + a { + subfield2 + } + b +} +``` + +After resolving the value for field `"a"`, the following multiple selection sets +are collected and merged together so `"subfield1"` and `"subfield2"` are +resolved in the same phase with the same value. + +CollectSubfields(objectType, fields, variableValues): + +- Let {collectedFieldsMap} be an empty ordered map of ordered sets. +- For each {field} in {fields}: + - Let {fieldSelectionSet} be the selection set of {field}. + - If {fieldSelectionSet} is null or empty, continue to the next field. + - Let {fieldCollectedFieldMap} be the result of {CollectFields(objectType, + fieldSelectionSet, variableValues)}. + - For each {responseName} and {subfields} in {fieldCollectedFieldMap}: + - Let {fieldsForResponseName} be the _field set_ value in + {collectedFieldsMap} for the key {responseName}; otherwise create the + entry with an empty ordered set. + - Add each fields from {subfields} to {fieldsForResponseName}. +- Return {collectedFieldsMap}. + +Note: All the {fields} passed to {CollectSubfields()} share the same _response +name_. + +### Executing Collected Fields + +To execute a _collected fields map_, the object type being evaluated and the +runtime value need to be known, as well as the runtime values for any variables. + +Execution will recursively resolve and complete the value of every entry in the +collected fields map, producing an entry in the result map with the same +_response name_ key. + +ExecuteCollectedFields(collectedFieldsMap, objectType, objectValue, +variableValues): + - Initialize {resultMap} to an empty ordered map. -- For each {groupedFieldSet} as {responseName} and {fields}: +- For each {responseName} and {fields} in {collectedFieldsMap}: - Let {fieldName} be the name of the first entry in {fields}. Note: This value is unaffected if an alias is used. - Let {fieldType} be the return type defined for the field {fieldName} of @@ -383,7 +583,8 @@ ExecuteSelectionSet(selectionSet, objectType, objectValue, variableValues): - Return {resultMap}. Note: {resultMap} is ordered by which fields appear first in the operation. This -is explained in greater detail in the Field Collection section below. +is explained in greater detail in the [Field Collection](#sec-Field-Collection) +section. **Errors and Non-Null Types** @@ -391,7 +592,7 @@ is explained in greater detail in the Field Collection section below. -If during {ExecuteSelectionSet()} a _response position_ with a non-null type +If during {ExecuteCollectedFields()} a _response position_ with a non-null type raises an _execution error_ then that error must propagate to the parent response position (the entire selection set in the case of a field, or the entire list in the case of a list position), either resolving to {null} if @@ -405,13 +606,14 @@ about this behavior. ### Normal and Serial Execution -Normally the executor can execute the entries in a grouped field set in whatever -order it chooses (normally in parallel). Because the resolution of fields other -than top-level mutation fields must always be side effect-free and idempotent, -the execution order must not affect the result, and hence the service has the -freedom to execute the field entries in whatever order it deems optimal. +Normally the executor can execute the entries in a _collected fields map_ in +whatever order it chooses (normally in parallel). Because the resolution of +fields other than top-level mutation fields must always be side effect-free and +idempotent, the execution order must not affect the result, and hence the +service has the freedom to execute the field entries in whatever order it deems +optimal. -For example, given the following grouped field set to be executed normally: +For example, given the following collected fields map to be executed normally: ```graphql example { @@ -431,10 +633,11 @@ before `street`). When executing a mutation, the selections in the top most selection set will be executed in serial order, starting with the first appearing field textually. -When executing a grouped field set serially, the executor must consider each -entry from the grouped field set in the order provided in the grouped field set. -It must determine the corresponding entry in the result map for each item to -completion before it continues on to the next item in the grouped field set: +When executing a collected fields map serially, the executor must consider each +entry from the collected fields map in the order provided in the collected +fields map. It must determine the corresponding entry in the result map for each +item to completion before it continues on to the next entry in the collected +fields map: For example, given the following mutation operation, the root _selection set_ must be executed serially: @@ -501,119 +704,13 @@ A correct executor must generate the following result for that _selection set_: } ``` -### Field Collection - -Before execution, the _selection set_ is converted to a grouped field set by -calling {CollectFields()}. Each entry in the grouped field set is a list of -fields that share a _response name_ (the alias if defined, otherwise the field -name). This ensures all fields with the same response name (including those in -referenced fragments) are executed at the same time. - -As an example, collecting the fields of this selection set would collect two -instances of the field `a` and one of field `b`: - -```graphql example -{ - a { - subfield1 - } - ...ExampleFragment -} - -fragment ExampleFragment on Query { - a { - subfield2 - } - b -} -``` - -The depth-first-search order of the field groups produced by {CollectFields()} -is maintained through execution, ensuring that fields appear in the executed -response in a stable and predictable order. - -CollectFields(objectType, selectionSet, variableValues, visitedFragments): - -- If {visitedFragments} is not provided, initialize it to the empty set. -- Initialize {groupedFields} to an empty ordered map of lists. -- For each {selection} in {selectionSet}: - - If {selection} provides the directive `@skip`, let {skipDirective} be that - directive. - - If {skipDirective}'s {if} argument is {true} or is a variable in - {variableValues} with the value {true}, continue with the next {selection} - in {selectionSet}. - - If {selection} provides the directive `@include`, let {includeDirective} be - that directive. - - If {includeDirective}'s {if} argument is not {true} and is not a variable - in {variableValues} with the value {true}, continue with the next - {selection} in {selectionSet}. - - If {selection} is a {Field}: - - Let {responseName} be the _response name_ of {selection} (the alias if - defined, otherwise the field name). - - Let {groupForResponseName} be the list in {groupedFields} for - {responseName}; if no such list exists, create it as an empty list. - - Append {selection} to the {groupForResponseName}. - - If {selection} is a {FragmentSpread}: - - Let {fragmentSpreadName} be the name of {selection}. - - If {fragmentSpreadName} is in {visitedFragments}, continue with the next - {selection} in {selectionSet}. - - Add {fragmentSpreadName} to {visitedFragments}. - - Let {fragment} be the Fragment in the current Document whose name is - {fragmentSpreadName}. - - If no such {fragment} exists, continue with the next {selection} in - {selectionSet}. - - Let {fragmentType} be the type condition on {fragment}. - - If {DoesFragmentTypeApply(objectType, fragmentType)} is {false}, continue - with the next {selection} in {selectionSet}. - - Let {fragmentSelectionSet} be the top-level selection set of {fragment}. - - Let {fragmentGroupedFieldSet} be the result of calling - {CollectFields(objectType, fragmentSelectionSet, variableValues, - visitedFragments)}. - - For each {fragmentGroup} in {fragmentGroupedFieldSet}: - - Let {responseName} be the response name shared by all fields in - {fragmentGroup}. - - Let {groupForResponseName} be the list in {groupedFields} for - {responseName}; if no such list exists, create it as an empty list. - - Append all items in {fragmentGroup} to {groupForResponseName}. - - If {selection} is an {InlineFragment}: - - Let {fragmentType} be the type condition on {selection}. - - If {fragmentType} is not {null} and {DoesFragmentTypeApply(objectType, - fragmentType)} is {false}, continue with the next {selection} in - {selectionSet}. - - Let {fragmentSelectionSet} be the top-level selection set of {selection}. - - Let {fragmentGroupedFieldSet} be the result of calling - {CollectFields(objectType, fragmentSelectionSet, variableValues, - visitedFragments)}. - - For each {fragmentGroup} in {fragmentGroupedFieldSet}: - - Let {responseName} be the response name shared by all fields in - {fragmentGroup}. - - Let {groupForResponseName} be the list in {groupedFields} for - {responseName}; if no such list exists, create it as an empty list. - - Append all items in {fragmentGroup} to {groupForResponseName}. -- Return {groupedFields}. - -DoesFragmentTypeApply(objectType, fragmentType): - -- If {fragmentType} is an Object Type: - - If {objectType} and {fragmentType} are the same type, return {true}, - otherwise return {false}. -- If {fragmentType} is an Interface Type: - - If {objectType} is an implementation of {fragmentType}, return {true} - otherwise return {false}. -- If {fragmentType} is a Union: - - If {objectType} is a possible type of {fragmentType}, return {true} - otherwise return {false}. - -Note: The steps in {CollectFields()} evaluating the `@skip` and `@include` -directives may be applied in either order since they apply commutatively. - ## Executing Fields -Each field requested in the grouped field set that is defined on the selected -objectType will result in an entry in the result map. Field execution first -coerces any provided argument values, then resolves a value for the field, and -finally completes that value either by recursively executing another selection -set or coercing a scalar value. +Each entry in a result map is the result of executing a field on an object type +selected by the name of that field in a _collected fields map_. Field execution +first coerces any provided argument values, then resolves a value for the field, +and finally completes that value either by recursively executing another +selection set or coercing a scalar value. ExecuteField(objectType, objectValue, fieldType, fields, variableValues): @@ -713,7 +810,8 @@ returned by {resolver} may itself be retrieved asynchronously. After resolving the value for a field, it is completed by ensuring it adheres to the expected return type. If the return type is another Object type, then the -field execution process continues recursively. +field execution process continues recursively by collecting and executing +subfields. CompleteValue(fieldType, fields, result, variableValues): @@ -738,8 +836,9 @@ CompleteValue(fieldType, fields, result, variableValues): - Let {objectType} be {fieldType}. - Otherwise if {fieldType} is an Interface or Union type. - Let {objectType} be {ResolveAbstractType(fieldType, result)}. - - Let {subSelectionSet} be the result of calling {MergeSelectionSets(fields)}. - - Return the result of evaluating {ExecuteSelectionSet(subSelectionSet, + - Let {collectedFieldsMap} be the result of calling + {CollectSubfields(objectType, fields, variableValues)}. + - Return the result of evaluating {ExecuteCollectedFields(collectedFieldsMap, objectType, result, variableValues)} _normally_ (allowing for parallelization). @@ -784,38 +883,6 @@ ResolveAbstractType(abstractType, objectValue): for determining the Object type of {abstractType} given the value {objectValue}. -**Merging Selection Sets** - -When more than one field of the same name is executed in parallel, the -_selection set_ for each of the fields are merged together when completing the -value in order to continue execution of the sub-selection sets. - -An example operation illustrating parallel fields with the same name with -sub-selections. - -```graphql example -{ - me { - firstName - } - me { - lastName - } -} -``` - -After resolving the value for `me`, the selection sets are merged together so -`firstName` and `lastName` can be resolved for one value. - -MergeSelectionSets(fields): - -- Let {selectionSet} be an empty list. -- For each {field} in {fields}: - - Let {fieldSelectionSet} be the selection set of {field}. - - If {fieldSelectionSet} is null or empty, continue to the next field. - - Append all selections in {fieldSelectionSet} to {selectionSet}. -- Return {selectionSet}. - ### Handling Execution Errors