diff --git a/src/HotChocolate/Fusion-vnext/src/Fusion.Execution/Execution/ExecutionState.cs b/src/HotChocolate/Fusion-vnext/src/Fusion.Execution/Execution/ExecutionState.cs index 9a0de27ecdd..cbca8269a81 100644 --- a/src/HotChocolate/Fusion-vnext/src/Fusion.Execution/Execution/ExecutionState.cs +++ b/src/HotChocolate/Fusion-vnext/src/Fusion.Execution/Execution/ExecutionState.cs @@ -252,8 +252,6 @@ public void SkipNode(OperationPlanContext context, ExecutionNode node) public bool EnqueueNextNodes(OperationPlanContext context, CancellationToken cancellationToken) { - _stack.Clear(); - if (_ready.Count == 0) { return false; @@ -261,14 +259,13 @@ public bool EnqueueNextNodes(OperationPlanContext context, CancellationToken can var isSorted = true; var previousId = int.MinValue; + var readyCount = _ready.Count; foreach (var node in _ready) { if ((uint)node.Id < (uint)_remainingDependencies.Length && _remainingDependencies[node.Id] == 0) { - _stack.Push(node); - if (node.Id < previousId) { isSorted = false; @@ -278,6 +275,39 @@ public bool EnqueueNextNodes(OperationPlanContext context, CancellationToken can } } + if (isSorted) + { + var enqueuedAny = false; + + for (var i = 0; i < readyCount; i++) + { + var node = _ready[i]; + + if ((uint)node.Id < (uint)_remainingDependencies.Length + && _remainingDependencies[node.Id] == 0) + { + StartNode(context, node, cancellationToken); + enqueuedAny = true; + } + } + + _ready.Clear(); + return enqueuedAny; + } + + _stack.Clear(); + + for (var i = 0; i < readyCount; i++) + { + var node = _ready[i]; + + if ((uint)node.Id < (uint)_remainingDependencies.Length + && _remainingDependencies[node.Id] == 0) + { + _stack.Push(node); + } + } + _ready.Clear(); if (_stack.Count == 0) @@ -285,7 +315,7 @@ public bool EnqueueNextNodes(OperationPlanContext context, CancellationToken can return false; } - if (!isSorted && _stack.Count > 1) + if (_stack.Count > 1) { _stack.Sort(static (a, b) => a.Id.CompareTo(b.Id)); } diff --git a/src/HotChocolate/Fusion-vnext/src/Fusion.Execution/Execution/Results/FetchResultStore.cs b/src/HotChocolate/Fusion-vnext/src/Fusion.Execution/Execution/Results/FetchResultStore.cs index 566b8877ee7..c2cb17bc526 100644 --- a/src/HotChocolate/Fusion-vnext/src/Fusion.Execution/Execution/Results/FetchResultStore.cs +++ b/src/HotChocolate/Fusion-vnext/src/Fusion.Execution/Execution/Results/FetchResultStore.cs @@ -507,9 +507,9 @@ public ImmutableArray CreateVariableValueSets( { var segment = selectionSet.Segments[i]; - foreach (var element in current) + if (segment.Kind is SelectionPathSegmentKind.InlineFragment) { - if (segment.Kind is SelectionPathSegmentKind.InlineFragment) + foreach (var element in current) { if (element.TryGetProperty(IntrospectionFieldNames.TypeNameSpan, out var value) && value.ValueKind is JsonValueKind.String @@ -518,7 +518,10 @@ public ImmutableArray CreateVariableValueSets( next.Add(element); } } - else if (segment.Kind is SelectionPathSegmentKind.Field) + } + else if (segment.Kind is SelectionPathSegmentKind.Field) + { + foreach (var element in current) { if (!element.TryGetProperty(segment.Name, out var value)) { @@ -549,9 +552,7 @@ public ImmutableArray CreateVariableValueSets( } } - var temp = current; - current = next; - next = temp; + (next, current) = (current, next); next.Clear(); if (current.Count == 0) @@ -623,7 +624,7 @@ private ImmutableArray BuildVariableValueSets( if (nextIndex > 0) { - seen ??= new Dictionary(VariableValueComparer.Instance) + seen ??= new Dictionary(elements.Count, VariableValueComparer.Instance) { [variableValueSets[0].Values] = 0 }; @@ -677,16 +678,25 @@ private ImmutableArray BuildVariableValueSetsSingleRequirementFa Dictionary? seenStrings = null; List?[]? additionalPaths = null; var nextIndex = 0; + var isNonNullRequirement = requirement.Type.Kind is SyntaxKind.NonNullType; - foreach (var result in elements) + for (var i = 0; i < elements.Count; i++) { - if (!result.TryGetProperty(fieldName, out var value) - || value.ValueKind is JsonValueKind.Undefined) + var result = elements[i]; + + if (!result.TryGetProperty(fieldName, out var value)) + { + continue; + } + + var valueKind = value.ValueKind; + + if (valueKind is JsonValueKind.Undefined) { continue; } - if (value.ValueKind is JsonValueKind.Null && requirement.Type.Kind == SyntaxKind.NonNullType) + if (valueKind is JsonValueKind.Null && isNonNullRequirement) { continue; } @@ -694,7 +704,7 @@ private ImmutableArray BuildVariableValueSetsSingleRequirementFa variableValueSets ??= new VariableValues[elements.Count]; IValueNode mappedValue; - if (value.ValueKind is JsonValueKind.String) + if (valueKind is JsonValueKind.String) { var stringValue = value.AssertString(); @@ -707,7 +717,7 @@ private ImmutableArray BuildVariableValueSetsSingleRequirementFa } mappedValue = ResultDataMapper.GetStringValueNode(stringValue); - seenStrings ??= new Dictionary(StringComparer.Ordinal); + seenStrings ??= new Dictionary(elements.Count, StringComparer.Ordinal); seenStrings[stringValue] = nextIndex; } else @@ -722,7 +732,7 @@ private ImmutableArray BuildVariableValueSetsSingleRequirementFa continue; } - seen ??= new Dictionary(SingleValueNodeComparer.Instance); + seen ??= new Dictionary(elements.Count, SingleValueNodeComparer.Instance); seen[mappedValue] = nextIndex; } @@ -766,7 +776,7 @@ private ImmutableArray BuildVariableValueSetsSingleRequirementSl if (nextIndex > 0) { - seen ??= new Dictionary(SingleValueNodeComparer.Instance) + seen ??= new Dictionary(elements.Count, SingleValueNodeComparer.Instance) { [variableValueSets[0].Values.Fields[0].Value] = 0 }; @@ -845,14 +855,14 @@ private ImmutableArray BuildVariableValueSetsTwoRequirementsFast continue; } - var mappedValue1 = ResultDataMapper.MapLeafValue(value1, ref buffer); - var mappedValue2 = ResultDataMapper.MapLeafValue(value2, ref buffer); + var mappedValue1 = MapRequirementLeafValue(value1, ref buffer); + var mappedValue2 = MapRequirementLeafValue(value2, ref buffer); variableValueSets ??= new VariableValues[elements.Count]; var key = new TwoValueNodeTuple(mappedValue1, mappedValue2); if (nextIndex > 0) { - seen ??= new Dictionary(TwoValueNodeTupleComparer.Instance) + seen ??= new Dictionary(elements.Count, TwoValueNodeTupleComparer.Instance) { [new TwoValueNodeTuple( variableValueSets[0].Values.Fields[0].Value, @@ -916,7 +926,7 @@ private ImmutableArray BuildVariableValueSetsTwoRequirementsSlow if (nextIndex > 0) { - seen ??= new Dictionary(TwoValueNodeTupleComparer.Instance) + seen ??= new Dictionary(elements.Count, TwoValueNodeTupleComparer.Instance) { [new TwoValueNodeTuple( variableValueSets[0].Values.Fields[0].Value, @@ -1015,15 +1025,15 @@ private ImmutableArray BuildVariableValueSetsThreeRequirementsFa continue; } - var mappedValue1 = ResultDataMapper.MapLeafValue(value1, ref buffer); - var mappedValue2 = ResultDataMapper.MapLeafValue(value2, ref buffer); - var mappedValue3 = ResultDataMapper.MapLeafValue(value3, ref buffer); + var mappedValue1 = MapRequirementLeafValue(value1, ref buffer); + var mappedValue2 = MapRequirementLeafValue(value2, ref buffer); + var mappedValue3 = MapRequirementLeafValue(value3, ref buffer); variableValueSets ??= new VariableValues[elements.Count]; var key = new ThreeValueNodeTuple(mappedValue1, mappedValue2, mappedValue3); if (nextIndex > 0) { - seen ??= new Dictionary(ThreeValueNodeTupleComparer.Instance) + seen ??= new Dictionary(elements.Count, ThreeValueNodeTupleComparer.Instance) { [new ThreeValueNodeTuple( variableValueSets[0].Values.Fields[0].Value, @@ -1099,7 +1109,7 @@ private ImmutableArray BuildVariableValueSetsThreeRequirementsSl if (nextIndex > 0) { - seen ??= new Dictionary(ThreeValueNodeTupleComparer.Instance) + seen ??= new Dictionary(elements.Count, ThreeValueNodeTupleComparer.Instance) { [new ThreeValueNodeTuple( variableValueSets[0].Values.Fields[0].Value, @@ -1202,6 +1212,14 @@ private static bool TryGetSimpleRequirementFieldName( return false; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static IValueNode MapRequirementLeafValue( + CompositeResultElement value, + ref PooledArrayWriter? buffer) + => value.ValueKind is JsonValueKind.String + ? ResultDataMapper.GetStringValueNode(value.AssertString()) + : ResultDataMapper.MapLeafValue(value, ref buffer); + private static void AppendUnrolledLists( CompositeResultElement list, List destination) @@ -1265,14 +1283,10 @@ private static SourceResultElement GetDataElement(SelectionPath sourcePath, Sour case SelectionPathSegmentKind.InlineFragment: if (!current.TryGetProperty(IntrospectionFieldNames.TypeNameSpan, out var typeNameProperty) - || typeNameProperty.ValueKind != JsonValueKind.String) - { - return default; - } - - var typeName = typeNameProperty.GetString()!; - - if (typeName != segment.Name) + || typeNameProperty.ValueKind != JsonValueKind.String + || !typeNameProperty.TextEqualsHelper( + segment.Name, + isPropertyName: false)) { return default; } diff --git a/src/HotChocolate/Fusion-vnext/src/Fusion.Execution/Execution/Results/ResultDataMapper.cs b/src/HotChocolate/Fusion-vnext/src/Fusion.Execution/Execution/Results/ResultDataMapper.cs index 4d1f1a4519d..54f2e3a555d 100644 --- a/src/HotChocolate/Fusion-vnext/src/Fusion.Execution/Execution/Results/ResultDataMapper.cs +++ b/src/HotChocolate/Fusion-vnext/src/Fusion.Execution/Execution/Results/ResultDataMapper.cs @@ -13,6 +13,7 @@ internal static class ResultDataMapper { private const int CachedNumericStringMax = 4096; private static readonly StringValueNode[] s_cachedNumericStrings = CreateCachedNumericStrings(); + private static readonly IntValueNode[] s_cachedNumericIntValues = CreateCachedNumericIntValues(); public static IValueNode? Map( CompositeResultElement result, @@ -136,6 +137,11 @@ private static IValueNode ParseLeafValue( case JsonValueKind.Number: if (value.TryGetInt64(out var intValue)) { + if ((ulong)intValue <= CachedNumericStringMax) + { + return s_cachedNumericIntValues[(int)intValue]; + } + return new IntValueNode(intValue); } @@ -224,6 +230,18 @@ private static StringValueNode[] CreateCachedNumericStrings() return values; } + private static IntValueNode[] CreateCachedNumericIntValues() + { + var values = new IntValueNode[CachedNumericStringMax + 1]; + + for (var i = 0; i < values.Length; i++) + { + values[i] = new IntValueNode(i); + } + + return values; + } + private static IValueNode? Visit(ObjectValueSelectionNode node, Context context) { var result = context.Result; diff --git a/src/HotChocolate/Fusion-vnext/src/Fusion.Execution/Execution/Results/ValueCompletion.cs b/src/HotChocolate/Fusion-vnext/src/Fusion.Execution/Execution/Results/ValueCompletion.cs index 53fb06def00..71937f634a4 100644 --- a/src/HotChocolate/Fusion-vnext/src/Fusion.Execution/Execution/Results/ValueCompletion.cs +++ b/src/HotChocolate/Fusion-vnext/src/Fusion.Execution/Execution/Results/ValueCompletion.cs @@ -16,6 +16,8 @@ internal sealed class ValueCompletion private readonly ISchemaDefinition _schema; private readonly IErrorHandler _errorHandler; private readonly ErrorHandlingMode _errorHandlingMode; + private readonly bool _haltOnError; + private readonly bool _haltOnNullViolation; private readonly int _maxDepth; public ValueCompletion( @@ -31,6 +33,8 @@ public ValueCompletion( _schema = schema; _errorHandler = errorHandler; _errorHandlingMode = errorHandlingMode; + _haltOnError = errorHandlingMode is ErrorHandlingMode.Halt; + _haltOnNullViolation = errorHandlingMode is ErrorHandlingMode.Propagate or ErrorHandlingMode.Halt; _maxDepth = maxDepth; } @@ -194,7 +198,7 @@ private bool TryCompleteValue( _store.AddError(error); - if (_errorHandlingMode is ErrorHandlingMode.Propagate or ErrorHandlingMode.Halt) + if (_haltOnNullViolation) { return false; } @@ -221,7 +225,7 @@ private bool TryCompleteValue( _store.AddError(errorWithPath); - if (_errorHandlingMode is ErrorHandlingMode.Halt) + if (_haltOnError) { return false; } @@ -268,6 +272,7 @@ private bool TryCompleteList( var isNullable = elementType.IsNullableType(); var isLeaf = elementType.IsLeafType(); var isNested = elementType.IsListType(); + var isAbstract = elementType.IsAbstractType(); target.SetArrayValue(source.GetArrayLength()); @@ -291,7 +296,7 @@ private bool TryCompleteList( _store.AddError(errorWithPath); - if (_errorHandlingMode is ErrorHandlingMode.Halt) + if (_haltOnError) { return false; } @@ -299,7 +304,7 @@ private bool TryCompleteList( if (element.IsNullOrUndefined()) { - if (!isNullable && _errorHandlingMode is ErrorHandlingMode.Propagate or ErrorHandlingMode.Halt) + if (!isNullable && _haltOnNullViolation) { return false; } @@ -308,59 +313,61 @@ private bool TryCompleteList( goto TryCompleteList_MoveNext; } - if (!HandleElement(element, enumerator.Current, errorTrieForIndex)) - { - if (!isNullable) - { - return false; - } + var targetElement = enumerator.Current; + bool completed; - enumerator.Current.SetNullValue(); - goto TryCompleteList_MoveNext; - } - -TryCompleteList_MoveNext: - i++; - } - - return true; - - bool HandleElement( - SourceResultElement sourceElement, - CompositeResultElement targetElement, - ErrorTrie? errorTrieForIndex) - { if (isNested) { - return TryCompleteList( - sourceElement, + completed = TryCompleteList( + element, targetElement, errorTrieForIndex, selection, elementType, depth); } - - if (isLeaf) + else if (isLeaf) { - targetElement.SetLeafValue(sourceElement); - return true; + targetElement.SetLeafValue(element); + completed = true; + } + else if (isAbstract) + { + completed = TryCompleteAbstractValue( + element, + targetElement, + errorTrieForIndex, + selection, + elementType, + depth); + } + else + { + completed = TryCompleteObjectValue( + selection, + elementType, + element, + errorTrieForIndex, + depth, + targetElement); } - if (elementType.IsAbstractType()) + if (!completed) { - return TryCompleteAbstractValue(sourceElement, - targetElement, errorTrieForIndex, selection, elementType, depth); + if (!isNullable) + { + return false; + } + + targetElement.SetNullValue(); + goto TryCompleteList_MoveNext; } - return TryCompleteObjectValue( - selection, - elementType, - sourceElement, - errorTrieForIndex, - depth, - targetElement); +TryCompleteList_MoveNext: + i++; } + + return true; } private bool TryCompleteObjectValue( diff --git a/src/HotChocolate/Fusion-vnext/src/Fusion.Execution/Text/Json/CompositeResultDocument.WriteTo.cs b/src/HotChocolate/Fusion-vnext/src/Fusion.Execution/Text/Json/CompositeResultDocument.WriteTo.cs index a028a66c65c..9f38a3cc7db 100644 --- a/src/HotChocolate/Fusion-vnext/src/Fusion.Execution/Text/Json/CompositeResultDocument.WriteTo.cs +++ b/src/HotChocolate/Fusion-vnext/src/Fusion.Execution/Text/Json/CompositeResultDocument.WriteTo.cs @@ -45,38 +45,31 @@ public void WriteValue(Cursor cursor, DbRow row) Debug.Assert(tokenType is not ElementTokenType.Reference); Debug.Assert(tokenType is not ElementTokenType.EndObject); Debug.Assert(tokenType is not ElementTokenType.EndArray); + var isSourceResult = (ElementFlags.SourceResult & row.Flags) == ElementFlags.SourceResult; switch (tokenType) { - case ElementTokenType.StartObject - when (ElementFlags.SourceResult & row.Flags) != ElementFlags.SourceResult: - WriteObject(cursor, row); - break; - case ElementTokenType.StartObject: - { - var sourceDocument = document._sources[row.SourceDocumentId]; - // Reconstruct the source cursor from stored Location (Chunk) and SizeOrLength (Row) - var sourceCursor = SourceResultDocument.Cursor.From(row.Location, row.SizeOrLength); - var formatter = new SourceResultDocument.RawJsonFormatter(sourceDocument, writer); - formatter.WriteValue(sourceCursor); - break; - } - - case ElementTokenType.StartArray - when (ElementFlags.SourceResult & row.Flags) != ElementFlags.SourceResult: - WriteArray(cursor, row); + if (isSourceResult) + { + WriteSourceValue(row); + } + else + { + WriteObject(cursor, row); + } break; case ElementTokenType.StartArray: - { - var sourceDocument = document._sources[row.SourceDocumentId]; - // Reconstruct the source cursor from stored Location (Chunk) and SizeOrLength (Row) - var sourceCursor = SourceResultDocument.Cursor.From(row.Location, row.SizeOrLength); - var formatter = new SourceResultDocument.RawJsonFormatter(sourceDocument, writer); - formatter.WriteValue(sourceCursor); + if (isSourceResult) + { + WriteSourceValue(row); + } + else + { + WriteArray(cursor, row); + } break; - } case ElementTokenType.None: case ElementTokenType.Null: @@ -93,14 +86,18 @@ public void WriteValue(Cursor cursor, DbRow row) case ElementTokenType.String: { - var value = document.ReadRawValue(row); + var value = isSourceResult + ? document._sources[row.SourceDocumentId].ReadRawValue(row.Location, row.SizeOrLength) + : document.ReadRawValue(row); writer.WriteStringValue(value, skipEscaping: true); break; } case ElementTokenType.Number: { - var value = document.ReadRawValue(row); + var value = isSourceResult + ? document._sources[row.SourceDocumentId].ReadRawValue(row.Location, row.SizeOrLength) + : document.ReadRawValue(row); writer.WriteNumberValue(value); break; } @@ -110,6 +107,15 @@ public void WriteValue(Cursor cursor, DbRow row) } } + private void WriteSourceValue(DbRow row) + { + var sourceDocument = document._sources[row.SourceDocumentId]; + // Reconstruct the source cursor from stored Location (Chunk) and SizeOrLength (Row) + var sourceCursor = SourceResultDocument.Cursor.From(row.Location, row.SizeOrLength); + var formatter = new SourceResultDocument.RawJsonFormatter(sourceDocument, writer); + formatter.WriteValue(sourceCursor); + } + private void WriteObject(Cursor start, DbRow startRow) { Debug.Assert(startRow.TokenType is ElementTokenType.StartObject);