Skip to content

Replace ExecuteSelectionSet with ExecuteGroupedFieldSet #1039

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 14 commits into
base: main
Choose a base branch
from

Conversation

benjie
Copy link
Member

@benjie benjie commented Aug 21, 2023

Essentially this PR raises the concept of "grouped field sets" to be a top-level concept, and replaces the ExecuteSelectionSet method with ExecuteGroupedFieldSet (which essentially just drops the first line of ExecuteSelectionSet, which was responsible for producing the grouped field set to be executed). It then refactors the rest of the spec to accommodate this change, reducing the repetition in ExecuteQuery, ExecuteMutation and ExecuteSubscriptionEvent; and removing MergeSelectionSets (which generated a "virtual" selection set to accomodate the ExecuteSelectionSet method), instead adding a CollectSubfields algorithm which generates a grouped field set directly, ready for execution.

I extracted this common refactoring from a number of my attempts to write spec changes for the @defer and @stream directives - it turns out that this refactoring of the spec was always needed as a base for my changes. Similarly, @yaacovCR found similar in his attempts to address this same problem, and raised #999 extracted from his solution. This PR was introduced independently of #999 (other than using the CollectSubfields algorithm name) however there is significant alignment, so @yaacovCR suggested that I raise it as an alternative PR.

It may be easier to review this PR in "split" view rather than "unified" view.

@netlify
Copy link

netlify bot commented Aug 21, 2023

Deploy Preview for graphql-spec-draft ready!

Name Link
🔨 Latest commit d68df95
🔍 Latest deploy log https://app.netlify.com/sites/graphql-spec-draft/deploys/680132705e410c0007997814
😎 Deploy Preview https://deploy-preview-1039--graphql-spec-draft.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify site configuration.

@benjie
Copy link
Member Author

benjie commented Feb 5, 2024

(Rebased on main)

@benjie benjie force-pushed the benjie/incremental-common branch from b342b58 to a52310e Compare September 19, 2024 11:51
@benjie
Copy link
Member Author

benjie commented Sep 19, 2024

(Rebased on main)

@yaacovCR

This comment was marked as resolved.

@benjie

This comment was marked as resolved.

@yaacovCR

This comment was marked as resolved.

@Keweiqu
Copy link
Contributor

Keweiqu commented Oct 3, 2024

@benjie we need to formally define "grouped field set" in Section 2 "Language". You might have done so in a prior PR that I missed. Here is the link to us defining "section sets" in section 2 https://spec.graphql.org/draft/#sec-Selection-Sets

JoviDeCroock added a commit to JoviDeCroock/graphql-spec that referenced this pull request Feb 15, 2025
@benjie benjie added the ✏️ Editorial PR is non-normative or does not influence implementation label Mar 6, 2025
@benjie
Copy link
Member Author

benjie commented Mar 6, 2025

This is up-to-date and ready-to-go again.

@yaacovCR or @JoviDeCroock; please can you confirm this latest text still aligns with GraphQL.js.

