Skip to content

Commit

Permalink
Fixed Operation Compiler for Conditional Selections. (#5977)
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelstaib authored Mar 21, 2023
1 parent bd27dc5 commit 106f0ed
Show file tree
Hide file tree
Showing 20 changed files with 487 additions and 108 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ private void OptimizeSelectionSet(CompilerContext context)
context,
_selectionLookup,
_contextData,
_createFieldPipeline);
_createFieldPipeline,
context.Path);

if (context.Optimizers.Count == 1)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -839,54 +839,6 @@ internal void RegisterNewSelection(Selection newSelection)
}
}

internal sealed class SelectionPath : IEquatable<SelectionPath>
{
private SelectionPath(string name, SelectionPath? parent = null)
{
Name = name;
Parent = parent;
}

public string Name { get; }

public SelectionPath? Parent { get; }

public static SelectionPath Root { get; } = new("$root");

public SelectionPath Append(string name) => new(name, this);

public bool Equals(SelectionPath? other)
{
if (other is null)
{
return false;
}

if (ReferenceEquals(this, other))
{
return true;
}

if (Name.EqualsOrdinal(other.Name))
{
if (ReferenceEquals(Parent, other.Parent))
{
return true;
}

return Equals(Parent, other.Parent);
}

return false;
}

public override bool Equals(object? obj)
=> ReferenceEquals(this, obj) || (obj is SelectionPath other && Equals(other));

public override int GetHashCode()
=> HashCode.Combine(Name, Parent);
}

private readonly struct SelectionSetRef : IEquatable<SelectionSetRef>
{
public SelectionSetRef(SelectionSetNode selectionSet, SelectionPath path)
Expand All @@ -909,4 +861,4 @@ public override bool Equals(object? obj)
public override int GetHashCode()
=> HashCode.Combine(SelectionSet, Path);
}
}
}
9 changes: 8 additions & 1 deletion src/HotChocolate/Core/src/Execution/Processing/Selection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,14 @@ internal void AddSelection(FieldNode selectionSyntax, long includeCondition = 0)
throw new NotSupportedException(Resources.PreparedSelection_ReadOnly);
}

