Skip to content

Commit 46afcc8

Browse files
[Fusion] Add conditions to plan nodes (#8789)
1 parent c55ea41 commit 46afcc8

File tree

77 files changed

+1442
-441
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

77 files changed

+1442
-441
lines changed

src/HotChocolate/Fusion-vnext/src/Fusion.Execution/Execution/Nodes/ExecutionNode.cs

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
using System.Diagnostics;
2+
using System.Runtime.CompilerServices;
3+
using System.Runtime.InteropServices;
4+
using HotChocolate.Language;
25

36
namespace HotChocolate.Fusion.Execution.Nodes;
47

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

16+
/// <summary>
17+
/// The unique id of this execution node.
18+
/// </summary>
1319
public abstract int Id { get; }
1420

21+
/// <summary>
22+
/// The type of this execution node.
23+
/// </summary>
1524
public abstract ExecutionNodeType Type { get; }
1625

26+
/// <summary>
27+
/// The conditions that need to be met to execute this node.
28+
/// </summary>
29+
public abstract ReadOnlySpan<ExecutionNodeCondition> Conditions { get; }
30+
1731
/// <summary>
1832
/// Gets the execution nodes that depend on this node to be completed
1933
/// before they can be executed.
@@ -37,7 +51,14 @@ public async Task ExecuteAsync(
3751

3852
try
3953
{
40-
status = await OnExecuteAsync(context, cancellationToken).ConfigureAwait(false);
54+
if (IsSkipped(context))
55+
{
56+
status = ExecutionStatus.Skipped;
57+
}
58+
else
59+
{
60+
status = await OnExecuteAsync(context, cancellationToken).ConfigureAwait(false);
61+
}
4162
}
4263
catch (Exception ex)
4364
{
@@ -174,4 +195,34 @@ public override bool Equals(object? obj)
174195

175196
public override int GetHashCode()
176197
=> Id;
198+
199+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
200+
private bool IsSkipped(OperationPlanContext context)
201+
{
202+
if (Conditions.IsEmpty)
203+
{
204+
return false;
205+
}
206+
207+
ref var condition = ref MemoryMarshal.GetReference(Conditions);
208+
ref var end = ref Unsafe.Add(ref condition, Conditions.Length);
209+
210+
while (Unsafe.IsAddressLessThan(ref condition, ref end))
211+
{
212+
if (!context.Variables.TryGetValue<BooleanValueNode>(condition.VariableName, out var booleanValueNode))
213+
{
214+
throw new InvalidOperationException(
215+
$"Expected to have a boolean value for variable '${condition.VariableName}'");
216+
}
217+
218+
if (booleanValueNode.Value != condition.PassingValue)
219+
{
220+
return true;
221+
}
222+
223+
condition = ref Unsafe.Add(ref condition, 1)!;
224+
}
225+
226+
return false;
227+
}
177228
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
using HotChocolate.Language;
2+
3+
namespace HotChocolate.Fusion.Execution.Nodes;
4+
5+
public sealed class ExecutionNodeCondition
6+
{
7+
public required string VariableName { get; init; }
8+
9+
public required bool PassingValue { get; init; }
10+
11+
public DirectiveNode? Directive { get; init; }
12+
13+
public override bool Equals(object? obj)
14+
{
15+
if (obj is not ExecutionNodeCondition other)
16+
{
17+
return false;
18+
}
19+
20+
return other.VariableName == VariableName
21+
&& other.PassingValue == PassingValue;
22+
}
23+
24+
public override int GetHashCode()
25+
{
26+
return HashCode.Combine(VariableName, PassingValue);
27+
}
28+
}

src/HotChocolate/Fusion-vnext/src/Fusion.Execution/Execution/Nodes/IntrospectionExecutionNode.cs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,12 @@ public sealed class IntrospectionExecutionNode : ExecutionNode
88
{
99
private readonly Selection[] _selections;
1010
private readonly string[] _responseNames;
11+
private readonly ExecutionNodeCondition[] _conditions;
1112

12-
public IntrospectionExecutionNode(int id, Selection[] selections)
13+
public IntrospectionExecutionNode(
14+
int id,
15+
Selection[] selections,
16+
ExecutionNodeCondition[] conditions)
1317
{
1418
ArgumentNullException.ThrowIfNull(selections);
1519

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

33+
/// <inheritdoc />
2834
public override int Id { get; }
2935

36+
/// <inheritdoc />
3037
public override ExecutionNodeType Type => ExecutionNodeType.Introspection;
3138

39+
/// <inheritdoc />
40+
public override ReadOnlySpan<ExecutionNodeCondition> Conditions => _conditions;
41+
42+
/// <summary>
43+
/// The introspection selections.
44+
/// </summary>
3245
public ReadOnlySpan<Selection> Selections => _selections;
3346

3447
protected override ValueTask<ExecutionStatus> OnExecuteAsync(

src/HotChocolate/Fusion-vnext/src/Fusion.Execution/Execution/Nodes/NodeFieldExecutionNode.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,18 @@ public sealed class NodeFieldExecutionNode : ExecutionNode
1212
private ExecutionNode _fallbackQuery = null!;
1313
private readonly string _responseName;
1414
private readonly IValueNode _idValue;
15+
private readonly ExecutionNodeCondition[] _conditions;
1516

1617
internal NodeFieldExecutionNode(
1718
int id,
1819
string responseName,
19-
IValueNode idValue)
20+
IValueNode idValue,
21+
ExecutionNodeCondition[] conditions)
2022
{
2123
_responseName = responseName;
2224
_idValue = idValue;
2325
Id = id;
26+
_conditions = conditions;
2427
}
2528

2629
/// <inheritdoc />
@@ -29,6 +32,9 @@ internal NodeFieldExecutionNode(
2932
/// <inheritdoc />
3033
public override ExecutionNodeType Type => ExecutionNodeType.Node;
3134

35+
/// <inheritdoc />
36+
public override ReadOnlySpan<ExecutionNodeCondition> Conditions => _conditions;
37+
3238
/// <summary>
3339
/// Gets the possible type branches for the node field.
3440
/// The key is the type name and the value the node to execute for that type.

src/HotChocolate/Fusion-vnext/src/Fusion.Execution/Execution/Nodes/OperationExecutionNode.cs

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ public sealed class OperationExecutionNode : ExecutionNode
1212
private readonly OperationRequirement[] _requirements;
1313
private readonly string[] _forwardedVariables;
1414
private readonly string[] _responseNames;
15+
private readonly ExecutionNodeCondition[] _conditions;
1516
private readonly OperationSourceText _operation;
1617
private readonly string? _schemaName;
1718
private readonly SelectionPath _target;
@@ -25,7 +26,8 @@ internal OperationExecutionNode(
2526
SelectionPath source,
2627
OperationRequirement[] requirements,
2728
string[] forwardedVariables,
28-
string[] responseNames)
29+
string[] responseNames,
30+
ExecutionNodeCondition[] conditions)
2931
{
3032
Id = id;
3133
_operation = operation;
@@ -35,18 +37,18 @@ internal OperationExecutionNode(
3537
_requirements = requirements;
3638
_forwardedVariables = forwardedVariables;
3739
_responseNames = responseNames;
40+
_conditions = conditions;
3841
}
3942

40-
/// <summary>
41-
/// Gets the plan unique node id.
42-
/// </summary>
43+
/// <inheritdoc />
4344
public override int Id { get; }
4445

45-
/// <summary>
46-
/// Gets the type of the execution node.
47-
/// </summary>
46+
/// <inheritdoc />
4847
public override ExecutionNodeType Type => ExecutionNodeType.Operation;
4948

49+
/// <inheritdoc />
50+
public override ReadOnlySpan<ExecutionNodeCondition> Conditions => _conditions;
51+
5052
/// <summary>
5153
/// Gets the operation definition that this execution node represents.
5254
/// </summary>

src/HotChocolate/Fusion-vnext/src/Fusion.Execution/Execution/Nodes/Serialization/JsonOperationPlanFormatter.cs

Lines changed: 56 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,13 @@
88

99
namespace HotChocolate.Fusion.Execution.Nodes.Serialization;
1010

11-
public sealed class JsonOperationPlanFormatter : OperationPlanFormatter
11+
public sealed class JsonOperationPlanFormatter(JsonWriterOptions? options = null) : OperationPlanFormatter
1212
{
13-
private readonly JsonWriterOptions _writerOptions;
14-
15-
public JsonOperationPlanFormatter(JsonWriterOptions? options = null)
13+
private readonly JsonWriterOptions _writerOptions = options ?? new JsonWriterOptions
1614
{
17-
_writerOptions = options ?? new JsonWriterOptions
18-
{
19-
Indented = false,
20-
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
21-
};
22-
}
15+
Indented = false,
16+
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
17+
};
2318

2419
public override string Format(OperationPlan plan, OperationPlanTrace? trace = null)
2520
{
@@ -124,7 +119,7 @@ private static void WriteNodes(
124119
break;
125120

126121
case NodeFieldExecutionNode nodeExecutionNode:
127-
WriteNodeNode(jsonWriter, nodeExecutionNode, nodeTrace);
122+
WriteNodeFieldNode(jsonWriter, nodeExecutionNode, nodeTrace);
128123
break;
129124
}
130125
}
@@ -191,7 +186,9 @@ private static void WriteOperationNode(
191186
jsonWriter.WriteEndArray();
192187
}
193188

194-
if (node.Requirements.Length > 0)
189+
TryWriteConditions(jsonWriter, node);
190+
191+
if (node.ForwardedVariables.Length > 0)
195192
{
196193
jsonWriter.WriteStartArray("forwardedVariables");
197194

@@ -243,6 +240,35 @@ private static void WriteIntrospectionNode(
243240

244241
jsonWriter.WriteEndArray();
245242

243+
TryWriteConditions(jsonWriter, node);
244+
245+
TryWriteNodeTrace(jsonWriter, trace);
246+
247+
jsonWriter.WriteEndObject();
248+
}
249+
250+
private static void WriteNodeFieldNode(Utf8JsonWriter jsonWriter, NodeFieldExecutionNode node, ExecutionNodeTrace? trace)
251+
{
252+
jsonWriter.WriteStartObject();
253+
jsonWriter.WriteNumber("id", node.Id);
254+
jsonWriter.WriteString("type", node.Type.ToString());
255+
256+
jsonWriter.WriteString("idValue", node.IdValue.ToString());
257+
jsonWriter.WriteString("responseName", node.ResponseName);
258+
259+
jsonWriter.WriteStartObject("branches");
260+
261+
foreach (var branch in node.Branches.OrderBy(kvp => kvp.Key))
262+
{
263+
jsonWriter.WriteNumber(branch.Key, branch.Value.Id);
264+
}
265+
266+
jsonWriter.WriteEndObject();
267+
268+
jsonWriter.WriteNumber("fallback", node.FallbackQuery.Id);
269+
270+
TryWriteConditions(jsonWriter, node);
271+
246272
TryWriteNodeTrace(jsonWriter, trace);
247273

248274
jsonWriter.WriteEndObject();
@@ -283,48 +309,33 @@ private static void TryWriteNodeTrace(Utf8JsonWriter jsonWriter, ExecutionNodeTr
283309
}
284310
}
285311

286-
private static void WriteObjectValueNode(Utf8JsonWriter jsonWriter, ObjectValueNode node)
312+
private static void TryWriteConditions(Utf8JsonWriter jsonWriter, ExecutionNode node)
287313
{
288-
jsonWriter.WriteStartObject();
289-
290-
foreach (var field in node.Fields)
314+
if (node.Conditions.Length > 0)
291315
{
292-
jsonWriter.WritePropertyName(field.Name.Value);
293-
WriteValueNode(jsonWriter, field.Value);
294-
}
316+
jsonWriter.WritePropertyName("conditions");
317+
jsonWriter.WriteStartArray();
295318

296-
jsonWriter.WriteEndObject();
319+
foreach (var condition in node.Conditions)
320+
{
321+
jsonWriter.WriteStartObject();
322+
jsonWriter.WriteString("variable", "$" + condition.VariableName);
323+
jsonWriter.WriteBoolean("passingValue", condition.PassingValue);
324+
jsonWriter.WriteEndObject();
325+
}
326+
327+
jsonWriter.WriteEndArray();
328+
}
297329
}
298330

299-
private static void WriteNodeNode(Utf8JsonWriter jsonWriter, NodeFieldExecutionNode nodeField, ExecutionNodeTrace? trace)
331+
private static void WriteObjectValueNode(Utf8JsonWriter jsonWriter, ObjectValueNode node)
300332
{
301333
jsonWriter.WriteStartObject();
302-
jsonWriter.WriteNumber("id", nodeField.Id);
303-
jsonWriter.WriteString("type", nodeField.Type.ToString());
304-
305-
jsonWriter.WriteString("idValue", nodeField.IdValue.ToString());
306-
jsonWriter.WriteString("responseName", nodeField.ResponseName);
307-
308-
jsonWriter.WriteStartObject("branches");
309-
310-
foreach (var branch in nodeField.Branches.OrderBy(kvp => kvp.Key))
311-
{
312-
jsonWriter.WriteNumber(branch.Key, branch.Value.Id);
313-
}
314-
315-
jsonWriter.WriteEndObject();
316334

317-
jsonWriter.WriteNumber("fallback", nodeField.FallbackQuery.Id);
318-
319-
if (trace is not null)
335+
foreach (var field in node.Fields)
320336
{
321-
if (!string.IsNullOrEmpty(trace.SpanId))
322-
{
323-
jsonWriter.WriteString("spanId", trace.SpanId);
324-
}
325-
326-
jsonWriter.WriteNumber("duration", trace.Duration.TotalMilliseconds);
327-
jsonWriter.WriteString("status", trace.Status.ToString());
337+
jsonWriter.WritePropertyName(field.Name.Value);
338+
WriteValueNode(jsonWriter, field.Value);
328339
}
329340

330341
jsonWriter.WriteEndObject();

0 commit comments

Comments
 (0)