From d7eaf72e53fc1494154f64fc03920d18b93fab36 Mon Sep 17 00:00:00 2001 From: Benjie Gillam Date: Mon, 18 Oct 2021 12:15:36 +0100 Subject: [PATCH 01/31] Clarify 'before execution begins' in response --- spec/Section 7 -- Response.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/spec/Section 7 -- Response.md b/spec/Section 7 -- Response.md index 071eb1058..14bbcff01 100644 --- a/spec/Section 7 -- Response.md +++ b/spec/Section 7 -- Response.md @@ -47,6 +47,10 @@ not be present in the result. If an error was raised during the execution that prevented a valid response, the `data` entry in the response should be `null`. +Note: Request errors (including those raised during {ExecuteRequest()}) occur +before execution begins; when a request error is raised the `data` entry should +not be present in the result. + ### Errors From b76671bcd1b34af545138f9eaca6e0acc8681c04 Mon Sep 17 00:00:00 2001 From: Benjie Gillam Date: Fri, 28 Apr 2023 16:58:49 +0100 Subject: [PATCH 02/31] 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 03/31] 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 04/31] 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 05/31] 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 06/31] 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 07/31] 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 08/31] 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 09/31] 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 10/31] 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 11/31] 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 5c2ad6a002ab02be5f66ccf204872e301d9abeb8 Mon Sep 17 00:00:00 2001 From: Martin Bonnin Date: Thu, 27 Mar 2025 18:40:22 +0100 Subject: [PATCH 12/31] Distinguish between Processing and Execution --- spec/Section 6 -- Execution.md | 81 ++++++++++++++++++---------------- 1 file changed, 44 insertions(+), 37 deletions(-) diff --git a/spec/Section 6 -- Execution.md b/spec/Section 6 -- Execution.md index b5ed9612f..f9d9444bb 100644 --- a/spec/Section 6 -- Execution.md +++ b/spec/Section 6 -- Execution.md @@ -8,7 +8,7 @@ A GraphQL service generates a response from a request via execution. - {document}: A {Document} which must contain GraphQL {OperationDefinition} and may contain {FragmentDefinition}. - {operationName} (optional): The name of the Operation in the Document to - execute. + process. - {variableValues} (optional): Values for any Variables defined by the Operation. - {initialValue} (optional): An initial value corresponding to the root type @@ -16,35 +16,41 @@ A GraphQL service generates a response from a request via execution. data available via a GraphQL Service. It is common for a GraphQL Service to always use the same initial value for every request. -Given this information, the result of {ExecuteRequest(schema, document, +Given this information, the result of {ProcessRequest(schema, document, operationName, variableValues, initialValue)} produces the response, to be formatted according to the Response section below. +:: Formally, _execution_ starts when executing the root selection set in +{ExecuteRootSelectionSet()}. For convenience, this section also contains +preliminary steps required for execution such as coercing variables or getting a +source event stream. + Note: GraphQL requests do not require any specific serialization format or transport mechanism. Message serialization and transport mechanisms should be chosen by the implementing service. -## Executing Requests +## Processing Requests -To execute a request, the executor must have a parsed {Document} and a selected +To process a request, the executor must have a parsed {Document} and a selected operation name to run if the document defines multiple operations, otherwise the document is expected to only contain a single operation. The result of the -request is determined by the result of executing this operation according to the -"Executing Operations” section below. +request is determined by the result of processing this operation according to +the "Processing Operations” section below. -ExecuteRequest(schema, document, operationName, variableValues, initialValue): +ProcessRequest(schema, document, operationName, variableValues, initialValue): - Let {operation} be the result of {GetOperation(document, operationName)}. - Let {coercedVariableValues} be the result of {CoerceVariableValues(schema, operation, variableValues)}. - If {operation} is a query operation: - - Return {ExecuteQuery(operation, schema, coercedVariableValues, + - Return {ProcessQuery(operation, schema, coercedVariableValues, initialValue)}. - Otherwise if {operation} is a mutation operation: - - Return {ExecuteMutation(operation, schema, coercedVariableValues, + - Return {ProcessMutation(operation, schema, coercedVariableValues, initialValue)}. - Otherwise if {operation} is a subscription operation: - - Return {Subscribe(operation, schema, coercedVariableValues, initialValue)}. + - Return {ProcessSubscription(operation, schema, coercedVariableValues, + initialValue)}. GetOperation(document, operationName): @@ -60,14 +66,14 @@ GetOperation(document, operationName): ### Validating Requests As explained in the Validation section, only requests which pass all validation -rules should be executed. If validation errors are known, they should be -reported in the list of "errors" in the response and the request must fail -without execution. +rules should be process. If validation errors are known, they should be reported +in the list of "errors" in the response and the request must fail without +processing. Typically validation is performed in the context of a request immediately before -execution, however a GraphQL service may execute a request without immediately +processing, however a GraphQL service may process a request without immediately validating it if that exact same request is known to have been validated before. -A GraphQL service should only execute requests which _at some point_ were known +A GraphQL service should only process requests which _at some point_ were known to be free of any validation errors, and have since not changed. For example: the request may be validated during development, provided it does @@ -114,7 +120,7 @@ CoerceVariableValues(schema, operation, variableValues): Note: This algorithm is very similar to {CoerceArgumentValues()}. -## Executing Operations +## Processing Operations The type system, as described in the "Type System" section of the spec, must provide a query root operation type. If mutations or subscriptions are @@ -124,12 +130,12 @@ 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. +An initial value may be provided when processing a query operation. -ExecuteQuery(query, schema, variableValues, initialValue): +ProcessQuery(query, schema, variableValues, initialValue): - Let {queryType} be the root Query type in {schema}. - Assert: {queryType} is an Object type. @@ -140,14 +146,14 @@ ExecuteQuery(query, schema, variableValues, initialValue): ### 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 mutations ensures against race conditions during these side-effects. -ExecuteMutation(mutation, schema, variableValues, initialValue): +ProcessMutation(mutation, schema, variableValues, initialValue): - Let {mutationType} be the root Mutation type in {schema}. - Assert: {mutationType} is an Object type. @@ -159,12 +165,13 @@ ExecuteMutation(mutation, schema, variableValues, initialValue): If the operation is a subscription, the result is an _event stream_ called the _response stream_ where each event in the event stream is the result of -executing the operation for each new event on an underlying _source stream_. +executing the operation ’s root _selection set_ for each new event on an +underlying _source stream_. -Executing a subscription operation creates a persistent function on the service +Processing a subscription operation creates a persistent function on the service that maps an underlying _source stream_ to a returned _response stream_. -Subscribe(subscription, schema, variableValues, initialValue): +ProcessSubscription(subscription, schema, variableValues, initialValue): - Let {sourceStream} be the result of running {CreateSourceEventStream(subscription, schema, variableValues, initialValue)}. @@ -173,8 +180,8 @@ Subscribe(subscription, schema, variableValues, initialValue): variableValues)}. - Return {responseStream}. -Note: In a large-scale subscription system, the {Subscribe()} and -{ExecuteSubscriptionEvent()} algorithms may be run on separate services to +Note: In a large-scale subscription system, the {ProcessSubscription()} and +{ProcessSubscriptionEvent()} algorithms may be run on separate services to maintain predictable scaling properties. See the section below on Supporting Subscriptions at Scale. @@ -295,7 +302,7 @@ MapSourceToResponseEvent(sourceStream, subscription, schema, variableValues): - Let {responseStream} be a new _event stream_. - When {sourceStream} emits {sourceValue}: - Let {response} be the result of running - {ExecuteSubscriptionEvent(subscription, schema, variableValues, + {ProcessSubscriptionEvent(subscription, schema, variableValues, sourceValue)}. - If internal {error} was raised: - Cancel {sourceStream}. @@ -310,12 +317,12 @@ MapSourceToResponseEvent(sourceStream, subscription, schema, variableValues): - Complete {responseStream} normally. - Return {responseStream}. -Note: Since {ExecuteSubscriptionEvent()} handles all _field error_, and _request +Note: Since {ProcessSubscriptionEvent()} handles all _field error_, and _request error_ only occur during {CreateSourceEventStream()}, the only remaining error -condition handled from {ExecuteSubscriptionEvent()} are internal exceptional +condition handled from {ProcessSubscriptionEvent()} are internal exceptional errors not described by this specification. -ExecuteSubscriptionEvent(subscription, schema, variableValues, initialValue): +ProcessSubscriptionEvent(subscription, schema, variableValues, initialValue): - Let {subscriptionType} be the root Subscription type in {schema}. - Assert: {subscriptionType} is an Object type. @@ -323,8 +330,8 @@ ExecuteSubscriptionEvent(subscription, schema, variableValues, initialValue): - Return {ExecuteRootSelectionSet(variableValues, initialValue, subscriptionType, selectionSet)}. -Note: The {ExecuteSubscriptionEvent()} algorithm is intentionally similar to -{ExecuteQuery()} since this is how each event result is produced. +Note: The {ProcessSubscriptionEvent()} algorithm is intentionally similar to +{ProcessQuery()} since this is how each event result is produced. #### Unsubscribe @@ -534,7 +541,7 @@ A valid GraphQL executor can resolve the four fields in whatever order it chose (however of course `birthday` must be resolved before `month`, and `address` before `street`). -When executing a mutation, the selections in the top most selection set will be +When processing 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 @@ -679,8 +686,8 @@ CoerceArgumentValues(objectType, field, variableValues): - Return {coercedValues}. Note: Variable values are not coerced because they are expected to be coerced -before executing the operation in {CoerceVariableValues()}, and valid operations -must only allow usage of variables of appropriate types. +before processing the operation in {CoerceVariableValues()}, and valid +operations must only allow usage of variables of appropriate types. ### Value Resolution From 6150becb900727f7971ec007a7309627b7402018 Mon Sep 17 00:00:00 2001 From: Benjie Gillam Date: Thu, 3 Apr 2025 11:03:36 +0100 Subject: [PATCH 13/31] Define 'execution' --- spec/Section 3 -- Type System.md | 2 +- spec/Section 4 -- Introspection.md | 2 +- spec/Section 6 -- Execution.md | 14 ++++++++++--- spec/Section 7 -- Response.md | 32 +++++++++++++++--------------- 4 files changed, 29 insertions(+), 21 deletions(-) diff --git a/spec/Section 3 -- Type System.md b/spec/Section 3 -- Type System.md index 945c916f6..ba9e3b956 100644 --- a/spec/Section 3 -- Type System.md +++ b/spec/Section 3 -- Type System.md @@ -487,7 +487,7 @@ information on the serialization of scalars in common JSON and other formats. If a GraphQL service expects a scalar type as input to an argument, coercion is observable and the rules must be well defined. If an input value does not match a coercion rule, a _request error_ must be raised (input values are validated -before execution begins). +before _execution_ begins). GraphQL has different constant literals to represent integer and floating-point input values, and coercion rules may apply differently depending on which type diff --git a/spec/Section 4 -- Introspection.md b/spec/Section 4 -- Introspection.md index b260559cb..93b37445e 100644 --- a/spec/Section 4 -- Introspection.md +++ b/spec/Section 4 -- Introspection.md @@ -72,7 +72,7 @@ GraphQL supports type name introspection within any _selection set_ in an operation, with the single exception of selections at the root of a subscription operation. Type name introspection is accomplished via the meta-field `__typename: String!` on any Object, Interface, or Union. It returns the name of -the concrete Object type at that point during execution. +the concrete Object type at that point during _execution_. This is most often used when querying against Interface or Union types to identify which actual Object type of the possible types has been returned. diff --git a/spec/Section 6 -- Execution.md b/spec/Section 6 -- Execution.md index 187c75a39..e649df645 100644 --- a/spec/Section 6 -- Execution.md +++ b/spec/Section 6 -- Execution.md @@ -32,6 +32,14 @@ document is expected to only contain a single operation. The result of the request is determined by the result of executing this operation according to the "Executing Operations” section below. +:: We define _execution_ as the process of executing the operation's root +selection set through {ExecuteSelectionSet()}, thus _execution_ begins when +{ExecuteSelectionSet()} is called for the first time in a request. The +{ExecuteRequest()} algorithm is a preamble for _execution_. + +Note: An error raised before _execution_ begins will typically be a _request +error_, and once _execution_ begins will typically be a _field error_. + ExecuteRequest(schema, document, operationName, variableValues, initialValue): - Let {operation} be the result of {GetOperation(document, operationName)}. @@ -62,10 +70,10 @@ GetOperation(document, operationName): As explained in the Validation section, only requests which pass all validation rules should be executed. If validation errors are known, they should be reported in the list of "errors" in the response and the request must fail -without execution. +without _execution_. Typically validation is performed in the context of a request immediately before -execution, however a GraphQL service may execute a request without immediately +_execution_, however a GraphQL service may execute a request without immediately validating it if that exact same request is known to have been validated before. A GraphQL service should only execute requests which _at some point_ were known to be free of any validation errors, and have since not changed. @@ -79,7 +87,7 @@ result to avoid validating the same request again in the future. If the operation has defined any variables, then the values for those variables need to be coerced using the input coercion rules of variable's declared type. If a _request error_ is encountered during input coercion of variable values, -then the operation fails without execution. +then the operation fails without _execution_. CoerceVariableValues(schema, operation, variableValues): diff --git a/spec/Section 7 -- Response.md b/spec/Section 7 -- Response.md index 81fcf4f05..b55524fc2 100644 --- a/spec/Section 7 -- Response.md +++ b/spec/Section 7 -- Response.md @@ -22,10 +22,10 @@ key `errors`. The value of this entry is described in the "Errors" section. If the request completed without raising any errors, this entry must not be present. -If the request included execution, the response map must contain an entry with +If the request included _execution_, the response map must contain an entry with key `data`. The value of this entry is described in the "Data" section. If the -request failed before execution, due to a syntax error, missing information, or -validation error, this entry must not be present. +request failed before _execution_, due to a syntax error, missing information, +or validation error, this entry must not be present. The response map may also contain an entry with key `extensions`. This entry, if set, must have a map as its value. This entry is reserved for implementers to @@ -47,20 +47,20 @@ subscription. A _response stream_ must be a stream of _response_. ### Data -The `data` entry in the response will be the result of the execution of the +The `data` entry in the response will be the result of the _execution_ of the requested operation. If the operation was a query, this output will be an object of the query root operation type; if the operation was a mutation, this output will be an object of the mutation root operation type. -If an error was raised before execution begins, the `data` entry should not be +If an error was raised before _execution_ begins, the `data` entry should not be present in the response. -If an error was raised during the execution that prevented a valid response, the -`data` entry in the response should be `null`. +If an error was raised during the _execution_ that prevented a valid response, +the `data` entry in the response should be `null`. Note: Request errors (including those raised during {ExecuteRequest()}) occur -before execution begins; when a request error is raised the `data` entry should -not be present in the result. +before _execution_ begins; when a request error is raised the `data` entry +should not be present in the result. ### Errors @@ -78,12 +78,12 @@ able to be returned. If the `data` entry in the response is present (including if it is the value {null}), the `errors` entry must be present if and only if one or more _field -error_ was raised during execution. +error_ was raised during _execution_. **Request Errors** :: A _request error_ is an error raised during a _request_ which results in no -response data. Typically raised before execution begins, a request error may +response data. Typically raised before _execution_ begins, a request error may occur due to a parse grammar or validation error in the _Document_, an inability to determine which operation to execute, or invalid input values for variables. @@ -101,10 +101,10 @@ during value resolution or failure to coerce the resulting value. A field error is typically the fault of a GraphQL service. -If a field error is raised, execution attempts to continue and a partial result -is produced (see [Handling Field Errors](#sec-Handling-Field-Errors)). The -`data` entry in the response must be present. The `errors` entry should include -this error. +If a field error is raised, _execution_ attempts to continue and a partial +result is produced (see [Handling Field Errors](#sec-Handling-Field-Errors)). +The `data` entry in the response must be present. The `errors` entry should +include this error. **Error Result Format** @@ -255,7 +255,7 @@ discouraged. ### Path :: A _path entry_ is an entry within an _error result_ that allows for -association with a particular field reached during GraphQL execution. +association with a particular field reached during GraphQL _execution_. The value for a _path entry_ must be a list of path segments starting at the root of the response and ending with the field to be associated with. Path From d60481a16837c6603f1ea15b98619bf3643897c4 Mon Sep 17 00:00:00 2001 From: Benjie Gillam Date: Thu, 3 Apr 2025 11:24:44 +0100 Subject: [PATCH 14/31] Correction --- spec/Section 6 -- Execution.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spec/Section 6 -- Execution.md b/spec/Section 6 -- Execution.md index e649df645..f924eb236 100644 --- a/spec/Section 6 -- Execution.md +++ b/spec/Section 6 -- Execution.md @@ -35,7 +35,8 @@ request is determined by the result of executing this operation according to the :: We define _execution_ as the process of executing the operation's root selection set through {ExecuteSelectionSet()}, thus _execution_ begins when {ExecuteSelectionSet()} is called for the first time in a request. The -{ExecuteRequest()} algorithm is a preamble for _execution_. +{ExecuteRequest()} algorithm contains the preamble for _execution_, followed by +the _execution_ itself. Note: An error raised before _execution_ begins will typically be a _request error_, and once _execution_ begins will typically be a _field error_. From aa55df7e070c8b179fc936d2989a7e44a33ab8cc Mon Sep 17 00:00:00 2001 From: Martin Bonnin Date: Thu, 3 Apr 2025 13:46:16 +0200 Subject: [PATCH 15/31] Update spec/Section 6 -- Execution.md Co-authored-by: Benoit 'BoD' Lubek --- spec/Section 6 -- Execution.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/Section 6 -- Execution.md b/spec/Section 6 -- Execution.md index f9d9444bb..897845053 100644 --- a/spec/Section 6 -- Execution.md +++ b/spec/Section 6 -- Execution.md @@ -66,7 +66,7 @@ GetOperation(document, operationName): ### Validating Requests As explained in the Validation section, only requests which pass all validation -rules should be process. If validation errors are known, they should be reported +rules should be processed. If validation errors are known, they should be reported in the list of "errors" in the response and the request must fail without processing. From 94ad1764e97df4f675f659b5905e0c5f706442f5 Mon Sep 17 00:00:00 2001 From: Martin Bonnin Date: Thu, 3 Apr 2025 13:46:21 +0200 Subject: [PATCH 16/31] Update spec/Section 6 -- Execution.md Co-authored-by: Benoit 'BoD' Lubek --- spec/Section 6 -- Execution.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/Section 6 -- Execution.md b/spec/Section 6 -- Execution.md index 897845053..2ac3c6775 100644 --- a/spec/Section 6 -- Execution.md +++ b/spec/Section 6 -- Execution.md @@ -165,7 +165,7 @@ ProcessMutation(mutation, schema, variableValues, initialValue): If the operation is a subscription, the result is an _event stream_ called the _response stream_ where each event in the event stream is the result of -executing the operation ’s root _selection set_ for each new event on an +executing the operation’s root _selection set_ for each new event on an underlying _source stream_. Processing a subscription operation creates a persistent function on the service From 15a6c1102e380fb9b274ad65ee7dec184e421578 Mon Sep 17 00:00:00 2001 From: Martin Bonnin Date: Thu, 3 Apr 2025 18:44:00 +0200 Subject: [PATCH 17/31] format --- 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 2ac3c6775..3be09b694 100644 --- a/spec/Section 6 -- Execution.md +++ b/spec/Section 6 -- Execution.md @@ -66,9 +66,9 @@ GetOperation(document, operationName): ### Validating Requests As explained in the Validation section, only requests which pass all validation -rules should be processed. If validation errors are known, they should be reported -in the list of "errors" in the response and the request must fail without -processing. +rules should be processed. If validation errors are known, they should be +reported in the list of "errors" in the response and the request must fail +without processing. Typically validation is performed in the context of a request immediately before processing, however a GraphQL service may process a request without immediately From 41450b7dcd5d058a5e4339d95b9a403f3459df59 Mon Sep 17 00:00:00 2001 From: Martin Bonnin Date: Thu, 17 Apr 2025 11:47:43 +0200 Subject: [PATCH 18/31] keep execute here --- spec/Section 6 -- Execution.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/Section 6 -- Execution.md b/spec/Section 6 -- Execution.md index 3be09b694..9a9a381e9 100644 --- a/spec/Section 6 -- Execution.md +++ b/spec/Section 6 -- Execution.md @@ -8,7 +8,7 @@ A GraphQL service generates a response from a request via execution. - {document}: A {Document} which must contain GraphQL {OperationDefinition} and may contain {FragmentDefinition}. - {operationName} (optional): The name of the Operation in the Document to - process. + execute. - {variableValues} (optional): Values for any Variables defined by the Operation. - {initialValue} (optional): An initial value corresponding to the root type From d68df95e56eef06285bb2da297e10eea3fb6f1b3 Mon Sep 17 00:00:00 2001 From: Lee Byron Date: Thu, 17 Apr 2025 06:48:34 -1000 Subject: [PATCH 19/31] 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 20/31] 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 21/31] 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 11f33ba5f535a2f45f7198a1c1b51bdc60e4776c Mon Sep 17 00:00:00 2001 From: Benjie Gillam Date: Thu, 1 May 2025 12:36:06 +0100 Subject: [PATCH 22/31] Editorial and eliding --- spec/Section 6 -- Execution.md | 124 ++++++++++++++++++--------------- 1 file changed, 66 insertions(+), 58 deletions(-) diff --git a/spec/Section 6 -- Execution.md b/spec/Section 6 -- Execution.md index 9fce38166..a8cf473b0 100644 --- a/spec/Section 6 -- Execution.md +++ b/spec/Section 6 -- Execution.md @@ -18,14 +18,9 @@ A GraphQL service generates a response from a request via execution. - {extensions} (optional): A map reserved for implementation-specific additional information. -Given this information, the result of {ProcessRequest(schema, document, -operationName, variableValues, initialValue)} produces the response, to be -formatted according to the Response section below. - -:: Formally, _execution_ starts when executing the root selection set in -{ExecuteRootSelectionSet()}. For convenience, this section also contains -preliminary steps required for execution such as coercing variables or getting a -source event stream. +Given this information, the result of {Request(schema, document, operationName, +variableValues, initialValue)} produces the response, to be formatted according +to the Response section below. Implementations should not add additional properties to a _request_, which may conflict with future editions of the GraphQL specification. Instead, @@ -40,34 +35,43 @@ chosen by the implementing service. ## Processing Requests + + + + To process a request, the executor must have a parsed {Document} and a selected operation name to run if the document defines multiple operations, otherwise the document is expected to only contain a single operation. The result of the -request is determined by the result of processing this operation according to -the "Processing Operations” section below. +request is determined by the result of performing this operation according to +the "Performing Operations” section below. + +:: We define _execution_ as the process of executing the operation's _root +selection set_ through {ExecuteRootSelectionSet()}, and hence _execution_ begins +when {ExecuteSelectionSet()} is called for the first time in a request. + +Note: A subscription operation may call {ExecuteSelectionSet()} a number of +times. -:: We define _execution_ as the process of executing the operation's root -selection set through {ExecuteSelectionSet()}, thus _execution_ begins when -{ExecuteSelectionSet()} is called for the first time in a request. The -{ExecuteRequest()} algorithm contains the preamble for _execution_, followed by -the _execution_ itself. +The {Request()} algorithm contains the preamble for execution, handling concerns +such as determining the operation and coercing the inputs, before passing the +request on to the relevant algorithm for the operation's type which then +performs any other necessary preliminary steps (for example establishing the +source event stream for subscription operations) and then initiates _execution_. Note: An error raised before _execution_ begins will typically be a _request -error_, and once _execution_ begins will typically be a _field error_. +error_, and once _execution_ begins will typically be an _execution error_. -ProcessRequest(schema, document, operationName, variableValues, initialValue): +Request(schema, document, operationName, variableValues, initialValue): - Let {operation} be the result of {GetOperation(document, operationName)}. - Let {coercedVariableValues} be the result of {CoerceVariableValues(schema, operation, variableValues)}. - If {operation} is a query operation: - - Return {ProcessQuery(operation, schema, coercedVariableValues, - initialValue)}. + - Return {Query(operation, schema, coercedVariableValues, initialValue)}. - Otherwise if {operation} is a mutation operation: - - Return {ProcessMutation(operation, schema, coercedVariableValues, - initialValue)}. + - Return {Mutation(operation, schema, coercedVariableValues, initialValue)}. - Otherwise if {operation} is a subscription operation: - - Return {ProcessSubscription(operation, schema, coercedVariableValues, + - Return {Subscription(operation, schema, coercedVariableValues, initialValue)}. GetOperation(document, operationName): @@ -83,27 +87,28 @@ GetOperation(document, operationName): ### Validating Requests -As explained in the Validation section, only requests which pass all validation -rules should be processed. If validation errors are known, they should be -reported in the list of "errors" in the response and the request must fail -without processing. +As explained in the Validation section, only operations from documents which +pass all validation rules should be executed. If validation errors are known, +they should be reported in the list of "errors" in the response and the request +must fail without execution. Typically validation is performed in the context of a request immediately before -processing, however a GraphQL service may execute a process without immediately -validating it if that exact same request is known to have been validated before. -A GraphQL service should only process requests which _at some point_ were known -to be free of any validation errors, and have since not changed. +processing, however a GraphQL service may process a request without immediately +validating the document if that exact same document is known to have been +validated before. A GraphQL service should only execute operations which _at +some point_ were known to be free of any validation errors, and have since not +changed. -For example: the request may be validated during development, provided it does -not later change, or a service may validate a request once and memoize the -result to avoid validating the same request again in the future. +For example: the document may be validated during development, provided it does +not later change, or a service may validate a document once and memoize the +result to avoid validating the same document again in the future. ### Coercing Variable Values If the operation has defined any variables, then the values for those variables need to be coerced using the input coercion rules of variable's declared type. If a _request error_ is encountered during input coercion of variable values, -then the operation fails without _execution_. +then the request fails without _execution_. CoerceVariableValues(schema, operation, variableValues): @@ -138,7 +143,11 @@ CoerceVariableValues(schema, operation, variableValues): Note: This algorithm is very similar to {CoerceArgumentValues()}. -## Processing Operations +## Performing Operations + + + + The type system, as described in the "Type System" section of the spec, must provide a query root operation type. If mutations or subscriptions are @@ -151,9 +160,9 @@ If the operation is a query, the result of the operation is the result of executing the operation’s _root selection set_ with the query root operation type. -An initial value may be provided when processing a query operation. +An initial value may be provided when performing a query operation. -ProcessQuery(query, schema, variableValues, initialValue): +Query(query, schema, variableValues, initialValue): - Let {queryType} be the root Query type in {schema}. - Assert: {queryType} is an Object type. @@ -171,7 +180,7 @@ 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 mutations ensures against race conditions during these side-effects. -ProcessMutation(mutation, schema, variableValues, initialValue): +Mutation(mutation, schema, variableValues, initialValue): - Let {mutationType} be the root Mutation type in {schema}. - Assert: {mutationType} is an Object type. @@ -183,13 +192,13 @@ ProcessMutation(mutation, schema, variableValues, initialValue): If the operation is a subscription, the result is an _event stream_ called the _response stream_ where each event in the event stream is the result of -executing the operation’s root _selection set_ for each new event on an +executing the operation’s _root selection set_ for each new event on an underlying _source stream_. -Processing a subscription operation creates a persistent function on the service +Performing a subscription operation creates a persistent function on the service that maps an underlying _source stream_ to a returned _response stream_. -ProcessSubscription(subscription, schema, variableValues, initialValue): +Subscription(subscription, schema, variableValues, initialValue): - Let {sourceStream} be the result of running {CreateSourceEventStream(subscription, schema, variableValues, initialValue)}. @@ -198,9 +207,9 @@ ProcessSubscription(subscription, schema, variableValues, initialValue): variableValues)}. - Return {responseStream}. -Note: In a large-scale subscription system, the {ProcessSubscription()} and -{ProcessSubscriptionEvent()} algorithms may be run on separate services to -maintain predictable scaling properties. See the section below on Supporting +Note: In a large-scale subscription system, the {Subscription()} and +{SubscriptionEvent()} algorithms may be run on separate services to maintain +predictable scaling properties. See the section below on Supporting Subscriptions at Scale. As an example, consider a chat application. To subscribe to new messages posted @@ -319,9 +328,8 @@ MapSourceToResponseEvent(sourceStream, subscription, schema, variableValues): - Let {responseStream} be a new _event stream_. - When {sourceStream} emits {sourceValue}: - - Let {response} be the result of running - {ProcessSubscriptionEvent(subscription, schema, variableValues, - sourceValue)}. + - Let {response} be the result of running {SubscriptionEvent(subscription, + schema, variableValues, sourceValue)}. - If internal {error} was raised: - Cancel {sourceStream}. - Complete {responseStream} with {error}. @@ -335,12 +343,12 @@ MapSourceToResponseEvent(sourceStream, subscription, schema, variableValues): - Complete {responseStream} normally. - Return {responseStream}. -Note: Since {ProcessSubscriptionEvent()} handles all _execution error_, and -_request error_ only occur during {CreateSourceEventStream()}, the only -remaining error condition handled from {ProcessSubscriptionEvent()} are internal -exceptional errors not described by this specification. +Note: Since {SubscriptionEvent()} handles all _execution error_, and _request +error_ only occur during {CreateSourceEventStream()}, the only remaining error +condition handled from {SubscriptionEvent()} are internal exceptional errors not +described by this specification. -ProcessSubscriptionEvent(subscription, schema, variableValues, initialValue): +SubscriptionEvent(subscription, schema, variableValues, initialValue): - Let {subscriptionType} be the root Subscription type in {schema}. - Assert: {subscriptionType} is an Object type. @@ -348,8 +356,8 @@ ProcessSubscriptionEvent(subscription, schema, variableValues, initialValue): - Return {ExecuteRootSelectionSet(variableValues, initialValue, subscriptionType, rootSelectionSet, "normal")}. -Note: The {ProcessSubscriptionEvent()} algorithm is intentionally similar to -{ProcessQuery()} since this is how each event result is produced. +Note: The {SubscriptionEvent()} algorithm is intentionally similar to {Query()} +since this is how each event result is produced. #### Unsubscribe @@ -644,7 +652,7 @@ A valid GraphQL executor can resolve the four fields in whatever order it chose (however of course `birthday` must be resolved before `month`, and `address` before `street`). -When processing a mutation, the selections in the top most selection set will be +When performing 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 @@ -791,9 +799,9 @@ CoerceArgumentValues(objectType, field, variableValues): Any _request error_ raised as a result of input coercion during {CoerceArgumentValues()} should be treated instead as an _execution error_. -Note: Variable values are not coerced because they are expected to be coerced -before processing the operation in {CoerceVariableValues()}, and valid -operations must only allow usage of variables of appropriate types. +Note: Variable values are not coerced because they are expected to be coerced in +{CoerceVariableValues()} before _execution_ begins, and valid operations must +only allow usage of variables of appropriate types. ### Value Resolution From 1f11a6bdcaabe64167598de59a4c6e60d2806a77 Mon Sep 17 00:00:00 2001 From: Benjie Gillam Date: Thu, 1 May 2025 12:40:22 +0100 Subject: [PATCH 23/31] Add clarity --- spec/Section 6 -- Execution.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/spec/Section 6 -- Execution.md b/spec/Section 6 -- Execution.md index a8cf473b0..6c5c7ffd7 100644 --- a/spec/Section 6 -- Execution.md +++ b/spec/Section 6 -- Execution.md @@ -93,11 +93,11 @@ they should be reported in the list of "errors" in the response and the request must fail without execution. Typically validation is performed in the context of a request immediately before -processing, however a GraphQL service may process a request without immediately -validating the document if that exact same document is known to have been -validated before. A GraphQL service should only execute operations which _at -some point_ were known to be free of any validation errors, and have since not -changed. +calling {Request()}, however a GraphQL service may process a request without +immediately validating the document if that exact same document is known to have +been validated before. A GraphQL service should only execute operations which +_at some point_ were known to be free of any validation errors, and have since +not changed. For example: the document may be validated during development, provided it does not later change, or a service may validate a document once and memoize the From ec28769b83ed07d2afe30c95576bbad25e33780c Mon Sep 17 00:00:00 2001 From: Benjie Gillam Date: Thu, 1 May 2025 12:46:09 +0100 Subject: [PATCH 24/31] Grammar --- spec/Section 6 -- Execution.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/Section 6 -- Execution.md b/spec/Section 6 -- Execution.md index 6c5c7ffd7..acaecbd6e 100644 --- a/spec/Section 6 -- Execution.md +++ b/spec/Section 6 -- Execution.md @@ -799,7 +799,7 @@ CoerceArgumentValues(objectType, field, variableValues): Any _request error_ raised as a result of input coercion during {CoerceArgumentValues()} should be treated instead as an _execution error_. -Note: Variable values are not coerced because they are expected to be coerced in +Note: Variable values are not coerced because they are expected to be coerced by {CoerceVariableValues()} before _execution_ begins, and valid operations must only allow usage of variables of appropriate types. From b428f39866ccb95c5fd23262c3dfddf782b363d8 Mon Sep 17 00:00:00 2001 From: Benjie Gillam Date: Thu, 1 May 2025 12:57:36 +0100 Subject: [PATCH 25/31] Reorder text --- spec/Section 6 -- Execution.md | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/spec/Section 6 -- Execution.md b/spec/Section 6 -- Execution.md index acaecbd6e..b17401676 100644 --- a/spec/Section 6 -- Execution.md +++ b/spec/Section 6 -- Execution.md @@ -45,6 +45,16 @@ document is expected to only contain a single operation. The result of the request is determined by the result of performing this operation according to the "Performing Operations” section below. +The {Request()} algorithm contains the preamble for _execution_, handling +concerns such as determining the operation and coercing the inputs, before +passing the request on to the relevant algorithm for the operation's type which +then performs any other necessary preliminary steps (for example establishing +the source event stream for subscription operations) and then initiates +_execution_. + +Note: An error raised before _execution_ begins will typically be a _request +error_, and once _execution_ begins will typically be an _execution error_. + :: We define _execution_ as the process of executing the operation's _root selection set_ through {ExecuteRootSelectionSet()}, and hence _execution_ begins when {ExecuteSelectionSet()} is called for the first time in a request. @@ -52,15 +62,6 @@ when {ExecuteSelectionSet()} is called for the first time in a request. Note: A subscription operation may call {ExecuteSelectionSet()} a number of times. -The {Request()} algorithm contains the preamble for execution, handling concerns -such as determining the operation and coercing the inputs, before passing the -request on to the relevant algorithm for the operation's type which then -performs any other necessary preliminary steps (for example establishing the -source event stream for subscription operations) and then initiates _execution_. - -Note: An error raised before _execution_ begins will typically be a _request -error_, and once _execution_ begins will typically be an _execution error_. - Request(schema, document, operationName, variableValues, initialValue): - Let {operation} be the result of {GetOperation(document, operationName)}. From 7ed055a2cff36d249e2ee3adc2cf3063e3dc7e18 Mon Sep 17 00:00:00 2001 From: Benjie Gillam Date: Thu, 1 May 2025 12:58:38 +0100 Subject: [PATCH 26/31] Fix typo and remove note --- spec/Section 6 -- Execution.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/spec/Section 6 -- Execution.md b/spec/Section 6 -- Execution.md index b17401676..6a71f53d1 100644 --- a/spec/Section 6 -- Execution.md +++ b/spec/Section 6 -- Execution.md @@ -57,10 +57,7 @@ error_, and once _execution_ begins will typically be an _execution error_. :: We define _execution_ as the process of executing the operation's _root selection set_ through {ExecuteRootSelectionSet()}, and hence _execution_ begins -when {ExecuteSelectionSet()} is called for the first time in a request. - -Note: A subscription operation may call {ExecuteSelectionSet()} a number of -times. +when {ExecuteRootSelectionSet()} is called for the first time in a request. Request(schema, document, operationName, variableValues, initialValue): From 1be44d052387e24cef8c66d74175747f40950041 Mon Sep 17 00:00:00 2001 From: Lee Byron Date: Tue, 1 Jul 2025 22:27:51 -0700 Subject: [PATCH 27/31] Define 'execution' as in 'before execution begins' Reintroduces #894 atop latest main Co-authored-by: Benjie --- spec/Section 3 -- Type System.md | 14 ++-- spec/Section 4 -- Introspection.md | 2 +- spec/Section 5 -- Validation.md | 7 +- spec/Section 6 -- Execution.md | 118 +++++++++++++++++------------ spec/Section 7 -- Response.md | 28 ++++--- 5 files changed, 98 insertions(+), 71 deletions(-) diff --git a/spec/Section 3 -- Type System.md b/spec/Section 3 -- Type System.md index 79953a4e0..6684075fd 100644 --- a/spec/Section 3 -- Type System.md +++ b/spec/Section 3 -- Type System.md @@ -328,7 +328,7 @@ GraphQL supports two abstract types: interfaces and unions. An `Interface` defines a list of fields; `Object` types and other Interface types which implement this Interface are guaranteed to implement those fields. Whenever a field claims it will return an Interface type, it will return a valid -implementing Object type during execution. +implementing Object type during _execution_. A `Union` defines a list of possible types; similar to interfaces, whenever the type system claims a union will be returned, one of the possible types will be @@ -505,7 +505,7 @@ information on the serialization of scalars in common JSON and other formats. If a GraphQL service expects a scalar type as input to an argument, coercion is observable and the rules must be well defined. If an input value does not match a coercion rule, a _request error_ must be raised (input values are validated -before execution begins). +before _execution_ begins). GraphQL has different constant literals to represent integer and floating-point input values, and coercion rules may apply differently depending on which type @@ -810,7 +810,7 @@ And will yield the subset of each object type queried: **Field Ordering** When querying an Object, the resulting mapping of fields are conceptually -ordered in the same order in which they were encountered during execution, +ordered in the same order in which they were encountered during _execution_, excluding fragments for which the type does not apply and fields or fragments that are skipped via `@skip` or `@include` directives. This ordering is correctly produced when using the {CollectFields()} algorithm. @@ -2058,8 +2058,8 @@ directive @example on Directives can also be used to annotate the type system definition language as well, which can be a useful tool for supplying additional metadata in order to -generate GraphQL execution services, produce client generated runtime code, or -many other useful extensions of the GraphQL semantics. +generate GraphQL services, produce client generated runtime code, or many other +useful extensions of the GraphQL semantics. In this example, the directive `@example` annotates field and argument definitions: @@ -2122,7 +2122,7 @@ directive @skip(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT ``` The `@skip` _built-in directive_ may be provided for fields, fragment spreads, -and inline fragments, and allows for conditional exclusion during execution as +and inline fragments, and allows for conditional exclusion during _execution_ as described by the `if` argument. In this example `experimentalField` will only be queried if the variable @@ -2142,7 +2142,7 @@ directive @include(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT The `@include` _built-in directive_ may be provided for fields, fragment spreads, and inline fragments, and allows for conditional inclusion during -execution as described by the `if` argument. +_execution_ as described by the `if` argument. In this example `experimentalField` will only be queried if the variable `$someTest` has the value `true` diff --git a/spec/Section 4 -- Introspection.md b/spec/Section 4 -- Introspection.md index d7f8e629f..39e2ca249 100644 --- a/spec/Section 4 -- Introspection.md +++ b/spec/Section 4 -- Introspection.md @@ -72,7 +72,7 @@ GraphQL supports type name introspection within any _selection set_ in an operation, with the single exception of selections at the root of a subscription operation. Type name introspection is accomplished via the meta-field `__typename: String!` on any Object, Interface, or Union. It returns the name of -the concrete Object type at that point during execution. +the concrete Object type at that point during _execution_. This is most often used when querying against Interface or Union types to identify which actual Object type of the possible types has been returned. diff --git a/spec/Section 5 -- Validation.md b/spec/Section 5 -- Validation.md index 206814d6d..b7e8c7e91 100644 --- a/spec/Section 5 -- Validation.md +++ b/spec/Section 5 -- Validation.md @@ -7,7 +7,8 @@ given GraphQL schema. An invalid request is still technically executable, and will always produce a stable result as defined by the algorithms in the Execution section, however that result may be ambiguous, surprising, or unexpected relative to a request -containing validation errors, so execution should only occur for valid requests. +containing validation errors, so _execution_ should only occur for valid +requests. Typically validation is performed in the context of a request immediately before execution, however a GraphQL service may execute a request without explicitly @@ -108,7 +109,7 @@ input FindDogInput { GraphQL execution will only consider the executable definitions Operation and Fragment. Type system definitions and extensions are not executable, and are not -considered during execution. +considered during _execution_. To avoid ambiguity, a document containing {TypeSystemDefinitionOrExtension} is invalid for execution. @@ -579,7 +580,7 @@ type that is either an Object, Interface or Union type. **Explanatory Text** If multiple field selections with the same _response name_ are encountered -during execution, the field and arguments to execute and the resulting value +during _execution_, the field and arguments to execute and the resulting value should be unambiguous. Therefore any two field selections which might both be encountered for the same object are only valid if they are equivalent. diff --git a/spec/Section 6 -- Execution.md b/spec/Section 6 -- Execution.md index b2c533bb3..c3b7c024e 100644 --- a/spec/Section 6 -- Execution.md +++ b/spec/Section 6 -- Execution.md @@ -18,9 +18,9 @@ A GraphQL service generates a response from a request via execution. - {extensions} (optional): A map reserved for implementation-specific additional information. -Given this information, the result of {ExecuteRequest(schema, document, -operationName, variableValues, initialValue)} produces the response, to be -formatted according to the Response section below. +Given this information, the result of {Request(schema, document, operationName, +variableValues, initialValue)} produces the response, to be formatted according +to the Response section below. Implementations should not add additional properties to a _request_, which may conflict with future editions of the GraphQL specification. Instead, @@ -39,27 +39,44 @@ and have no effect on the observable execution, validation, or response of a GraphQL document. Descriptions and comments on executable documents MAY be used for non-observable purposes, such as logging and other developer tools. -## Executing Requests +## Processing Requests -To execute a request, the executor must have a parsed {Document} and a selected + + + + +To process a request, the executor must have a parsed {Document} and a selected operation name to run if the document defines multiple operations, otherwise the document is expected to only contain a single operation. The result of the -request is determined by the result of executing this operation according to the -"Executing Operations” section below. +request is determined by the result of performing this operation according to +the "Performing Operations” section below. + +The {Request()} algorithm contains the preamble for _execution_, handling +concerns such as determining the operation and coercing the inputs, before +passing the request on to the relevant algorithm for the operation's type which +then performs any other necessary preliminary steps (for example establishing +the source event stream for subscription operations) and then initiates +_execution_. + +Note: An error raised before _execution_ begins will typically be a _request +error_, and once _execution_ begins will typically be an _execution error_. + +:: We define _execution_ as the process of executing the operation's _root +selection set_ through {ExecuteRootSelectionSet()}, and hence _execution_ begins +when {ExecuteRootSelectionSet()} is called for the first time in a request. -ExecuteRequest(schema, document, operationName, variableValues, initialValue): +Request(schema, document, operationName, variableValues, initialValue): - Let {operation} be the result of {GetOperation(document, operationName)}. - Let {coercedVariableValues} be the result of {CoerceVariableValues(schema, operation, variableValues)}. - If {operation} is a query operation: - - Return {ExecuteQuery(operation, schema, coercedVariableValues, - initialValue)}. + - Return {Query(operation, schema, coercedVariableValues, initialValue)}. - Otherwise if {operation} is a mutation operation: - - Return {ExecuteMutation(operation, schema, coercedVariableValues, - initialValue)}. + - Return {Mutation(operation, schema, coercedVariableValues, initialValue)}. - Otherwise if {operation} is a subscription operation: - - Return {Subscribe(operation, schema, coercedVariableValues, initialValue)}. + - Return {Subscription(operation, schema, coercedVariableValues, + initialValue)}. GetOperation(document, operationName): @@ -74,27 +91,28 @@ GetOperation(document, operationName): ### Validating Requests -As explained in the Validation section, only requests which pass all validation -rules should be executed. If validation errors are known, they should be -reported in the list of "errors" in the response and the request must fail -without execution. +As explained in the Validation section, only operations from documents which +pass all validation rules should be executed. If validation errors are known, +they should be reported in the list of "errors" in the response and the request +must fail without execution. Typically validation is performed in the context of a request immediately before -execution, however a GraphQL service may execute a request without immediately -validating it if that exact same request is known to have been validated before. -A GraphQL service should only execute requests which _at some point_ were known -to be free of any validation errors, and have since not changed. +calling {Request()}, however a GraphQL service may process a request without +immediately validating the document if that exact same document is known to have +been validated before. A GraphQL service should only execute operations which +_at some point_ were known to be free of any validation errors, and have since +not changed. -For example: the request may be validated during development, provided it does -not later change, or a service may validate a request once and memoize the -result to avoid validating the same request again in the future. +For example: the document may be validated during development, provided it does +not later change, or a service may validate a document once and memoize the +result to avoid validating the same document again in the future. ### Coercing Variable Values If the operation has defined any variables, then the values for those variables need to be coerced using the input coercion rules of variable's declared type. If a _request error_ is encountered during input coercion of variable values, -then the operation fails without execution. +then the request fails without _execution_. CoerceVariableValues(schema, operation, variableValues): @@ -131,7 +149,11 @@ CoerceVariableValues(schema, operation, variableValues): Note: This algorithm is very similar to {CoerceArgumentValues()}. -## Executing Operations +## Performing Operations + + + + The type system, as described in the "Type System" section of the spec, must provide a query root operation type. If mutations or subscriptions are @@ -144,9 +166,9 @@ If the operation is a query, the result of the operation is the result of executing the operation’s _root selection set_ with the query root operation type. -An initial value may be provided when executing a query operation. +An initial value may be provided when performing a query operation. -ExecuteQuery(query, schema, variableValues, initialValue): +Query(query, schema, variableValues, initialValue): - Let {queryType} be the root Query type in {schema}. - Assert: {queryType} is an Object type. @@ -164,7 +186,7 @@ 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 mutations ensures against race conditions during these side-effects. -ExecuteMutation(mutation, schema, variableValues, initialValue): +Mutation(mutation, schema, variableValues, initialValue): - Let {mutationType} be the root Mutation type in {schema}. - Assert: {mutationType} is an Object type. @@ -176,12 +198,13 @@ ExecuteMutation(mutation, schema, variableValues, initialValue): If the operation is a subscription, the result is an _event stream_ called the _response stream_ where each event in the event stream is the result of -executing the operation for each new event on an underlying _source stream_. +executing the operation’s _root selection set_ for each new event on an +underlying _source stream_. -Executing a subscription operation creates a persistent function on the service +Performing a subscription operation creates a persistent function on the service that maps an underlying _source stream_ to a returned _response stream_. -Subscribe(subscription, schema, variableValues, initialValue): +Subscription(subscription, schema, variableValues, initialValue): - Let {sourceStream} be the result of running {CreateSourceEventStream(subscription, schema, variableValues, initialValue)}. @@ -190,9 +213,9 @@ Subscribe(subscription, schema, variableValues, initialValue): variableValues)}. - Return {responseStream}. -Note: In a large-scale subscription system, the {Subscribe()} and -{ExecuteSubscriptionEvent()} algorithms may be run on separate services to -maintain predictable scaling properties. See the section below on Supporting +Note: In a large-scale subscription system, the {Subscription()} and +{SubscriptionEvent()} algorithms may be run on separate services to maintain +predictable scaling properties. See the section below on Supporting Subscriptions at Scale. As an example, consider a chat application. To subscribe to new messages posted @@ -313,8 +336,7 @@ MapSourceToResponseEvent(sourceStream, subscription, schema, variableValues): - Let {responseStream} be a new _event stream_. - When {sourceStream} emits {sourceValue}: - Let {executionResult} be the result of running - {ExecuteSubscriptionEvent(subscription, schema, variableValues, - sourceValue)}. + {SubscriptionEvent(subscription, schema, variableValues, sourceValue)}. - If internal {error} was raised: - Cancel {sourceStream}. - Complete {responseStream} with {error}. @@ -328,12 +350,12 @@ MapSourceToResponseEvent(sourceStream, subscription, schema, variableValues): - Complete {responseStream} normally. - Return {responseStream}. -Note: Since {ExecuteSubscriptionEvent()} handles all _execution error_, and -_request error_ only occur during {CreateSourceEventStream()}, the only -remaining error condition handled from {ExecuteSubscriptionEvent()} are internal -exceptional errors not described by this specification. +Note: Since {SubscriptionEvent()} handles all _execution error_, and _request +error_ only occur during {CreateSourceEventStream()}, the only remaining error +condition handled from {SubscriptionEvent()} are internal exceptional errors not +described by this specification. -ExecuteSubscriptionEvent(subscription, schema, variableValues, initialValue): +SubscriptionEvent(subscription, schema, variableValues, initialValue): - Let {subscriptionType} be the root Subscription type in {schema}. - Assert: {subscriptionType} is an Object type. @@ -341,8 +363,8 @@ ExecuteSubscriptionEvent(subscription, schema, variableValues, initialValue): - Return {ExecuteRootSelectionSet(variableValues, initialValue, subscriptionType, rootSelectionSet, "normal")}. -Note: The {ExecuteSubscriptionEvent()} algorithm is intentionally similar to -{ExecuteQuery()} since this is how each event result is produced. +Note: The {SubscriptionEvent()} algorithm is intentionally similar to {Query()} +since this is how each event result is produced. #### Unsubscribe @@ -638,7 +660,7 @@ A valid GraphQL executor can resolve the four fields in whatever order it chose (however of course `birthday` must be resolved before `month`, and `address` before `street`). -When executing a mutation, the selections in the top most selection set will be +When performing 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 collected fields map serially, the executor must consider each @@ -788,9 +810,9 @@ CoerceArgumentValues(objectType, field, variableValues): Any _request error_ raised as a result of input coercion during {CoerceArgumentValues()} should be treated instead as an _execution error_. -Note: Variable values are not coerced because they are expected to be coerced -before executing the operation in {CoerceVariableValues()}, and valid operations -must only allow usage of variables of appropriate types. +Note: Variable values are not coerced because they are expected to be coerced by +{CoerceVariableValues()} before _execution_ begins, and valid operations must +only allow usage of variables of appropriate types. Note: Implementations are encouraged to optimize the coercion of an argument's default value by doing so only once and caching the resulting coerced value. diff --git a/spec/Section 7 -- Response.md b/spec/Section 7 -- Response.md index f79100cd2..3f974aa37 100644 --- a/spec/Section 7 -- Response.md +++ b/spec/Section 7 -- Response.md @@ -24,11 +24,11 @@ An _execution result_ must be map. The _execution result_ must contain an entry with key {"data"}. The value of this entry is described in the "Data" section. -If execution raised any errors, the _execution result_ must contain an entry -with key {"errors"}. The value of this entry must be a non-empty list of -_execution error_ raised during execution. Each error must be a map as described -in the "Errors" section below. If the request completed without raising any -errors, this entry must not be present. +If any errors are raised during _execution_, the _execution result_ must contain +an entry with key {"errors"}. The value of this entry must be a non-empty list +of _execution error_ raised during execution. Each error must be a map as +described in the "Errors" section below. If the request completed without +raising any errors, this entry must not be present. Note: When {"errors"} is present in a _execution result_, it may be helpful for it to appear first when serialized to make it more apparent that errors are @@ -115,20 +115,24 @@ found at `["hero", "friends"]`, the hero's first friend at ### Data -The {"data"} entry in the _execution result_ will be the result of the execution -of the requested operation. If the operation was a query, this output will be an -object of the query root operation type; if the operation was a mutation, this -output will be an object of the mutation root operation type. +The {"data"} entry in the _execution result_ will be the result of the +_execution_ of the requested operation. If the operation was a query, this +output will be an object of the query root operation type; if the operation was +a mutation, this output will be an object of the mutation root operation type. The response data is the result of accumulating the resolved result of all response positions during execution. -If an error was raised before execution begins, the _response_ must be a +If an error was raised before _execution_ begins, the _response_ must be a _request error result_ which will result in no response data. -If an error was raised during the execution that prevented a valid response, the +If an error was raised during _execution_ that prevented a valid response, the {"data"} entry in the response should be `null`. +Note: Request errors (including those raised during {Request()}) occur before +_execution_ begins; when a request error is raised the {"data"} entry should not +be present in the result. + ### Errors The {"errors"} entry in the _execution result_ or _request error result_ is a @@ -138,7 +142,7 @@ of data described by the error result format below. **Request Errors** :: A _request error_ is an error raised during a _request_ which results in no -response data. Typically raised before execution begins, a request error may +response data. Typically raised before _execution_ begins, a request error may occur due to a parse grammar or validation error in the _Document_, an inability to determine which operation to execute, or invalid input values for variables. From f16f52f0237c17290c7297912092ef5a2f599520 Mon Sep 17 00:00:00 2001 From: Benjie Gillam Date: Wed, 2 Jul 2025 16:06:56 +0100 Subject: [PATCH 28/31] Revert to previous terminology and algorithm names. --- spec/Section 6 -- Execution.md | 82 ++++++++++++++++------------------ spec/Section 7 -- Response.md | 6 +-- 2 files changed, 41 insertions(+), 47 deletions(-) diff --git a/spec/Section 6 -- Execution.md b/spec/Section 6 -- Execution.md index c3b7c024e..86a5e0c13 100644 --- a/spec/Section 6 -- Execution.md +++ b/spec/Section 6 -- Execution.md @@ -18,9 +18,9 @@ A GraphQL service generates a response from a request via execution. - {extensions} (optional): A map reserved for implementation-specific additional information. -Given this information, the result of {Request(schema, document, operationName, -variableValues, initialValue)} produces the response, to be formatted according -to the Response section below. +Given this information, the result of {ExecuteRequest(schema, document, +operationName, variableValues, initialValue)} produces the response, to be +formatted according to the Response section below. Implementations should not add additional properties to a _request_, which may conflict with future editions of the GraphQL specification. Instead, @@ -39,19 +39,15 @@ and have no effect on the observable execution, validation, or response of a GraphQL document. Descriptions and comments on executable documents MAY be used for non-observable purposes, such as logging and other developer tools. -## Processing Requests +## Executing Requests - - - - -To process a request, the executor must have a parsed {Document} and a selected +To execute a request, the executor must have a parsed {Document} and a selected operation name to run if the document defines multiple operations, otherwise the document is expected to only contain a single operation. The result of the -request is determined by the result of performing this operation according to -the "Performing Operations” section below. +request is determined by the result of executing this operation according to the +"Executing Operations” section below. -The {Request()} algorithm contains the preamble for _execution_, handling +The {ExecuteRequest()} algorithm contains the preamble for _execution_, handling concerns such as determining the operation and coercing the inputs, before passing the request on to the relevant algorithm for the operation's type which then performs any other necessary preliminary steps (for example establishing @@ -65,18 +61,19 @@ error_, and once _execution_ begins will typically be an _execution error_. selection set_ through {ExecuteRootSelectionSet()}, and hence _execution_ begins when {ExecuteRootSelectionSet()} is called for the first time in a request. -Request(schema, document, operationName, variableValues, initialValue): +ExecuteRequest(schema, document, operationName, variableValues, initialValue): - Let {operation} be the result of {GetOperation(document, operationName)}. - Let {coercedVariableValues} be the result of {CoerceVariableValues(schema, operation, variableValues)}. - If {operation} is a query operation: - - Return {Query(operation, schema, coercedVariableValues, initialValue)}. + - Return {ExecuteQuery(operation, schema, coercedVariableValues, + initialValue)}. - Otherwise if {operation} is a mutation operation: - - Return {Mutation(operation, schema, coercedVariableValues, initialValue)}. -- Otherwise if {operation} is a subscription operation: - - Return {Subscription(operation, schema, coercedVariableValues, + - Return {ExecuteMutation(operation, schema, coercedVariableValues, initialValue)}. +- Otherwise if {operation} is a subscription operation: + - Return {Subscribe(operation, schema, coercedVariableValues, initialValue)}. GetOperation(document, operationName): @@ -97,11 +94,11 @@ they should be reported in the list of "errors" in the response and the request must fail without execution. Typically validation is performed in the context of a request immediately before -calling {Request()}, however a GraphQL service may process a request without -immediately validating the document if that exact same document is known to have -been validated before. A GraphQL service should only execute operations which -_at some point_ were known to be free of any validation errors, and have since -not changed. +calling {ExecuteRequest()}, however a GraphQL service may execute a request +without immediately validating the document if that exact same document is known +to have been validated before. A GraphQL service should only execute operations +which _at some point_ were known to be free of any validation errors, and have +since not changed. For example: the document may be validated during development, provided it does not later change, or a service may validate a document once and memoize the @@ -149,11 +146,7 @@ CoerceVariableValues(schema, operation, variableValues): Note: This algorithm is very similar to {CoerceArgumentValues()}. -## Performing Operations - - - - +## Executing Operations The type system, as described in the "Type System" section of the spec, must provide a query root operation type. If mutations or subscriptions are @@ -166,9 +159,9 @@ If the operation is a query, the result of the operation is the result of executing the operation’s _root selection set_ with the query root operation type. -An initial value may be provided when performing a query operation. +An initial value may be provided when executing a query operation. -Query(query, schema, variableValues, initialValue): +ExecuteQuery(query, schema, variableValues, initialValue): - Let {queryType} be the root Query type in {schema}. - Assert: {queryType} is an Object type. @@ -186,7 +179,7 @@ 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 mutations ensures against race conditions during these side-effects. -Mutation(mutation, schema, variableValues, initialValue): +ExecuteMutation(mutation, schema, variableValues, initialValue): - Let {mutationType} be the root Mutation type in {schema}. - Assert: {mutationType} is an Object type. @@ -201,10 +194,10 @@ _response stream_ where each event in the event stream is the result of executing the operation’s _root selection set_ for each new event on an underlying _source stream_. -Performing a subscription operation creates a persistent function on the service +Executing a subscription operation creates a persistent function on the service that maps an underlying _source stream_ to a returned _response stream_. -Subscription(subscription, schema, variableValues, initialValue): +Subscribe(subscription, schema, variableValues, initialValue): - Let {sourceStream} be the result of running {CreateSourceEventStream(subscription, schema, variableValues, initialValue)}. @@ -213,9 +206,9 @@ Subscription(subscription, schema, variableValues, initialValue): variableValues)}. - Return {responseStream}. -Note: In a large-scale subscription system, the {Subscription()} and -{SubscriptionEvent()} algorithms may be run on separate services to maintain -predictable scaling properties. See the section below on Supporting +Note: In a large-scale subscription system, the {Subscribe()} and +{ExecuteSubscriptionEvent()} algorithms may be run on separate services to +maintain predictable scaling properties. See the section below on Supporting Subscriptions at Scale. As an example, consider a chat application. To subscribe to new messages posted @@ -336,7 +329,8 @@ MapSourceToResponseEvent(sourceStream, subscription, schema, variableValues): - Let {responseStream} be a new _event stream_. - When {sourceStream} emits {sourceValue}: - Let {executionResult} be the result of running - {SubscriptionEvent(subscription, schema, variableValues, sourceValue)}. + {ExecuteSubscriptionEvent(subscription, schema, variableValues, + sourceValue)}. - If internal {error} was raised: - Cancel {sourceStream}. - Complete {responseStream} with {error}. @@ -350,12 +344,12 @@ MapSourceToResponseEvent(sourceStream, subscription, schema, variableValues): - Complete {responseStream} normally. - Return {responseStream}. -Note: Since {SubscriptionEvent()} handles all _execution error_, and _request -error_ only occur during {CreateSourceEventStream()}, the only remaining error -condition handled from {SubscriptionEvent()} are internal exceptional errors not -described by this specification. +Note: Since {ExecuteSubscriptionEvent()} handles all _execution error_, and +_request error_ only occur during {CreateSourceEventStream()}, the only +remaining error condition handled from {ExecuteSubscriptionEvent()} are internal +exceptional errors not described by this specification. -SubscriptionEvent(subscription, schema, variableValues, initialValue): +ExecuteSubscriptionEvent(subscription, schema, variableValues, initialValue): - Let {subscriptionType} be the root Subscription type in {schema}. - Assert: {subscriptionType} is an Object type. @@ -363,8 +357,8 @@ SubscriptionEvent(subscription, schema, variableValues, initialValue): - Return {ExecuteRootSelectionSet(variableValues, initialValue, subscriptionType, rootSelectionSet, "normal")}. -Note: The {SubscriptionEvent()} algorithm is intentionally similar to {Query()} -since this is how each event result is produced. +Note: The {ExecuteSubscriptionEvent()} algorithm is intentionally similar to +{ExecuteQuery()} since this is how each event result is produced. #### Unsubscribe @@ -660,7 +654,7 @@ A valid GraphQL executor can resolve the four fields in whatever order it chose (however of course `birthday` must be resolved before `month`, and `address` before `street`). -When performing a mutation, the selections in the top most selection set will be +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 collected fields map serially, the executor must consider each diff --git a/spec/Section 7 -- Response.md b/spec/Section 7 -- Response.md index 3f974aa37..8ab1ace95 100644 --- a/spec/Section 7 -- Response.md +++ b/spec/Section 7 -- Response.md @@ -129,9 +129,9 @@ _request error result_ which will result in no response data. If an error was raised during _execution_ that prevented a valid response, the {"data"} entry in the response should be `null`. -Note: Request errors (including those raised during {Request()}) occur before -_execution_ begins; when a request error is raised the {"data"} entry should not -be present in the result. +Note: Request errors (including those raised during {ExecuteRequest()}) occur +before _execution_ begins; when a request error is raised the {"data"} entry +should not be present in the result. ### Errors From 94241a78d020fb0bdeea483a0f50d54beff890ba Mon Sep 17 00:00:00 2001 From: Benjie Gillam Date: Wed, 2 Jul 2025 16:58:41 +0100 Subject: [PATCH 29/31] Rename 'execution' to 'operation execution' --- spec/Section 3 -- Type System.md | 18 ++++++------ spec/Section 4 -- Introspection.md | 2 +- spec/Section 5 -- Validation.md | 13 +++++---- spec/Section 6 -- Execution.md | 46 +++++++++++++++++------------- spec/Section 7 -- Response.md | 38 ++++++++++++------------ 5 files changed, 63 insertions(+), 54 deletions(-) diff --git a/spec/Section 3 -- Type System.md b/spec/Section 3 -- Type System.md index 6684075fd..89c1c5fbd 100644 --- a/spec/Section 3 -- Type System.md +++ b/spec/Section 3 -- Type System.md @@ -328,7 +328,7 @@ GraphQL supports two abstract types: interfaces and unions. An `Interface` defines a list of fields; `Object` types and other Interface types which implement this Interface are guaranteed to implement those fields. Whenever a field claims it will return an Interface type, it will return a valid -implementing Object type during _execution_. +implementing Object type during _operation execution_. A `Union` defines a list of possible types; similar to interfaces, whenever the type system claims a union will be returned, one of the possible types will be @@ -505,7 +505,7 @@ information on the serialization of scalars in common JSON and other formats. If a GraphQL service expects a scalar type as input to an argument, coercion is observable and the rules must be well defined. If an input value does not match a coercion rule, a _request error_ must be raised (input values are validated -before _execution_ begins). +before _operation execution_ begins). GraphQL has different constant literals to represent integer and floating-point input values, and coercion rules may apply differently depending on which type @@ -810,10 +810,10 @@ And will yield the subset of each object type queried: **Field Ordering** When querying an Object, the resulting mapping of fields are conceptually -ordered in the same order in which they were encountered during _execution_, -excluding fragments for which the type does not apply and fields or fragments -that are skipped via `@skip` or `@include` directives. This ordering is -correctly produced when using the {CollectFields()} algorithm. +ordered in the same order in which they were encountered during _operation +execution_, excluding fragments for which the type does not apply and fields or +fragments that are skipped via `@skip` or `@include` directives. This ordering +is correctly produced when using the {CollectFields()} algorithm. Response serialization formats capable of representing ordered maps should maintain this ordering. Serialization formats which can only represent unordered @@ -2122,8 +2122,8 @@ directive @skip(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT ``` The `@skip` _built-in directive_ may be provided for fields, fragment spreads, -and inline fragments, and allows for conditional exclusion during _execution_ as -described by the `if` argument. +and inline fragments, and allows for conditional exclusion during _operation +execution_ as described by the `if` argument. In this example `experimentalField` will only be queried if the variable `$someTest` has the value `false`. @@ -2142,7 +2142,7 @@ directive @include(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT The `@include` _built-in directive_ may be provided for fields, fragment spreads, and inline fragments, and allows for conditional inclusion during -_execution_ as described by the `if` argument. +_operation execution_ as described by the `if` argument. In this example `experimentalField` will only be queried if the variable `$someTest` has the value `true` diff --git a/spec/Section 4 -- Introspection.md b/spec/Section 4 -- Introspection.md index 39e2ca249..5d3307cee 100644 --- a/spec/Section 4 -- Introspection.md +++ b/spec/Section 4 -- Introspection.md @@ -72,7 +72,7 @@ GraphQL supports type name introspection within any _selection set_ in an operation, with the single exception of selections at the root of a subscription operation. Type name introspection is accomplished via the meta-field `__typename: String!` on any Object, Interface, or Union. It returns the name of -the concrete Object type at that point during _execution_. +the concrete Object type at that point during _operation execution_. This is most often used when querying against Interface or Union types to identify which actual Object type of the possible types has been returned. diff --git a/spec/Section 5 -- Validation.md b/spec/Section 5 -- Validation.md index b7e8c7e91..5ed5c1a00 100644 --- a/spec/Section 5 -- Validation.md +++ b/spec/Section 5 -- Validation.md @@ -7,8 +7,8 @@ given GraphQL schema. An invalid request is still technically executable, and will always produce a stable result as defined by the algorithms in the Execution section, however that result may be ambiguous, surprising, or unexpected relative to a request -containing validation errors, so _execution_ should only occur for valid -requests. +containing validation errors, so _operation execution_ should only occur for +valid requests. Typically validation is performed in the context of a request immediately before execution, however a GraphQL service may execute a request without explicitly @@ -109,7 +109,7 @@ input FindDogInput { GraphQL execution will only consider the executable definitions Operation and Fragment. Type system definitions and extensions are not executable, and are not -considered during _execution_. +considered during _operation execution_. To avoid ambiguity, a document containing {TypeSystemDefinitionOrExtension} is invalid for execution. @@ -580,9 +580,10 @@ type that is either an Object, Interface or Union type. **Explanatory Text** If multiple field selections with the same _response name_ are encountered -during _execution_, the field and arguments to execute and the resulting value -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 _operation execution_, the field and arguments to execute and the +resulting value 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()} before execution. diff --git a/spec/Section 6 -- Execution.md b/spec/Section 6 -- Execution.md index 86a5e0c13..f0e25ddd1 100644 --- a/spec/Section 6 -- Execution.md +++ b/spec/Section 6 -- Execution.md @@ -47,19 +47,15 @@ document is expected to only contain a single operation. The result of the request is determined by the result of executing this operation according to the "Executing Operations” section below. -The {ExecuteRequest()} algorithm contains the preamble for _execution_, handling -concerns such as determining the operation and coercing the inputs, before -passing the request on to the relevant algorithm for the operation's type which -then performs any other necessary preliminary steps (for example establishing -the source event stream for subscription operations) and then initiates -_execution_. +The {ExecuteRequest()} algorithm performs the preamble for _operation +execution_, completing preliminary steps before invoking the appropriate +algorithm based on the operation's type. These steps include determining the +operation to execute, coercing input values, and, for subscriptions, +establishing the source event stream. -Note: An error raised before _execution_ begins will typically be a _request -error_, and once _execution_ begins will typically be an _execution error_. - -:: We define _execution_ as the process of executing the operation's _root -selection set_ through {ExecuteRootSelectionSet()}, and hence _execution_ begins -when {ExecuteRootSelectionSet()} is called for the first time in a request. +Note: An error raised before _operation execution_ begins will typically be a +_request error_, and once _operation execution_ begins will typically be an +_execution error_. ExecuteRequest(schema, document, operationName, variableValues, initialValue): @@ -73,7 +69,10 @@ ExecuteRequest(schema, document, operationName, variableValues, initialValue): - Return {ExecuteMutation(operation, schema, coercedVariableValues, initialValue)}. - Otherwise if {operation} is a subscription operation: - - Return {Subscribe(operation, schema, coercedVariableValues, initialValue)}. + - Let {sourceStream} be the result of running + {CreateSourceEventStream(operation, schema, coercedVariableValues, + initialValue)}. + - Return {Subscribe(sourceStream, operation, schema, coercedVariableValues)}. GetOperation(document, operationName): @@ -91,7 +90,7 @@ GetOperation(document, operationName): As explained in the Validation section, only operations from documents which pass all validation rules should be executed. If validation errors are known, they should be reported in the list of "errors" in the response and the request -must fail without execution. +must fail without _operation execution_. Typically validation is performed in the context of a request immediately before calling {ExecuteRequest()}, however a GraphQL service may execute a request @@ -109,7 +108,7 @@ result to avoid validating the same document again in the future. If the operation has defined any variables, then the values for those variables need to be coerced using the input coercion rules of variable's declared type. If a _request error_ is encountered during input coercion of variable values, -then the request fails without _execution_. +then the request fails without _operation execution_. CoerceVariableValues(schema, operation, variableValues): @@ -153,6 +152,12 @@ provide a query root operation type. If mutations or subscriptions are supported, it must also provide a mutation or subscription root operation type, respectively. +:: We define _operation execution_ as the process of producing output data from +from a GraphQL operation; thus operation execution begins when the execution +algorithm for that operation type is called: {ExecuteQuery()} for query +operations, {ExecuteMutation()} for mutation operations, and {Subscribe()} for +subscription operations. + ### Query If the operation is a query, the result of the operation is the result of @@ -197,10 +202,8 @@ underlying _source stream_. Executing a subscription operation creates a persistent function on the service that maps an underlying _source stream_ to a returned _response stream_. -Subscribe(subscription, schema, variableValues, initialValue): +Subscribe(sourceStream, subscription, schema, variableValues): -- Let {sourceStream} be the result of running - {CreateSourceEventStream(subscription, schema, variableValues, initialValue)}. - Let {responseStream} be the result of running {MapSourceToResponseEvent(sourceStream, subscription, schema, variableValues)}. @@ -307,6 +310,9 @@ CreateSourceEventStream(subscription, schema, variableValues, initialValue): argumentValues)}. - Return {sourceStream}. +Note: The call to {CreateSourceEventStream()} occurs before _operation +execution_ begins, and thus an error raised here will be a _request error_. + ResolveFieldEventStream(subscriptionType, rootValue, fieldName, argumentValues): - Let {resolver} be the internal function provided by {subscriptionType} for @@ -805,8 +811,8 @@ Any _request error_ raised as a result of input coercion during {CoerceArgumentValues()} should be treated instead as an _execution error_. Note: Variable values are not coerced because they are expected to be coerced by -{CoerceVariableValues()} before _execution_ begins, and valid operations must -only allow usage of variables of appropriate types. +{CoerceVariableValues()} before _operation execution_ begins, and valid +operations must only allow usage of variables of appropriate types. Note: Implementations are encouraged to optimize the coercion of an argument's default value by doing so only once and caching the resulting coerced value. diff --git a/spec/Section 7 -- Response.md b/spec/Section 7 -- Response.md index 8ab1ace95..45462291a 100644 --- a/spec/Section 7 -- Response.md +++ b/spec/Section 7 -- Response.md @@ -24,11 +24,11 @@ An _execution result_ must be map. The _execution result_ must contain an entry with key {"data"}. The value of this entry is described in the "Data" section. -If any errors are raised during _execution_, the _execution result_ must contain -an entry with key {"errors"}. The value of this entry must be a non-empty list -of _execution error_ raised during execution. Each error must be a map as -described in the "Errors" section below. If the request completed without -raising any errors, this entry must not be present. +If any errors are raised during _operation execution_, the _execution result_ +must contain an entry with key {"errors"}. The value of this entry must be a +non-empty list of _execution error_ raised during operation execution. Each +error must be a map as described in the "Errors" section below. If the request +completed without raising any errors, this entry must not be present. Note: When {"errors"} is present in a _execution result_, it may be helpful for it to appear first when serialized to make it more apparent that errors are @@ -115,23 +115,23 @@ found at `["hero", "friends"]`, the hero's first friend at ### Data -The {"data"} entry in the _execution result_ will be the result of the -_execution_ of the requested operation. If the operation was a query, this -output will be an object of the query root operation type; if the operation was -a mutation, this output will be an object of the mutation root operation type. +The {"data"} entry in the _execution result_ will be the result of the request's +_operation execution_. If the operation was a query, this output will be an +object of the query root operation type; if the operation was a mutation, this +output will be an object of the mutation root operation type. The response data is the result of accumulating the resolved result of all response positions during execution. -If an error was raised before _execution_ begins, the _response_ must be a -_request error result_ which will result in no response data. +If an error was raised before _operation execution_ begins, the _response_ must +be a _request error result_ which will result in no response data. -If an error was raised during _execution_ that prevented a valid response, the -{"data"} entry in the response should be `null`. +If an error was raised during _operation execution_ that prevented a valid +response, the {"data"} entry in the response should be `null`. Note: Request errors (including those raised during {ExecuteRequest()}) occur -before _execution_ begins; when a request error is raised the {"data"} entry -should not be present in the result. +before _operation execution_ begins; when a request error is raised the {"data"} +entry should not be present in the result. ### Errors @@ -142,9 +142,11 @@ of data described by the error result format below. **Request Errors** :: A _request error_ is an error raised during a _request_ which results in no -response data. Typically raised before _execution_ begins, a request error may -occur due to a parse grammar or validation error in the _Document_, an inability -to determine which operation to execute, or invalid input values for variables. +response data. Typically raised before _operation execution_ begins, a request +error may occur due to a parse grammar or validation error in the _Document_, an +inability to determine which operation to execute, invalid input values for +variables, or failure to establish a source event stream for a subscription +operation. A request error is typically the fault of the requesting client. From bc5618ca84f8cb2f5920f5e0a134e631069b9357 Mon Sep 17 00:00:00 2001 From: Benjie Gillam Date: Thu, 3 Jul 2025 18:27:51 +0100 Subject: [PATCH 30/31] Slight tweaks for subscriptions --- spec/Section 6 -- Execution.md | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/spec/Section 6 -- Execution.md b/spec/Section 6 -- Execution.md index f0e25ddd1..40093d267 100644 --- a/spec/Section 6 -- Execution.md +++ b/spec/Section 6 -- Execution.md @@ -152,11 +152,10 @@ provide a query root operation type. If mutations or subscriptions are supported, it must also provide a mutation or subscription root operation type, respectively. -:: We define _operation execution_ as the process of producing output data from -from a GraphQL operation; thus operation execution begins when the execution -algorithm for that operation type is called: {ExecuteQuery()} for query -operations, {ExecuteMutation()} for mutation operations, and {Subscribe()} for -subscription operations. +:: The result of a GraphQL request is produced through _operation execution_. +Operation execution begins when the execution algorithm for the operation type +is invoked: {ExecuteQuery()} for query operations, {ExecuteMutation()} for +mutation operations, and {Subscribe()} for subscription operations. ### Query @@ -197,10 +196,10 @@ ExecuteMutation(mutation, schema, variableValues, initialValue): If the operation is a subscription, the result is an _event stream_ called the _response stream_ where each event in the event stream is the result of executing the operation’s _root selection set_ for each new event on an -underlying _source stream_. +underlying _source stream_ established during {ExecuteRequest()}. Executing a subscription operation creates a persistent function on the service -that maps an underlying _source stream_ to a returned _response stream_. +that maps this underlying _source stream_ to a returned _response stream_. Subscribe(sourceStream, subscription, schema, variableValues): From 8606033f1198bb1a794b79630df07fd7c53fd2d4 Mon Sep 17 00:00:00 2001 From: Benjie Gillam Date: Thu, 3 Jul 2025 18:29:03 +0100 Subject: [PATCH 31/31] Clarity --- spec/Section 6 -- Execution.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/Section 6 -- Execution.md b/spec/Section 6 -- Execution.md index 40093d267..3fa54a450 100644 --- a/spec/Section 6 -- Execution.md +++ b/spec/Section 6 -- Execution.md @@ -152,7 +152,7 @@ provide a query root operation type. If mutations or subscriptions are supported, it must also provide a mutation or subscription root operation type, respectively. -:: The result of a GraphQL request is produced through _operation execution_. +:: The result of a GraphQL operation is produced through _operation execution_. Operation execution begins when the execution algorithm for the operation type is invoked: {ExecuteQuery()} for query operations, {ExecuteMutation()} for mutation operations, and {Subscribe()} for subscription operations.