@Keweiqu Since a "grouped field set" is only part of execution and doesn't have an expression in the language I've not defined it in chapter 2, but in chapter 6 instead. (Here's where the current spec first introduces the term: https://spec.graphql.org/draft/#sel-FANRFABAB5EnhJ )

Copy link
Contributor

@mjmahone mjmahone left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nits because we agreed to name things responseName instead of responseKey

leebyron added a commit that referenced this pull request Apr 17, 2025
* Consistently use 'response key' not 'response name'

* Extract definition of response key from #1039

* Utilise definition

* response key -> response name

* Fix names in algorithms

* latest changes

---------

Co-authored-by: Lee Byron <[email protected]>
@leebyron
Copy link
Collaborator

@benjie this change looks good but has some conflicts with the other changes pulled out and recently merged. Want to rebase and fix? Then I'll get this one merged

Comment on lines 377 to 382
:: 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.
Copy link
Member Author

@benjie benjie Apr 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are the only lines that have actually been changed in Field Collection, everything else was just moved.

(To prove this, take the current Field Collection text and paste it over this PR, the diff should render as:

diff --git i/spec/Section 6 -- Execution.md w/spec/Section 6 -- Execution.md
index f81e38d..549930f 100644
--- i/spec/Section 6 -- Execution.md	
+++ w/spec/Section 6 -- Execution.md	
@@ -374,12 +374,11 @@ serial):
 
 ### 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.
+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 name_ (the alias if defined, otherwise the field
+name). This ensures all fields with the same response name (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`:

)

@benjie
Copy link
Member Author

benjie commented Apr 17, 2025

@leebyron I opted for a merge instead as it was less work, this is now good to review again.

(I diffed the diffs and only the expected changes have come through, I also searched for "response key" and /^key/ to ensure no older keys still exist. This also addresses Matt's feedback I think.)

Copy link
Collaborator

@leebyron leebyron left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Really love this cleanup, much closer to GraphQL.js

@leebyron
Copy link
Collaborator

@benjie I took a pass at some re-ordering of the content here, I'd be curious for your feedback.

Also, I think "Grouped Field Set" has become a confusing name. Ideally this term "Collect" is what we use for this concept (instead of "group") and it's a bit weird this is called a "Set" when in fact its type is Map<string, List<Field>>. What do you think about renaming this?

"Collected Field Map"?

@leebyron leebyron force-pushed the benjie/incremental-common branch from 553dc1a to d68df95 Compare April 17, 2025 16:55
@benjie

This comment was marked as outdated.

@benjie
Copy link
Member Author

benjie commented Apr 17, 2025

Scratch that. I'd be happy with "Collected Fields Map", but I think it's important "field" is plural, either via fields or via list or set.

:: A collected fields map 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).

Executing a Collected Fields Map

To execute a collected fields map, 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.

Each entry in the collected fields map represents a response name which produces
an entry into a result map.

ExecuteCollectedFieldsMap(collectedFieldsMap, objectType, objectValue,
variableValues):

  • Initialize {resultMap} to an empty ordered map.
  • For each {collectedFieldsMap} as {responseName} and {fields}:
    • Let {fieldName} be the name of the first entry in {fields}. Note: This value
      is unaffected if an alias is used.
    • Let {fieldType} be the return type defined for the field {fieldName} of
      {objectType}.
    • If {fieldType} is defined:
      • Let {responseValue} be {ExecuteField(objectType, objectValue, fieldType,
        fields, variableValues)}.
      • Set {responseValue} as the value for {responseName} in {resultMap}.
  • Return {resultMap}.

Copy link
Member Author

@benjie benjie left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the edits @leebyron! I'm generally in favour, but have a few suggestions.

Comment on lines +354 to +355
executed, then of those all subfields are collected, then each executed. This
process continues until there are no more subfields to collect and execute.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be interpreted as a breadth-first execution (completing each layer before moving on to the next), but we actually execute depth first via value completion (assuming every field resolves synchronously).

Here's a small edit to avoid this misinterpretation

Suggested change
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.

Comment on lines +359 to +360
:: 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.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 Very happy to have this defined!

Suggested change
:: 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.
:: A _root selection set_ is the top level _selection set_ provided by a GraphQL
operation. A root selection set always selects from a _root operation type_.


- Let {groupedFieldSet} be the result of {CollectFields(objectType,
selectionSet, variableValues)}.
- Let {data} be the result of running {ExecuteGroupedFieldSet(groupedFieldSet,
objectType, initialValue, variableValues)} _serially_ if {executionMode} is
{"serial"}, otherwise _normally_).
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should restore the parenthesis, they add clarity to what "normally" means.

Suggested change
{"serial"}, otherwise _normally_).
{"serial"}, otherwise _normally_ (allowing parallelization)).

Comment on lines +392 to +394
:: 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).
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we choose not to rename "grouped field set", we should at least define it in terms of a set:

Suggested change
:: 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 an
ordered set of selected fields that share that _response name_ (the field alias
if defined, otherwise the field's name).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we accept this, we should also make a few other changes such as:

-Initialize groupedFields to an empty ordered map of lists.
+Initialize groupedFields to an empty ordered map of ordered sets.

Comment on lines +497 to +500
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.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Text like this already exists in the spec as it stands today, but it is confusing. The "in parallel" sounds like it applies to serial versus parallel execution, but it doesn't since this still holds for mutation fields. Further the "same name" is also not quite right, because it's the response name that matters, not the actual field name - { cat: pet(id: 1) { name } dog: pet(id: 2) { age } } would not merge selection sets.

The thing is, we don't execute a field (in the GraphQL request document AST sense1 - i.e. a "field selection") we actually execute a field set (i.e. a "set of field selections").

How about:

Suggested change
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.

Footnotes

  1. We do execute a field in the GraphQL schema sense. Using "field" for both of these is quite confusing, I find. Maybe we should be more careful to use "field selection" rather than "field".

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
✏️ Editorial PR is non-normative or does not influence implementation
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants