From 1df6a26e8efbc8df6199e3982e6475708bdbb475 Mon Sep 17 00:00:00 2001 From: Rob Richard Date: Wed, 15 Jul 2026 15:14:23 -0400 Subject: [PATCH] Spec edits for incremental delivery, Execution: CollectFields Co-authored-by: Yaacov Rydzinski --- spec/Section 6 -- Execution.md | 120 ++++++++++++++++++++++++++------- 1 file changed, 97 insertions(+), 23 deletions(-) diff --git a/spec/Section 6 -- Execution.md b/spec/Section 6 -- Execution.md index 5bde7a6c1..8a460bdd3 100644 --- a/spec/Section 6 -- Execution.md +++ b/spec/Section 6 -- Execution.md @@ -405,7 +405,7 @@ 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 +:: A _field set_ is an ordered set of _field details_ 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: @@ -439,10 +439,46 @@ The depth-first-search order of each _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): +CollectFields(objectType, selectionSet, variableValues, deferUsage, +visitedFragments): + +The {CollectFields()} algorithm makes use of the following data types: + +:: A _defer usage_ is an unordered map representing the usage of a `@defer` +directive within a given operation. _Defer usage_ are "abstract" in that they +include information about the `@defer` directive from the AST of the GraphQL +document. A single _defer usage_ may be used to create many "concrete" Delivery +Groups when a `@defer` is included within a list type. + +A _defer usage_ contains the following information: + +- {label}: the `label` argument provided by the given `@defer` directive, if + any, otherwise {undefined}. +- {parentDeferUsage}: a defer usage corresponding to the `@defer` directive + enclosing this `@defer` directive, if any, otherwise {undefined}. + +The {parentDeferUsage} entry is used to build distinct Execution Groups as +discussed within the Execution Plan Generation section below. + +:: A _field detail_ is an unordered map containing the following entries: + +- {field}: the field selection. +- {deferUsage}: the _defer usage_ enclosing the selection, if any, otherwise + {undefined}. + +:: A _collected fields map_ is an ordered map of _response name_ to lists of +Field Details. + +The {CollectFields()} algorithm returns: + +- {collectedFieldsMap}: the _collected fields map_ for the fields in the + selection set. +- {newDeferUsages}: a list of new _defer usage_ encountered during this field + collection. - If {visitedFragments} is not provided, initialize it to the empty set. - Initialize {collectedFieldsMap} to an empty ordered map of ordered sets. +- Initialize {newDeferUsages} to an empty list. - For each {selection} in {selectionSet}: - If {selection} provides the directive `@skip`, let {skipDirective} be that directive. @@ -457,15 +493,26 @@ CollectFields(objectType, selectionSet, variableValues, visitedFragments): - If {selection} is a {Field}: - Let {responseName} be the _response name_ of {selection} (the alias if defined, otherwise the field name). + - Let {fieldDetails} be a new unordered map containing {field} and + {deferUsage}. + - Set the corresponding entries on {fieldDetails} to {selection} and + {deferUsage}, respectively. - 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}. + - Add {fieldDetails} to the {fieldsForResponseName}. - If {selection} is a {FragmentSpread}: - Let {fragmentSpreadName} be the name of {selection}. + - If {selection} 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 an _execution + error_. - If {fragmentSpreadName} is in {visitedFragments}, continue with the next {selection} in {selectionSet}. - - Add {fragmentSpreadName} to {visitedFragments}. + - If {deferDirective} is not defined: + - 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 @@ -474,31 +521,51 @@ 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 {fragmentCollectedFieldsMap} be the result of calling - {CollectFields(objectType, fragmentSelectionSet, variableValues, - visitedFragments)}. - - For each {responseName} and {fragmentFields} in + - If {deferDirective} is defined: + - Let {label} be the corresponding entry on {deferDirective}. + - Let {parentDeferUsage} be {deferUsage}. + - Let {fragmentDeferUsage} be an unordered map containing {label} and + {parentDeferUsage}. + - Otherwise, let {fragmentDeferUsage} be {deferUsage}. + - Let {fragmentCollectedFieldsMap} and {fragmentNewDeferUsages} be the + result of calling {CollectFields(objectType, fragmentSelectionSet, + variableValues, fragmentDeferUsage, visitedFragments)}. + - For each {responseName} and {fragmentFieldSet} in {fragmentCollectedFieldsMap}: - 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}. + - Add each item from {fragmentFieldSet} to {fieldsForResponseName}. + - 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 {fragmentCollectedFieldsMap} be the result of calling - {CollectFields(objectType, fragmentSelectionSet, variableValues, - visitedFragments)}. - - For each {responseName} and {fragmentFields} in + - If {selection} 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 an _execution + error_. + - If {deferDirective} is defined: + - Let {label} be the corresponding entry on {deferDirective}. + - Let {parentDeferUsage} be {deferUsage}. + - Let {fragmentDeferUsage} be an unordered map containing {label} and + {parentDeferUsage}. + - Otherwise, let {fragmentDeferUsage} be {deferUsage}. + - Let {fragmentCollectedFieldsMap} and {fragmentNewDeferUsages} be the + result of calling {CollectFields(objectType, fragmentSelectionSet, + variableValues, fragmentDeferUsage, visitedFragments)}. + - For each {responseName} and {fragmentFieldSet} in {fragmentCollectedFieldsMap}: - 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}. + - Add each item from {fragmentFieldSet} to {fieldsForResponseName}. + - Append all items in {fragmentNewDeferUsages} to {newDeferUsages}. +- Return {collectedFieldsMap} and {newDeferUsages}. DoesFragmentTypeApply(objectType, fragmentType): @@ -515,6 +582,10 @@ DoesFragmentTypeApply(objectType, fragmentType): Note: The steps in {CollectFields()} evaluating the `@skip` and `@include` directives may be applied in either order since they apply commutatively. +Note: When completing a List field, the {CollectFields} algorithm is invoked +with the same arguments for each element of the list. GraphQL Services may +choose to memoize their implementations of {CollectFields}. + **Merging Selection Sets** In order to execute the sub-selections of an object typed field, all _selection @@ -550,20 +621,23 @@ 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 {newDeferUsages} be an empty list. +- For each {fieldDetails} in {fields}: + - 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 {fieldCollectedFieldsMap} be the result of {CollectFields(objectType, - fieldSelectionSet, variableValues)}. - - For each {responseName} and {subfields} in {fieldCollectedFieldsMap}: + - Let {subCollectedFieldsMap} and {subNewDeferUsages} be the result of + {CollectFields(objectType, fieldSelectionSet, variableValues, deferUsage)}. + - For each {responseName} and {subfields} in {subCollectedFieldsMap}: - 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}. + - Add each item from {subfields} to {fieldsForResponseName}. + - Append all items in {subNewDeferUsages} to {newDeferUsages}. +- Return {collectedFieldsMap} and {newDeferUsages}. -Note: All the {fields} passed to {CollectSubfields()} share the same _response -name_. +Note: All the {fieldDetailsList} passed to {CollectSubfields()} share the same +_response name_. ### Executing Collected Fields