Skip to content

Commit

Permalink
[Fusion] Fixed duplicated selections (#7677)
Browse files Browse the repository at this point in the history
  • Loading branch information
tobias-tengler authored Nov 12, 2024
1 parent 7e32dc0 commit 2cddf9d
Show file tree
Hide file tree
Showing 14 changed files with 1,789 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,11 @@ public Config(
IReadOnlyList<string> path,
TransportFeatures transportFeatures)
{
// This is a temporary solution to selections being duplicated during request planning.
// It should be properly fixed with new planner in the future.
var rewriter = new SelectionRewriter();
document = rewriter.RewriteDocument(document, null);

string[]? buffer = null;
var usedCapacity = 0;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

namespace HotChocolate.Fusion.Planning;

internal sealed class DefaultRequestDocumentFormatter(FusionGraphConfiguration configuration)
: RequestDocumentFormatter(configuration)
{
}
internal sealed class DefaultRequestDocumentFormatter(
FusionGraphConfiguration configuration)
: RequestDocumentFormatter(configuration);
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@ internal sealed class NodeRequestDocumentFormatter(
ISchema schema)
: RequestDocumentFormatter(configuration)
{
private readonly ISchema _schema = schema;

internal RequestDocument CreateRequestDocument(
QueryPlanContext context,
SelectionExecutionStep executionStep,
Expand Down Expand Up @@ -113,7 +111,7 @@ private SelectionSetNode CreateSelectionSetNode(
{
var selectionNodes = new List<ISelectionNode>();
var typeSelectionNodes = new List<ISelectionNode>();
var entityType = _schema.GetType<ObjectType>(entityTypeName);
var entityType = schema.GetType<ObjectType>(entityTypeName);
var selectionSet = (SelectionSet)context.Operation.GetSelectionSet(parentSelection, entityType);

CreateSelectionNodes(
Expand Down
169 changes: 169 additions & 0 deletions src/HotChocolate/Fusion/src/Core/Utilities/SelectionRewriter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
using System.Collections.Immutable;
using HotChocolate.Execution.Processing;
using HotChocolate.Language;

namespace HotChocolate.Fusion.Utilities;

public sealed class SelectionRewriter
{
public DocumentNode RewriteDocument(DocumentNode document, string? operationName)
{
var operation = document.GetOperation(operationName);
var context = new Context();

RewriteFields(operation.SelectionSet, context);

var newSelectionSet = new SelectionSetNode(
null,
context.Selections.ToImmutable());

var newOperation = new OperationDefinitionNode(
null,
operation.Name,
operation.Operation,
operation.VariableDefinitions,
RewriteDirectives(operation.Directives),
newSelectionSet);

return new DocumentNode(ImmutableArray<IDefinitionNode>.Empty.Add(newOperation));
}

private void RewriteFields(SelectionSetNode selectionSet, Context context)
{
foreach (var selection in selectionSet.Selections)
{
switch (selection)
{
case FieldNode field:
RewriteField(field, context);
break;

case InlineFragmentNode inlineFragment:
RewriteInlineFragment(inlineFragment, context);
break;

case FragmentSpreadNode fragmentSpread:
context.Selections.Add(fragmentSpread);
break;
}
}
}

private void RewriteField(FieldNode fieldNode, Context context)
{
if (fieldNode.SelectionSet is null)
{
var node = fieldNode.WithLocation(null);

if (context.Visited.Add(node))
{
context.Selections.Add(node);
}
}
else
{
var fieldContext = new Context();

RewriteFields(fieldNode.SelectionSet, fieldContext);

var newSelectionSetNode = new SelectionSetNode(
null,
fieldContext.Selections.ToImmutable());

var newFieldNode = new FieldNode(
null,
fieldNode.Name,
fieldNode.Alias,
RewriteDirectives(fieldNode.Directives),
RewriteArguments(fieldNode.Arguments),
newSelectionSetNode);

if (context.Visited.Add(newFieldNode))
{
context.Selections.Add(newFieldNode);
}
}
}

private void RewriteInlineFragment(InlineFragmentNode inlineFragment, Context context)
{
if ((inlineFragment.TypeCondition is null ||
inlineFragment.TypeCondition.Name.Value.Equals(context.Type, StringComparison.Ordinal)) &&
inlineFragment.Directives.Count == 0)
{
RewriteFields(inlineFragment.SelectionSet, context);
return;
}

var inlineFragmentContext = new Context(inlineFragment.TypeCondition?.Name.Value);

RewriteFields(inlineFragment.SelectionSet, inlineFragmentContext);

var newSelectionSetNode = new SelectionSetNode(
null,
inlineFragmentContext.Selections.ToImmutable());

var newInlineFragment = new InlineFragmentNode(
null,
inlineFragment.TypeCondition,
RewriteDirectives(inlineFragment.Directives),
newSelectionSetNode);

context.Selections.Add(newInlineFragment);
}

private IReadOnlyList<DirectiveNode> RewriteDirectives(IReadOnlyList<DirectiveNode> directives)
{
if (directives.Count == 0)
{
return directives;
}

if (directives.Count == 1)
{
var directive = directives[0];
var newDirective = new DirectiveNode(directive.Name.Value, RewriteArguments(directive.Arguments));
return ImmutableArray<DirectiveNode>.Empty.Add(newDirective);
}

var buffer = new DirectiveNode[directives.Count];
for (var i = 0; i < buffer.Length; i++)
{
var directive = directives[i];
buffer[i] = new DirectiveNode(directive.Name.Value, RewriteArguments(directive.Arguments));
}

return ImmutableArray.Create(buffer);
}

private IReadOnlyList<ArgumentNode> RewriteArguments(IReadOnlyList<ArgumentNode> arguments)
{
if (arguments.Count == 0)
{
return arguments;
}

if (arguments.Count == 1)
{
return ImmutableArray<ArgumentNode>.Empty.Add(arguments[0].WithLocation(null));
}

var buffer = new ArgumentNode[arguments.Count];
for (var i = 0; i < buffer.Length; i++)
{
buffer[i] = arguments[i].WithLocation(null);
}

return ImmutableArray.Create(buffer);
}

private class Context(string? typeName = null)
{
public string? Type => typeName;

public ImmutableArray<ISelectionNode>.Builder Selections { get; } =
ImmutableArray.CreateBuilder<ISelectionNode>();

public HashSet<ISelectionNode> Visited { get; } = new(SyntaxComparer.BySyntax);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,12 @@ private void RewriteField(FieldNode fieldNode, Context context)
{
if (fieldNode.SelectionSet is null)
{
context.Selections.Add(fieldNode.WithLocation(null));
var node = fieldNode.WithLocation(null);

if (context.Visited.Add(node))
{
context.Selections.Add(node);
}
}
else
{
Expand All @@ -76,7 +81,10 @@ private void RewriteField(FieldNode fieldNode, Context context)
RewriteArguments(fieldNode.Arguments),
newSelectionSetNode);

context.Selections.Add(newFieldNode);
if (context.Visited.Add(newFieldNode))
{
context.Selections.Add(newFieldNode);
}
}
}

Expand Down Expand Up @@ -139,7 +147,10 @@ private void InlineFragmentDefinition(
RewriteDirectives(fragmentSpread.Directives),
selectionSet);

context.Selections.Add(inlineFragment);
if (context.Visited.Add(inlineFragment))
{
context.Selections.Add(inlineFragment);
}
}
}

Expand Down Expand Up @@ -210,6 +221,8 @@ public readonly ref struct Context(
public ImmutableArray<ISelectionNode>.Builder Selections { get; } =
ImmutableArray.CreateBuilder<ISelectionNode>();

public HashSet<ISelectionNode> Visited { get; } = new(SyntaxComparer.BySyntax);

public FragmentDefinitionNode GetFragmentDefinition(string name)
=> fragments[name];

Expand Down
Loading

0 comments on commit 2cddf9d

Please sign in to comment.