diff --git a/cspell.yml b/cspell.yml index e8aa73355..430c503c3 100644 --- a/cspell.yml +++ b/cspell.yml @@ -11,6 +11,7 @@ words: - parallelization - structs - subselection + - errored # Fictional characters / examples - alderaan - hagrid diff --git a/spec/Section 6 -- Execution.md b/spec/Section 6 -- Execution.md index 0f23fd44c..a3bf7c91d 100644 --- a/spec/Section 6 -- Execution.md +++ b/spec/Section 6 -- Execution.md @@ -251,12 +251,13 @@ CreateSourceEventStream(subscription, schema, variableValues, initialValue): - Let {groupedFieldSet} 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}. -- 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}. +- Let {fieldDetailsList} be the value of the first entry in {groupedFieldSet}. +- Let {fieldDetails} be the first entry in {fieldDetailsList}. +- Let {field} be the corresponding entry on {fieldDetails}. +- Let {fieldName} be the name of {field}. Note: This value is unaffected if an + alias is used. - Let {argumentValues} be the result of {CoerceArgumentValues(subscriptionType, - field, variableValues)}. + node, variableValues)}. - Let {fieldStream} be the result of running {ResolveFieldEventStream(subscriptionType, initialValue, fieldName, argumentValues)}. @@ -326,15 +327,134 @@ this grouped field set and return the resulting {data} and {errors}. ExecuteRootSelectionSet(variableValues, initialValue, objectType, selectionSet, serial): +- Let {incrementalResults} be the result of + {YieldIncrementalResults(variableValues, initialValue, objectType, + selectionSet, serial)}. +- Wait for the first result in {incrementalResults} to be available. +- Let {initialResult} be that result. +- If {hasNext} is {false} on {initialResult}, return {initialResult} except for + {hasNext}. +- Return {initialResult} and {incrementalResults}. + +### Yielding Incremental Results + +The procedure for yielding incremental results is specified by the +{YieldIncrementalResults()} algorithm. + +YieldIncrementalResults(variableValues, initialValue, objectType, selectionSet, +serial): + +- Initialize {pendingResults} to an empty directed acyclic graph. +- Let {data}, {errors}, and {futures} be the result of {ExecuteInitialResult( + variableValues, initialValue, objectType, selectionSet, serial, + pendingResults)}. +- For each {future} of {futures}: + - Add {future} to {pendingResults} as a node directed from each of the + {deferredFragments} it completes, adding each of {deferredFragments} to + {pendingResults}, if necessary, as a new node, directed from its {parent}, + if defined, recursively adding {parent} as necessary, until {future} is + connected to a {parent} already present within {pendingResults}. +- Remove any root Deferred Fragment nodes in {pendingResults} contain no direct + child Futures, repeatedly as necessary, promoting any direct child Deferred + Fragments to root nodes. +- If {pendingResults} is empty: + - Yield an unordered map containing {data} and {errors}. + - Complete this incremental result stream and return. +- Let {newPendingResults} be the set of root nodes in {pendingResults}. +- Let {pending} be the result of {GetPending(newPendingResults)}. +- Let {hasNext} be {true}. +- Yield an unordered map containing {data}, {errors}, {pending}, and {hasNext}. +- For each completed child Future node of a root node in {pendingResults}: + - Let {completedFuture} be that Future; let {result} be its result. + - If {incrementalResult} is defined with a non-empty {incremental} entry: + - If {data} on {result} is {null}: + - Initialize {completed} to an empty list. + - Let {deferredFragments} be the parents of {completedFuture}. + - Initialize {completed} to an empty list. + - For each {deferredFragment} of {deferredFragments}: + - Remove {deferredFragment} and all of its descendant nodes from + {pendingResults}, except for any descendant future nodes with other + parents. + - Let {id} be the unique identifier for {deferredFragment}. + - Let {completedEntry} be an unordered map containing {id} and the value + for {errors} on {result}. + - Append {completedEntry} to {completed}. + - Let {hasNext} be {false}, if {pendingResults} is empty. + - Yield an unordered map containing {completed} and {hasNext}. + - Continue to the next completed child Future node. + - For each {future} of {futures} on {result}: + - Add {future} to {pendingResults}, following the same procedure as above. + - Let {completedDeferredFragments} be the set of Deferred Fragment root nodes + in {pendingResults} containing only completed Future nodes. + - If {completedDeferredFragments} is empty, continue to the next completed + child Future node. + - Let {completedDeferredGroupedFieldSets} be the set of child Future nodes of + {completedDeferredFragments}. + - Initialize {incremental} and {completed} to empty lists. + - For each {future} of {completedDeferredGroupedFieldSets}: + - Let {id} and {subPath} be the result of {GetIdAndSubPath(future, + pendingResults)}. + - Let {result} be the result of that {future}. + - Let {data} and {errors} be the corresponding entries on {result}. + - Let {incrementalEntry} be an unordered map containing {id}, {subPath}, + {data}, and {errors}. + - Append {incrementalEntry} to {incremental}. + - Remove {future} from {pendingResults}. + - For each {deferredFragment} of {completedDeferredFragments}: + - Let {id} be unique identifier for {deferredFragment}. + - Let {completedEntry} be an unordered map containing {id}. + - Append {completedEntry} to {completed}. + - Remove {deferredFragment} from {pendingResults}, promoting its child + Deferred Fragments to root nodes. + - Remove any root Deferred Fragment nodes in {pendingResults} contain no + direct child Futures, repeatedly as necessary, promoting any direct child + Deferred Fragments to root nodes. + - Let {hasNext} be {false} if {pendingResults} is empty. + - Let {incrementalResult} be an unordered map containing {incremental}, + {completed}, and {hasNext}. + - Let {newPendingResults} be the set of new root nodes in {pendingResults}, + promoted by the above steps. + - If {newPendingResults} is not empty: + - Let {pending} be the result of {GetPending(newPendingResults)}. + - Set the corresponding entry on {incrementalResult} to {pending}. + - Yield {incrementalResult}. +- Complete this incremental result stream. + +ExecuteInitialResult(variableValues, initialValue, objectType, selectionSet, +serial, pendingResults): + - If {serial} is not provided, initialize it to {false}. -- Let {groupedFieldSet} be the result of {CollectFields(objectType, - selectionSet, variableValues)}. -- Let {data} be the result of running {ExecuteGroupedFieldSet(groupedFieldSet, - objectType, initialValue, variableValues)} _serially_ if {serial} is {true}, - _normally_ (allowing parallelization) otherwise. +- Let {groupedFieldSet} and {newDeferUsages} be the result of + {CollectFields(objectType, selectionSet, variableValues)}. +- Let {fieldPlan} be the result of {BuildFieldPlan(groupedFieldSet)}. +- Let {data} and {futures} be the result of {ExecuteFieldPlan(newDeferUsages, + fieldPlan, objectType, initialValue, variableValues, serial, pendingResults)}. - Let {errors} be the list of all _field error_ raised while executing the - selection set. -- Return an unordered map containing {data} and {errors}. + {groupedFieldSet}. +- Return {data}, {errors}, and {futures}. + +GetIdAndSubPath(future, pendingResults): + +- Let {deferredFragments} be the Deferred Fragments incrementally completed by + {future} at {path}. +- Let {releasedDeferredFragments} be the members of {deferredFragments} that are + root nodes within {pendingResults}. +- Let {bestDeferredFragment} be the member of {releasedDeferredFragments} with + the shortest {path} entry. +- Let {subPath} be the portion of {path} not contained by the {path} entry of + {bestDeferredFragment}. +- Let {id} be the unique identifier for {bestDeferredFragment}. +- Return {id} and {subPath}. + +GetPending(newPendingResults): + +- Initialize {pending} to an empty list. +- For each {newPendingResult} of {newPendingResults}: + - Let {id} be the unique identifier for {newPendingResult}. + - Let {path} and {label} be the corresponding entries on {newPendingResult}. + - Let {pendingEntry} be an unordered map containing {id}, {path}, and {label}. + - Append {pendingEntry} to {pending}. +- Return {pending}. ### Field Collection @@ -367,10 +487,12 @@ 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): +CollectFields(objectType, selectionSet, variableValues, deferUsage, +visitedFragments): - If {visitedFragments} is not provided, initialize it to the empty set. - Initialize {groupedFields} to an empty ordered map of lists. +- Initialize {newDeferUsages} to an empty list. - For each {selection} in {selectionSet}: - If {selection} provides the directive `@skip`, let {skipDirective} be that directive. @@ -385,14 +507,24 @@ CollectFields(objectType, selectionSet, variableValues, visitedFragments): - If {selection} is a {Field}: - Let {responseKey} be the response key of {selection} (the alias if defined, otherwise the field name). + - Let {fieldDetails} be a new unordered map containing {deferUsage}. + - Set the entry for {field} on {fieldDetails} to {selection}. and + {deferUsage}. - 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}. + - Append {fieldDetails} to the {groupForResponseKey}. - 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}. + - If {fragmentSpreadName} provides the directive `@defer` and its {if} + argument is not {false} and is not a variable in {variableValues} with the + value {false}: + - Let {deferDirective} be that directive. + - If this execution is for a subscription operation, raise a _field + error_. + - If {deferDirective} is not defined: + - 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 @@ -401,31 +533,45 @@ CollectFields(objectType, selectionSet, variableValues, 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 - {CollectFields(objectType, fragmentSelectionSet, variableValues, - visitedFragments)}. + - If {deferDirective} is defined, let {fragmentDeferUsage} be + {deferDirective} and append it to {newDeferUsages}. + - Otherwise, let {fragmentDeferUsage} be {deferUsage}. + - Let {fragmentGroupedFieldSet} and {fragmentNewDeferUsages} be the result + of calling {CollectFields(objectType, fragmentSelectionSet, + variableValues, fragmentDeferUsage, 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}. + - Append all items in {fragmentNewDeferUsages} to {newDeferUsages}. - 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)}. + - If {InlineFragment} provides the directive `@defer` and its {if} argument + is not {false} and is not a variable in {variableValues} with the value + {false}: + - Let {deferDirective} be that directive. + - If this execution is for a subscription operation, raise a _field + error_. + - If {deferDirective} is defined, let {fragmentDeferUsage} be + {deferDirective} and append it to {newDeferUsages}. + - Otherwise, let {fragmentDeferUsage} be {deferUsage}. + - Let {fragmentGroupedFieldSet} and {fragmentNewDeferUsages} be the result + of calling {CollectFields(objectType, fragmentSelectionSet, + variableValues, fragmentDeferUsage, 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}. + - Append all items in {fragmentNewDeferUsages} to {newDeferUsages}. +- Return {groupedFields} and {newDeferUsages}. DoesFragmentTypeApply(objectType, fragmentType): @@ -442,6 +588,104 @@ DoesFragmentTypeApply(objectType, fragmentType): Note: The steps in {CollectFields()} evaluating the `@skip` and `@include` directives may be applied in either order since they apply commutatively. +### Field Plan Generation + +BuildFieldPlan(originalGroupedFieldSet, parentDeferUsages): + +- If {parentDeferUsages} is not provided, initialize it to the empty set. +- Initialize {fieldPlan} to an empty ordered map. +- For each {responseKey} and {groupForResponseKey} of {groupedFieldSet}: + - Let {deferUsageSet} be the result of + {GetDeferUsageSet(groupForResponseKey)}. + - Let {groupedFieldSet} be the entry in {fieldPlan} for any equivalent set to + {deferUsageSet}; if no such map exists, create it as an empty ordered map. + - Set the entry for {responseKey} in {groupedFieldSet} to + {groupForResponseKey}. +- Return {fieldPlan}. + +GetDeferUsageSet(fieldDetailsList): + +- Let {deferUsageSet} be the set containing the {deferUsage} entry from each + item in {fieldDetailsList}. +- For each {deferUsage} of {deferUsageSet}: + - Let {ancestors} be the set of {deferUsage} entries that are ancestors of + {deferUsage}, collected by recursively following the {parent} entry on + {deferUsage}. + - If any of {ancestors} is contained by {deferUsageSet}, remove {deferUsage} + from {deferUsageSet}. +- Return {deferUsageSet}. + +## Executing a Field Plan + +To execute a field plan, the object value being evaluated and the object type +need to be known, as well as whether the non-deferred grouped field set must be +executed serially, or may be executed in parallel. + +ExecuteFieldPlan(newDeferUsages, fieldPlan, objectType, objectValue, +variableValues, serial, pendingResults, path, deferUsageSet, deferMap): + +- If {path} is not provided, initialize it to an empty list. +- Let {newDeferMap} be the result of {GetNewDeferMap(newDeferUsages, path, + deferMap)}. +- Let {groupedFieldSet} be the entry in {fieldPlan} for the set equivalent to + {deferUsageSet}. +- Let {newGroupedFieldSets} be the remaining portion of {fieldPlan}. +- Allowing for parallelization, perform the following steps: + - Let {data} and {nestedFutures} be the result of running + {ExecuteGroupedFieldSet(groupedFieldSet, objectType, objectValue, + variableValues, pendingResults, path, deferUsageSet, newDeferMap)} + _serially_ if {serial} is {true}, _normally_ (allowing parallelization) + otherwise. + - Let {futures} be the result of {ExecuteDeferredGroupedFieldSets(objectType, + objectValue, variableValues, newGroupedFieldSets, pendingResults, path, + newDeferMap)}. +- Append all items in {nestedFutures} to {futures}. +- Return {data} and {futures}. + +GetNewDeferMap(newDeferUsages, path, deferMap): + +- If {newDeferUsages} is empty, return {deferMap}: +- Let {newDeferMap} be a new unordered map containing all entries in {deferMap}. +- For each {deferUsage} in {newDeferUsages}: + - Let {parentDeferUsage} and {label} be the corresponding entries on + {deferUsage}. + - Let {parent} be the entry in {deferMap} for {parentDeferUsage}. + - Let {newDeferredFragment} be an unordered map containing {parent}, {path} + and {label}. + - Set the entry for {deferUsage} in {newDeferMap} to {newDeferredFragment}. +- Return {newDeferMap}. + +ExecuteDeferredGroupedFieldSets(objectType, objectValue, variableValues, +newGroupedFieldSets, pendingResults, path, deferMap): + +- Initialize {futures} to an empty list. +- For each {deferUsageSet} and {groupedFieldSet} in {newGroupedFieldSets}: + - Let {deferredFragments} be an empty list. + - For each {deferUsage} in {deferUsageSet}: + - Let {deferredFragment} be the entry for {deferUsage} in {deferMap}. + - Append {deferredFragment} to {deferredFragments}. + - Let {future} represent the future execution of + {ExecuteDeferredGroupedFieldSet(groupedFieldSet, objectType, objectValue, + variableValues, deferredFragments, path, deferUsageSet, deferMap)}, + incrementally completing {deferredFragments} at {path}. + - Append {future} to {futures}. + - Let initiation of {future} be triggered by the presence of any of the + Deferred Fragments in {deferUsageSet} within {pendingResults} as root nodes, + or if early execution is desired, following any implementation specific + deferral, whichever occurs first. +- Return {futures}. + +ExecuteDeferredGroupedFieldSet(groupedFieldSet, objectType, objectValue, +variableValues, pendingResults, path, deferUsageSet, deferMap): + +- Let {data} and {futures} be the result of running + {ExecuteGroupedFieldSet(groupedFieldSet, objectType, objectValue, + variableValues, pendingResults, path, deferUsageSet, deferMap)} _normally_ + (allowing parallelization). +- Let {errors} be the list of all _field error_ raised while executing the + {groupedFieldSet}. +- Return an unordered map containing {data}, {errors}, and {futures}. + ## Executing a Grouped Field Set To execute a grouped field set, the object value being evaluated and the object @@ -451,23 +695,26 @@ be executed in parallel. Each represented field in the grouped field set produces an entry into a response map. -ExecuteGroupedFieldSet(groupedFieldSet, objectType, objectValue, -variableValues): +ExecuteGroupedFieldSet(groupedFieldSet, objectType, objectValue, variableValues, +pendingResults, path, deferUsageSet, deferMap): - Initialize {resultMap} to an empty ordered map. +- Initialize {futures} to an empty list. - For each {groupedFieldSet} as {responseKey} and {fields}: - 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 {objectType}. - If {fieldType} is defined: - - Let {responseValue} be {ExecuteField(objectType, objectValue, fieldType, - fields, variableValues)}. + - Let {responseValue} and {fieldFutures} be the result of + {ExecuteField(objectType, objectValue, fieldType, fields, variableValues, + pendingResults, path)}. - Set {responseValue} as the value for {responseKey} in {resultMap}. -- Return {resultMap}. + - Append all items in {fieldFutures} to {futures}. +- Return {resultMap} and {futures}. 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 section above. **Errors and Non-Null Fields** @@ -585,16 +832,19 @@ 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): +ExecuteField(objectType, objectValue, fieldType, fieldDetailsList, +variableValues, pendingResults, path, deferUsageSet, deferMap): -- Let {field} be the first entry in {fields}. +- Let {fieldDetails} be the first entry in {fieldDetailsList}. +- Let {field} be the corresponding entry on {fieldDetails}. - Let {fieldName} be the field name of {field}. +- Append {fieldName} to {path}. - Let {argumentValues} be the result of {CoerceArgumentValues(objectType, field, variableValues)} - Let {resolvedValue} be {ResolveFieldValue(objectType, objectValue, fieldName, argumentValues)}. - Return the result of {CompleteValue(fieldType, fields, resolvedValue, - variableValues)}. + variableValues, pendingResults, path, deferUsageSet, deferMap)}. ### Coercing Field Arguments @@ -681,6 +931,8 @@ an underlying database or networked service to produce a value. This necessitates the rest of a GraphQL executor to handle an asynchronous execution flow. In addition, an implementation for collections may leverage asynchronous iterators or asynchronous generators provided by many programming languages. +This may be particularly helpful when used in conjunction with the `@stream` +directive. ### Value Completion @@ -688,22 +940,22 @@ 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. -CompleteValue(fieldType, fields, result, variableValues): +CompleteValue(fieldType, fieldDetailsList, result, variableValues, +pendingResults, path, deferUsageSet, deferMap): - If the {fieldType} is a Non-Null type: - Let {innerType} be the inner type of {fieldType}. - - Let {completedResult} be the result of calling {CompleteValue(innerType, - fields, result, variableValues)}. + - Let {completedResult} and {futures} be the result of calling + {CompleteValue(innerType, fields, result, variableValues, path)}. - If {completedResult} is {null}, raise a _field error_. - - Return {completedResult}. + - Return {completedResult} and {futures}. - If {result} is {null} (or another internal value similar to {null} such as {undefined}), return {null}. - If {fieldType} is a List type: - If {result} is not a collection of values, raise a _field error_. - Let {innerType} be the inner type of {fieldType}. - - Return a list where each list item is the result of calling - {CompleteValue(innerType, fields, resultItem, variableValues)}, where - {resultItem} is each item in {result}. + - Return the result of {CompleteListValue(innerType, fieldDetailsList, result, + variableValues, pendingResults, path, deferUsageSet, deferMap)}. - If {fieldType} is a Scalar or Enum type: - Return the result of {CoerceResult(fieldType, result)}. - If {fieldType} is an Object, Interface, or Union type: @@ -711,11 +963,28 @@ 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 {groupedFieldSet} be the result of calling {CollectSubfields(objectType, - fields, variableValues)}. - - Return the result of evaluating {ExecuteGroupedFieldSet(groupedFieldSet, - objectType, result, variableValues)} _normally_ (allowing for - parallelization). + - Let {groupedFieldSet} and {newDeferUsages} be the result of calling + {CollectSubfields(objectType, fieldDetailsList, variableValues)}. + - Let {fieldPlan} be the result of {BuildFieldPlan(groupedFieldSet, + deferUsageSet)}. + - Return the result of {ExecuteFieldPlan(newDeferUsages, fieldPlan, + objectType, result, variableValues, false, pendingResults, path, + deferUsageSet, deferMap)}. + +CompleteListValue(innerType, fieldDetailsList, result, variableValues, +pendingResults, path, deferUsageSet, deferMap): + +- Initialize {items} and {futures} to empty lists. +- Let {index} be {0}. +- For each {item} of {result}: + - Let {itemPath} be {path} with {index} appended. + - Let {completedItem} and {itemFutures} be the result of calling + {CompleteValue(innerType, fieldDetailsList, item, variableValues, + pendingResults, itemPath)}. + - Append {completedItem} to {items}. + - Append all items in {itemFutures} to {futures}. + - Increment {index}. +- Return {items} and {futures}. **Coercing Results** @@ -781,18 +1050,21 @@ sub-selections. After resolving the value for `me`, the selection sets are merged together so `firstName` and `lastName` can be resolved for one value. -CollectSubfields(objectType, fields, variableValues): +CollectSubfields(objectType, fieldDetailsList, variableValues): -- Let {groupedFieldSet} be an empty map. -- For each {field} in {fields}: +- Initialize {groupedFieldSet} to an empty ordered map of lists. +- Initialize {newDeferUsages} to an empty list. +- For each {fieldDetails} in {fieldDetailsList}: + - Let {field} and {deferUsage} be the corresponding entries on {fieldDetails}. - Let {fieldSelectionSet} be the selection set of {field}. - If {fieldSelectionSet} is null or empty, continue to the next field. - - Let {subGroupedFieldSet} be the result of {CollectFields(objectType, - fieldSelectionSet, variableValues)}. + - Let {subGroupedFieldSet} and {subNewDeferUsages} be the result of + {CollectFields(objectType, fieldSelectionSet, variableValues, deferUsage)}. - For each {subGroupedFieldSet} as {responseKey} and {subfields}: - Let {groupForResponseKey} be the list in {groupedFieldSet} for {responseKey}; if no such list exists, create it as an empty list. - Append all fields in {subfields} to {groupForResponseKey}. + - Append all defer usages in {subNewDeferUsages} to {newDeferUsages}. - Return {groupedFieldSet}. ### Handling Field Errors