Skip to content
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
@@ -1,4 +1,7 @@
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using HotChocolate.Language;

namespace HotChocolate.Fusion.Execution.Nodes;

Expand All @@ -10,10 +13,21 @@ public abstract class ExecutionNode : IEquatable<ExecutionNode>
private int _dependentCount;
private int _dependencyCount;

/// <summary>
/// The unique id of this execution node.
/// </summary>
public abstract int Id { get; }

/// <summary>
/// The type of this execution node.
/// </summary>
public abstract ExecutionNodeType Type { get; }

/// <summary>
/// The conditions that need to be met to execute this node.
/// </summary>
public abstract ReadOnlySpan<ExecutionNodeCondition> Conditions { get; }

/// <summary>
/// Gets the execution nodes that depend on this node to be completed
/// before they can be executed.
Expand All @@ -37,7 +51,14 @@ public async Task ExecuteAsync(

try
{
status = await OnExecuteAsync(context, cancellationToken).ConfigureAwait(false);
if (IsSkipped(context))
{
status = ExecutionStatus.Skipped;
}
else
{
status = await OnExecuteAsync(context, cancellationToken).ConfigureAwait(false);
}
}
catch (Exception ex)
{
Expand Down Expand Up @@ -174,4 +195,34 @@ public override bool Equals(object? obj)

public override int GetHashCode()
=> Id;

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private bool IsSkipped(OperationPlanContext context)
{
if (Conditions.IsEmpty)
{
return false;
}

ref var condition = ref MemoryMarshal.GetReference(Conditions);
ref var end = ref Unsafe.Add(ref condition, Conditions.Length);

while (Unsafe.IsAddressLessThan(ref condition, ref end))
{
if (!context.Variables.TryGetValue<BooleanValueNode>(condition.VariableName, out var booleanValueNode))
{
throw new InvalidOperationException(
$"Expected to have a boolean value for variable '${condition.VariableName}'");
}

if (booleanValueNode.Value != condition.PassingValue)
{
return true;
}

condition = ref Unsafe.Add(ref condition, 1)!;
}

return false;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using HotChocolate.Language;

namespace HotChocolate.Fusion.Execution.Nodes;

public sealed class ExecutionNodeCondition
{
public required string VariableName { get; init; }

public required bool PassingValue { get; init; }

public DirectiveNode? Directive { get; init; }

public override bool Equals(object? obj)
{
if (obj is not ExecutionNodeCondition other)
{
return false;
}

return other.VariableName == VariableName
&& other.PassingValue == PassingValue;
}

public override int GetHashCode()
{
return HashCode.Combine(VariableName, PassingValue);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,12 @@ public sealed class IntrospectionExecutionNode : ExecutionNode
{
private readonly Selection[] _selections;
private readonly string[] _responseNames;
private readonly ExecutionNodeCondition[] _conditions;

public IntrospectionExecutionNode(int id, Selection[] selections)
public IntrospectionExecutionNode(
int id,
Selection[] selections,
ExecutionNodeCondition[] conditions)
{
ArgumentNullException.ThrowIfNull(selections);

Expand All @@ -23,12 +27,21 @@ public IntrospectionExecutionNode(int id, Selection[] selections)
Id = id;
_selections = selections;
_responseNames = selections.Select(t => t.ResponseName).ToArray();
_conditions = conditions;
}

/// <inheritdoc />
public override int Id { get; }

/// <inheritdoc />
public override ExecutionNodeType Type => ExecutionNodeType.Introspection;

/// <inheritdoc />
public override ReadOnlySpan<ExecutionNodeCondition> Conditions => _conditions;

/// <summary>
/// The introspection selections.
/// </summary>
public ReadOnlySpan<Selection> Selections => _selections;

protected override ValueTask<ExecutionStatus> OnExecuteAsync(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,18 @@ public sealed class NodeFieldExecutionNode : ExecutionNode
private ExecutionNode _fallbackQuery = null!;
private readonly string _responseName;
private readonly IValueNode _idValue;
private readonly ExecutionNodeCondition[] _conditions;

internal NodeFieldExecutionNode(
int id,
string responseName,
IValueNode idValue)
IValueNode idValue,
ExecutionNodeCondition[] conditions)
{
_responseName = responseName;
_idValue = idValue;
Id = id;
_conditions = conditions;
}

/// <inheritdoc />
Expand All @@ -29,6 +32,9 @@ internal NodeFieldExecutionNode(
/// <inheritdoc />
public override ExecutionNodeType Type => ExecutionNodeType.Node;

/// <inheritdoc />
public override ReadOnlySpan<ExecutionNodeCondition> Conditions => _conditions;

/// <summary>
/// Gets the possible type branches for the node field.
/// The key is the type name and the value the node to execute for that type.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ public sealed class OperationExecutionNode : ExecutionNode
private readonly OperationRequirement[] _requirements;
private readonly string[] _forwardedVariables;
private readonly string[] _responseNames;
private readonly ExecutionNodeCondition[] _conditions;
private readonly OperationSourceText _operation;
private readonly string? _schemaName;
private readonly SelectionPath _target;
Expand All @@ -25,7 +26,8 @@ internal OperationExecutionNode(
SelectionPath source,
OperationRequirement[] requirements,
string[] forwardedVariables,
string[] responseNames)
string[] responseNames,
ExecutionNodeCondition[] conditions)
{
Id = id;
_operation = operation;
Expand All @@ -35,18 +37,18 @@ internal OperationExecutionNode(
_requirements = requirements;
_forwardedVariables = forwardedVariables;
_responseNames = responseNames;
_conditions = conditions;
}

/// <summary>
/// Gets the plan unique node id.
/// </summary>
/// <inheritdoc />
public override int Id { get; }

/// <summary>
/// Gets the type of the execution node.
/// </summary>
/// <inheritdoc />
public override ExecutionNodeType Type => ExecutionNodeType.Operation;

/// <inheritdoc />
public override ReadOnlySpan<ExecutionNodeCondition> Conditions => _conditions;

/// <summary>
/// Gets the operation definition that this execution node represents.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,13 @@

namespace HotChocolate.Fusion.Execution.Nodes.Serialization;

public sealed class JsonOperationPlanFormatter : OperationPlanFormatter
public sealed class JsonOperationPlanFormatter(JsonWriterOptions? options = null) : OperationPlanFormatter
{
private readonly JsonWriterOptions _writerOptions;

public JsonOperationPlanFormatter(JsonWriterOptions? options = null)
private readonly JsonWriterOptions _writerOptions = options ?? new JsonWriterOptions
{
_writerOptions = options ?? new JsonWriterOptions
{
Indented = false,
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
};
}
Indented = false,
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
};

public override string Format(OperationPlan plan, OperationPlanTrace? trace = null)
{
Expand Down Expand Up @@ -124,7 +119,7 @@ private static void WriteNodes(
break;

case NodeFieldExecutionNode nodeExecutionNode:
WriteNodeNode(jsonWriter, nodeExecutionNode, nodeTrace);
WriteNodeFieldNode(jsonWriter, nodeExecutionNode, nodeTrace);
break;
}
}
Expand Down Expand Up @@ -191,7 +186,9 @@ private static void WriteOperationNode(
jsonWriter.WriteEndArray();
}

if (node.Requirements.Length > 0)
TryWriteConditions(jsonWriter, node);

if (node.ForwardedVariables.Length > 0)
{
jsonWriter.WriteStartArray("forwardedVariables");

Expand Down Expand Up @@ -243,6 +240,35 @@ private static void WriteIntrospectionNode(

jsonWriter.WriteEndArray();

TryWriteConditions(jsonWriter, node);

TryWriteNodeTrace(jsonWriter, trace);

jsonWriter.WriteEndObject();
}

private static void WriteNodeFieldNode(Utf8JsonWriter jsonWriter, NodeFieldExecutionNode node, ExecutionNodeTrace? trace)
{
jsonWriter.WriteStartObject();
jsonWriter.WriteNumber("id", node.Id);
jsonWriter.WriteString("type", node.Type.ToString());

jsonWriter.WriteString("idValue", node.IdValue.ToString());
jsonWriter.WriteString("responseName", node.ResponseName);

jsonWriter.WriteStartObject("branches");

foreach (var branch in node.Branches.OrderBy(kvp => kvp.Key))
{
jsonWriter.WriteNumber(branch.Key, branch.Value.Id);
}

jsonWriter.WriteEndObject();

jsonWriter.WriteNumber("fallback", node.FallbackQuery.Id);

TryWriteConditions(jsonWriter, node);

TryWriteNodeTrace(jsonWriter, trace);

jsonWriter.WriteEndObject();
Expand Down Expand Up @@ -283,48 +309,33 @@ private static void TryWriteNodeTrace(Utf8JsonWriter jsonWriter, ExecutionNodeTr
}
}

private static void WriteObjectValueNode(Utf8JsonWriter jsonWriter, ObjectValueNode node)
private static void TryWriteConditions(Utf8JsonWriter jsonWriter, ExecutionNode node)
{
jsonWriter.WriteStartObject();

foreach (var field in node.Fields)
if (node.Conditions.Length > 0)
{
jsonWriter.WritePropertyName(field.Name.Value);
WriteValueNode(jsonWriter, field.Value);
}
jsonWriter.WritePropertyName("conditions");
jsonWriter.WriteStartArray();

jsonWriter.WriteEndObject();
foreach (var condition in node.Conditions)
{
jsonWriter.WriteStartObject();
jsonWriter.WriteString("variable", "$" + condition.VariableName);
jsonWriter.WriteBoolean("passingValue", condition.PassingValue);
jsonWriter.WriteEndObject();
}

jsonWriter.WriteEndArray();
}
}

private static void WriteNodeNode(Utf8JsonWriter jsonWriter, NodeFieldExecutionNode nodeField, ExecutionNodeTrace? trace)
private static void WriteObjectValueNode(Utf8JsonWriter jsonWriter, ObjectValueNode node)
{
jsonWriter.WriteStartObject();
jsonWriter.WriteNumber("id", nodeField.Id);
jsonWriter.WriteString("type", nodeField.Type.ToString());

jsonWriter.WriteString("idValue", nodeField.IdValue.ToString());
jsonWriter.WriteString("responseName", nodeField.ResponseName);

jsonWriter.WriteStartObject("branches");

foreach (var branch in nodeField.Branches.OrderBy(kvp => kvp.Key))
{
jsonWriter.WriteNumber(branch.Key, branch.Value.Id);
}

jsonWriter.WriteEndObject();

jsonWriter.WriteNumber("fallback", nodeField.FallbackQuery.Id);

if (trace is not null)
foreach (var field in node.Fields)
{
if (!string.IsNullOrEmpty(trace.SpanId))
{
jsonWriter.WriteString("spanId", trace.SpanId);
}

jsonWriter.WriteNumber("duration", trace.Duration.TotalMilliseconds);
jsonWriter.WriteString("status", trace.Status.ToString());
jsonWriter.WritePropertyName(field.Name.Value);
WriteValueNode(jsonWriter, field.Value);
}

jsonWriter.WriteEndObject();
Expand Down
Loading
Loading