Skip to content
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

Incremental Delivery spec draft #1110

Open
wants to merge 41 commits into
base: main
Choose a base branch
from
Open
Changes from 1 commit
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
7ff9d0b
Extract common logic from ExecuteQuery, ExecuteMutation and ExecuteSu…
benjie Apr 28, 2023
8f4168b
Change ExecuteSelectionSet to ExecuteGroupedFieldSet
benjie Apr 28, 2023
69de3fd
Correct reference to MergeSelectionSets
benjie Aug 21, 2023
667364a
moves Field Collection section earlier
yaacovCR Feb 15, 2024
bb23a1b
Introduce `@defer` directive
yaacovCR Feb 15, 2024
65be49a
refactor a few lines out of YieldSubsequentResults
yaacovCR Jun 13, 2024
12513e1
add a word or two about which child nodes are being promoted
yaacovCR Jun 18, 2024
7284e2d
be more graphy
yaacovCR Jun 18, 2024
3fd7b90
fix timing
yaacovCR Jun 19, 2024
d3ab7a3
reuse function
yaacovCR Jun 19, 2024
853b031
fix
yaacovCR Jun 19, 2024
57d6193
rename BuildGraph to GraphFromRecords
yaacovCR Jun 19, 2024
fe42e8c
reword recursive abort case
yaacovCR Jun 19, 2024
d8966dc
bring BuildFieldPlan in line with implementation
yaacovCR Jul 17, 2024
136afea
rename "deferred grouped field set record" to "execution group"
yaacovCR Jul 17, 2024
f11f956
rename ExecuteExecutionGroup to CollectExecutionGroup
yaacovCR Jul 17, 2024
5e0a10a
properly initialize deferUsages with their parents
yaacovCR Jul 18, 2024
5490ed1
move Field Collection back to where it was
yaacovCR Jul 18, 2024
22dfbb8
f
yaacovCR Jul 18, 2024
b80d53b
use fieldDetailsList consistently
yaacovCR Jul 18, 2024
66536f4
add info re: data structures
yaacovCR Jul 18, 2024
a88da21
rename FieldPlan to ExecutionPlan
yaacovCR Jul 20, 2024
741605b
path => label
yaacovCR Jul 24, 2024
7251c7b
add missing arguments
yaacovCR Jul 25, 2024
2d121f1
add missing return value
yaacovCR Jul 25, 2024
c2d83a0
fix some renaming around CollectExecutionGroups and ExecuteExecutionG…
yaacovCR Jul 25, 2024
ccc26f2
Correct argument name
yaacovCR Aug 26, 2024
879818f
clarify errors from ExecuteExecutionPlan
yaacovCR Aug 26, 2024
a020ea1
add initial versions of explanations for the algorithms in the "Execu…
yaacovCR Aug 26, 2024
a6c164d
add subheadings
yaacovCR Sep 5, 2024
f15235a
adjust heading
yaacovCR Sep 6, 2024
50f644d
Initialize graph
yaacovCR Sep 6, 2024
afe40cd
adjust YieldSubsequentResults algorithm per review
yaacovCR Sep 6, 2024
606a6f1
reuse GetIncrementalResult() for the error case
yaacovCR Sep 6, 2024
f44deb5
add descriptions and fix bug within GetNewRootNodes, it needs the old…
yaacovCR Sep 6, 2024
f8f6f35
finish addressing review comments
yaacovCR Sep 6, 2024
4f8e668
add missing word
yaacovCR Sep 6, 2024
89983ce
Add Response Section for defer/stream (#4)
robrichard Sep 18, 2024
d40130d
Add directives and validation sections (#5)
robrichard Sep 18, 2024
f7e9124
Add examples to Response section
robrichard Nov 1, 2024
8e9cbcb
skip deferred fragment spread when already in visited fragments
robrichard Jan 9, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
add descriptions and fix bug within GetNewRootNodes, it needs the old…
… root nodes before the graph was adjusted
yaacovCR authored and robrichard committed Nov 1, 2024
commit f44deb5ca01d62c81bdd6e50753b86d8db1b8099
91 changes: 77 additions & 14 deletions spec/Section 6 -- Execution.md
Original file line number Diff line number Diff line change
@@ -354,14 +354,50 @@ incremental portion of the Execution Plan.
### Yielding Incremental Results

The procedure for yielding incremental results is specified by the
{YieldIncrementalResults()} algorithm.
{YieldIncrementalResults()} algorithm. The incremental state is stored within a
graph, with root nodes representing the currently pending delivery groups.

For example, given the following operation:

```graphql example
{
...SlowFragment @defer
fastField
}

fragment SlowFragment on Query {
...SlowestFragment @defer
slowField
}

fragment SlowestFragment on Query {
slowestField
}
```

A valid GraphQL executor deferring `SlowFragment` must include a `pending` entry
to that effect within the initial result, while the `pending` entry for
`SlowestFragment` should be delivered together with `SlowFragment`.

Delivery group nodes may have three different types of child nodes:

1. Other delivery group nodes, i.e. the node representing `SlowFragment` should
have a child node representing `SlowestFragment`.
2. Pending incremental data nodes, i.e. the node for `SlowFragment` should
initially have a node for `slowField`.
3. Completed incremental data nodes, i.e. when `slowField` is completed, the
pending incremental data node for `slowField` should be replaced with a node
representing the completed data.

The {YieldIncrementalResults()} algorithm is responsible for updating the graph
as it yields the incremental results.

YieldIncrementalResults(data, errors, incrementalDataRecords):

- Let {graph} be the result of {GraphFromRecords(incrementalDataRecords)}.
- Let {rootNodes} be the result of {GetNewRootNodes(graph)}.
- Update {graph} to the subgraph rooted at nodes in {rootNodes}.
- Yield the result of {GetInitialResult(data, errors, pendingResults)}.
- Yield the result of {GetInitialResult(data, errors, rootNodes)}.
- For each completed child Pending Incremental Data node of a root node in
{graph}:
- Let {incrementalDataRecord} be the Pending Incremental Data for that node;
@@ -373,7 +409,7 @@ YieldIncrementalResults(data, errors, incrementalDataRecords):
- Append {GetCompletedEntry(parent, errors)} to {completed}.
- Remove {node} and all of its descendant nodes from {graph}, except for
any descendant Incremental Data Record nodes with other parents.
- Yield the result of {GetIncrementalResult(graph, completed)}.
- Yield the result of {GetSubsequentResult(graph, completed)}.
- Continue to the next completed Pending Incremental Data node.
- Replace {node} in {graph} with a new node corresponding to the Completed
Incremental Data for {result}.
@@ -397,11 +433,11 @@ YieldIncrementalResults(data, errors, incrementalDataRecords):
- Append {GetCompletedEntry(completedDeferredFragment)} to {completed}.
- Remove {completedDeferredFragment} from {graph}, promoting its child
Deferred Fragment nodes to root nodes.
- Let {newRootNodes} be the result of {GetNewRootNodes(graph)}.
- Let {newRootNodes} be the result of {GetNewRootNodes(graph, rootNodes)}.
- Add all nodes in {newRootNodes} to {rootNodes}.
- Update {graph} to the subgraph rooted at nodes in {rootNodes}.
- Let {pending} be the result of {GetPendingEntry(newRootNodes)}.
- Yield the result of {GetIncrementalResult(graph, incremental, completed,
- Yield the result of {GetSubsequentResult(graph, incremental, completed,
pending)}.
- Complete this incremental result stream.

@@ -418,35 +454,55 @@ GraphFromRecords(incrementalDataRecords, graph):
to {newGraph}, or the {parent} is not defined.
- Return {newGraph}.

GetNewRootNodes(graph):
The {GetNewRootNodes()} algorithm is responsible for determining the new root
nodes that must be reported as pending. Any delivery groups without any
execution groups should not be reported as pending, and any child delivery
groups for these "empty" delivery groups should be reported as pending in their
stead.

GetNewRootNodes(graph, oldRootNodes):

- Initialize {newPendingResults} to the empty set.
- Initialize {newRootNodes} to the empty set.
- Initialize {rootNodes} to the set of root nodes in {graph}.
- For each {rootNode} of {rootNodes}:
- If {rootNode} has no children Pending Incremental Data nodes:
- Let {children} be the set of child Deferred Fragment nodes of {rootNode}.
- Add each of the nodes in {children} to {rootNodes}.
- Continue to the next {rootNode} of {rootNodes}.
- Add {rootNode} to {newPendingResults}.
- Return {newPendingResults}.
- If {oldRootNodes} does not contain {rootNode}, add {rootNode} to
{newRootNodes}.
- Return {newRootNodes}.

Formatting of the initial result is defined by the {GetInitialResult()}
algorithm. It will only be called when there is an incremental result stream,
and so `hasNext` will always be set to {true}.

GetInitialResult(data, errors, pendingResults):

- Let {pending} be the result of {GetPendingEntry(pendingResults)}.
- Let {hasNext} be {true}.
- Return an unordered map containing {data}, {errors}, {pending}, and {hasNext}.

GetPendingEntry(pendingResults):
Formatting the `pending` of initial and subsequentResults is defined by the
{GetPendingEntry()} algorithm. Given a set of new root nodes added to the graph,
{GetPendingEntry()} returns a list of formatted `pending` entries.

GetPendingEntry(newRootNodes):

- Initialize {pending} to an empty list.
- For each {pendingResult} of {pendingResult}:
- Let {id} be a unique identifier for {pendingResult}.
- Let {path} and {label} be the corresponding entries on {pendingResult}.
- For each {newRootNode} of {newRootNodes}:
- Let {id} be a unique identifier for {newRootNode}.
- Let {path} and {label} be the corresponding entries on {newRootNode}.
- Let {pendingEntry} be an unordered map containing {id}, {path}, and {label}.
- Append {pendingEntry} to {pending}.
- Return {pending}.

GetIncrementalResult(graph, completed, incremental, pending):
Formatting of subsequent incremental results is defined by the
{GetSubsequentResult()} algorithm. Given the current graph, and any `completed`,
`incremental`, and `pending` entries, it produces an appropriately formatted
subsequent incremental response.

GetSubsequentResult(graph, completed, incremental, pending):

- Let {hasNext} be {false} if {graph} is empty, otherwise, {true}.
- Let {incrementalResult} be an unordered map containing {hasNext}.
@@ -458,6 +514,10 @@ GetIncrementalResult(graph, completed, incremental, pending):
- Set the corresponding entry on {incrementalResult} to {pending}.
- Return {incrementalResult}.

Formatting of subsequent incremental results is defined by the
{GetSubsequentResult()} algorithm. Execution groups are tagged with the `id` and
`subPath` combination optimized to produce the shortest `subPath`.

GetIncrementalEntry(incrementalDataRecord, graph):

- Let {deferredFragments} be the Deferred Fragments incrementally completed by
@@ -473,6 +533,9 @@ GetIncrementalEntry(incrementalDataRecord, graph):
- Let {id} be the unique identifier for {bestDeferredFragment}.
- Return an unordered map containing {id}, {subPath}, {data}, and {errors}.

Formatting of completed incremental results is defined by the
{GetCompletedEntry()} algorithm.

GetCompletedEntry(pendingResult, errors):

- Let {id} be the unique identifier for {pendingResult}.