From b76671bcd1b34af545138f9eaca6e0acc8681c04 Mon Sep 17 00:00:00 2001 From: Benjie Gillam Date: Fri, 28 Apr 2023 16:58:49 +0100 Subject: [PATCH 01/13] 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/13] 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/13] 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/13] 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/13] 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/13] 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/13] 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/13] 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/13] 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/13] 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/13] 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/13] 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/13] 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).