Skip to content

Commit

Permalink
introduce CollectRootFields and CollectSubfields
Browse files Browse the repository at this point in the history
Rather than merging subSelectionSets of a field set using MergeSelectionSets and then calling CollectFields, introducing CollectSubfields allows the field set's groupedSubfieldSet to be calculated directly.

This may be helpful if the specification were ever to be altered such that additional state beyond the current selection set were to be required to calculate the response, i.e. if it were to be required to know the originating selectionSet of a given field within the fieldSet for determining when to communicate a reference signal.

See graphql#998 (comment)
  • Loading branch information
yaacovCR committed Nov 6, 2022
1 parent 3aa021f commit 48a2e99
Show file tree
Hide file tree
Showing 2 changed files with 54 additions and 48 deletions.
2 changes: 1 addition & 1 deletion spec/Section 5 -- Validation.md
Expand Up @@ -463,7 +463,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 {CollectFields()}.

For simple hand-written GraphQL, this rule is obviously a clear developer error,
however nested fragments can make this difficult to detect manually.
Expand Down
100 changes: 53 additions & 47 deletions spec/Section 6 -- Execution.md
Expand Up @@ -131,7 +131,9 @@ 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,
- Let {groupedFieldSet} be the result of {CollectRootFields(queryType,
selectionSet, variableValues)}.
- Let {data} be the result of running {ExecuteFieldSet(groupedFieldSet,
queryType, initialValue, variableValues)} _normally_ (allowing
parallelization).
- Let {errors} be the list of all _field error_ raised while executing the
Expand All @@ -153,7 +155,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 {data} be the result of running {ExecuteSelectionSet(selectionSet,
- Let {groupedFieldSet} be the result of {CollectRootFields(mutationType,
selectionSet, variableValues)}.
- Let {data} be the result of running {ExecuteFieldSet(groupedFieldSet,
mutationType, initialValue, variableValues)} _serially_.
- Let {errors} be the list of all _field error_ raised while executing the
selection set.
Expand Down Expand Up @@ -256,7 +260,7 @@ CreateSourceEventStream(subscription, schema, variableValues, initialValue):
- Let {subscriptionType} be the root Subscription type in {schema}.
- Assert: {subscriptionType} is an Object type.
- Let {selectionSet} be the top level Selection Set in {subscription}.
- Let {groupedFieldSet} be the result of {CollectFields(subscriptionType,
- Let {groupedFieldSet} be the result of {CollectRootFields(subscriptionType,
selectionSet, variableValues)}.
- If {groupedFieldSet} does not have exactly one entry, raise a _request error_.
- Let {fields} be the value of the first entry in {groupedFieldSet}.
Expand Down Expand Up @@ -301,7 +305,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 {data} be the result of running {ExecuteSelectionSet(selectionSet,
- Let {groupedFieldSet} be the result of {CollectRootFields(subscriptionType,
selectionSet, variableValues)}.
- Let {data} be the result of running {ExecuteFieldSet(groupedFieldSet,
subscriptionType, initialValue, variableValues)} _normally_ (allowing
parallelization).
- Let {errors} be the list of all _field error_ raised while executing the
Expand All @@ -322,20 +328,17 @@ Unsubscribe(responseStream):

- Cancel {responseStream}

## Executing Selection Sets
## Executing Field Sets

To execute a 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.
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):
ExecuteFieldSet(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
Expand Down Expand Up @@ -490,10 +493,12 @@ The depth-first-search order of the field groups produced by {CollectFields()}
is maintained through execution, ensuring that fields appear in the executed
response in a stable and predictable order.

CollectFields(objectType, selectionSet, variableValues, visitedFragments):
CollectFields(objectType, selectionSet, variableValues, groupedFieldSet,
visitedFragments):

- If {groupedFieldSet} is not provided, initialize it an empty ordered map of
lists.
- 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.
Expand All @@ -508,7 +513,7 @@ CollectFields(objectType, selectionSet, variableValues, visitedFragments):
- If {selection} is a {Field}:
- Let {responseKey} be the response key of {selection} (the alias if
defined, otherwise the field name).
- Let {groupForResponseKey} be the list in {groupedFields} for
- Let {groupForResponseKey} be the list in {groupedFieldSet} for
{responseKey}; if no such list exists, create it as an empty list.
- Append {selection} to the {groupForResponseKey}.
- If {selection} is a {FragmentSpread}:
Expand All @@ -524,31 +529,16 @@ CollectFields(objectType, selectionSet, variableValues, visitedFragments):
- If {DoesFragmentTypeApply(objectType, fragmentType)} is false, continue
with the next {selection} in {selectionSet}.
- Let {fragmentSelectionSet} be the top-level selection set of {fragment}.
- Let {fragmentGroupedFieldSet} be the result of calling
{CollectFields(objectType, fragmentSelectionSet, variableValues,
visitedFragments)}.
- 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}.
- Call {CollectFields(objectType, fragmentSelectionSet, variableValues,
groupedFieldSet, visitedFragments)}.
- 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}.
- Call {CollectFields(objectType, fragmentSelectionSet, variableValues,
groupedFieldSet, visitedFragments)}.

DoesFragmentTypeApply(objectType, fragmentType):

Expand All @@ -565,6 +555,31 @@ DoesFragmentTypeApply(objectType, fragmentType):
Note: The steps in {CollectFields()} evaluating the `@skip` and `@include`
directives may be applied in either order since they apply commutatively.

### Root Field Collection

Root field collection processes the operation's top-level selection set:

CollectRootFields(rootType, operationSelectionSet, variableValues):

- Initialize {groupedFieldSet} to an empty ordered map of lists.
- Call {CollectFields(rootType, operationSelectionSet, groupedFieldSet)}.
- Return {groupedFieldSet}.

### Object Subfield Collection

Root field collection processes the operation's top-level selection set:

CollectSubfields(objectType, groupedFieldSet, variableValues):

- Initialize {groupedSubfieldSet} to an empty ordered map of lists.
- For each {groupedFieldSet} as {responseKey} and {fields}:
- For each {field} in {fields}:
- Let {fieldSelectionSet} be the selection set of {field}.
- If {fieldSelectionSet} is null or empty, continue to the next field.
- Call {CollectFields(objectType, fieldSelectionSet, variableValues,
groupedSubfieldSet)}.
- Return {groupedSubfieldSet}.

## Executing Fields

Each field requested in the grouped field set that is defined on the selected
Expand Down Expand Up @@ -692,8 +707,8 @@ 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 {groupedSubfieldSet} be the result of calling {CollectSubfields(fields)}.
- Return the result of evaluating {ExecuteFieldSet(groupedSubfieldSet,
objectType, result, variableValues)} _normally_ (allowing for
parallelization).

Expand Down Expand Up @@ -761,15 +776,6 @@ 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):

- Let {selectionSet} be an empty list.
- 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}.

### Handling Field Errors

A _field error_ is an error raised from a particular field during value
Expand Down

0 comments on commit 48a2e99

Please sign in to comment.