Skip to content

Commit 807db21

Browse files
[Fusion] Fix bug with shared parent selection (#8378)
1 parent ecb937c commit 807db21

File tree

5 files changed

+322
-8
lines changed

5 files changed

+322
-8
lines changed

src/HotChocolate/Fusion/src/Core/Planning/Pipeline/ExecutionStepDiscoveryMiddleware.cs

Lines changed: 44 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,8 @@ parentSelectionPath is null
196196
}
197197

198198
executionStep.AllSelections.Add(selection);
199-
executionStep.RootSelections.Add(new RootSelection(selection, resolver));
199+
var rootSelection = new RootSelection(selection, resolver);
200+
executionStep.RootSelections.Add(rootSelection);
200201

201202
if (resolver is not null)
202203
{
@@ -205,7 +206,7 @@ parentSelectionPath is null
205206

206207
if (selection.SelectionSet is not null)
207208
{
208-
CollectNestedSelections(
209+
var couldPlanChildSelections = CollectNestedSelections(
209210
context,
210211
backlog,
211212
operation,
@@ -216,6 +217,15 @@ parentSelectionPath is null
216217
preferBatching,
217218
context.ParentSelections,
218219
subgraphsInContext);
220+
221+
if (!couldPlanChildSelections)
222+
{
223+
executionStep.AllSelections.Remove(selection);
224+
executionStep.RootSelections.Remove(rootSelection);
225+
executionStep.SelectionResolvers.Remove(selection);
226+
227+
(leftovers ??=[]).Add(selection);
228+
}
219229
}
220230

221231
path.RemoveAt(pathIndex);
@@ -295,7 +305,7 @@ private void HandleSpecialQuerySelections(
295305
}
296306
}
297307

298-
private void CollectNestedSelections(
308+
private bool CollectNestedSelections(
299309
QueryPlanContext context,
300310
Queue<BacklogItem> backlog,
301311
IOperation operation,
@@ -312,9 +322,10 @@ private void CollectNestedSelections(
312322
preferBatching = parentSelection.Type.IsListType();
313323
}
314324

325+
var overallCouldPlanSelections = false;
315326
foreach (var possibleType in operation.GetPossibleTypes(parentSelection))
316327
{
317-
CollectNestedSelections(
328+
var couldPlanSelections = CollectNestedSelections(
318329
context,
319330
backlog,
320331
operation,
@@ -326,10 +337,17 @@ private void CollectNestedSelections(
326337
preferBatching,
327338
parentSelectionLookup,
328339
subgraphsInContext);
340+
341+
if (couldPlanSelections)
342+
{
343+
overallCouldPlanSelections = true;
344+
}
329345
}
346+
347+
return overallCouldPlanSelections;
330348
}
331349

332-
private void CollectNestedSelections(
350+
private bool CollectNestedSelections(
333351
QueryPlanContext context,
334352
Queue<BacklogItem> backlog,
335353
IOperation operation,
@@ -399,7 +417,7 @@ private void CollectNestedSelections(
399417

400418
if (selection.SelectionSet is not null)
401419
{
402-
CollectNestedSelections(
420+
var couldPlanChildSelections = CollectNestedSelections(
403421
context,
404422
backlog,
405423
operation,
@@ -410,6 +428,14 @@ private void CollectNestedSelections(
410428
preferBatching,
411429
parentSelectionLookup,
412430
subgraphsInContext);
431+
432+
if (!couldPlanChildSelections)
433+
{
434+
executionStep.AllSelections.Remove(selection);
435+
executionStep.SelectionResolvers.Remove(selection);
436+
437+
(leftovers ??=[]).Add(selection);
438+
}
413439
}
414440
}
415441
else
@@ -422,6 +448,16 @@ private void CollectNestedSelections(
422448

423449
if (leftovers is not null)
424450
{
451+
var isTypeResolvableFromOtherSubgraph = declaringType.Resolvers
452+
.Any(r => r.SubgraphName != executionStep.SubgraphName);
453+
454+
var couldNotResolveAnySelections = leftovers.Count == selectionSet.Selections.Count;
455+
456+
if (couldNotResolveAnySelections && !isTypeResolvableFromOtherSubgraph)
457+
{
458+
return false;
459+
}
460+
425461
var selectionSetPath = CreateSelectionPath(rootSelectionPath, path);
426462

427463
if (selectionSetPath is not null)
@@ -444,6 +480,8 @@ private void CollectNestedSelections(
444480
preferBatching
445481
);
446482
}
483+
484+
return true;
447485
}
448486

449487
private static void TryEnqueueBacklogItem(
@@ -644,7 +682,6 @@ private void TrySetEntityResolver(
644682
string subgraph,
645683
SelectionExecutionStep executionStep,
646684
HashSet<string> subgraphsInContext)
647-
648685
{
649686
if (parentSelection is null || !selectionSetTypeMetadata.Resolvers.ContainsResolvers(subgraph))
650687
{

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

Lines changed: 127 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2442,7 +2442,7 @@ type ResaleSurveyFeedback {
24422442
await snapshot.MatchMarkdownAsync();
24432443
}
24442444

2445-
[Fact(Skip = "Fix this test in the new planner")]
2445+
[Fact]
24462446
public async Task Field_Below_Shared_Field_Only_Available_On_One_Subgraph_Type_Of_Shared_Field_Not_Node()
24472447
{
24482448
// arrange
@@ -2664,6 +2664,132 @@ type Query {
26642664
MatchMarkdownSnapshot(request, result);
26652665
}
26662666

2667+
[Fact]
2668+
public async Task Viewer_Bug_1()
2669+
{
2670+
// arrange
2671+
var subgraphA = await TestSubgraph.CreateAsync(
2672+
"""
2673+
type Query {
2674+
exclusiveSubgraphA: ExclusiveSubgraphA
2675+
viewer: Viewer
2676+
}
2677+
2678+
type ExclusiveSubgraphA {
2679+
id: ID!
2680+
}
2681+
2682+
type Viewer {
2683+
name: String
2684+
}
2685+
""");
2686+
var subgraphB = await TestSubgraph.CreateAsync(
2687+
"""
2688+
type Query {
2689+
viewer: Viewer
2690+
}
2691+
2692+
type Viewer {
2693+
exclusiveSubgraphB: String
2694+
}
2695+
""");
2696+
2697+
using var subgraphs = new TestSubgraphCollection(output, [subgraphA, subgraphB]);
2698+
var executor = await subgraphs.GetExecutorAsync();
2699+
2700+
var request = Parse(
2701+
"""
2702+
query testQuery {
2703+
exclusiveSubgraphA {
2704+
__typename
2705+
}
2706+
viewer {
2707+
exclusiveSubgraphB
2708+
}
2709+
}
2710+
""");
2711+
2712+
// act
2713+
await using var result = await executor.ExecuteAsync(
2714+
OperationRequestBuilder
2715+
.New()
2716+
.SetDocument(request)
2717+
.Build());
2718+
2719+
// assert
2720+
var snapshot = new Snapshot();
2721+
CollectSnapshotData(snapshot, request, result);
2722+
await snapshot.MatchMarkdownAsync();
2723+
}
2724+
2725+
[Fact]
2726+
public async Task Viewer_Bug_2()
2727+
{
2728+
// arrange
2729+
var subgraphA = await TestSubgraph.CreateAsync(
2730+
"""
2731+
type Query {
2732+
exclusiveSubgraphA: ExclusiveSubgraphA
2733+
viewer: Viewer
2734+
}
2735+
2736+
type ExclusiveSubgraphA {
2737+
id: ID!
2738+
}
2739+
2740+
type Viewer {
2741+
subType: SubType
2742+
}
2743+
2744+
type SubType {
2745+
subgraphA: String
2746+
}
2747+
""");
2748+
var subgraphB = await TestSubgraph.CreateAsync(
2749+
"""
2750+
type Query {
2751+
viewer: Viewer
2752+
}
2753+
2754+
type Viewer {
2755+
subType: SubType
2756+
}
2757+
2758+
type SubType {
2759+
subgraphB: String
2760+
}
2761+
""");
2762+
2763+
using var subgraphs = new TestSubgraphCollection(output, [subgraphA, subgraphB]);
2764+
var executor = await subgraphs.GetExecutorAsync();
2765+
2766+
var request = Parse(
2767+
"""
2768+
query testQuery {
2769+
exclusiveSubgraphA {
2770+
__typename
2771+
}
2772+
viewer {
2773+
subType {
2774+
subgraphB
2775+
}
2776+
}
2777+
}
2778+
""");
2779+
2780+
// act
2781+
await using var result = await executor.ExecuteAsync(
2782+
OperationRequestBuilder
2783+
.New()
2784+
.SetDocument(request)
2785+
.Build());
2786+
2787+
// assert
2788+
var snapshot = new Snapshot();
2789+
CollectSnapshotData(snapshot, request, result);
2790+
await snapshot.MatchMarkdownAsync();
2791+
}
2792+
26672793
[Fact]
26682794
public async Task Two_Arguments_Differing_Nullability_Does_Not_Duplicate_Forwarded_Variables()
26692795
{

src/HotChocolate/Fusion/test/Core.Tests/__snapshots__/DemoIntegrationTests.Field_Below_Shared_Field_Only_Available_On_One_Subgraph_Type_Of_Shared_Field_Not_Node.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,3 +93,4 @@ query($productId: ID!) {
9393
}
9494
}
9595
```
96+
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
# Viewer_Bug_1
2+
3+
## Result
4+
5+
```json
6+
{
7+
"data": {
8+
"exclusiveSubgraphA": {
9+
"__typename": "ExclusiveSubgraphA"
10+
},
11+
"viewer": {
12+
"exclusiveSubgraphB": "string"
13+
}
14+
}
15+
}
16+
```
17+
18+
## Request
19+
20+
```graphql
21+
query testQuery {
22+
exclusiveSubgraphA {
23+
__typename
24+
}
25+
viewer {
26+
exclusiveSubgraphB
27+
}
28+
}
29+
```
30+
31+
## QueryPlan Hash
32+
33+
```text
34+
1A0ADF8B4DE457E0B7C85E08324A394F9F2FEB91
35+
```
36+
37+
## QueryPlan
38+
39+
```json
40+
{
41+
"document": "query testQuery { exclusiveSubgraphA { __typename } viewer { exclusiveSubgraphB } }",
42+
"operation": "testQuery",
43+
"rootNode": {
44+
"type": "Sequence",
45+
"nodes": [
46+
{
47+
"type": "Parallel",
48+
"nodes": [
49+
{
50+
"type": "Resolve",
51+
"subgraph": "Subgraph_1",
52+
"document": "query testQuery_1 { exclusiveSubgraphA { __typename } }",
53+
"selectionSetId": 0
54+
},
55+
{
56+
"type": "Resolve",
57+
"subgraph": "Subgraph_2",
58+
"document": "query testQuery_2 { viewer { exclusiveSubgraphB } }",
59+
"selectionSetId": 0
60+
}
61+
]
62+
},
63+
{
64+
"type": "Compose",
65+
"selectionSetIds": [
66+
0
67+
]
68+
}
69+
]
70+
}
71+
}
72+
```
73+

0 commit comments

Comments
 (0)