Skip to content

Commit 220ec23

Browse files
tobias-tenglermichaelstaib
authored andcommitted
[Fusion] Fixed duplicated selections (#7677)
1 parent 0fa9b52 commit 220ec23

12 files changed

+1734
-11
lines changed

src/HotChocolate/Fusion/src/Core/Execution/Nodes/ResolverNodeBase.Config.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,11 @@ public Config(
6262
IReadOnlyList<string> path,
6363
TransportFeatures transportFeatures)
6464
{
65+
// This is a temporary solution to selections being duplicated during request planning.
66+
// It should be properly fixed with new planner in the future.
67+
var rewriter = new SelectionRewriter();
68+
document = rewriter.RewriteDocument(document, null);
69+
6570
string[]? buffer = null;
6671
var usedCapacity = 0;
6772

src/HotChocolate/Fusion/src/Core/Planning/RequestFormatters/DefaultRequestDocumentFormatter.cs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
namespace HotChocolate.Fusion.Planning;
44

5-
internal sealed class DefaultRequestDocumentFormatter(FusionGraphConfiguration configuration)
6-
: RequestDocumentFormatter(configuration)
7-
{
8-
}
5+
internal sealed class DefaultRequestDocumentFormatter(
6+
FusionGraphConfiguration configuration)
7+
: RequestDocumentFormatter(configuration);

src/HotChocolate/Fusion/src/Core/Planning/RequestFormatters/NodeRequestDocumentFormatter.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,6 @@ internal sealed class NodeRequestDocumentFormatter(
1515
ISchema schema)
1616
: RequestDocumentFormatter(configuration)
1717
{
18-
private readonly ISchema _schema = schema;
19-
2018
internal RequestDocument CreateRequestDocument(
2119
QueryPlanContext context,
2220
SelectionExecutionStep executionStep,
@@ -113,7 +111,7 @@ private SelectionSetNode CreateSelectionSetNode(
113111
{
114112
var selectionNodes = new List<ISelectionNode>();
115113
var typeSelectionNodes = new List<ISelectionNode>();
116-
var entityType = _schema.GetType<ObjectType>(entityTypeName);
114+
var entityType = schema.GetType<ObjectType>(entityTypeName);
117115
var selectionSet = (SelectionSet)context.Operation.GetSelectionSet(parentSelection, entityType);
118116

119117
CreateSelectionNodes(
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
using System.Collections.Immutable;
2+
using HotChocolate.Execution.Processing;
3+
using HotChocolate.Language;
4+
5+
namespace HotChocolate.Fusion.Utilities;
6+
7+
public sealed class SelectionRewriter
8+
{
9+
public DocumentNode RewriteDocument(DocumentNode document, string? operationName)
10+
{
11+
var operation = document.GetOperation(operationName);
12+
var context = new Context();
13+
14+
RewriteFields(operation.SelectionSet, context);
15+
16+
var newSelectionSet = new SelectionSetNode(
17+
null,
18+
context.Selections.ToImmutable());
19+
20+
var newOperation = new OperationDefinitionNode(
21+
null,
22+
operation.Name,
23+
operation.Operation,
24+
operation.VariableDefinitions,
25+
RewriteDirectives(operation.Directives),
26+
newSelectionSet);
27+
28+
return new DocumentNode(ImmutableArray<IDefinitionNode>.Empty.Add(newOperation));
29+
}
30+
31+
private void RewriteFields(SelectionSetNode selectionSet, Context context)
32+
{
33+
foreach (var selection in selectionSet.Selections)
34+
{
35+
switch (selection)
36+
{
37+
case FieldNode field:
38+
RewriteField(field, context);
39+
break;
40+
41+
case InlineFragmentNode inlineFragment:
42+
RewriteInlineFragment(inlineFragment, context);
43+
break;
44+
45+
case FragmentSpreadNode fragmentSpread:
46+
context.Selections.Add(fragmentSpread);
47+
break;
48+
}
49+
}
50+
}
51+
52+
private void RewriteField(FieldNode fieldNode, Context context)
53+
{
54+
if (fieldNode.SelectionSet is null)
55+
{
56+
var node = fieldNode.WithLocation(null);
57+
58+
if (context.Visited.Add(node))
59+
{
60+
context.Selections.Add(node);
61+
}
62+
}
63+
else
64+
{
65+
var fieldContext = new Context();
66+
67+
RewriteFields(fieldNode.SelectionSet, fieldContext);
68+
69+
var newSelectionSetNode = new SelectionSetNode(
70+
null,
71+
fieldContext.Selections.ToImmutable());
72+
73+
var newFieldNode = new FieldNode(
74+
null,
75+
fieldNode.Name,
76+
fieldNode.Alias,
77+
RewriteDirectives(fieldNode.Directives),
78+
RewriteArguments(fieldNode.Arguments),
79+
newSelectionSetNode);
80+
81+
if (context.Visited.Add(newFieldNode))
82+
{
83+
context.Selections.Add(newFieldNode);
84+
}
85+
}
86+
}
87+
88+
private void RewriteInlineFragment(InlineFragmentNode inlineFragment, Context context)
89+
{
90+
if ((inlineFragment.TypeCondition is null ||
91+
inlineFragment.TypeCondition.Name.Value.Equals(context.Type, StringComparison.Ordinal)) &&
92+
inlineFragment.Directives.Count == 0)
93+
{
94+
RewriteFields(inlineFragment.SelectionSet, context);
95+
return;
96+
}
97+
98+
var inlineFragmentContext = new Context(inlineFragment.TypeCondition?.Name.Value);
99+
100+
RewriteFields(inlineFragment.SelectionSet, inlineFragmentContext);
101+
102+
var newSelectionSetNode = new SelectionSetNode(
103+
null,
104+
inlineFragmentContext.Selections.ToImmutable());
105+
106+
var newInlineFragment = new InlineFragmentNode(
107+
null,
108+
inlineFragment.TypeCondition,
109+
RewriteDirectives(inlineFragment.Directives),
110+
newSelectionSetNode);
111+
112+
context.Selections.Add(newInlineFragment);
113+
}
114+
115+
private IReadOnlyList<DirectiveNode> RewriteDirectives(IReadOnlyList<DirectiveNode> directives)
116+
{
117+
if (directives.Count == 0)
118+
{
119+
return directives;
120+
}
121+
122+
if (directives.Count == 1)
123+
{
124+
var directive = directives[0];
125+
var newDirective = new DirectiveNode(directive.Name.Value, RewriteArguments(directive.Arguments));
126+
return ImmutableArray<DirectiveNode>.Empty.Add(newDirective);
127+
}
128+
129+
var buffer = new DirectiveNode[directives.Count];
130+
for (var i = 0; i < buffer.Length; i++)
131+
{
132+
var directive = directives[i];
133+
buffer[i] = new DirectiveNode(directive.Name.Value, RewriteArguments(directive.Arguments));
134+
}
135+
136+
return ImmutableArray.Create(buffer);
137+
}
138+
139+
private IReadOnlyList<ArgumentNode> RewriteArguments(IReadOnlyList<ArgumentNode> arguments)
140+
{
141+
if (arguments.Count == 0)
142+
{
143+
return arguments;
144+
}
145+
146+
if (arguments.Count == 1)
147+
{
148+
return ImmutableArray<ArgumentNode>.Empty.Add(arguments[0].WithLocation(null));
149+
}
150+
151+
var buffer = new ArgumentNode[arguments.Count];
152+
for (var i = 0; i < buffer.Length; i++)
153+
{
154+
buffer[i] = arguments[i].WithLocation(null);
155+
}
156+
157+
return ImmutableArray.Create(buffer);
158+
}
159+
160+
private class Context(string? typeName = null)
161+
{
162+
public string? Type => typeName;
163+
164+
public ImmutableArray<ISelectionNode>.Builder Selections { get; } =
165+
ImmutableArray.CreateBuilder<ISelectionNode>();
166+
167+
public HashSet<ISelectionNode> Visited { get; } = new(SyntaxComparer.BySyntax);
168+
}
169+
}

0 commit comments

Comments
 (0)