Skip to content

Commit ecb937c

Browse files
[Fusion] Fix null being returned for batch item (#8383)
1 parent e6ebe5e commit ecb937c

File tree

3 files changed

+346
-69
lines changed

3 files changed

+346
-69
lines changed

src/HotChocolate/Fusion/src/Core/Execution/Nodes/ResolveByKeyBatch.cs

Lines changed: 19 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -142,12 +142,12 @@ private static void InitializeRequests(FusionExecutionContext context, List<Exec
142142
private void ProcessResult(
143143
FusionExecutionContext context,
144144
GraphQLResponse response,
145-
BatchExecutionState[] batchExecutionState,
145+
List<BatchExecutionState> batchExecutionState,
146146
string subgraphName)
147147
{
148148
var result = UnwrapResult(response, Requires);
149-
ref var batchState = ref MemoryMarshal.GetArrayDataReference(batchExecutionState);
150-
ref var end = ref Unsafe.Add(ref batchState, batchExecutionState.Length);
149+
ref var batchState = ref MemoryMarshal.GetReference(CollectionsMarshal.AsSpan(batchExecutionState));
150+
ref var end = ref Unsafe.Add(ref batchState, batchExecutionState.Count);
151151
var pathLength = Path.Length;
152152
var first = true;
153153

@@ -195,13 +195,13 @@ private void ProcessResult(
195195
}
196196

197197
private static Dictionary<string, IValueNode> BuildVariables(
198-
BatchExecutionState[] batchExecutionState,
198+
List<BatchExecutionState> batchExecutionState,
199199
string[] requires,
200200
Dictionary<string, ITypeNode> argumentTypes)
201201
{
202202
var first = batchExecutionState[0];
203203

204-
if (batchExecutionState.Length == 1)
204+
if (batchExecutionState.Count == 1)
205205
{
206206
return first.VariableValues;
207207
}
@@ -210,8 +210,8 @@ private static Dictionary<string, IValueNode> BuildVariables(
210210
var uniqueState = new List<BatchExecutionState>();
211211
var processed = new HashSet<string>();
212212

213-
ref var batchState = ref MemoryMarshal.GetArrayDataReference(batchExecutionState);
214-
ref var end = ref Unsafe.Add(ref batchState, batchExecutionState.Length);
213+
ref var batchState = ref MemoryMarshal.GetReference(CollectionsMarshal.AsSpan(batchExecutionState));
214+
ref var end = ref Unsafe.Add(ref batchState, batchExecutionState.Count);
215215

216216
while (Unsafe.IsAddressLessThan(ref batchState, ref end))
217217
{
@@ -339,44 +339,38 @@ private static JsonElement LiftData(JsonElement data, string[] path)
339339
return current;
340340
}
341341

342-
private static BatchExecutionState[] CreateBatchBatchState(List<ExecutionState> executionState, string[] requires)
342+
private static List<BatchExecutionState> CreateBatchBatchState(List<ExecutionState> executionState, string[] requires)
343343
{
344-
var batchExecutionState = new BatchExecutionState[executionState.Count];
344+
var batchExecutionState = new List<BatchExecutionState>(executionState.Count);
345345

346346
ref var state = ref MemoryMarshal.GetReference(CollectionsMarshal.AsSpan(executionState));
347-
ref var batchState = ref MemoryMarshal.GetArrayDataReference(batchExecutionState);
348347
ref var end = ref Unsafe.Add(ref state, executionState.Count);
349348

350349
if (requires.Length == 1)
351350
{
352351
while (Unsafe.IsAddressLessThan(ref state, ref end))
353352
{
354-
var key = FormatKeyValue(state.VariableValues[requires[0]]);
355-
batchState = new BatchExecutionState(key, state);
353+
if (state.VariableValues.ContainsKey(requires[0]))
354+
{
355+
var key = FormatKeyValue(state.VariableValues[requires[0]]);
356+
var batchState = new BatchExecutionState(key, state);
357+
358+
batchExecutionState.Add(batchState);
359+
}
356360

357361
state = ref Unsafe.Add(ref state, 1)!;
358-
batchState = ref Unsafe.Add(ref batchState, 1)!;
359362
}
360363
}
361364
else
362365
{
363-
var keyBuilder = new StringBuilder();
364-
365366
while (Unsafe.IsAddressLessThan(ref state, ref end))
366367
{
367-
ref var key = ref MemoryMarshal.GetArrayDataReference(requires);
368-
ref var keyEnd = ref Unsafe.Add(ref key, requires.Length);
369-
370-
while (Unsafe.IsAddressLessThan(ref key, ref keyEnd))
371-
{
372-
keyBuilder.Append(FormatKeyValue(state.VariableValues[key]));
373-
key = ref Unsafe.Add(ref key, 1)!;
374-
}
368+
var lastKey = requires[^1];
369+
var batchState = new BatchExecutionState(lastKey, state);
375370

376-
batchState = new BatchExecutionState(key, state);
371+
batchExecutionState.Add(batchState);
377372

378373
state = ref Unsafe.Add(ref state, 1)!;
379-
batchState = ref Unsafe.Add(ref batchState, 1)!;
380374
}
381375
}
382376

src/HotChocolate/Fusion/test/Core.Tests/DemoIntegrationTests.cs

Lines changed: 133 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -134,21 +134,21 @@ type Product implements Node {
134134
using var subgraphs = new TestSubgraphCollection(output, [subgraphA, subgraphB]);
135135
var executor = await subgraphs.GetExecutorAsync();
136136
var request = Parse("""
137-
query {
138-
productsA {
139-
id
140-
name
141-
price
142-
reviewCount
143-
}
144-
productsB {
145-
id
146-
name
147-
price
148-
reviewCount
149-
}
150-
}
151-
""");
137+
query {
138+
productsA {
139+
id
140+
name
141+
price
142+
reviewCount
143+
}
144+
productsB {
145+
id
146+
name
147+
price
148+
reviewCount
149+
}
150+
}
151+
""");
152152

153153
// act
154154
var result = await executor.ExecuteAsync(
@@ -260,10 +260,10 @@ public async Task Authors_And_Reviews_Query_GetUserReviews_Report_Cost()
260260
// act
261261
var fusionGraph = await new FusionGraphComposer(logFactory: _logFactory)
262262
.ComposeAsync(
263-
[
264-
demoProject.Reviews2.ToConfiguration(Reviews2ExtensionWithCostSdl),
265-
demoProject.Accounts.ToConfiguration(AccountsExtensionWithCostSdl)
266-
]);
263+
[
264+
demoProject.Reviews2.ToConfiguration(Reviews2ExtensionWithCostSdl),
265+
demoProject.Accounts.ToConfiguration(AccountsExtensionWithCostSdl)
266+
]);
267267

268268
var executor = await new ServiceCollection()
269269
.AddSingleton(demoProject.HttpClientFactory)
@@ -1656,11 +1656,7 @@ public async Task Forward_Nested_Variables_No_OpName()
16561656
.New()
16571657
.SetDocument(request)
16581658
.SetVariableValues(
1659-
new Dictionary<string, object?>
1660-
{
1661-
{ "id", "UHJvZHVjdDox" },
1662-
{ "first", 1 },
1663-
})
1659+
new Dictionary<string, object?> { { "id", "UHJvZHVjdDox" }, { "first", 1 }, })
16641660
.Build());
16651661

16661662
// assert
@@ -2028,10 +2024,7 @@ public async Task GetFirstPage_With_After_Null()
20282024

20292025
// act
20302026
var fusionGraph = await new FusionGraphComposer(logFactory: _logFactory).ComposeAsync(
2031-
new[]
2032-
{
2033-
demoProject.Appointment.ToConfiguration(),
2034-
},
2027+
new[] { demoProject.Appointment.ToConfiguration(), },
20352028
new FusionFeatureCollection(FusionFeatures.NodeField));
20362029

20372030
var executor = await new ServiceCollection()
@@ -2232,6 +2225,114 @@ type ResaleSurveyFeedback {
22322225
await snapshot.MatchMarkdownAsync();
22332226
}
22342227

2228+
[Fact]
2229+
public async Task BatchExecutionState_With_Multiple_Variable_Values_Some_Items_Null()
2230+
{
2231+
// arrange
2232+
var subgraphA = await TestSubgraph.CreateAsync(
2233+
"""
2234+
type Query {
2235+
node(id: ID!): Node
2236+
nodes(ids: [ID!]!): [Node]!
2237+
}
2238+
2239+
interface Node {
2240+
id: ID!
2241+
}
2242+
2243+
type User implements Node {
2244+
id: ID!
2245+
displayName: String!
2246+
}
2247+
""");
2248+
var subgraphB = await TestSubgraph.CreateAsync(
2249+
"""
2250+
type Query {
2251+
node(id: ID!): Node
2252+
nodes(ids: [ID!]!): [Node]! @null(atIndex: 1)
2253+
userBySlug(slug: String!): User
2254+
}
2255+
2256+
interface Node {
2257+
id: ID!
2258+
}
2259+
2260+
type User implements Node {
2261+
relativeUrl: String!
2262+
id: ID!
2263+
}
2264+
""");
2265+
var subgraphC = await TestSubgraph.CreateAsync(
2266+
"""
2267+
type Query {
2268+
node(id: ID!): Node
2269+
nodes(ids: [ID!]!): [Node]!
2270+
}
2271+
2272+
interface Node {
2273+
id: ID!
2274+
}
2275+
2276+
type User implements Node {
2277+
id: ID!
2278+
feedbacks: FeedbacksConnection
2279+
}
2280+
2281+
type FeedbacksConnection {
2282+
edges: [FeedbacksEdge!]
2283+
}
2284+
2285+
type FeedbacksEdge {
2286+
node: ResaleFeedback!
2287+
}
2288+
2289+
type ResaleFeedback implements Node {
2290+
feedback: ResaleSurveyFeedback
2291+
id: ID!
2292+
}
2293+
2294+
type ResaleSurveyFeedback {
2295+
buyer: User
2296+
}
2297+
""");
2298+
2299+
using var subgraphs = new TestSubgraphCollection(output, [subgraphA, subgraphB, subgraphC]);
2300+
var executor = await subgraphs.GetExecutorAsync();
2301+
2302+
var request = Parse(
2303+
"""
2304+
query {
2305+
userBySlug(slug: "me") {
2306+
feedbacks {
2307+
edges {
2308+
node {
2309+
feedback {
2310+
buyer {
2311+
relativeUrl
2312+
displayName
2313+
}
2314+
}
2315+
}
2316+
}
2317+
}
2318+
}
2319+
}
2320+
2321+
""");
2322+
2323+
// act
2324+
await using var result = await executor.ExecuteAsync(
2325+
OperationRequestBuilder
2326+
.New()
2327+
.SetDocument(request)
2328+
.Build());
2329+
2330+
// assert
2331+
var snapshot = new Snapshot();
2332+
CollectSnapshotData(snapshot, request, result);
2333+
await snapshot.MatchMarkdownAsync();
2334+
}
2335+
22352336
[Fact]
22362337
public async Task BatchExecutionState_With_Multiple_Variable_Values_And_Forwarded_Variable()
22372338
{
@@ -2401,10 +2502,7 @@ type Query {
24012502
}
24022503
}
24032504
""")
2404-
.SetVariableValues(new Dictionary<string, object?>
2405-
{
2406-
["productId"] = "UHJvZHVjdAppMzg2MzE4NTk="
2407-
})
2505+
.SetVariableValues(new Dictionary<string, object?> { ["productId"] = "UHJvZHVjdAppMzg2MzE4NTk=" })
24082506
.Build();
24092507

24102508
// act
@@ -2478,10 +2576,7 @@ type Query {
24782576
}
24792577
}
24802578
""")
2481-
.SetVariableValues(new Dictionary<string, object?>
2482-
{
2483-
["productId"] = "UHJvZHVjdAppMzg2MzE4NTk="
2484-
})
2579+
.SetVariableValues(new Dictionary<string, object?> { ["productId"] = "UHJvZHVjdAppMzg2MzE4NTk=" })
24852580
.Build();
24862581

24872582
// act
@@ -2559,10 +2654,7 @@ type Query {
25592654
}
25602655
}
25612656
""")
2562-
.SetVariableValues(new Dictionary<string, object?>
2563-
{
2564-
["productId"] = "UHJvZHVjdAppMzg2MzE4NTk="
2565-
})
2657+
.SetVariableValues(new Dictionary<string, object?> { ["productId"] = "UHJvZHVjdAppMzg2MzE4NTk=" })
25662658
.Build();
25672659

25682660
// act
@@ -2580,10 +2672,7 @@ public async Task Two_Arguments_Differing_Nullability_Does_Not_Duplicate_Forward
25802672

25812673
// act
25822674
var fusionGraph = await new FusionGraphComposer(logFactory: _logFactory).ComposeAsync(
2583-
new[]
2584-
{
2585-
demoProject.Accounts.ToConfiguration(AccountsExtensionSdl)
2586-
});
2675+
new[] { demoProject.Accounts.ToConfiguration(AccountsExtensionSdl) });
25872676

25882677
var executor = await new ServiceCollection()
25892678
.AddSingleton(demoProject.HttpClientFactory)

0 commit comments

Comments
 (0)