if (includeCondition is not 0 &&
if (includeCondition == 0)
{
if (_includeConditions.Length > 0)
{
_includeConditions = Array.Empty<long>();
}
}
else if (_includeConditions.Length > 0 &&
Array.IndexOf(_includeConditions, includeCondition) == -1)
{
var next = _includeConditions.Length;
Expand Down
95 changes: 95 additions & 0 deletions src/HotChocolate/Core/src/Execution/Processing/SelectionPath.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
using System;
using HotChocolate.Utilities;

namespace HotChocolate.Execution.Processing;

/// <summary>
/// Represents GraphQL selection path which is used in the operation compiler.
/// </summary>
public sealed class SelectionPath : IEquatable<SelectionPath>
{
private SelectionPath(string name, SelectionPath? parent = null)
{
Name = name;
Parent = parent;
}

/// <summary>
/// Gets the name of the current path segment.
/// </summary>
public string Name { get; }

/// <summary>
/// Gets the parent path segment.
/// </summary>
public SelectionPath? Parent { get; }

/// <summary>
/// Gets the root path segment.
/// </summary>
public static SelectionPath Root { get; } = new("$root");

/// <summary>
/// Creates a new path segment.
/// </summary>
/// <param name="name">
/// The name of the path segment.
/// </param>
/// <returns>
/// Returns a new path segment.
/// </returns>
public SelectionPath Append(string name) => new(name, this);

/// <summary>
/// Indicates whether the current path is equal to another path.
/// </summary>
/// <param name="other">A path to compare with this path.</param>
/// <returns>
/// <see langword="true" /> if the current path is equal to the
/// <paramref name="other" /> parameter; otherwise, <see langword="false" />.
/// </returns>
public bool Equals(SelectionPath? other)
{
if (other is null)
{
return false;
}

if (ReferenceEquals(this, other))
{
return true;
}

if (Name.EqualsOrdinal(other.Name))
{
if (ReferenceEquals(Parent, other.Parent))
{
return true;
}

return Equals(Parent, other.Parent);
}

return false;
}

/// <summary>
/// Indicates whether the current path is equal to another path.
/// </summary>
/// <param name="obj">
/// An object to compare with this path.
/// </param>
/// <returns>
/// <see langword="true" /> if the current path is equal to the
/// <paramref name="obj" /> parameter; otherwise, <see langword="false" />.
/// </returns>
public override bool Equals(object? obj)
=> Equals(obj as SelectionPath);

/// <summary>
/// Returns the hash code for this path.
/// </summary>
/// <returns></returns>
public override int GetHashCode()
=> HashCode.Combine(Name, Parent);
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,15 @@ internal SelectionSetOptimizerContext(
OperationCompiler.CompilerContext compilerContext,
Dictionary<Selection, OperationCompiler.SelectionSetInfo[]> selectionLookup,
Dictionary<string, object?> contextData,
CreateFieldPipeline createFieldPipeline)
CreateFieldPipeline createFieldPipeline,
SelectionPath path)
{
_compiler = compiler;
_compilerContext = compilerContext;
_selectionLookup = selectionLookup;
_createFieldPipeline = createFieldPipeline;
ContextData = contextData;
Path = path;
}

/// <summary>
Expand All @@ -60,6 +62,11 @@ public IReadOnlyDictionary<string, Selection> Selections
/// </summary>
public IDictionary<string, object?> ContextData { get; }

/// <summary>
/// Gets the current selection path.
/// </summary>
public SelectionPath Path { get; }

/// <summary>
/// Gets the next operation unique selection id.
/// </summary>
Expand Down Expand Up @@ -95,6 +102,26 @@ public void SetResolver(
public FieldDelegate CompileResolverPipeline(IObjectField field, FieldNode selection)
=> _createFieldPipeline(Schema, field, selection);

/// <summary>
/// Adds an additional selection for internal purposes.
/// </summary>
/// <param name="newSelection">
/// The new optimized selection.
/// </param>
/// <exception cref="ArgumentNullException">
/// <paramref name="newSelection"/> is <c>null</c>.
/// </exception>
public void AddSelection(Selection newSelection)
{
if (newSelection is null)
{
throw new ArgumentNullException(nameof(newSelection));
}

_compilerContext.Fields.Add(newSelection.ResponseName, newSelection);
_compiler.RegisterNewSelection(newSelection);
}

/// <summary>
/// Adds an additional selection for internal purposes.
/// </summary>
Expand All @@ -104,16 +131,68 @@ public FieldDelegate CompileResolverPipeline(IObjectField field, FieldNode selec
/// <param name="newSelection">
/// The new optimized selection.
/// </param>
/// <exception cref="ArgumentNullException">
/// <paramref name="newSelection"/> is <c>null</c>.
/// </exception>
/// <exception cref="ArgumentException">
/// The <paramref name="responseName"/> is not a valid GraphQL field name.
/// </exception>
[Obsolete("Use AddSelection(Selection) instead.")]
public void AddSelection(string responseName, Selection newSelection)
{
if (newSelection is null)
{
throw new ArgumentNullException(nameof(newSelection));
}

responseName.EnsureGraphQLName();

if (!newSelection.ResponseName.EqualsOrdinal(responseName))
{
throw new ArgumentException(
SelectionSetOptimizerContext_AddSelection_ResponseNameNotTheSame);
}

_compilerContext.Fields.Add(responseName, newSelection);
_compiler.RegisterNewSelection(newSelection);
}

/// <summary>
/// Replaces an existing selection with an optimized version.
/// </summary>
/// <param name="newSelection">
/// The new optimized selection.
/// </param>
/// <exception cref="ArgumentNullException">
/// <paramref name="newSelection"/> is <c>null</c>.
/// </exception>
/// <exception cref="ArgumentException">
/// - There is no existing selection with the specified
/// <paramref name="newSelection"/>.ResponseName.
/// </exception>
public void ReplaceSelection(Selection newSelection)
{
if (newSelection is null)
{
throw new ArgumentNullException(nameof(newSelection));
}

if (!_compilerContext.Fields.TryGetValue(
newSelection.ResponseName,
out var currentSelection))
{
throw new ArgumentException($"The `{newSelection.ResponseName}` does not exist.");
}

_compilerContext.Fields[newSelection.ResponseName] = newSelection;

if (_selectionLookup.TryGetValue(currentSelection, out var selectionSetInfos))
{
_selectionLookup.Remove(currentSelection);
_selectionLookup.Add(newSelection, selectionSetInfos);
}
}

/// <summary>
/// Replaces an existing selection with an optimized version.
/// </summary>
Expand All @@ -123,10 +202,15 @@ public void AddSelection(string responseName, Selection newSelection)
/// <param name="newSelection">
/// The new optimized selection.
/// </param>
/// <exception cref="ArgumentNullException">
/// <paramref name="newSelection"/> is <c>null</c>.
/// </exception>
/// <exception cref="ArgumentException">
/// - The <paramref name="responseName"/> is not a valid GraphQL field name.
/// - There is no existing selection with the specified <paramref name="responseName"/>.
/// - <see cref="Selection.ResponseName"/> is not equal to <paramref name="responseName"/>.
/// </exception>
[Obsolete("Use ReplaceSelection(Selection) instead.")]
public void ReplaceSelection(string responseName, Selection newSelection)
{
if (!responseName.IsValidGraphQLName())
Expand All @@ -135,6 +219,11 @@ public void ReplaceSelection(string responseName, Selection newSelection)
string.Format(SelectionSetOptimizerContext_InvalidFieldName, responseName));
}

if (newSelection is null)
{
throw new ArgumentNullException(nameof(newSelection));
}

if (!_compilerContext.Fields.TryGetValue(responseName, out var currentSelection))
{
throw new ArgumentException($"The `{responseName}` does not exist.");
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions src/HotChocolate/Core/src/Execution/Properties/Resources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -327,4 +327,7 @@
<data name="MiddlewareContext_ReplaceArguments_NullNotAllowed" xml:space="preserve">
<value>It is not allowed to replace the arguments with null.</value>
</data>
<data name="SelectionSetOptimizerContext_AddSelection_ResponseNameNotTheSame" xml:space="preserve">
<value>The provided response name must be the same as on the selection.</value>
</data>
</root>
Loading

0 comments on commit 106f0ed

Please sign in to comment.