From b76671bcd1b34af545138f9eaca6e0acc8681c04 Mon Sep 17 00:00:00 2001 From: Benjie Gillam Date: Fri, 28 Apr 2023 16:58:49 +0100 Subject: [PATCH 01/18] Extract common logic from ExecuteQuery, ExecuteMutation and ExecuteSubscriptionEvent --- spec/Section 6 -- Execution.md | 44 +++++++++++++++++++++------------- 1 file changed, 27 insertions(+), 17 deletions(-) diff --git a/spec/Section 6 -- Execution.md b/spec/Section 6 -- Execution.md index 5b8594e30..0e1cd2edc 100644 --- a/spec/Section 6 -- Execution.md +++ b/spec/Section 6 -- Execution.md @@ -134,12 +134,8 @@ 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 _field error_ raised while executing the - selection set. -- Return an unordered map containing {data} and {errors}. +- Return {ExecuteRootSelectionSet(variableValues, initialValue, queryType, + selectionSet)}. ### Mutation @@ -156,11 +152,8 @@ 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 _field error_ raised while executing the - selection set. -- Return an unordered map containing {data} and {errors}. +- Return {ExecuteRootSelectionSet(variableValues, initialValue, mutationType, + selectionSet, true)}. ### Subscription @@ -304,12 +297,8 @@ 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 _field error_ raised while executing the - selection set. -- Return an unordered map containing {data} and {errors}. +- Return {ExecuteRootSelectionSet(variableValues, initialValue, + subscriptionType, selectionSet)}. Note: The {ExecuteSubscriptionEvent()} algorithm is intentionally similar to {ExecuteQuery()} since this is how each event result is produced. @@ -325,6 +314,27 @@ Unsubscribe(responseStream): - Cancel {responseStream}. +## Executing the Root Selection Set + +To execute the root 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 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). + +ExecuteRootSelectionSet(variableValues, initialValue, objectType, selectionSet, +serial): + +- If {serial} is not provided, initialize it to {false}. +- Let {data} be the result of running {ExecuteSelectionSet(selectionSet, + 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}. + ## Executing Selection Sets To execute a _selection set_, the object value being evaluated and the object From c9837a48d2c280e884a52e50261ebc13b9b60880 Mon Sep 17 00:00:00 2001 From: Benjie Gillam Date: Fri, 28 Apr 2023 17:20:43 +0100 Subject: [PATCH 02/18] Change ExecuteSelectionSet to ExecuteGroupedFieldSet --- spec/Section 6 -- Execution.md | 50 ++++++++++++++++++++-------------- 1 file changed, 30 insertions(+), 20 deletions(-) diff --git a/spec/Section 6 -- Execution.md b/spec/Section 6 -- Execution.md index 0e1cd2edc..86abd1c70 100644 --- a/spec/Section 6 -- Execution.md +++ b/spec/Section 6 -- Execution.md @@ -324,31 +324,34 @@ 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, we execute +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 {data} be the result of running {ExecuteSelectionSet(selectionSet, +- 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}. -## Executing Selection Sets +## Executing a Grouped Field Set -To execute a _selection set_, the object value being evaluated and the object +To execute a grouped field 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. -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 response -map. +Each represented field in the grouped field set produces an entry into a +response map. -ExecuteSelectionSet(selectionSet, objectType, objectValue, variableValues): +ExecuteGroupedFieldSet(groupedFieldSet, objectType, objectValue, +variableValues): -- Let {groupedFieldSet} be the result of {CollectFields(objectType, - selectionSet, variableValues)}. - Initialize {resultMap} to an empty ordered map. - For each {groupedFieldSet} as {responseKey} and {fields}: - Let {fieldName} be the name of the first entry in {fields}. Note: This value @@ -366,8 +369,8 @@ is explained in greater detail in the Field Collection section below. **Errors and Non-Null Fields** -If during {ExecuteSelectionSet()} a field with a non-null {fieldType} raises a -_field error_ then that error must propagate to this entire selection set, +If during {ExecuteGroupedFieldSet()} a field with a non-null {fieldType} raises +a _field error_ then that error must propagate to this entire selection set, either resolving to {null} if allowed or further propagated to a parent field. If this occurs, any sibling fields which have not yet executed or have not yet @@ -707,8 +710,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 {groupedFieldSet} be the result of calling {CollectSubfields(objectType, + fields, variableValues)}. + - Return the result of evaluating {ExecuteGroupedFieldSet(groupedFieldSet, objectType, result, variableValues)} _normally_ (allowing for parallelization). @@ -755,9 +759,10 @@ ResolveAbstractType(abstractType, 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. +When more than one field of the same name is executed in parallel, during value +completion each related _selection set_ is collected together to produce a +single grouped field set in order to continue execution of the sub-selection +sets. An example operation illustrating parallel fields with the same name with sub-selections. @@ -776,14 +781,19 @@ sub-selections. After resolving the value for `me`, the selection sets are merged together so `firstName` and `lastName` can be resolved for one value. -MergeSelectionSets(fields): +CollectSubfields(objectType, fields, variableValues): -- Let {selectionSet} be an empty list. +- Let {groupedFieldSet} be an empty map. - 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}. + - Let {subGroupedFieldSet} be the result of {CollectFields(objectType, + fieldSelectionSet, variableValues)}. + - 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}. +- Return {groupedFieldSet}. ### Handling Field Errors From a52310e7241c87a73afd175444b34aaf5d02cb56 Mon Sep 17 00:00:00 2001 From: Benjie Gillam Date: Mon, 21 Aug 2023 12:15:34 +0100 Subject: [PATCH 03/18] Correct reference to MergeSelectionSets --- spec/Section 5 -- Validation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/Section 5 -- Validation.md b/spec/Section 5 -- Validation.md index 75af96ffd..969d99f88 100644 --- a/spec/Section 5 -- Validation.md +++ b/spec/Section 5 -- Validation.md @@ -467,7 +467,7 @@ 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()}. For simple hand-written GraphQL, this rule is obviously a clear developer error, however nested fragments can make this difficult to detect manually. From 60a9c35a2e32c6c587c773a98b3857b22957df8f Mon Sep 17 00:00:00 2001 From: Yaacov Rydzinski Date: Thu, 17 Oct 2024 12:09:29 +0300 Subject: [PATCH 04/18] move Field Collection section earlier (#1111) --- spec/Section 6 -- Execution.md | 212 ++++++++++++++++----------------- 1 file changed, 106 insertions(+), 106 deletions(-) diff --git a/spec/Section 6 -- Execution.md b/spec/Section 6 -- Execution.md index 86abd1c70..67a8840cc 100644 --- a/spec/Section 6 -- Execution.md +++ b/spec/Section 6 -- Execution.md @@ -340,6 +340,112 @@ serial): selection set. - Return an unordered map containing {data} and {errors}. +### 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 key (the alias if defined, otherwise the field +name). This ensures all fields with the same response key (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 {responseKey} be the response key 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}. + - 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 {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}. + - 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 {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}. + +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 a Grouped Field Set To execute a grouped field set, the object value being evaluated and the object @@ -477,112 +583,6 @@ 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 key (the alias if defined, otherwise the field -name). This ensures all fields with the same response key (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 {responseKey} be the response key 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}. - - 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 {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}. - - 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 {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}. - -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 From 213fd2a6bf8d780bf0b1b1ae71146f3e3737bc5f Mon Sep 17 00:00:00 2001 From: Benjie Gillam Date: Thu, 6 Mar 2025 21:23:09 +0000 Subject: [PATCH 05/18] Define 'grouped field set' --- spec/Section 2 -- Language.md | 5 +++-- spec/Section 6 -- Execution.md | 14 ++++++++------ 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/spec/Section 2 -- Language.md b/spec/Section 2 -- Language.md index 76b5fadcb..595790bcd 100644 --- a/spec/Section 2 -- Language.md +++ b/spec/Section 2 -- Language.md @@ -466,8 +466,9 @@ These two operations are semantically identical: Alias : Name : -By default a field's response key in the response object will use that field's -name. However, you can define a different response key by specifying an alias. +:: A _response key_ is the key in the response object that correlates with a +field's result. By default the response key will use the field's name; however, +you can define a different response key by specifying an alias. In this example, we can fetch two profile pictures of different sizes and ensure the resulting response object will not have duplicate keys: diff --git a/spec/Section 6 -- Execution.md b/spec/Section 6 -- Execution.md index 2c44a2da5..14593e138 100644 --- a/spec/Section 6 -- Execution.md +++ b/spec/Section 6 -- Execution.md @@ -347,7 +347,7 @@ 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, we execute +First, the selection set is turned into a _grouped field set_; then, we execute this grouped field set and return the resulting {data} and {errors}. ExecuteRootSelectionSet(variableValues, initialValue, objectType, selectionSet, @@ -365,11 +365,13 @@ serial): ### 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 key (the alias if defined, otherwise the field -name). This ensures all fields with the same response key (including those in -referenced fragments) are executed at the same time. +Before execution, the _selection set_ is converted to a _grouped field set_ by +calling {CollectFields()}. + +:: A _grouped field set_ is a map where each entry is a list of field selections +(including those in referenced fragments) that share a _response key_ (the alias +if defined, otherwise the field name). This ensures all fields with the same +_response key_ 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`: From 383cf8ed17d5e8c0387550fd74ad6543b867a918 Mon Sep 17 00:00:00 2001 From: Benjie Gillam Date: Thu, 6 Mar 2025 21:24:37 +0000 Subject: [PATCH 06/18] that -> which --- spec/Section 2 -- Language.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/Section 2 -- Language.md b/spec/Section 2 -- Language.md index 595790bcd..520f1f1de 100644 --- a/spec/Section 2 -- Language.md +++ b/spec/Section 2 -- Language.md @@ -466,7 +466,7 @@ These two operations are semantically identical: Alias : Name : -:: A _response key_ is the key in the response object that correlates with a +:: A _response key_ is the key in the response object which correlates with a field's result. By default the response key will use the field's name; however, you can define a different response key by specifying an alias. From 48a789b194db7db31b110851b662d04f76ee8604 Mon Sep 17 00:00:00 2001 From: Benjie Gillam Date: Thu, 6 Mar 2025 21:27:11 +0000 Subject: [PATCH 07/18] More similar to prior wording --- spec/Section 6 -- Execution.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/Section 6 -- Execution.md b/spec/Section 6 -- Execution.md index 14593e138..d9f09a3a3 100644 --- a/spec/Section 6 -- Execution.md +++ b/spec/Section 6 -- Execution.md @@ -369,9 +369,9 @@ Before execution, the _selection set_ is converted to a _grouped field set_ by calling {CollectFields()}. :: A _grouped field set_ is a map where each entry is a list of field selections -(including those in referenced fragments) that share a _response key_ (the alias -if defined, otherwise the field name). This ensures all fields with the same -_response key_ are executed at the same time. +that share a _response key_ (the alias if defined, otherwise the field name). +This ensures all fields with the same response key (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`: From 0b9eed7d0857f37e89814125b4054a829534fd91 Mon Sep 17 00:00:00 2001 From: Benjie Gillam Date: Thu, 6 Mar 2025 21:28:28 +0000 Subject: [PATCH 08/18] Remove reason from definition --- spec/Section 6 -- Execution.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/spec/Section 6 -- Execution.md b/spec/Section 6 -- Execution.md index d9f09a3a3..719c716e8 100644 --- a/spec/Section 6 -- Execution.md +++ b/spec/Section 6 -- Execution.md @@ -365,13 +365,12 @@ serial): ### Field Collection -Before execution, the _selection set_ is converted to a _grouped field set_ by -calling {CollectFields()}. - :: A _grouped field set_ is a map where each entry is a list of field selections that share a _response key_ (the alias if defined, otherwise the field name). -This ensures all fields with the same response key (including those in -referenced fragments) are executed at the same time. + +Before execution, the _selection set_ is converted to a _grouped field set_ by +calling {CollectFields()}. This ensures all fields with the same response key +(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`: From 0728c4ae03ccdff0254fb934365f243f5e2faddb Mon Sep 17 00:00:00 2001 From: Benjie Gillam Date: Thu, 6 Mar 2025 21:32:35 +0000 Subject: [PATCH 09/18] subGroupedFieldSet -> fieldGroupedFieldSet --- spec/Section 6 -- Execution.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/Section 6 -- Execution.md b/spec/Section 6 -- Execution.md index 719c716e8..ba8cb6b51 100644 --- a/spec/Section 6 -- Execution.md +++ b/spec/Section 6 -- Execution.md @@ -812,9 +812,9 @@ CollectSubfields(objectType, fields, variableValues): - 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 {subGroupedFieldSet} be the result of {CollectFields(objectType, + - Let {fieldGroupedFieldSet} be the result of {CollectFields(objectType, fieldSelectionSet, variableValues)}. - - For each {subGroupedFieldSet} as {responseKey} and {subfields}: + - For each {fieldGroupedFieldSet} 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}. From 140c3dabbdfff44dae76cb90661000fd6c418249 Mon Sep 17 00:00:00 2001 From: Benjie Gillam Date: Thu, 6 Mar 2025 21:36:34 +0000 Subject: [PATCH 10/18] Add note for clarity --- spec/Section 6 -- Execution.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/spec/Section 6 -- Execution.md b/spec/Section 6 -- Execution.md index ba8cb6b51..b5ed9612f 100644 --- a/spec/Section 6 -- Execution.md +++ b/spec/Section 6 -- Execution.md @@ -820,6 +820,9 @@ CollectSubfields(objectType, fields, variableValues): - Append all fields in {subfields} to {groupForResponseKey}. - Return {groupedFieldSet}. +Note: All the {fields} passed to {CollectSubfields()} share the same _response +key_. + ### Handling Field Errors A _field error_ is an error raised from a particular field during value From d68df95e56eef06285bb2da297e10eea3fb6f1b3 Mon Sep 17 00:00:00 2001 From: Lee Byron Date: Thu, 17 Apr 2025 06:48:34 -1000 Subject: [PATCH 11/18] move field collections into one section, section reworking, minor word tweaking, enum --- spec/Section 6 -- Execution.md | 173 +++++++++++++++++++-------------- 1 file changed, 98 insertions(+), 75 deletions(-) diff --git a/spec/Section 6 -- Execution.md b/spec/Section 6 -- Execution.md index f81e38dba..3c83fe796 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,15 +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 {rootSelectionSet} be the _root selection set_ in {query}. - Return {ExecuteRootSelectionSet(variableValues, initialValue, queryType, - selectionSet)}. + 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 @@ -160,9 +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 {rootSelectionSet} be the _root selection set_ in {mutation}. - Return {ExecuteRootSelectionSet(variableValues, initialValue, mutationType, - selectionSet, true)}. + rootSelectionSet, "serial")}. ### Subscription @@ -328,9 +328,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 {rootSelectionSet} be the _root selection set_ in {subscription}. - Return {ExecuteRootSelectionSet(variableValues, initialValue, - subscriptionType, selectionSet)}. + subscriptionType, rootSelectionSet, "normal")}. Note: The {ExecuteSubscriptionEvent()} algorithm is intentionally similar to {ExecuteQuery()} since this is how each event result is produced. @@ -346,43 +346,56 @@ Unsubscribe(responseStream): - Cancel {responseStream}. -## Executing the Root Selection Set +## Executing Selection Sets -To execute the root 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. +The process of executing a GraphQL operation is to recursively execute every +selected field in the operation. To do this, first all initially selected fields +from the operation's top most _root selection set_ are collected, then each +executed, then of those all 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 type. + +To execute the root selection set, the initial value being evaluated and the +root type must be known, as well as whether it must be executed serially, or may +be executed 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, we execute -this grouped field set and return the resulting {data} and {errors}. +First, the _selection set_ is collected into a _grouped field set_ which is then +executed, returning the resulting {data} and {errors}. ExecuteRootSelectionSet(variableValues, initialValue, objectType, selectionSet, -serial): +executionMode): -- 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. + objectType, initialValue, variableValues)} _serially_ if {executionMode} is + {"serial"}, otherwise _normally_). - 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 -:: A _grouped field set_ is a map where each entry is a list of field selections -that share a _response name_ (the alias if defined, otherwise the field name). - Before execution, the _selection set_ is converted to a _grouped field set_ by -calling {CollectFields()}. This ensures all fields with the same response name -(including those in referenced fragments) are executed at the same time. +calling {CollectFields()}. This ensures all fields with the same response name, +including those in referenced fragments, are executed at the same time. + +:: A _grouped field set_ is a map where each entry is a _response name_ and a +list of selected fields that share that _response name_ (the field alias if +defined, otherwise the field's name). -As an example, collecting the fields of this selection set would collect two -instances of the field `a` and one of field `b`: +As an example, collecting the fields of this selection set would result in a +grouped field set with two entries, `"a"` and `"b"`, with two instances of the +field `a` and one of field `b`: ```graphql example { @@ -479,14 +492,64 @@ DoesFragmentTypeApply(objectType, fragmentType): Note: The steps in {CollectFields()} evaluating the `@skip` and `@include` directives may be applied in either order since they apply commutatively. -## Executing a Grouped Field Set +**Merging Selection Sets** + +When more than one field of the same name is executed in parallel, during value +completion each related _selection set_ is collected together to produce a +single _grouped field set_ in order to continue execution of the sub-selection +sets. + +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 +} +``` -To execute a grouped field 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. +After resolving the value for field `"a"`, the following multiple selection sets +are merged together so `"subfield1"` and `"subfield2"` are resolved in the same +phase with the same value. -Each represented field in the grouped field set produces an entry into a result -map. +CollectSubfields(objectType, fields, variableValues): + +- Let {groupedFieldSet} be an empty map. +- 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 {fieldGroupedFieldSet} be the result of {CollectFields(objectType, + fieldSelectionSet, variableValues)}. + - For each {fieldGroupedFieldSet} as {responseName} and {subfields}: + - Let {groupForResponseName} be the list in {groupedFieldSet} for + {responseName}; if no such list exists, create it as an empty list. + - Append all fields in {subfields} to {groupForResponseName}. +- Return {groupedFieldSet}. + +Note: All the {fields} passed to {CollectSubfields()} share the same _response +name_. + +### Executing a Grouped Field Set + +To execute a _grouped field 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 (see +[Normal and Serial Execution](#sec-Normal-and-Serial-Execution). + +Each entry in the grouped field set represents a _response name_ which produces +an entry into a result map. ExecuteGroupedFieldSet(groupedFieldSet, objectType, objectValue, variableValues): @@ -504,7 +567,8 @@ 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** @@ -800,47 +864,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, during value -completion each related _selection set_ is collected together to produce a -single grouped field set 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. - -CollectSubfields(objectType, fields, variableValues): - -- Let {groupedFieldSet} be an empty map. -- 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 {fieldGroupedFieldSet} be the result of {CollectFields(objectType, - fieldSelectionSet, variableValues)}. - - For each {fieldGroupedFieldSet} as {responseName} and {subfields}: - - Let {groupForResponseName} be the list in {groupedFieldSet} for - {responseName}; if no such list exists, create it as an empty list. - - Append all fields in {subfields} to {groupForResponseName}. -- Return {groupedFieldSet}. - -Note: All the {fields} passed to {CollectSubfields()} share the same _response -name_. - ### Handling Execution Errors From 180a51c995d702d1bf3e2dbee4c4099c90573508 Mon Sep 17 00:00:00 2001 From: Benjie Date: Thu, 24 Apr 2025 16:50:08 +0100 Subject: [PATCH 12/18] Apply suggestions from code review --- spec/Section 6 -- Execution.md | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/spec/Section 6 -- Execution.md b/spec/Section 6 -- Execution.md index 3c83fe796..88d85bc8b 100644 --- a/spec/Section 6 -- Execution.md +++ b/spec/Section 6 -- Execution.md @@ -351,13 +351,14 @@ Unsubscribe(responseStream): The process of executing a GraphQL operation is to recursively execute every selected field in the operation. To do this, first all initially selected fields from the operation's top most _root selection set_ are collected, then each -executed, then of those all subfields are collected, then each executed. This -process continues until there are no more subfields to collect and execute. +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 type. +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 it must be executed serially, or may @@ -378,7 +379,7 @@ executionMode): selectionSet, variableValues)}. - Let {data} be the result of running {ExecuteGroupedFieldSet(groupedFieldSet, objectType, initialValue, variableValues)} _serially_ if {executionMode} is - {"serial"}, otherwise _normally_). + {"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}. @@ -494,10 +495,10 @@ directives may be applied in either order since they apply commutatively. **Merging Selection Sets** -When more than one field of the same name is executed in parallel, during value -completion each related _selection set_ is collected together to produce a -single _grouped field set_ in order to continue execution of the sub-selection -sets. +When a field is executed, during value completion the _selection set_ of each of +the related field selections with the same response name are collected together +to produce a single _grouped field set_ in order to continue execution of the +sub-selection sets. An example operation illustrating parallel fields with the same name with sub-selections. From 3c6dfb3d401f19423d59907e538257a21cc54e80 Mon Sep 17 00:00:00 2001 From: Benjie Gillam Date: Fri, 25 Apr 2025 09:20:52 +0100 Subject: [PATCH 13/18] Rename 'ExecuteGroupedFieldSet' to 'ExecuteCollectedFields' --- spec/Section 3 -- Type System.md | 2 +- spec/Section 6 -- Execution.md | 42 +++++++++++++++++++++----------- 2 files changed, 29 insertions(+), 15 deletions(-) diff --git a/spec/Section 3 -- Type System.md b/spec/Section 3 -- Type System.md index 47d3efb3d..e917375d6 100644 --- a/spec/Section 3 -- Type System.md +++ b/spec/Section 3 -- Type System.md @@ -757,7 +757,7 @@ type Person { } ``` -Valid operations must supply a nested field set for any field that returns an +Valid operations must supply a selection of fields for any field that returns an object, so this operation is not valid: ```graphql counter-example diff --git a/spec/Section 6 -- Execution.md b/spec/Section 6 -- Execution.md index 88d85bc8b..018aa533c 100644 --- a/spec/Section 6 -- Execution.md +++ b/spec/Section 6 -- Execution.md @@ -377,7 +377,7 @@ executionMode): - Let {groupedFieldSet} be the result of {CollectFields(objectType, selectionSet, variableValues)}. -- Let {data} be the result of running {ExecuteGroupedFieldSet(groupedFieldSet, +- Let {data} be the result of running {ExecuteCollectedFields(groupedFieldSet, 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 @@ -386,17 +386,26 @@ executionMode): ### Field Collection -Before execution, the _selection set_ is converted to a _grouped field set_ by -calling {CollectFields()}. This ensures all fields with the same response name, -including those in referenced fragments, are executed at the same time. +Before execution, the _root selection set_ is converted to a _grouped field set_ +by calling {CollectFields()}. This ensures all fields with the same response +name, including those in referenced fragments, are executed at the same time. -:: A _grouped field set_ is a map where each entry is a _response name_ and a -list of selected fields that share that _response name_ (the field alias if -defined, otherwise the field's name). +:: A _grouped field set_ is a map where each entry is a _response name_ and its +associated _field set_. A _grouped field set_ may be produced from a selection +set via {CollectFields()} or from the selection sets of a _field set_ via +{CollectSubfields()}. -As an example, collecting the fields of this selection set would result in a -grouped field set with two entries, `"a"` and `"b"`, with two instances of the -field `a` and one of field `b`: +:: A _field set_ is a list of selected fields that share the same _response +name_ (the field alias if defined, otherwise the field's name). + +Note: The order of field selections in a _field set_ is significant, hence the +algorithms in this specification model it as a list. Any later duplicated field +selections in a field set will not impact its interpretation, so using an +ordered set would yield equivalent results. + +As an example, collecting the fields of this query's selection set would result +in a grouped field set with two entries, `"a"` and `"b"`, with two instances of +the field `a` and one of field `b`: ```graphql example { @@ -542,7 +551,12 @@ CollectSubfields(objectType, fields, variableValues): Note: All the {fields} passed to {CollectSubfields()} share the same _response name_. -### Executing a Grouped Field Set +### Executing Collected Fields + +The {CollectFields()} and {CollectSubfields()} algorithms transitively collect +the field selections from a _selection set_ or the associated selection sets of +a _field set_ respectively, and split them into groups by their _response name_ +to produce a _grouped field set_. To execute a _grouped field set_, the object value being evaluated and the object type need to be known, as well as whether it must be executed serially, @@ -552,7 +566,7 @@ or may be executed in parallel (see Each entry in the grouped field set represents a _response name_ which produces an entry into a result map. -ExecuteGroupedFieldSet(groupedFieldSet, objectType, objectValue, +ExecuteCollectedFields(groupedFieldSet, objectType, objectValue, variableValues): - Initialize {resultMap} to an empty ordered map. @@ -577,7 +591,7 @@ section. -If during {ExecuteGroupedFieldSet()} 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 @@ -820,7 +834,7 @@ CompleteValue(fieldType, fields, result, variableValues): - Let {objectType} be {ResolveAbstractType(fieldType, result)}. - Let {groupedFieldSet} be the result of calling {CollectSubfields(objectType, fields, variableValues)}. - - Return the result of evaluating {ExecuteGroupedFieldSet(groupedFieldSet, + - Return the result of evaluating {ExecuteCollectedFields(groupedFieldSet, objectType, result, variableValues)} _normally_ (allowing for parallelization). From d0fb75cf5c1668f10abe752e2d03bfbb784c83e3 Mon Sep 17 00:00:00 2001 From: Lee Byron Date: Mon, 30 Jun 2025 16:42:27 -0700 Subject: [PATCH 14/18] Lee editorial + manual merge #1161 --- spec/Section 3 -- Type System.md | 2 +- spec/Section 5 -- Validation.md | 2 +- spec/Section 6 -- Execution.md | 80 ++++++++++++++++---------------- 3 files changed, 41 insertions(+), 43 deletions(-) diff --git a/spec/Section 3 -- Type System.md b/spec/Section 3 -- Type System.md index cdbfc806e..3762b7804 100644 --- a/spec/Section 3 -- Type System.md +++ b/spec/Section 3 -- Type System.md @@ -757,7 +757,7 @@ type Person { } ``` -Valid operations must supply a selection of fields for any field that returns an +Valid operations must supply a _selection set_ for any field which returns an object, so this operation is not valid: ```graphql counter-example diff --git a/spec/Section 5 -- Validation.md b/spec/Section 5 -- Validation.md index af9d7e33d..4d826f3a5 100644 --- a/spec/Section 5 -- Validation.md +++ b/spec/Section 5 -- Validation.md @@ -468,7 +468,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 {CollectSubfields()}. +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 018aa533c..e90df4eb5 100644 --- a/spec/Section 6 -- Execution.md +++ b/spec/Section 6 -- Execution.md @@ -348,12 +348,11 @@ Unsubscribe(responseStream): ## Executing Selection Sets -The process of executing a GraphQL operation is to recursively execute every -selected field in the operation. To do this, 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 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 @@ -361,8 +360,8 @@ and execute. 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 it must be executed serially, or may -be executed in parallel (see +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), @@ -386,22 +385,23 @@ executionMode): ### Field Collection -Before execution, the _root selection set_ is converted to a _grouped field set_ -by calling {CollectFields()}. This ensures all fields with the same response -name, including those in referenced fragments, are executed at the same time. +Before execution, each _selection set_ is converted to a _grouped field set_ by +calling {CollectFields()}. This ensures all fields with the same response name, +including those in referenced fragments, are executed at the same time. :: A _grouped field set_ is a map where each entry is a _response name_ and its associated _field set_. A _grouped field set_ may be produced from a selection set via {CollectFields()} or from the selection sets of a _field set_ via {CollectSubfields()}. -:: A _field set_ is a list of selected fields that share the same _response -name_ (the field alias if defined, otherwise the field's name). +:: 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 a _field set_ is significant, hence the -algorithms in this specification model it as a list. Any later duplicated field -selections in a field set will not impact its interpretation, so using an -ordered set would yield equivalent results. +algorithms in this specification model it as an ordered set. As an example, collecting the fields of this query's selection set would result in a grouped field set with two entries, `"a"` and `"b"`, with two instances of @@ -430,7 +430,7 @@ 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. +- Initialize {groupedFieldSet} to an empty ordered map of ordered sets. - For each {selection} in {selectionSet}: - If {selection} provides the directive `@skip`, let {skipDirective} be that directive. @@ -445,9 +445,9 @@ 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 {groupForResponseName} be the list in {groupedFields} for - {responseName}; if no such list exists, create it as an empty list. - - Append {selection} to the {groupForResponseName}. + - Let {fieldsForResponseName} be the _field set_ in {groupedFieldSet} for + {responseName}; if no such set exists, create it as an empty set. + - Append {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 @@ -467,9 +467,9 @@ CollectFields(objectType, selectionSet, 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}. + - Let {fieldsForResponseName} be the _field set_ in {groupedFieldSet} for + {responseName}; if no such set exists, create it as an empty set. + - Append all items in {fragmentGroup} to {fieldsForResponseName}. - If {selection} is an {InlineFragment}: - Let {fragmentType} be the type condition on {selection}. - If {fragmentType} is not {null} and {DoesFragmentTypeApply(objectType, @@ -482,10 +482,10 @@ CollectFields(objectType, selectionSet, 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}. + - Let {fieldsForResponseName} be the _field set_ in {groupedFieldSet} for + {responseName}; if no such set exists, create it as an empty set. + - Append all items in {fragmentGroup} to {fieldsForResponseName}. +- Return {groupedFieldSet}. DoesFragmentTypeApply(objectType, fragmentType): @@ -493,10 +493,10 @@ DoesFragmentTypeApply(objectType, fragmentType): - 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} + - 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} + - If {objectType} is a possible type of {fragmentType}, return {true}, otherwise return {false}. Note: The steps in {CollectFields()} evaluating the `@skip` and `@include` @@ -504,10 +504,10 @@ directives may be applied in either order since they apply commutatively. **Merging Selection Sets** -When a field is executed, during value completion the _selection set_ of each of -the related field selections with the same response name are collected together -to produce a single _grouped field set_ in order to continue execution of the -sub-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 _grouped field set_ representing the subfields to +be executed next. An example operation illustrating parallel fields with the same name with sub-selections. @@ -536,16 +536,16 @@ phase with the same value. CollectSubfields(objectType, fields, variableValues): -- Let {groupedFieldSet} be an empty map. +- Let {groupedFieldSet} 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 {fieldGroupedFieldSet} be the result of {CollectFields(objectType, fieldSelectionSet, variableValues)}. - For each {fieldGroupedFieldSet} as {responseName} and {subfields}: - - Let {groupForResponseName} be the list in {groupedFieldSet} for - {responseName}; if no such list exists, create it as an empty list. - - Append all fields in {subfields} to {groupForResponseName}. + - Let {fieldsForResponseName} be the _field set_ in {groupedFieldSet} for + {responseName}; if no such set exists, create it as an empty set. + - Add each fields in {subfields} to {fieldsForResponseName}. - Return {groupedFieldSet}. Note: All the {fields} passed to {CollectSubfields()} share the same _response @@ -558,10 +558,8 @@ the field selections from a _selection set_ or the associated selection sets of a _field set_ respectively, and split them into groups by their _response name_ to produce a _grouped field set_. -To execute a _grouped field 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 (see -[Normal and Serial Execution](#sec-Normal-and-Serial-Execution). +To execute a _grouped field set_, the object type being evaluated and the +runtime value need to be known, as well as the runtime values for any variables. Each entry in the grouped field set represents a _response name_ which produces an entry into a result map. From 9c4a5290429c78f98f96934444953446ca4fda03 Mon Sep 17 00:00:00 2001 From: Lee Byron Date: Mon, 30 Jun 2025 17:09:43 -0700 Subject: [PATCH 15/18] also rename grouped field set -> collected fields map --- spec/Section 5 -- Validation.md | 4 +- spec/Section 6 -- Execution.md | 147 +++++++++++++++++--------------- 2 files changed, 79 insertions(+), 72 deletions(-) diff --git a/spec/Section 5 -- Validation.md b/spec/Section 5 -- Validation.md index 4d826f3a5..953a380fe 100644 --- a/spec/Section 5 -- Validation.md +++ b/spec/Section 5 -- Validation.md @@ -259,9 +259,9 @@ query getName { - For each subscription operation definition {subscription} in the document: - Let {selectionSet} be the top level selection set on {subscription}. - Let {variableValues} be the empty set. - - Let {groupedFieldSet} be the result of {CollectFields(subscriptionType, + - Let {collectedFieldsMap} be the result of {CollectFields(subscriptionType, selectionSet, variableValues)}. - - {groupedFieldSet} must have exactly one entry, which must not be an + - {collectedFieldsMap} must have exactly one entry, which must not be an introspection field. **Explanatory Text** diff --git a/spec/Section 6 -- Execution.md b/spec/Section 6 -- Execution.md index e90df4eb5..4b5315eed 100644 --- a/spec/Section 6 -- Execution.md +++ b/spec/Section 6 -- Execution.md @@ -268,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}. @@ -368,31 +369,32 @@ 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 collected into a _grouped field set_ which is then -executed, returning the resulting {data} and {errors}. +First, the _selection set_ is collected into a _collected fields map_ which is +then executed, returning the resulting {data} and {errors}. 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(groupedFieldSet, - objectType, initialValue, variableValues)} _serially_ if {executionMode} is - {"serial"}, otherwise _normally_ (allowing parallelization)). +- 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 _grouped field set_ by -calling {CollectFields()}. This ensures all fields with the same response name, -including those in referenced fragments, are executed at the same time. +Before execution, each _selection set_ is converted to a _collected fields map_ +by calling {CollectFields()}. This ensures all fields with the same response +name, including those in referenced fragments, are executed at the same time. -:: A _grouped field set_ is a map where each entry is a _response name_ and its -associated _field set_. A _grouped field set_ may be produced from a selection -set via {CollectFields()} or from the selection sets of a _field set_ via -{CollectSubfields()}. +:: 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). @@ -400,12 +402,13 @@ 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 a _field set_ is significant, hence the -algorithms in this specification model it as an ordered set. +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 grouped field set with two entries, `"a"` and `"b"`, with two instances of -the field `a` and one of field `b`: +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 { @@ -423,14 +426,14 @@ fragment ExampleFragment on Query { } ``` -The depth-first-search order of the field groups produced by {CollectFields()} -is maintained through execution, ensuring that fields appear in the executed +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 {groupedFieldSet} to an empty ordered map of ordered sets. +- 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. @@ -445,8 +448,9 @@ 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 {fieldsForResponseName} be the _field set_ in {groupedFieldSet} for - {responseName}; if no such set exists, create it as an empty set. + - Let {fieldsForResponseName} be the _field set_ value in + {collectedFieldsMap} for the key {responseName}; otherwise create it as an + empty ordered set. - Append {selection} to the {fieldsForResponseName}. - If {selection} is a {FragmentSpread}: - Let {fragmentSpreadName} be the name of {selection}. @@ -461,31 +465,31 @@ 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 + - Let {fragmentCollectedFieldMap} 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 {fieldsForResponseName} be the _field set_ in {groupedFieldSet} for - {responseName}; if no such set exists, create it as an empty set. - - Append all items in {fragmentGroup} to {fieldsForResponseName}. + - For each {fragmentCollectedFieldMap} as {responseName} and + {fragmentFields}: + - Let {fieldsForResponseName} be the _field set_ value in + {collectedFieldsMap} for the key {responseName}; otherwise create it as + an empty ordered set. + - Add each items 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 {fragmentGroupedFieldSet} be the result of calling + - Let {fragmentCollectedFieldMap} 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 {fieldsForResponseName} be the _field set_ in {groupedFieldSet} for - {responseName}; if no such set exists, create it as an empty set. - - Append all items in {fragmentGroup} to {fieldsForResponseName}. -- Return {groupedFieldSet}. + - For each {fragmentCollectedFieldMap} as {responseName} and + {fragmentFields}: + - Let {fieldsForResponseName} be the _field set_ value in + {collectedFieldsMap} for the key {responseName}; otherwise create it as + an empty ordered set. + - Append all items in {fragmentFields} to {fieldsForResponseName}. +- Return {collectedFieldsMap}. DoesFragmentTypeApply(objectType, fragmentType): @@ -506,8 +510,8 @@ directives may be applied in either order since they apply commutatively. 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 _grouped field set_ representing the subfields to -be executed next. +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. @@ -536,17 +540,18 @@ phase with the same value. CollectSubfields(objectType, fields, variableValues): -- Let {groupedFieldSet} be an empty ordered map of ordered sets. +- 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 {fieldGroupedFieldSet} be the result of {CollectFields(objectType, + - Let {fieldCollectedFieldMap} be the result of {CollectFields(objectType, fieldSelectionSet, variableValues)}. - - For each {fieldGroupedFieldSet} as {responseName} and {subfields}: - - Let {fieldsForResponseName} be the _field set_ in {groupedFieldSet} for - {responseName}; if no such set exists, create it as an empty set. - - Add each fields in {subfields} to {fieldsForResponseName}. -- Return {groupedFieldSet}. + - For each {fieldCollectedFieldMap} as {responseName} and {subfields}: + - Let {fieldsForResponseName} be the _field set_ value in + {collectedFieldsMap} for the key {responseName}; otherwise create it as 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_. @@ -555,20 +560,20 @@ name_. The {CollectFields()} and {CollectSubfields()} algorithms transitively collect the field selections from a _selection set_ or the associated selection sets of -a _field set_ respectively, and split them into groups by their _response name_ -to produce a _grouped field set_. +a _field set_ respectively, and split them into sets by their _response name_ to +produce a _collected fields map_. -To execute a _grouped field set_, the object type being evaluated and the +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. -Each entry in the grouped field set represents a _response name_ which produces -an entry into a result map. +Each entry in the collected fields map represents a _response name_ which +produces an entry into a result map. -ExecuteCollectedFields(groupedFieldSet, objectType, objectValue, +ExecuteCollectedFields(collectedFieldsMap, objectType, objectValue, variableValues): - Initialize {resultMap} to an empty ordered map. -- For each {groupedFieldSet} as {responseName} and {fields}: +- For each {collectedFieldsMap} as {responseName} 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 @@ -603,13 +608,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 { @@ -629,10 +635,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: @@ -701,7 +708,7 @@ A correct executor must generate the following result for that _selection set_: ## Executing Fields -Each field requested in the grouped field set that is defined on the selected +Each field from the _collected fields map_ 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 @@ -830,9 +837,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 {groupedFieldSet} be the result of calling {CollectSubfields(objectType, - fields, variableValues)}. - - Return the result of evaluating {ExecuteCollectedFields(groupedFieldSet, + - 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). From 97d43ba7e36867a7ea5330c275840fa2ba672aca Mon Sep 17 00:00:00 2001 From: Lee Byron Date: Mon, 30 Jun 2025 18:16:58 -0700 Subject: [PATCH 16/18] include changes after merging master --- spec/Section 3 -- Type System.md | 4 ++-- spec/Section 5 -- Validation.md | 41 ++++++++++++++++---------------- spec/Section 6 -- Execution.md | 22 ++++++++--------- 3 files changed, 34 insertions(+), 33 deletions(-) diff --git a/spec/Section 3 -- Type System.md b/spec/Section 3 -- Type System.md index 1e5f03832..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 _selection set_ for any field which 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 1a55cb23a..9d915ddba 100644 --- a/spec/Section 5 -- Validation.md +++ b/spec/Section 5 -- Validation.md @@ -311,16 +311,17 @@ query getName { 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 {fragmentCollectedFieldMap} as {responseName} and + {fragmentFields}: + - 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 {fragmentCollectedFieldMap} as {responseName} and + {fragmentFields}: + - 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` diff --git a/spec/Section 6 -- Execution.md b/spec/Section 6 -- Execution.md index 8e1c0597e..2e4eb3f7b 100644 --- a/spec/Section 6 -- Execution.md +++ b/spec/Section 6 -- Execution.md @@ -449,9 +449,9 @@ CollectFields(objectType, selectionSet, variableValues, visitedFragments): - 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 it as an - empty ordered set. - - Append {selection} to the {fieldsForResponseName}. + {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 @@ -471,9 +471,9 @@ CollectFields(objectType, selectionSet, variableValues, visitedFragments): - For each {fragmentCollectedFieldMap} as {responseName} and {fragmentFields}: - Let {fieldsForResponseName} be the _field set_ value in - {collectedFieldsMap} for the key {responseName}; otherwise create it as - an empty ordered set. - - Add each items from {fragmentFields} to {fieldsForResponseName}. + {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, @@ -486,9 +486,9 @@ CollectFields(objectType, selectionSet, variableValues, visitedFragments): - For each {fragmentCollectedFieldMap} as {responseName} and {fragmentFields}: - Let {fieldsForResponseName} be the _field set_ value in - {collectedFieldsMap} for the key {responseName}; otherwise create it as - an empty ordered set. - - Append all items in {fragmentFields} to {fieldsForResponseName}. + {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): @@ -535,8 +535,8 @@ fragment ExampleFragment on Query { ``` After resolving the value for field `"a"`, the following multiple selection sets -are merged together so `"subfield1"` and `"subfield2"` are resolved in the same -phase with the same value. +are collected and merged together so `"subfield1"` and `"subfield2"` are +resolved in the same phase with the same value. CollectSubfields(objectType, fields, variableValues): From de56b6873eff19e228398f010e399043f9a7080e Mon Sep 17 00:00:00 2001 From: Lee Byron Date: Tue, 1 Jul 2025 09:19:42 -0700 Subject: [PATCH 17/18] fixup map iterations --- spec/Section 5 -- Validation.md | 8 ++++---- spec/Section 6 -- Execution.md | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/spec/Section 5 -- Validation.md b/spec/Section 5 -- Validation.md index 9d915ddba..e3085a85f 100644 --- a/spec/Section 5 -- Validation.md +++ b/spec/Section 5 -- Validation.md @@ -338,8 +338,8 @@ CollectSubscriptionFields(objectType, selectionSet, visitedFragments): - Let {fragmentCollectedFieldMap} be the result of calling {CollectSubscriptionFields(objectType, fragmentSelectionSet, visitedFragments)}. - - For each {fragmentCollectedFieldMap} as {responseName} and - {fragmentFields}: + - 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. @@ -353,8 +353,8 @@ CollectSubscriptionFields(objectType, selectionSet, visitedFragments): - Let {fragmentCollectedFieldMap} be the result of calling {CollectSubscriptionFields(objectType, fragmentSelectionSet, visitedFragments)}. - - For each {fragmentCollectedFieldMap} as {responseName} and - {fragmentFields}: + - 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. diff --git a/spec/Section 6 -- Execution.md b/spec/Section 6 -- Execution.md index 2e4eb3f7b..ab006c96f 100644 --- a/spec/Section 6 -- Execution.md +++ b/spec/Section 6 -- Execution.md @@ -468,8 +468,8 @@ CollectFields(objectType, selectionSet, variableValues, visitedFragments): - Let {fragmentCollectedFieldMap} be the result of calling {CollectFields(objectType, fragmentSelectionSet, variableValues, visitedFragments)}. - - For each {fragmentCollectedFieldMap} as {responseName} and - {fragmentFields}: + - 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. @@ -483,8 +483,8 @@ CollectFields(objectType, selectionSet, variableValues, visitedFragments): - Let {fragmentCollectedFieldMap} be the result of calling {CollectFields(objectType, fragmentSelectionSet, variableValues, visitedFragments)}. - - For each {fragmentCollectedFieldMap} as {responseName} and - {fragmentFields}: + - 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. @@ -546,10 +546,10 @@ CollectSubfields(objectType, fields, variableValues): - If {fieldSelectionSet} is null or empty, continue to the next field. - Let {fieldCollectedFieldMap} be the result of {CollectFields(objectType, fieldSelectionSet, variableValues)}. - - For each {fieldCollectedFieldMap} as {responseName} and {subfields}: + - For each {responseName} and {subfields} in {fieldCollectedFieldMap}: - Let {fieldsForResponseName} be the _field set_ value in - {collectedFieldsMap} for the key {responseName}; otherwise create it as an - empty ordered set. + {collectedFieldsMap} for the key {responseName}; otherwise create the + entry with an empty ordered set. - Add each fields from {subfields} to {fieldsForResponseName}. - Return {collectedFieldsMap}. @@ -573,7 +573,7 @@ ExecuteCollectedFields(collectedFieldsMap, objectType, objectValue, variableValues): - Initialize {resultMap} to an empty ordered map. -- For each {collectedFieldsMap} 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 From cc17846e44b4fe6339511c44b524abc2ebe7a02a Mon Sep 17 00:00:00 2001 From: Lee Byron Date: Tue, 1 Jul 2025 09:46:48 -0700 Subject: [PATCH 18/18] editorial --- spec/Section 6 -- Execution.md | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/spec/Section 6 -- Execution.md b/spec/Section 6 -- Execution.md index ab006c96f..142193b8d 100644 --- a/spec/Section 6 -- Execution.md +++ b/spec/Section 6 -- Execution.md @@ -388,8 +388,10 @@ executionMode): ### Field Collection Before execution, each _selection set_ is converted to a _collected fields map_ -by calling {CollectFields()}. This ensures all fields with the same response -name, including those in referenced fragments, are executed at the same time. +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 @@ -558,16 +560,12 @@ name_. ### Executing Collected Fields -The {CollectFields()} and {CollectSubfields()} algorithms transitively collect -the field selections from a _selection set_ or the associated selection sets of -a _field set_ respectively, and split them into sets by their _response name_ to -produce a _collected fields map_. - 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. -Each entry in the collected fields map represents a _response name_ which -produces an entry into a result map. +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): @@ -608,7 +606,7 @@ about this behavior. ### Normal and Serial Execution -Normally the executor can execute the entries in a collected fields map in +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 @@ -708,11 +706,11 @@ A correct executor must generate the following result for that _selection set_: ## Executing Fields -Each field from the _collected fields map_ 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): @@ -812,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):