From 165f62f48349813decdb8323962dfab819fa8c7b Mon Sep 17 00:00:00 2001 From: Yaacov Rydzinski Date: Tue, 13 Feb 2024 21:46:51 +0200 Subject: [PATCH] Introduce `@defer` --- spec/Section 6 -- Execution.md | 423 +++++++++++++++++++++++++++++---- 1 file changed, 372 insertions(+), 51 deletions(-) diff --git a/spec/Section 6 -- Execution.md b/spec/Section 6 -- Execution.md index b21cc6460..7029effc1 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,13 @@ this grouped field set and return the resulting {data} and {errors}. ExecuteRootSelectionSet(variableValues, initialValue, objectType, selectionSet, serial): -- 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 {errors} be the list of all _field error_ raised while executing the - selection set. -- Return an unordered map containing {data} and {errors}. +- 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 not present on {initialResult}, return {initialResult}. +- Return {initialResult} and {incrementalResults}. ### Field Collection @@ -367,10 +366,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 +386,23 @@ 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 Field Details record created from {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 +411,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 +466,273 @@ 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): + +- Initialize {deferUsageSet} to the empty set. +- Let {inInitialResult} be {false}. +- For each {fieldDetails} in {fieldDetailsList}: + - Let {deferUsage} be the corresponding entry on {fieldDetails}. + - If {deferUsage} is not defined: + - Let {inInitialResult} be {true}. + - Continue to the next {fieldDetails} in {fieldDetailsList}. + - Add {deferUsage} to {deferUsageSet}. +- If {inInitialResult} is true, reset {deferUsageSet} to the empty set; + otherwise, let {deferUsageSet} be the result of + {FilterDeferUsages(deferUsageSet)}. +- Return {deferUsageSet}. + +FilterDeferUsages(deferUsages): + +- Initialize {filteredDeferUsages} to the empty set. +- For each {deferUsage} in {deferUsages}: + - Let {ancestors} be the result of {GetAncestors(deferUsage)}. + - For each {ancestor} of {ancestors}: + - If {ancestor} is in {deferUsages}. + - Continue to the next {deferUsage} in {deferUsages}. + - Add {deferUsage} to {filteredDeferUsages}. +- Return {filteredDeferUsages}. + +GetAncestors(deferUsage): + +- Initialize {ancestors} to an empty list. +- Let {parentDeferUsage} be the corresponding entry on {deferUsage}. +- If {parentDeferUsage} is not defined, return {ancestors}. +- Append {parentDeferUsage} to {ancestors}. +- Append all the items in {GetAncestors(parentDeferUsage)} to {ancestors}. +- Return {ancestors}. + +### 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 graph. +- Let {incrementalEventStream} be the result of + IncrementalEventStream(variableValues, initialValue, objectType, selectionSet, + serial, pendingResults). +- Initialize {ids} to a new unordered map. +- Return a new event stream which yields events as follows: +- Repeat the following steps: + - Initialize {newPendingResults} and {futures} to empty lists. + - Let {completedDeferredFragments} be the set of root nodes in + {pendingResults} containing only completed Future nodes. + - If {completedDeferredFragments} is empty: + - Wait for the next {completedFuture} on {incrementalEventStream}: + - Let {result} be the result of {completedFuture}. + - If {result} is the Initial Result: + - Let {data} and {errors} be the corresponding entries on {result}. + - If {futures} is empty: + - Yield an unordered map consisting of {errors} and {data}. + - Complete this incremental result stream and return. + - Let {hasNext} be {true}. + - Let {update} be an unordered map consisting of {errors}, {data}, + {pending}, and {hasNext}. + - Otherwise, {result} incrementally completes Deferred Fragments: + - Let {deferredFragments} be those Deferred Fragments. + - If {data} is {null}: + - For each {deferredFragment} of {deferredFragments}: + - Let {id} be the entry on {ids} for {deferredFragment}. + - Let {completedEntry} be an unordered map containing {id} and + {errors}. + - Append {completedEntry} to {completed}. + - Remove {deferredFragment} and all of its descendant nodes from + {pendingResults}, but for any descendant future nodes with other + parents. + - Let {update} be an unordered map consisting of {completed}. + - Otherwise: + - Mark the node for {completedFuture} in {pendingResults} as completed + via {result}. + - Let {completedDeferredFragments} be the set of root nodes in + {pendingResults} containing only completed Future nodes. + - If {completedDeferredFragments} is not empty: + - Let {completedFutures} be the set of direct Future children of + {completedDeferredFragments}. + - For each {future} of {completedFutures}: + - Let {result} be the result of {future}. + - Let {id} and {subPath} be the result of {GetIdAndSubPath(result, ids)}. + - Let {data} and {errors} be the corresponding entries on {result}. + - Append all items in {newPendingResults} on {result} to + {newPendingResults}. + - Add all items in {futures} on {result} to {futures}. + - 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 the entry on {ids} for {deferredFragment}. + - Let {completedEntry} be an unordered map containing {id}. + - Append {completedEntry} to {completed}. + - Remove {deferredFragment} from {pendingResults}. + - For each {newPendingResult} in {newPendingResults}: + - Let {parent} be the corresponding entry on {newPendingResult}. + - Add {newPendingResult} to {pendingResults} as a new node directed from + {parent}, if it is also present in {pendingResults}. + - For each {future} of {futures}: + - Let {deferredFragments} be the Deferred Fragments completed by {future}. + - Add {future} to {pendingResults} as a node directed from each of + {deferredFragments}. + - While any root nodes in {pendingResults} representing Deferred Fragments + contain no direct child Futures, remove those root nodes from + {pendingResults}. + - Reset {newPendingResults} to the set of new root nodes added to + {pendingResults}. + - Initialize {pending} to an empty list. + - For each {newPendingResult} of {newPendingResults}: + - Let {id} be a unique identifier for {newPendingResult}. + - Set the entry for {newPendingResult} of {ids} to {ids}. + - 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}. + - Set the corresponding entry on {update} to {pending}. + - If {update} contains any non-empty entries, yield {update}. + - If {pendingResults} is empty, complete this incremental result stream and + return. + +IncrementalEventStream(variableValues, initialValue, objectType, selectionSet, +serial, pendingResults): + +- Let {incrementalEventStream} be an observable event stream to which events can + be pushed via the method {push}. +- Let {initialFuture} represent the future execution of + {ExecuteInitialResult(variableValues, initialValue, objectType, selectionSet, + serial, pendingResults)}. +- Initiate {initialFuture}. +- Call {YieldCompletedChildren(initialFuture, push)}. +- Return {incrementalEventStream}. + +YieldCompletedChildren(future, push): + +- Wait for {future} to complete. +- Yield {future}. +- Let {result} be the result of {future}. +- Let {futures} be the corresponding entry on {result}. +- Allowing for parallelization, for each {child} in {futures}: + - Call {YieldCompletedChildren(child)}. + +ExecuteInitialResult(variableValues, initialValue, objectType, selectionSet, +serial, pendingResults): + +- If {serial} is not provided, initialize it to {false}. +- Let {groupedFieldSet} and {newDeferUsages} be the result of + {CollectFields(objectType, selectionSet, variableValues)}. +- Let {fieldPlan} be the result of {BuildFieldPlan(groupedFieldSet)}. +- Let {data}, {newPendingResults}, 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 + {groupedFieldSet}. +- Let {initialResult} be an unordered map consisting of {data}, {errors}, + {newPendingResults}, and {futures}. +- Return {initialResult}. + +GetIdAndSubPath(result, ids): + +- Let {deferredFragments} be the corresponding entry on {deferredResult}. +- Let {releasedDeferredFragments} be the members of {deferredFragments} that + have entries defined in {ids}. +- Let {bestDeferredFragment} be the member of {releasedDeferredFragments} with + the shortest {path} entry. +- Let {path} be the corresponding entry on {deferredResult}. +- Let {subPath} be the portion of {path} not contained by the {path} entry of + {bestDeferredFragment}. +- Return {id} and {subPath}. + +## 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 {newPendingResults} and {newDeferMap} be the result of + {GetNewDeferredFragments(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}, {nestedNewPendingResults}, 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 {nestedNewPendingResults} and {nestedFutures} to + {newPendingResults} and {futures}. +- Return {data}, {newPendingResults}, and {futures}. + +GetNewDeferredFragments(newDeferUsages, path, deferMap): + +- If {newDeferUsages} is empty: + - Return {deferMap}. +- Initialize {newDeferredFragments} to an empty list. +- Let {newDeferMap} be a new unordered map of Defer Usage records to Deferred + Fragment records containing all of the 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}. + - Append {newDeferredFragment} to {newDeferredFragments}. + - Set the entry for {deferUsage} in {newDeferMap} to {newDeferredFragment}. +- Return {newDeferredFragments} and {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}. + - 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}, {newPendingResults}, 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 {path}, {data}, {errors}, + {newPendingResults}, and {futures}. + ## Executing a Grouped Field Set To execute a grouped field set, the object value being evaluated and the object @@ -451,20 +742,24 @@ 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 {newPendingResults} and {futures} to empty lists. - 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}, {fieldNewPendingResults}, 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 {fieldNewPendingResults} and {fieldFutures} to + {newPendingResults} and {futures}, respectively. +- Return {resultMap}, {newPendingResults}, 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 above. @@ -585,16 +880,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 +979,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 +988,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}, {newPendingResults}, 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}, {newPendingResults}, 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 +1011,29 @@ 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 {newPendingResults} and {futures} to empty lists. +- Let {fieldDetails} be the first entry in {fieldDetailsList}. +- Let {field} be the corresponding entry on {fieldDetails}. +- Let {items} be an empty list. +- For each {resultItem} of {result}: + - Let {completedItem}, {itemNewPendingResults}, and {itemFutures} be the + result of calling {CompleteValue(innerType, fieldDetailsList, item, + variableValues, pendingResults, itemPath)}. + - Append {completedItem} to {items}. + - Append all items in {itemNewPendingResults}, and {itemFutures} to + {newPendingResults}, and {futures}, respectively. +- Return {items}, {newPendingResults}, and {futures}. **Coercing Results** @@ -781,18 +1099,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