Skip to content

Commit 7f8f9e5

Browse files
[Fusion] Support fulfilling lookups through shared path (#8667)
1 parent 1d18b29 commit 7f8f9e5

File tree

27 files changed

+2118
-59
lines changed

27 files changed

+2118
-59
lines changed

src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Satisfiability/RequirementsValidator.cs

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -287,7 +287,7 @@ private ImmutableArray<SatisfiabilityError> ValidateSourceSchemaTransition(
287287
var lookupDirectives =
288288
schema.GetPossibleFusionLookupDirectives(type, transitionToSchemaName);
289289

290-
if (!lookupDirectives.Any() && !HasPathInSchema(context.Path, transitionToSchemaName))
290+
if (!lookupDirectives.Any() && !CanTransitionToSchemaThroughPath(context.Path, transitionToSchemaName))
291291
{
292292
errors.Add(
293293
new SatisfiabilityError(
@@ -339,13 +339,31 @@ private ImmutableArray<SatisfiabilityError> ValidateSourceSchemaTransition(
339339
return [.. errors];
340340
}
341341

342-
private bool HasPathInSchema(SatisfiabilityPath path, string schemaName)
342+
/// <summary>
343+
/// We check whether the path we're currently on exists one-to-one
344+
/// on the given schema or whether a type on the path has a lookup
345+
/// on the given schema.
346+
/// </summary>
347+
private bool CanTransitionToSchemaThroughPath(
348+
SatisfiabilityPath path,
349+
string schemaName)
343350
{
344-
var stack = new Stack<SatisfiabilityPathItem>(path);
345-
346-
while (stack.TryPop(out var item))
351+
foreach (var pathItem in path)
347352
{
348-
if (!item.Field.ExistsInSchema(schemaName))
353+
var lookupDirectives =
354+
schema.GetPossibleFusionLookupDirectives(
355+
pathItem.Type,
356+
schemaName);
357+
358+
var hasLookups = lookupDirectives.Count > 0;
359+
var fieldExists = pathItem.Field.ExistsInSchema(schemaName);
360+
361+
if (hasLookups && fieldExists)
362+
{
363+
return true;
364+
}
365+
366+
if (!fieldExists)
349367
{
350368
return false;
351369
}

src/HotChocolate/Fusion-vnext/src/Fusion.Composition/SatisfiabilityValidator.cs

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,7 @@ private ImmutableArray<SatisfiabilityError> ValidateSourceSchemaTransition(
278278
var lookupDirectives =
279279
schema.GetPossibleFusionLookupDirectives(type, transitionToSchemaName);
280280

281-
if (!lookupDirectives.Any() && !HasPathInSchema(context.Path, transitionToSchemaName))
281+
if (!lookupDirectives.Any() && !CanTransitionToSchemaThroughPath(context.Path, transitionToSchemaName))
282282
{
283283
errors.Add(
284284
new SatisfiabilityError(
@@ -329,15 +329,31 @@ private ImmutableArray<SatisfiabilityError> ValidateSourceSchemaTransition(
329329
return [.. errors];
330330
}
331331

332-
private bool HasPathInSchema(
332+
/// <summary>
333+
/// We check whether the path we're currently on exists one-to-one
334+
/// on the given schema or whether a type on the path has a lookup
335+
/// on the given schema.
336+
/// </summary>
337+
private bool CanTransitionToSchemaThroughPath(
333338
SatisfiabilityPath path,
334339
string schemaName)
335340
{
336-
var stack = new Stack<SatisfiabilityPathItem>(path);
337-
338-
while (stack.TryPop(out var item))
341+
foreach (var pathItem in path)
339342
{
340-
if (!item.Field.ExistsInSchema(schemaName))
343+
var lookupDirectives =
344+
schema.GetPossibleFusionLookupDirectives(
345+
pathItem.Type,
346+
schemaName);
347+
348+
var hasLookups = lookupDirectives.Count > 0;
349+
var fieldExists = pathItem.Field.ExistsInSchema(schemaName);
350+
351+
if (hasLookups && fieldExists)
352+
{
353+
return true;
354+
}
355+
356+
if (!fieldExists)
341357
{
342358
return false;
343359
}

src/HotChocolate/Fusion-vnext/src/Fusion.Execution.Types/Extensions/CompositeTypeExtensions.cs

Lines changed: 0 additions & 16 deletions
This file was deleted.

src/HotChocolate/Fusion-vnext/src/Fusion.Execution.Types/Extensions/FusionSchemaDefinitionExtensions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ namespace HotChocolate.Fusion.Types;
55
/// <summary>
66
/// Provides extension methods for <see cref="ISchemaDefinition"/>.
77
/// </summary>
8-
public static class FusionSchemaDefinitionExtensions
8+
internal static class FusionSchemaDefinitionExtensions
99
{
1010
internal static SchemaEnvironment? TryGetEnvironment(this ISchemaDefinition schema)
1111
=> schema.Features.Get<SchemaEnvironment>();
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
using HotChocolate.Types;
2+
3+
namespace HotChocolate.Fusion.Types;
4+
5+
internal static class TypeDefinitionExtensions
6+
{
7+
public static bool ExistsInSchema(this ITypeDefinition type, string schemaName)
8+
{
9+
return type switch
10+
{
11+
FusionObjectTypeDefinition objectType => objectType.Sources.ContainsSchema(schemaName),
12+
FusionInterfaceTypeDefinition interfaceType => interfaceType.Sources.ContainsSchema(schemaName),
13+
FusionUnionTypeDefinition unionType => unionType.Sources.ContainsSchema(schemaName),
14+
_ => false
15+
};
16+
}
17+
}

src/HotChocolate/Fusion-vnext/src/Fusion.Execution.Types/Metadata/Lookup.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ namespace HotChocolate.Fusion.Types.Metadata;
1010
/// <summary>
1111
/// Represents a lookup field in a source schema.
1212
/// </summary>
13-
[DebuggerDisplay("{FieldName}:{FieldType} ({SchemaName})")]
13+
[DebuggerDisplay("{FieldName}:{FieldType.Name} ({SchemaName})")]
1414
public sealed class Lookup : INeedsCompletion
1515
{
1616
private readonly string _declaringTypeName;

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,20 @@ public static SelectionPath Parse(string s)
166166
return new SelectionPath(builder.ToImmutable());
167167
}
168168

169+
/// <summary>
170+
/// Constructs <see cref="SelectionPath"/> from the given <paramref name="segments"/>.
171+
/// </summary>
172+
/// <param name="segments">
173+
/// The segments to initialize the <see cref="SelectionPath"/> with.
174+
/// </param>
175+
/// <returns>
176+
/// A new <see cref="SelectionPath"/> initialized with the <paramref name="segments"/>.
177+
/// </returns>
178+
public static SelectionPath From(ImmutableArray<Segment> segments)
179+
{
180+
return new SelectionPath(segments);
181+
}
182+
169183
/// <summary>
170184
/// Returns the portion of this path that comes <em>after</em> <paramref name="basePath"/>.
171185
/// </summary>

src/HotChocolate/Fusion-vnext/src/Fusion.Execution/Execution/OperationPlanContext.cs

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -220,16 +220,23 @@ internal void AddPartialResults(
220220
ReadOnlySpan<SourceSchemaResult> results,
221221
ReadOnlySpan<string> responseNames)
222222
{
223-
var canExecutionContinue = _resultStore.AddPartialResults(sourcePath, results, responseNames);
223+
var canExecutionContinue = _resultStore.AddPartialResults(sourcePath, results, responseNames);
224224

225-
if (!canExecutionContinue)
226-
{
227-
ExecutionState.CancelProcessing();
228-
}
225+
if (!canExecutionContinue)
226+
{
227+
ExecutionState.CancelProcessing();
228+
}
229229
}
230230

231231
internal void AddPartialResults(ObjectResult result, ReadOnlySpan<Selection> selections)
232-
=> _resultStore.AddPartialResults(result, selections);
232+
{
233+
var canExecutionContinue = _resultStore.AddPartialResults(result, selections);
234+
235+
if (!canExecutionContinue)
236+
{
237+
ExecutionState.CancelProcessing();
238+
}
239+
}
233240

234241
internal void AddErrors(IError error, ReadOnlySpan<string> responseNames, params ReadOnlySpan<Path> paths)
235242
{

src/HotChocolate/Fusion-vnext/src/Fusion.Execution/Execution/Results/FetchResultStore.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ public bool AddPartialResults(
122122
}
123123
}
124124

125-
public void AddPartialResults(ObjectResult result, ReadOnlySpan<Selection> selections)
125+
public bool AddPartialResults(ObjectResult result, ReadOnlySpan<Selection> selections)
126126
{
127127
ObjectDisposedException.ThrowIf(_disposed, this);
128128
ArgumentNullException.ThrowIfNull(result);
@@ -145,8 +145,15 @@ public void AddPartialResults(ObjectResult result, ReadOnlySpan<Selection> selec
145145
continue;
146146
}
147147

148+
if (_root.IsInvalidated)
149+
{
150+
return false;
151+
}
152+
148153
result.MoveFieldTo(selection.ResponseName, _root);
149154
}
155+
156+
return true;
150157
}
151158
finally
152159
{

src/HotChocolate/Fusion-vnext/src/Fusion.Execution/Execution/Results/ValueCompletion.cs

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -366,13 +366,24 @@ private bool TryCompleteObjectValue(
366366

367367
var operation = selection.DeclaringSelectionSet.DeclaringOperation;
368368
var selectionSet = operation.GetSelectionSet(selection, objectType);
369-
var objectResult = _resultPoolSession.RentObjectResult();
370369

371-
objectResult.Initialize(_resultPoolSession, selectionSet, _includeFlags);
370+
ObjectResult objectResult;
371+
// In the case of shared paths, an object result might have already been initialized
372+
// by another operation node, so we reuse it.
373+
if (parent is ObjectFieldResult { Value: { } existingObjectResult })
374+
{
375+
objectResult = existingObjectResult;
376+
}
377+
else
378+
{
379+
objectResult = _resultPoolSession.RentObjectResult();
372380

373-
// we set the value early so that in the error case we can correctly
374-
// traverse along the parent path and propagate errors.
375-
parent.SetNextValue(objectResult);
381+
objectResult.Initialize(_resultPoolSession, selectionSet, _includeFlags);
382+
383+
// we set the value early so that in the error case we can correctly
384+
// traverse along the parent path and propagate errors.
385+
parent.SetNextValue(objectResult);
386+
}
376387

377388
foreach (var property in data.EnumerateObject())
378389
{

0 commit comments

Comments
 (0)