Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed Operation Compiler for Conditional Selections. #5977

Merged
merged 4 commits into from
Mar 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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