Skip to content

Commit dee2704

Browse files
[Fusion] Fix mutation execution backlog dropping batched follow-up operations (#9847)
1 parent a43a58c commit dee2704

3 files changed

Lines changed: 376 additions & 1 deletion

File tree

src/HotChocolate/Fusion/src/Fusion.Execution/Execution/ExecutionState.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ public void FillBacklog(IOperationPlan plan)
9595
{
9696
// we skip root nodes as they are enqueued by the algorithm
9797
// one by one.
98-
if (node.Dependencies.Length == 0)
98+
if (node.Dependencies.Length == 0 && node.OptionalDependencies.Length == 0)
9999
{
100100
continue;
101101
}

src/HotChocolate/Fusion/test/Fusion.AspNetCore.Tests/MutationTests.cs

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,104 @@ public async Task Multiple_Mutation()
9595
await MatchSnapshotAsync(gateway, request, result);
9696
}
9797

98+
[Fact]
99+
public async Task Mutation_Root_With_Batched_Node_Lookups_Executes_Followups()
100+
{
101+
// arrange
102+
using var serverA = CreateSourceSchema(
103+
"A",
104+
"""
105+
type Query {
106+
node(id: ID!): Node @lookup @shareable
107+
}
108+
109+
type Mutation {
110+
createReview(input: CreateReviewInput!): Review
111+
}
112+
113+
input CreateReviewInput {
114+
id: ID!
115+
}
116+
117+
interface Node {
118+
id: ID!
119+
}
120+
121+
type Review implements Node {
122+
id: ID!
123+
body: String
124+
author: Author
125+
product: Product
126+
}
127+
128+
type Author implements Node {
129+
id: ID!
130+
}
131+
132+
type Product implements Node {
133+
id: ID!
134+
}
135+
""");
136+
137+
using var serverB = CreateSourceSchema(
138+
"B",
139+
"""
140+
type Query {
141+
node(id: ID!): Node @lookup @shareable
142+
}
143+
144+
interface Node {
145+
id: ID!
146+
}
147+
148+
type Author implements Node {
149+
id: ID!
150+
name: String
151+
}
152+
153+
type Product implements Node {
154+
id: ID!
155+
name: String
156+
}
157+
""");
158+
159+
using var gateway = await CreateCompositeSchemaAsync(
160+
[
161+
("A", serverA),
162+
("B", serverB)
163+
]);
164+
165+
// act
166+
using var client = GraphQLHttpClient.Create(gateway.CreateClient());
167+
168+
var request = new HotChocolate.Transport.OperationRequest(
169+
"""
170+
mutation createReview($input: CreateReviewInput!) {
171+
createReview(input: $input) {
172+
id
173+
body
174+
author { id name }
175+
product { id name }
176+
}
177+
}
178+
""",
179+
variables: new Dictionary<string, object?>
180+
{
181+
["input"] = new Dictionary<string, object?>
182+
{
183+
// Review:1
184+
["id"] = "UmV2aWV3OjE="
185+
}
186+
});
187+
188+
using var result = await client.PostAsync(
189+
request,
190+
new Uri("http://localhost:5000/graphql"));
191+
192+
// assert
193+
await MatchSnapshotAsync(gateway, request, result);
194+
}
195+
98196
public static class SourceSchema1
99197
{
100198
public class Query
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,277 @@
1+
title: Mutation_Root_With_Batched_Node_Lookups_Executes_Followups
2+
request:
3+
document: |
4+
mutation createReview($input: CreateReviewInput!) {
5+
createReview(input: $input) {
6+
id
7+
body
8+
author {
9+
id
10+
name
11+
}
12+
product {
13+
id
14+
name
15+
}
16+
}
17+
}
18+
variables: |
19+
{
20+
"input": {
21+
"id": "UmV2aWV3OjE="
22+
}
23+
}
24+
response:
25+
body: |
26+
{
27+
"data": {
28+
"createReview": {
29+
"id": "UmV2aWV3OjE=",
30+
"body": "Review: UmV2aWV3OjE=",
31+
"author": {
32+
"id": "QXV0aG9yOjI=",
33+
"name": "Author: QXV0aG9yOjI="
34+
},
35+
"product": {
36+
"id": "UHJvZHVjdDoz",
37+
"name": "Product: UHJvZHVjdDoz"
38+
}
39+
}
40+
}
41+
}
42+
sourceSchemas:
43+
- name: A
44+
schema: |
45+
schema {
46+
query: Query
47+
mutation: Mutation
48+
}
49+
50+
type Query {
51+
node(id: ID!): Node @lookup @shareable
52+
}
53+
54+
type Mutation {
55+
createReview(input: CreateReviewInput!): Review
56+
}
57+
58+
type Author implements Node {
59+
id: ID!
60+
}
61+
62+
type Product implements Node {
63+
id: ID!
64+
}
65+
66+
type Review implements Node {
67+
id: ID!
68+
body: String
69+
author: Author
70+
product: Product
71+
}
72+
73+
interface Node {
74+
id: ID!
75+
}
76+
77+
input CreateReviewInput {
78+
id: ID!
79+
}
80+
interactions:
81+
- request:
82+
accept: application/graphql-response+json; charset=utf-8, application/json; charset=utf-8, application/jsonl; charset=utf-8, text/event-stream; charset=utf-8
83+
document: |
84+
mutation createReview_df7efcac_1($input: CreateReviewInput!) {
85+
createReview(input: $input) {
86+
id
87+
body
88+
author {
89+
id
90+
}
91+
product {
92+
id
93+
}
94+
}
95+
}
96+
variables: |
97+
{
98+
"input": {
99+
"id": "UmV2aWV3OjE="
100+
}
101+
}
102+
response:
103+
results:
104+
- |
105+
{
106+
"data": {
107+
"createReview": {
108+
"id": "UmV2aWV3OjE=",
109+
"body": "Review: UmV2aWV3OjE=",
110+
"author": {
111+
"id": "QXV0aG9yOjI="
112+
},
113+
"product": {
114+
"id": "UHJvZHVjdDoz"
115+
}
116+
}
117+
}
118+
}
119+
- name: B
120+
schema: |
121+
schema {
122+
query: Query
123+
}
124+
125+
type Query {
126+
node(id: ID!): Node @lookup @shareable
127+
}
128+
129+
type Author implements Node {
130+
id: ID!
131+
name: String
132+
}
133+
134+
type Product implements Node {
135+
id: ID!
136+
name: String
137+
}
138+
139+
interface Node {
140+
id: ID!
141+
}
142+
interactions:
143+
- request:
144+
accept: application/jsonl; charset=utf-8, text/event-stream; charset=utf-8, application/graphql-response+json; charset=utf-8, application/json; charset=utf-8
145+
kind: OperationBatch
146+
items:
147+
- document: |
148+
query createReview_df7efcac_2($__fusion_1_id: ID!) {
149+
node(id: $__fusion_1_id) {
150+
__typename
151+
... on Product {
152+
name
153+
}
154+
}
155+
}
156+
variables: |
157+
{
158+
"__fusion_1_id": "UHJvZHVjdDoz"
159+
}
160+
- document: |
161+
query createReview_df7efcac_3($__fusion_2_id: ID!) {
162+
node(id: $__fusion_2_id) {
163+
__typename
164+
... on Author {
165+
name
166+
}
167+
}
168+
}
169+
variables: |
170+
{
171+
"__fusion_2_id": "QXV0aG9yOjI="
172+
}
173+
response:
174+
contentType: application/jsonl; charset=utf-8
175+
results:
176+
- |
177+
{
178+
"data": {
179+
"node": {
180+
"__typename": "Product",
181+
"name": "Product: UHJvZHVjdDoz"
182+
}
183+
}
184+
}
185+
- |
186+
{
187+
"data": {
188+
"node": {
189+
"__typename": "Author",
190+
"name": "Author: QXV0aG9yOjI="
191+
}
192+
}
193+
}
194+
operationPlan:
195+
operation:
196+
- document: |
197+
mutation createReview($input: CreateReviewInput!) {
198+
createReview(input: $input) {
199+
id
200+
body
201+
author {
202+
id
203+
id @fusion__requirement
204+
name
205+
}
206+
product {
207+
id
208+
id @fusion__requirement
209+
name
210+
}
211+
}
212+
}
213+
name: createReview
214+
hash: df7efcac5bc495273d4ff34bb964367c
215+
searchSpace: 1
216+
expandedNodes: 2
217+
nodes:
218+
- id: 1
219+
type: Operation
220+
schema: A
221+
operation: |
222+
mutation createReview_df7efcac_1($input: CreateReviewInput!) {
223+
createReview(input: $input) {
224+
id
225+
body
226+
author {
227+
id
228+
}
229+
product {
230+
id
231+
}
232+
}
233+
}
234+
forwardedVariables:
235+
- input
236+
- id: 2
237+
type: Operation
238+
schema: B
239+
operation: |
240+
query createReview_df7efcac_2($__fusion_1_id: ID!) {
241+
node(id: $__fusion_1_id) {
242+
__typename
243+
... on Product {
244+
name
245+
}
246+
}
247+
}
248+
source: $.node<Product>
249+
target: $.createReview.product
250+
batchingGroupId: 2
251+
requirements:
252+
- name: __fusion_1_id
253+
selectionMap: >-
254+
id
255+
dependencies:
256+
- id: 1
257+
- id: 3
258+
type: Operation
259+
schema: B
260+
operation: |
261+
query createReview_df7efcac_3($__fusion_2_id: ID!) {
262+
node(id: $__fusion_2_id) {
263+
__typename
264+
... on Author {
265+
name
266+
}
267+
}
268+
}
269+
source: $.node<Author>
270+
target: $.createReview.author
271+
batchingGroupId: 2
272+
requirements:
273+
- name: __fusion_2_id
274+
selectionMap: >-
275+
id
276+
dependencies:
277+
- id: 1

0 commit comments

Comments
 (0)