diff --git a/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/CertificationSchema/AnnotationBased/CertificationTests.cs b/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/CertificationSchema/AnnotationBased/CertificationTests.cs index db599224a22..2e7e16f41b2 100644 --- a/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/CertificationSchema/AnnotationBased/CertificationTests.cs +++ b/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/CertificationSchema/AnnotationBased/CertificationTests.cs @@ -32,8 +32,8 @@ public async Task Subgraph_SDL() }"); // assert - Assert.IsType( - Assert.IsType( + Assert.IsType( + Assert.IsType( Assert.IsType(result).Data) .GetValueOrDefault("_service")) .GetValueOrDefault("sdl") diff --git a/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/CertificationSchema/CodeFirst/CertificationTests.cs b/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/CertificationSchema/CodeFirst/CertificationTests.cs index d53385c19ed..a394d616573 100644 --- a/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/CertificationSchema/CodeFirst/CertificationTests.cs +++ b/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/CertificationSchema/CodeFirst/CertificationTests.cs @@ -32,8 +32,8 @@ public async Task Subgraph_SDL() }"); // assert - Assert.IsType( - Assert.IsType( + Assert.IsType( + Assert.IsType( Assert.IsType(result).Data) .GetValueOrDefault("_service")) .GetValueOrDefault("sdl") diff --git a/src/HotChocolate/Core/benchmark/Execution.Benchmarks/ResultDataBenchmarks.cs b/src/HotChocolate/Core/benchmark/Execution.Benchmarks/ResultDataBenchmarks.txt similarity index 100% rename from src/HotChocolate/Core/benchmark/Execution.Benchmarks/ResultDataBenchmarks.cs rename to src/HotChocolate/Core/benchmark/Execution.Benchmarks/ResultDataBenchmarks.txt diff --git a/src/HotChocolate/Core/src/Abstractions/Execution/IResultData.cs b/src/HotChocolate/Core/src/Abstractions/Execution/IResultData.cs deleted file mode 100644 index 99c688dcfed..00000000000 --- a/src/HotChocolate/Core/src/Abstractions/Execution/IResultData.cs +++ /dev/null @@ -1,8 +0,0 @@ -#nullable enable - -namespace HotChocolate.Execution; - -public interface IResultData -{ - IResultData? Parent { get; } -} diff --git a/src/HotChocolate/Core/src/Abstractions/Execution/IResultList.cs b/src/HotChocolate/Core/src/Abstractions/Execution/IResultList.cs deleted file mode 100644 index f5ef9624675..00000000000 --- a/src/HotChocolate/Core/src/Abstractions/Execution/IResultList.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System.Collections.Generic; - -#nullable enable - -namespace HotChocolate.Execution; - -public interface IResultList - : IReadOnlyList - , IResultData -{ -} diff --git a/src/HotChocolate/Core/src/Abstractions/Execution/IResultMap.cs b/src/HotChocolate/Core/src/Abstractions/Execution/IResultMap.cs deleted file mode 100644 index 94ee80bff02..00000000000 --- a/src/HotChocolate/Core/src/Abstractions/Execution/IResultMap.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System.Collections.Generic; - -#nullable enable - -namespace HotChocolate.Execution; - -public interface IResultMap - : IReadOnlyList - , IResultData -{ -} diff --git a/src/HotChocolate/Core/src/Abstractions/Execution/IResultMapList.cs b/src/HotChocolate/Core/src/Abstractions/Execution/IResultMapList.cs deleted file mode 100644 index 1b437ecd677..00000000000 --- a/src/HotChocolate/Core/src/Abstractions/Execution/IResultMapList.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System.Collections.Generic; - -#nullable enable - -namespace HotChocolate.Execution; - -public interface IResultMapList - : IReadOnlyList - , IResultData -{ -} diff --git a/src/HotChocolate/Core/src/Abstractions/Execution/ResultValue.cs b/src/HotChocolate/Core/src/Abstractions/Execution/ResultValue.cs deleted file mode 100644 index 982c73c3825..00000000000 --- a/src/HotChocolate/Core/src/Abstractions/Execution/ResultValue.cs +++ /dev/null @@ -1,106 +0,0 @@ -using System; -using HotChocolate.Properties; - -#nullable enable - -namespace HotChocolate.Execution; - -/// -/// Represents an entry in a -/// -public readonly struct ResultValue : IEquatable -{ - /// - /// Creates a new result value. - /// - /// The name of the entry. - /// The value of the entry. - /// - /// Specifies if the is allowed to be null. - /// - /// - /// is null or . - /// - public ResultValue(string name, object? value, bool isNullable = true) - { - if (string.IsNullOrEmpty(name)) - { - throw new ArgumentException( - AbstractionResources.ResultValue_NameIsNullOrEmpty, - nameof(name)); - } - - Name = name; - Value = value; - IsNullable = isNullable; - } - - /// - /// Gets the name of this entry. - /// - public string Name { get; } - - /// - /// Gets the value of this entry. - /// - public object? Value { get; } - - /// - /// Specifies if is allowed to be empty. - /// - public bool IsNullable { get; } - - /// - /// Specifies if this entry is fully initialized. - /// - public bool IsInitialized => Name is not null; - - /// Indicates whether this instance and a specified object are equal. - /// The object to compare with the current instance. - /// - /// if and this instance are the same type - /// and represent the same value; otherwise, . - /// - public override bool Equals(object? obj) - => obj is ResultValue value && Equals(value); - - /// - /// Indicates whether the current object is equal to another object of the same type. - /// - /// An object to compare with this object. - /// - /// if the current object is equal to the - /// parameter; otherwise, . - /// - public bool Equals(ResultValue other) - { - if (IsInitialized != other.IsInitialized) - { - return false; - } - - if (IsInitialized == false) - { - return true; - } - - return string.Equals(Name, other.Name, StringComparison.Ordinal) && - Equals(Value, other.Value); - } - - /// - /// Returns the hash code for this instance. - /// - /// - /// A 32-bit signed integer that is the hash code for this instance. - /// - public override int GetHashCode() - { - unchecked - { - var hash = (Name?.GetHashCode() ?? 0) * 3; - hash ^= (Value?.GetHashCode() ?? 0) * 7; - return hash; - } - } -} diff --git a/src/HotChocolate/Core/src/Execution/DependencyInjection/InternalServiceCollectionExtensions.cs b/src/HotChocolate/Core/src/Execution/DependencyInjection/InternalServiceCollectionExtensions.cs index ed8b7f3b8f9..9b7143f6590 100644 --- a/src/HotChocolate/Core/src/Execution/DependencyInjection/InternalServiceCollectionExtensions.cs +++ b/src/HotChocolate/Core/src/Execution/DependencyInjection/InternalServiceCollectionExtensions.cs @@ -43,14 +43,11 @@ internal static class InternalServiceCollectionExtensions internal static IServiceCollection TryAddResultPool( this IServiceCollection services, - int maximumRetained = 512) + int maximumRetained = ResultPoolDefaults.MaximumRetained, + int maximumArrayCapacity = ResultPoolDefaults.MaximumAllowedCapacity) { - services.TryAddSingleton>>( - _ => new ResultMapPool(maximumRetained)); - services.TryAddSingleton>>( - _ => new ResultMapListPool(maximumRetained)); - services.TryAddSingleton>>( - _ => new ResultListPool(maximumRetained)); + services.TryAddSingleton(_ => new ObjectResultPool(maximumRetained, maximumArrayCapacity)); + services.TryAddSingleton(_ => new ListResultPool(maximumRetained, maximumArrayCapacity)); services.TryAddSingleton(); return services; } @@ -63,7 +60,6 @@ internal static class InternalServiceCollectionExtensions _ => new ExecutionTaskPool( new ResolverTaskPoolPolicy(), maximumRetained)); - return services; } diff --git a/src/HotChocolate/Core/src/Execution/Extensions/OperationContextExtensions.cs b/src/HotChocolate/Core/src/Execution/Extensions/OperationContextExtensions.cs index 03277dd691d..8541a9bebe7 100644 --- a/src/HotChocolate/Core/src/Execution/Extensions/OperationContextExtensions.cs +++ b/src/HotChocolate/Core/src/Execution/Extensions/OperationContextExtensions.cs @@ -38,9 +38,9 @@ internal static class OperationContextExtensions public static IOperationContext SetData( this IOperationContext context, - ResultMap resultMap) + ObjectResult objectResult) { - context.Result.SetData(resultMap); + context.Result.SetData(objectResult); return context; } diff --git a/src/HotChocolate/Core/src/Execution/HotChocolate.Execution.csproj b/src/HotChocolate/Core/src/Execution/HotChocolate.Execution.csproj index 13e2d3f3036..e44f28489ff 100644 --- a/src/HotChocolate/Core/src/Execution/HotChocolate.Execution.csproj +++ b/src/HotChocolate/Core/src/Execution/HotChocolate.Execution.csproj @@ -156,6 +156,15 @@ ValueCompletion.cs + + ResultBuilder.cs + + + ResultBuilder.cs + + + ResultBuilder.cs + diff --git a/src/HotChocolate/Core/src/Execution/Instrumentation/ApolloTracingResultBuilder.cs b/src/HotChocolate/Core/src/Execution/Instrumentation/ApolloTracingResultBuilder.cs index 9e60e44bcc4..081210951d8 100644 --- a/src/HotChocolate/Core/src/Execution/Instrumentation/ApolloTracingResultBuilder.cs +++ b/src/HotChocolate/Core/src/Execution/Instrumentation/ApolloTracingResultBuilder.cs @@ -12,10 +12,10 @@ internal class ApolloTracingResultBuilder private readonly ConcurrentQueue _resolverRecords = new ConcurrentQueue(); private TimeSpan _duration; - private ResultMap? _parsingResult; + private ObjectResult? _parsingResult; private DateTimeOffset _startTime; private long _startTimestamp; - private ResultMap? _validationResult; + private ObjectResult? _validationResult; public void SetRequestStartTime( DateTimeOffset startTime, @@ -27,18 +27,18 @@ internal class ApolloTracingResultBuilder public void SetParsingResult(long startTimestamp, long endTimestamp) { - _parsingResult = new ResultMap(); + _parsingResult = new ObjectResult(); _parsingResult.EnsureCapacity(2); - _parsingResult.SetValue(0, StartOffset, startTimestamp - _startTimestamp); - _parsingResult.SetValue(1, Duration, endTimestamp - startTimestamp); + _parsingResult.SetValueUnsafe(0, StartOffset, startTimestamp - _startTimestamp); + _parsingResult.SetValueUnsafe(1, Duration, endTimestamp - startTimestamp); } public void SetValidationResult(long startTimestamp, long endTimestamp) { - _validationResult = new ResultMap(); + _validationResult = new ObjectResult(); _validationResult.EnsureCapacity(2); - _validationResult.SetValue(0, StartOffset, startTimestamp - _startTimestamp); - _validationResult.SetValue(1, Duration, endTimestamp - startTimestamp); + _validationResult.SetValueUnsafe(0, StartOffset, startTimestamp - _startTimestamp); + _validationResult.SetValueUnsafe(1, Duration, endTimestamp - startTimestamp); } public void AddResolverResult(ApolloTracingResolverRecord record) @@ -51,7 +51,7 @@ public void SetRequestDuration(TimeSpan duration) _duration = duration; } - public IResultMap Build() + public ObjectResult Build() { if (_parsingResult is null) { @@ -67,37 +67,37 @@ public IResultMap Build() SetValidationResult(_startTimestamp, _startTimestamp); } - var executionResult = new ResultMap(); - executionResult.EnsureCapacity(1); - executionResult.SetValue(0, ApolloTracingResultKeys.Resolvers, BuildResolverResults()); + var result = new ObjectResult(); + result.EnsureCapacity(1); + result.SetValueUnsafe(0, ApolloTracingResultKeys.Resolvers, BuildResolverResults()); - var result = new ResultMap(); - result.EnsureCapacity(7); - result.SetValue(0, ApolloTracingResultKeys.Version, _apolloTracingVersion); - result.SetValue(1, StartTime, _startTime.ToRfc3339DateTimeString()); - result.SetValue(2, EndTime, _startTime.Add(_duration).ToRfc3339DateTimeString()); - result.SetValue(3, Duration, _duration.Ticks * _ticksToNanosecondsMultiplicator); - result.SetValue(4, Parsing, _parsingResult); - result.SetValue(5, ApolloTracingResultKeys.Validation, _validationResult); - result.SetValue(6, ApolloTracingResultKeys.Execution, executionResult); - return result; + var details = new ObjectResult(); + details.EnsureCapacity(7); + details.SetValueUnsafe(0, ApolloTracingResultKeys.Version, _apolloTracingVersion); + details.SetValueUnsafe(1, StartTime, _startTime.ToRfc3339DateTimeString()); + details.SetValueUnsafe(2, EndTime, _startTime.Add(_duration).ToRfc3339DateTimeString()); + details.SetValueUnsafe(3, Duration, _duration.Ticks * _ticksToNanosecondsMultiplicator); + details.SetValueUnsafe(4, Parsing, _parsingResult); + details.SetValueUnsafe(5, ApolloTracingResultKeys.Validation, _validationResult); + details.SetValueUnsafe(6, ApolloTracingResultKeys.Execution, result); + return details; } - private ResultMap[] BuildResolverResults() + private ObjectResult[] BuildResolverResults() { var i = 0; - var results = new ResultMap[_resolverRecords.Count]; + var results = new ObjectResult[_resolverRecords.Count]; foreach (var record in _resolverRecords) { - var result = new ResultMap(); + var result = new ObjectResult(); result.EnsureCapacity(6); - result.SetValue(0, ApolloTracingResultKeys.Path, record.Path); - result.SetValue(1, ParentType, record.ParentType); - result.SetValue(2, FieldName, record.FieldName); - result.SetValue(3, ReturnType, record.ReturnType); - result.SetValue(4, StartOffset, record.StartTimestamp - _startTimestamp); - result.SetValue(5, Duration, record.EndTimestamp - record.StartTimestamp); + result.SetValueUnsafe(0, ApolloTracingResultKeys.Path, record.Path); + result.SetValueUnsafe(1, ParentType, record.ParentType); + result.SetValueUnsafe(2, FieldName, record.FieldName); + result.SetValueUnsafe(3, ReturnType, record.ReturnType); + result.SetValueUnsafe(4, StartOffset, record.StartTimestamp - _startTimestamp); + result.SetValueUnsafe(5, Duration, record.EndTimestamp - record.StartTimestamp); results[i++] = result; } diff --git a/src/HotChocolate/Core/src/Execution/Processing/ArgumentMap.cs b/src/HotChocolate/Core/src/Execution/Processing/ArgumentMap.cs index bd8b52240d3..1a101a55169 100644 --- a/src/HotChocolate/Core/src/Execution/Processing/ArgumentMap.cs +++ b/src/HotChocolate/Core/src/Execution/Processing/ArgumentMap.cs @@ -10,11 +10,13 @@ namespace HotChocolate.Execution.Processing; internal sealed class ArgumentMap : IArgumentMap { private readonly IReadOnlyDictionary _arguments; + private readonly bool _isFinal; + private readonly bool _hasErrors; public ArgumentMap(IReadOnlyDictionary arguments) { _arguments = arguments; - IsFinal = true; + _isFinal = true; if (_arguments.Count > 0) { @@ -22,12 +24,12 @@ public ArgumentMap(IReadOnlyDictionary arguments) { if (!argument.IsFullyCoerced) { - IsFinal = false; + _isFinal = false; } if (argument.HasError) { - HasErrors = true; + _hasErrors = true; } } } @@ -35,11 +37,11 @@ public ArgumentMap(IReadOnlyDictionary arguments) public ArgumentValue this[string key] => _arguments[key]; - public bool IsFinalNoErrors => IsFinal && !HasErrors; + public bool IsFinalNoErrors => _isFinal && !_hasErrors; - public bool IsFinal { get; } + public bool IsFinal => _isFinal; - public bool HasErrors { get; } + public bool HasErrors => _hasErrors; public IEnumerable Keys => _arguments.Keys; diff --git a/src/HotChocolate/Core/src/Execution/Processing/DeferredFragment.cs b/src/HotChocolate/Core/src/Execution/Processing/DeferredFragment.cs index cb76ef202ea..e85b153ff5d 100644 --- a/src/HotChocolate/Core/src/Execution/Processing/DeferredFragment.cs +++ b/src/HotChocolate/Core/src/Execution/Processing/DeferredFragment.cs @@ -64,7 +64,7 @@ internal sealed class DeferredFragment : IDeferredExecutionTask /// public async Task ExecuteAsync(IOperationContext operationContext) { - var resultMap = EnqueueResolverTasks( + var parentResult = EnqueueResolverTasks( operationContext, Fragment.SelectionSet, Parent, @@ -77,7 +77,7 @@ internal sealed class DeferredFragment : IDeferredExecutionTask .TrySetNext(true) .SetLabel(Label) .SetPath(Path) - .SetData(resultMap) + .SetData(parentResult) .BuildResult(); } } diff --git a/src/HotChocolate/Core/src/Execution/Processing/DeferredStream.cs b/src/HotChocolate/Core/src/Execution/Processing/DeferredStream.cs index fd271b9156d..8ff6708e7fe 100644 --- a/src/HotChocolate/Core/src/Execution/Processing/DeferredStream.cs +++ b/src/HotChocolate/Core/src/Execution/Processing/DeferredStream.cs @@ -98,7 +98,7 @@ internal sealed class DeferredStream : IDeferredExecutionTask .TrySetNext(true) .SetLabel(Label) .SetPath(operationContext.PathFactory.Append(Path, Index)) - .SetData((ResultMap)_task.ChildTask.ResultMap[0].Value!) + .SetData((ObjectResult)_task.ChildTask.ParentResult[0].Value!) .BuildResult(); _task.ChildTask.CompleteUnsafe(); diff --git a/src/HotChocolate/Core/src/Execution/Processing/IHasResultDataParent.cs b/src/HotChocolate/Core/src/Execution/Processing/IHasResultDataParent.cs deleted file mode 100644 index 2b4d2fecfc7..00000000000 --- a/src/HotChocolate/Core/src/Execution/Processing/IHasResultDataParent.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace HotChocolate.Execution.Processing; - -internal interface IHasResultDataParent -{ - IResultData? Parent { get; set; } -} diff --git a/src/HotChocolate/Core/src/Execution/Processing/IOperationContext.cs b/src/HotChocolate/Core/src/Execution/Processing/IOperationContext.cs index 24973e18aa0..f580df4a52c 100644 --- a/src/HotChocolate/Core/src/Execution/Processing/IOperationContext.cs +++ b/src/HotChocolate/Core/src/Execution/Processing/IOperationContext.cs @@ -75,7 +75,7 @@ internal interface IOperationContext : IHasContextData /// /// The result helper which provides utilities to build up the result. /// - IResultHelper Result { get; } + ResultBuilder Result { get; } /// /// The work scheduler organizes the processing of request tasks. diff --git a/src/HotChocolate/Core/src/Execution/Processing/IResultHelper.cs b/src/HotChocolate/Core/src/Execution/Processing/IResultHelper.cs deleted file mode 100644 index f52fa9c6bc7..00000000000 --- a/src/HotChocolate/Core/src/Execution/Processing/IResultHelper.cs +++ /dev/null @@ -1,49 +0,0 @@ -using System.Collections.Generic; -using HotChocolate.Language; - -namespace HotChocolate.Execution.Processing; - -internal interface IResultHelper -{ - ResultMapList RentResultMapList(); - - ResultMap RentResultMap(int count); - - ResultList RentResultList(); - - IReadOnlyList Errors { get; } - - void SetData(ResultMap resultMap); - - void SetExtension(string key, object? value); - - void SetContextData(string key, object? value); - - void SetPath(Path? path); - - void SetLabel(string? label); - - void SetHasNext(bool value); - - /// - /// Adds an error thread-safe to the result object. - /// - /// The error that shall be added. - /// The affected field. - void AddError(IError error, FieldNode? selection = null); - - /// - /// Adds a errors thread-safe to the result object. - /// - /// The errors that shall be added. - /// The affected field. - void AddErrors(IEnumerable errors, FieldNode? selection = null); - - void AddNonNullViolation(FieldNode selection, Path path, IResultMap parent); - - IQueryResult BuildResult(); - - void DropResult(); - - void Clear(); -} diff --git a/src/HotChocolate/Core/src/Execution/Processing/MiddlewareContext.Pooling.cs b/src/HotChocolate/Core/src/Execution/Processing/MiddlewareContext.Pooling.cs index dc199b7b349..8fd0a15126b 100644 --- a/src/HotChocolate/Core/src/Execution/Processing/MiddlewareContext.Pooling.cs +++ b/src/HotChocolate/Core/src/Execution/Processing/MiddlewareContext.Pooling.cs @@ -14,7 +14,7 @@ public MiddlewareContext() public void Initialize( IOperationContext operationContext, ISelection selection, - ResultMap resultMap, + ObjectResult parentResult, int responseIndex, object? parent, Path path, @@ -23,7 +23,7 @@ public MiddlewareContext() _operationContext = operationContext; _services = operationContext.Services; _selection = selection; - ResultMap = resultMap; + ParentResult = parentResult; ResponseIndex = responseIndex; _parent = parent; _parser = _operationContext.Services.GetRequiredService(); @@ -52,7 +52,7 @@ public void Clean() IsResultModified = false; ValueType = null; ResponseIndex = default; - ResultMap = default!; + ParentResult = default!; HasErrors = false; Arguments = default!; RequestAborted = default!; diff --git a/src/HotChocolate/Core/src/Execution/Processing/MiddlewareContext.State.cs b/src/HotChocolate/Core/src/Execution/Processing/MiddlewareContext.State.cs index c8560937d0d..ba6e14c1a80 100644 --- a/src/HotChocolate/Core/src/Execution/Processing/MiddlewareContext.State.cs +++ b/src/HotChocolate/Core/src/Execution/Processing/MiddlewareContext.State.cs @@ -18,7 +18,7 @@ internal partial class MiddlewareContext public IType? ValueType { get; set; } - public ResultMap ResultMap { get; private set; } = default!; + public ObjectResult ParentResult { get; private set; } = default!; public bool HasErrors { get; private set; } diff --git a/src/HotChocolate/Core/src/Execution/Processing/OperationContext.Pooling.cs b/src/HotChocolate/Core/src/Execution/Processing/OperationContext.Pooling.cs index bc342c1d245..78e95c52f7a 100644 --- a/src/HotChocolate/Core/src/Execution/Processing/OperationContext.Pooling.cs +++ b/src/HotChocolate/Core/src/Execution/Processing/OperationContext.Pooling.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Concurrent; +using System.Runtime.CompilerServices; using HotChocolate.Execution.Processing.Tasks; using HotChocolate.Fetching; using Microsoft.Extensions.ObjectPool; @@ -12,7 +13,7 @@ internal sealed partial class OperationContext private readonly ConcurrentBag _cleanupActions = new(); private readonly ObjectPool _resolverTaskPool; private readonly WorkScheduler _workScheduler; - private readonly ResultHelper _resultHelper; + private readonly ResultBuilder _resultHelper; private readonly PooledPathFactory _pathFactory; private IRequestContext _requestContext = default!; private IOperation _operation = default!; @@ -30,7 +31,7 @@ internal sealed partial class OperationContext { _resolverTaskPool = resolverTaskPool; _workScheduler = new WorkScheduler(this); - _resultHelper = new ResultHelper(resultPool); + _resultHelper = new ResultBuilder(resultPool); _pathFactory = new PooledPathFactory(indexerPathSegmentPool, namePathSegmentPool); } @@ -82,6 +83,7 @@ public void Clean() } } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private void AssertInitialized() { if (!_isInitialized) diff --git a/src/HotChocolate/Core/src/Execution/Processing/OperationContext.cs b/src/HotChocolate/Core/src/Execution/Processing/OperationContext.cs index d1afbc6334b..d4c7be9deab 100644 --- a/src/HotChocolate/Core/src/Execution/Processing/OperationContext.cs +++ b/src/HotChocolate/Core/src/Execution/Processing/OperationContext.cs @@ -46,7 +46,7 @@ public IServiceProvider Services } } - public IResultHelper Result + public ResultBuilder Result { get { diff --git a/src/HotChocolate/Core/src/Abstractions/Execution/IResultMemoryOwner.cs b/src/HotChocolate/Core/src/Execution/Processing/Result/IResultMemoryOwner.cs similarity index 74% rename from src/HotChocolate/Core/src/Abstractions/Execution/IResultMemoryOwner.cs rename to src/HotChocolate/Core/src/Execution/Processing/Result/IResultMemoryOwner.cs index 22661d9b367..f49e8352ef5 100644 --- a/src/HotChocolate/Core/src/Abstractions/Execution/IResultMemoryOwner.cs +++ b/src/HotChocolate/Core/src/Execution/Processing/Result/IResultMemoryOwner.cs @@ -1,14 +1,14 @@ -using System; - #nullable enable -namespace HotChocolate.Execution; +using System; + +namespace HotChocolate.Execution.Processing; /// -/// This interface represents the owner of the rented objects associated -/// with the result data structure. -/// -/// When this object is disposed it will return the objects representing the +/// This interface represents the owner of the rented objects associated +/// with the result data structure. +/// +/// When this object is disposed it will return the objects representing the /// object structure back to the object pools. /// public interface IResultMemoryOwner : IDisposable @@ -16,5 +16,5 @@ public interface IResultMemoryOwner : IDisposable /// /// The data object structure representing the GraphQL result. /// - IResultMap? Data { get; } + ObjectResult? Data { get; } } diff --git a/src/HotChocolate/Core/src/Execution/Processing/Result/ListResult.cs b/src/HotChocolate/Core/src/Execution/Processing/Result/ListResult.cs new file mode 100644 index 00000000000..a1a51f1f3a8 --- /dev/null +++ b/src/HotChocolate/Core/src/Execution/Processing/Result/ListResult.cs @@ -0,0 +1,110 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Runtime.InteropServices; + +namespace HotChocolate.Execution.Processing; + +/// +/// Represents an optimized list result that is used by the execution engine +/// to store completed elements. +/// +public sealed class ListResult : ResultData, IReadOnlyList +{ + private object?[] _buffer = Array.Empty(); + private int _capacity; + private int _count; + + public int Capacity => _capacity; + + /// + public int Count => _count; + + /// + public object? this[int index] + { + get + { + return _buffer[index]; + } + } + + /// + /// Defines if the elements of this list are nullable. + /// + internal bool IsNullable { get; set; } + + internal void AddUnsafe(object? item) + => _buffer[_count++] = item; + + internal void SetUnsafe(int index, object? item) + => _buffer[index] = item; + + /// + /// Ensures that the result object has enough capacity on the buffer + /// to store the expected fields. + /// + /// + /// The capacity needed. + /// + internal void EnsureCapacity(int capacity) + { + if (_capacity > 0) + { + Reset(); + } + + if (_buffer.Length < capacity) + { + Array.Resize(ref _buffer, capacity); + } + + _capacity = capacity; + } + + /// + /// Grows the internal capacity. + /// + internal void Grow() + { + if (_capacity == 0) + { + EnsureCapacity(4); + return; + } + + var newCapacity = _capacity * 2; + Array.Resize(ref _buffer, newCapacity); + _capacity = newCapacity; + } + + /// + /// Resets the result object. + /// + internal void Reset() + { + if (_capacity > 0) + { + _buffer.AsSpan().Slice(0, _capacity).Clear(); + _capacity = 0; + _count = 0; + } + } + + internal ref object? GetReference() + => ref MemoryMarshal.GetReference(_buffer.AsSpan()); + + /// + public IEnumerator GetEnumerator() + { + for (var i = 0; i < _capacity; i++) + { + yield return _buffer[i]; + } + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } +} diff --git a/src/HotChocolate/Core/src/Execution/Processing/Result/ListResultPool.cs b/src/HotChocolate/Core/src/Execution/Processing/Result/ListResultPool.cs new file mode 100644 index 00000000000..34409492df7 --- /dev/null +++ b/src/HotChocolate/Core/src/Execution/Processing/Result/ListResultPool.cs @@ -0,0 +1,54 @@ +using Microsoft.Extensions.ObjectPool; + +namespace HotChocolate.Execution.Processing; + +internal sealed class ListResultPool : DefaultObjectPool> +{ + public ListResultPool(int maximumRetained, int maxAllowedCapacity) + : base(new BufferPolicy(maxAllowedCapacity), maximumRetained) + { + } + + private sealed class BufferPolicy : PooledObjectPolicy> + { + private readonly ObjectPolicy _objectPolicy; + + public BufferPolicy(int maxAllowedCapacity) + { + _objectPolicy = new ObjectPolicy(maxAllowedCapacity); + } + + public override ResultBucket Create() + => new(16, _objectPolicy); + + public override bool Return(ResultBucket obj) + { + obj.Reset(); + return true; + } + } + + private sealed class ObjectPolicy : PooledObjectPolicy + { + private readonly int _maxAllowedCapacity; + + public ObjectPolicy(int maxAllowedCapacity) + { + _maxAllowedCapacity = maxAllowedCapacity; + } + + public override ListResult Create() => new(); + + public override bool Return(ListResult obj) + { + if (obj.Count > _maxAllowedCapacity) + { + obj.Reset(); + return false; + } + + obj.Reset(); + return true; + } + } +} diff --git a/src/HotChocolate/Core/src/Execution/Processing/Result/ObjectFieldResult.cs b/src/HotChocolate/Core/src/Execution/Processing/Result/ObjectFieldResult.cs new file mode 100644 index 00000000000..97605b0ffd1 --- /dev/null +++ b/src/HotChocolate/Core/src/Execution/Processing/Result/ObjectFieldResult.cs @@ -0,0 +1,47 @@ +using System; + +namespace HotChocolate.Execution.Processing; + +public sealed class ObjectFieldResult +{ + private Flags _flags = Flags.Nullable; + private string _name = default!; + private object? _value; + + public string Name => _name; + + public object? Value => _value; + + internal bool IsNullable => (_flags & Flags.Nullable) == Flags.Nullable; + + internal bool IsInitialized => (_flags & Flags.Initialized) == Flags.Initialized; + + internal void Set(string name, object? value, bool isNullable) + { + _name = name; + _value = value; + + if (isNullable) + { + _flags = Flags.Nullable | Flags.Initialized; + } + else + { + _flags = Flags.Initialized; + } + } + + internal void Reset() + { + _name = default!; + _value = null; + _flags = Flags.Nullable; + } + + [Flags] + private enum Flags : byte + { + Initialized = 1, + Nullable = 2 + } +} diff --git a/src/HotChocolate/Core/src/Execution/Processing/Result/ObjectResult.cs b/src/HotChocolate/Core/src/Execution/Processing/Result/ObjectResult.cs new file mode 100644 index 00000000000..5f8e52f2f94 --- /dev/null +++ b/src/HotChocolate/Core/src/Execution/Processing/Result/ObjectResult.cs @@ -0,0 +1,226 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using HotChocolate.Utilities; + +namespace HotChocolate.Execution.Processing; + +/// +/// Represents an optimized object result that is used by the execution engine +/// to store completed values. +/// +public sealed class ObjectResult + : ResultData + , IReadOnlyDictionary + , IEnumerable +{ + private ObjectFieldResult[] _buffer = Array.Empty(); + private int _capacity; + + /// + /// Gets the capacity of this object result. + /// It essentially specifies how many field results can be stored. + /// + internal int Capacity => _capacity; + + /// + /// This indexer allows direct access to the underlying buffer + /// to access a . + /// + internal ObjectFieldResult this[int index] => _buffer[index]; + + /// + /// Gets a reference to the first in the buffer. + /// + internal ref ObjectFieldResult GetReference() + => ref MemoryMarshal.GetReference(_buffer.AsSpan()); + + /// + /// Sets a field value in the buffer. + /// Note: Set will not validate if the buffer has enough space. + /// + /// + /// The index in the buffer on which the value shall be stored. + /// + /// + /// The name of the field. + /// + /// + /// The field value. + /// + /// + /// Specifies if the value is allowed to be null. + /// + internal void SetValueUnsafe(int index, string name, object? value, bool isNullable = true) + => _buffer[index].Set(name, value, isNullable); + + /// + /// Removes a field value from the buffer. + /// Note: Remove will not validate if the buffer has enough space. + /// + /// + /// The index in the buffer on which the value shall be removed. + /// + internal void RemoveValueUnsafe(int index) + => _buffer[index].Reset(); + + /// + /// Searches within the capacity of the buffer to find a field value that matches + /// the specified . + /// + /// + /// The name of the field to search for. + /// + /// + /// The index on the buffer where the field value is located. + /// + /// + /// Returns the field value or null. + /// + internal ObjectFieldResult? TryGetValue(string name, out int index) + { + ref var searchSpace = ref MemoryMarshal.GetReference(_buffer.AsSpan()); + + for(var i = 0; i < _capacity; i++) + { + var item = Unsafe.Add(ref searchSpace, i); + if (name.EqualsOrdinal(item.Name)) + { + index = i; + return item; + } + } + + index = -1; + return default; + } + + /// + /// Ensures that the result object has enough capacity on the buffer + /// to store the expected fields. + /// + /// + /// The capacity needed. + /// + internal void EnsureCapacity(int capacity) + { + if (_capacity > 0) + { + Reset(); + } + + if (_buffer.Length < capacity) + { + var oldCapacity = _buffer.Length; + Array.Resize(ref _buffer, capacity); + + for (var i = oldCapacity; i < _buffer.Length; i++) + { + var field = new ObjectFieldResult(); + _buffer[i] = field; + } + } + + _capacity = capacity; + } + + /// + /// Resets the result object. + /// + internal void Reset() + { + ref var searchSpace = ref MemoryMarshal.GetReference(_buffer.AsSpan()); + + for(var i = 0; i < _capacity; i++) + { + Unsafe.Add(ref searchSpace, i).Reset(); + } + + _capacity = 0; + } + + object? IReadOnlyDictionary.this[string key] + => TryGetValue(key, out _)?.Value; + + IEnumerable IReadOnlyDictionary.Keys + { + get + { + for (var i = 0; i < _capacity; i++) + { + var field = _buffer[i]; + + if (field.IsInitialized) + { + yield return field.Name; + } + } + } + } + + IEnumerable IReadOnlyDictionary.Values + { + get + { + for (var i = 0; i < _capacity; i++) + { + var field = _buffer[i]; + + if (field.IsInitialized) + { + yield return field.Value; + } + } + } + } + + int IReadOnlyCollection>.Count => _capacity; + + bool IReadOnlyDictionary.ContainsKey(string key) + => TryGetValue(key, out _)?.Name is not null; + + bool IReadOnlyDictionary.TryGetValue(string key, out object? value) + { + var field = TryGetValue(key, out _); + + if (field?.Name is not null) + { + value = field.Value; + return true; + } + + value = null; + return true; + } + + public IEnumerator GetEnumerator() + { + for (var i = 0; i < _capacity; i++) + { + var field = _buffer[i]; + + if (field.IsInitialized) + { + yield return field; + } + } + } + + IEnumerator> + IEnumerable>.GetEnumerator() + { + for (var i = 0; i < _capacity; i++) + { + var field = _buffer[i]; + + if (field.IsInitialized) + { + yield return new KeyValuePair(field.Name, field.Value); + } + } + } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); +} diff --git a/src/HotChocolate/Core/src/Execution/Processing/Result/ObjectResultPool.cs b/src/HotChocolate/Core/src/Execution/Processing/Result/ObjectResultPool.cs new file mode 100644 index 00000000000..948444010c1 --- /dev/null +++ b/src/HotChocolate/Core/src/Execution/Processing/Result/ObjectResultPool.cs @@ -0,0 +1,55 @@ +using Microsoft.Extensions.ObjectPool; +using static HotChocolate.Execution.Processing.ResultPoolDefaults; + +namespace HotChocolate.Execution.Processing; + +internal sealed class ObjectResultPool : DefaultObjectPool> +{ + public ObjectResultPool(int maximumRetained, int maxAllowedCapacity) + : base(new BufferPolicy(maxAllowedCapacity), maximumRetained) + { + } + + private sealed class BufferPolicy : PooledObjectPolicy> + { + private readonly ObjectPolicy _objectPolicy; + + public BufferPolicy(int maxAllowedCapacity) + { + _objectPolicy = new ObjectPolicy(maxAllowedCapacity); + } + + public override ResultBucket Create() + => new(BucketSize, _objectPolicy); + + public override bool Return(ResultBucket obj) + { + obj.Reset(); + return true; + } + } + + private sealed class ObjectPolicy : PooledObjectPolicy + { + private readonly int _maxAllowedCapacity; + + public ObjectPolicy(int maxAllowedCapacity) + { + _maxAllowedCapacity = maxAllowedCapacity; + } + + public override ObjectResult Create() => new(); + + public override bool Return(ObjectResult obj) + { + if (obj.Capacity > _maxAllowedCapacity) + { + obj.Reset(); + return false; + } + + obj.Reset(); + return true; + } + } +} diff --git a/src/HotChocolate/Core/src/Execution/Processing/ResultObjectBuffer.cs b/src/HotChocolate/Core/src/Execution/Processing/Result/ResultBucket.cs similarity index 91% rename from src/HotChocolate/Core/src/Execution/Processing/ResultObjectBuffer.cs rename to src/HotChocolate/Core/src/Execution/Processing/Result/ResultBucket.cs index 333e02b6e4a..b1fc8f07cb5 100644 --- a/src/HotChocolate/Core/src/Execution/Processing/ResultObjectBuffer.cs +++ b/src/HotChocolate/Core/src/Execution/Processing/Result/ResultBucket.cs @@ -4,14 +4,14 @@ namespace HotChocolate.Execution.Processing; -internal sealed class ResultObjectBuffer where T : class +internal sealed class ResultBucket where T : class { private readonly int _capacity; private readonly IPooledObjectPolicy _policy; private readonly T?[] _buffer; private int _index; - public ResultObjectBuffer(int capacity, IPooledObjectPolicy policy) + public ResultBucket(int capacity, IPooledObjectPolicy policy) { _capacity = capacity; _policy = policy; diff --git a/src/HotChocolate/Core/src/Execution/Processing/Result/ResultBuilder.NonNullHandling.cs b/src/HotChocolate/Core/src/Execution/Processing/Result/ResultBuilder.NonNullHandling.cs new file mode 100644 index 00000000000..1c2debe4833 --- /dev/null +++ b/src/HotChocolate/Core/src/Execution/Processing/Result/ResultBuilder.NonNullHandling.cs @@ -0,0 +1,89 @@ +using System; +using System.Collections.Generic; +using HotChocolate.Language; + +namespace HotChocolate.Execution.Processing; + +internal sealed partial class ResultBuilder +{ + private static bool ApplyNonNullViolations( + List errors, + List violations, + HashSet errorFields) + { + if (violations.Count is 0) + { + return true; + } + + while (violations.TryPop(out var violation)) + { + var path = violation.Path; + ResultData? parent = violation.Parent; + + if (!errorFields.Contains(violation.Selection)) + { + errors.Add(ErrorHelper.NonNullOutputFieldViolation(path, violation.Selection)); + } + + while (parent is not null) + { + switch (parent) + { + case ObjectResult obj: + if (path is not NamePathSegment nps) + { + return false; + } + + var field = obj.TryGetValue(nps.Name, out var index); + + if (field is null) + { + return false; + } + + if (field.IsNullable) + { + field.Set(field.Name, null, true); + return true; + } + + path = path.Parent; + parent = obj.Parent; + break; + + case ListResult list: + if (list.IsNullable) + { + list.SetUnsafe(((IndexerPathSegment)path).Index, null); + return true; + } + + path = path.Parent; + parent = parent.Parent; + break; + + default: + throw new ArgumentOutOfRangeException(nameof(parent)); + } + } + } + + return false; + } + + private sealed class NonNullViolation + { + public NonNullViolation(FieldNode selection, Path path, ObjectResult parent) + { + Selection = selection; + Path = path; + Parent = parent; + } + + public FieldNode Selection { get; } + public Path Path { get; } + public ObjectResult Parent { get; } + } +} diff --git a/src/HotChocolate/Core/src/Execution/Processing/Result/ResultBuilder.ObjectResult.cs b/src/HotChocolate/Core/src/Execution/Processing/Result/ResultBuilder.ObjectResult.cs new file mode 100644 index 00000000000..23afcb31bc0 --- /dev/null +++ b/src/HotChocolate/Core/src/Execution/Processing/Result/ResultBuilder.ObjectResult.cs @@ -0,0 +1,48 @@ +#nullable enable + +namespace HotChocolate.Execution.Processing; + +internal sealed partial class ResultBuilder +{ + private readonly ResultPool _resultPool; + private readonly object _objectSync = new(); + private ResultBucket _objectBucket = default!; + private readonly object _listSync = new(); + private ResultBucket _listBucket = default!; + + public ObjectResult RentObject(int capacity) + { + lock (_objectSync) + { + while (true) + { + if (_objectBucket.TryPop(out var obj)) + { + obj.EnsureCapacity(capacity); + return obj; + } + + _objectBucket = _resultPool.GetObjectBucket(); + _resultOwner.ObjectBuckets.Add(_objectBucket); + } + } + } + + public ListResult RentList(int capacity) + { + lock (_listSync) + { + while (true) + { + if (_listBucket.TryPop(out var obj)) + { + obj.EnsureCapacity(capacity); + return obj; + } + + _listBucket = _resultPool.GetListBucket(); + _resultOwner.ListBuckets.Add(_listBucket); + } + } + } +} diff --git a/src/HotChocolate/Core/src/Execution/Processing/Result/ResultBuilder.Pooling.cs b/src/HotChocolate/Core/src/Execution/Processing/Result/ResultBuilder.Pooling.cs new file mode 100644 index 00000000000..edddb7b5753 --- /dev/null +++ b/src/HotChocolate/Core/src/Execution/Processing/Result/ResultBuilder.Pooling.cs @@ -0,0 +1,36 @@ +namespace HotChocolate.Execution.Processing; + +internal sealed partial class ResultBuilder +{ + + public ResultBuilder(ResultPool resultPool) + { + _resultPool = resultPool; + InitializeResult(); + } + + public void Clear() + { + _errors.Clear(); + _fieldErrors.Clear(); + _nonNullViolations.Clear(); + _extensions.Clear(); + _contextData.Clear(); + + InitializeResult(); + + _data = null; + _path = null; + _label = null; + _hasNext = null; + } + + private void InitializeResult() + { + _resultOwner = new ResultMemoryOwner(_resultPool); + _objectBucket = _resultPool.GetObjectBucket(); + _resultOwner.ObjectBuckets.Add(_objectBucket); + _listBucket = _resultPool.GetListBucket(); + _resultOwner.ListBuckets.Add(_listBucket); + } +} diff --git a/src/HotChocolate/Core/src/Execution/Processing/Result/ResultBuilder.cs b/src/HotChocolate/Core/src/Execution/Processing/Result/ResultBuilder.cs new file mode 100644 index 00000000000..fa72073dd4a --- /dev/null +++ b/src/HotChocolate/Core/src/Execution/Processing/Result/ResultBuilder.cs @@ -0,0 +1,139 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using HotChocolate.Execution.Properties; +using HotChocolate.Language; + +namespace HotChocolate.Execution.Processing; + +internal sealed partial class ResultBuilder +{ + private readonly object _syncErrors = new(); + private readonly List _errors = new(); + private readonly HashSet _fieldErrors = new(); + private readonly List _nonNullViolations = new(); + + private readonly object _syncExtensions = new(); + private readonly Dictionary _extensions = new(); + private readonly Dictionary _contextData = new(); + + private ResultMemoryOwner _resultOwner = default!; + private ObjectResult? _data; + private Path? _path; + private string? _label; + private bool? _hasNext; + + public IReadOnlyList Errors => _errors; + + public void SetData(ObjectResult data) + => _data = data; + + public void SetExtension(string key, object? value) + { + lock (_syncExtensions) + { + _extensions[key] = value; + } + } + + public void SetContextData(string key, object? value) + { + lock (_syncExtensions) + { + _contextData[key] = value; + } + } + + public void SetPath(Path? path) + => _path = path; + + public void SetLabel(string? label) + => _label = label; + + public void SetHasNext(bool value) + => _hasNext = value; + + public void AddError(IError error, FieldNode? selection = null) + { + lock (_syncErrors) + { + _errors.Add(error); + if (selection is { }) + { + _fieldErrors.Add(selection); + } + } + } + + public void AddErrors(IEnumerable errors, FieldNode? selection = null) + { + lock (_syncErrors) + { + _errors.AddRange(errors); + + if (selection is { }) + { + _fieldErrors.Add(selection); + } + } + } + + public void AddNonNullViolation(FieldNode selection, Path path, ObjectResult parent) + { + lock (_syncErrors) + { + _nonNullViolations.Add(new NonNullViolation(selection, path, parent)); + } + } + + public IQueryResult BuildResult() + { + if (!ApplyNonNullViolations(_errors, _nonNullViolations, _fieldErrors)) + { + // The non-null violation cased the whole result being deleted. + _data = null; + _resultOwner.Dispose(); + } + + if (_data is null && _errors.Count == 0 && _hasNext is not false) + { + throw new InvalidOperationException( + Resources.ResultHelper_BuildResult_InvalidResult); + } + + var result = new QueryResult + ( + _data, + _errors.Count == 0 ? null : new List(_errors), + CreateExtensionData(_extensions), + CreateExtensionData(_contextData), + _label, + _path, + _hasNext + ); + + if (_data is not null) + { + result.RegisterForCleanup(_resultOwner); + } + + return result; + } + + private static IReadOnlyDictionary? CreateExtensionData( + Dictionary data) + { + if (data.Count == 0) + { + return null; + } + + return ImmutableDictionary.CreateRange(data); + } + + public void DiscardResult() + => _resultOwner.Dispose(); + +} + + diff --git a/src/HotChocolate/Core/src/Execution/Processing/Result/ResultData.cs b/src/HotChocolate/Core/src/Execution/Processing/Result/ResultData.cs new file mode 100644 index 00000000000..d0aa53b4fbe --- /dev/null +++ b/src/HotChocolate/Core/src/Execution/Processing/Result/ResultData.cs @@ -0,0 +1,14 @@ +#nullable enable + +namespace HotChocolate.Execution.Processing; + +/// +/// Represents a result data object like an object or list. +/// +public abstract class ResultData +{ + /// + /// Gets the parent result data object. + /// + internal virtual ResultData? Parent { get; set; } +} diff --git a/src/HotChocolate/Core/src/Execution/Processing/Result/ResultMemoryOwner.cs b/src/HotChocolate/Core/src/Execution/Processing/Result/ResultMemoryOwner.cs new file mode 100644 index 00000000000..b39ee9e8d64 --- /dev/null +++ b/src/HotChocolate/Core/src/Execution/Processing/Result/ResultMemoryOwner.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; + +namespace HotChocolate.Execution.Processing; + +internal sealed class ResultMemoryOwner : IDisposable +{ + private readonly ResultPool _resultPool; + private bool _disposed; + + public ResultMemoryOwner(ResultPool resultPool) + { + _resultPool = resultPool; + } + + public ObjectResult? Data { get; set; } + + public List> ObjectBuckets { get; } = new(); + + public List> ListBuckets { get; } = new(); + + public void Dispose() + { + if (!_disposed) + { + _resultPool.Return(ObjectBuckets); + _resultPool.Return(ListBuckets); + _disposed = true; + } + } +} diff --git a/src/HotChocolate/Core/src/Execution/Processing/Result/ResultPool.cs b/src/HotChocolate/Core/src/Execution/Processing/Result/ResultPool.cs new file mode 100644 index 00000000000..76688ba099c --- /dev/null +++ b/src/HotChocolate/Core/src/Execution/Processing/Result/ResultPool.cs @@ -0,0 +1,39 @@ +using System.Collections.Generic; + +namespace HotChocolate.Execution.Processing; + +internal sealed class ResultPool +{ + private readonly ObjectResultPool _objectResultPool; + private readonly ListResultPool _listResultPool; + + public ResultPool( + ObjectResultPool objectResultPool, + ListResultPool listResultPool) + { + _objectResultPool = objectResultPool; + _listResultPool = listResultPool; + } + + public ResultBucket GetObjectBucket() + => _objectResultPool.Get(); + + public ResultBucket GetListBucket() + => _listResultPool.Get(); + + public void Return(IList> buffers) + { + for (var i = 0; i < buffers.Count; i++) + { + _objectResultPool.Return(buffers[i]); + } + } + + public void Return(IList> buffers) + { + for (var i = 0; i < buffers.Count; i++) + { + _listResultPool.Return(buffers[i]); + } + } +} diff --git a/src/HotChocolate/Core/src/Execution/Processing/Result/ResultPoolDefaults.cs b/src/HotChocolate/Core/src/Execution/Processing/Result/ResultPoolDefaults.cs new file mode 100644 index 00000000000..a23e383748f --- /dev/null +++ b/src/HotChocolate/Core/src/Execution/Processing/Result/ResultPoolDefaults.cs @@ -0,0 +1,8 @@ +namespace HotChocolate.Execution.Processing; + +internal static class ResultPoolDefaults +{ + public const int MaximumRetained = 512; + public const int BucketSize = 16; + public const int MaximumAllowedCapacity = 256; +} diff --git a/src/HotChocolate/Core/src/Execution/Processing/ResultHelper.Pooling.cs b/src/HotChocolate/Core/src/Execution/Processing/ResultHelper.Pooling.cs deleted file mode 100644 index d322817a605..00000000000 --- a/src/HotChocolate/Core/src/Execution/Processing/ResultHelper.Pooling.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace HotChocolate.Execution.Processing; - -internal sealed partial class ResultHelper -{ - public void Clear() - { - _errors.Clear(); - _fieldErrors.Clear(); - _nonNullViolations.Clear(); - _extensions.Clear(); - _contextData.Clear(); - _resultOwner = new ResultMemoryOwner(_resultPool); - _data = null; - _path = null; - _label = null; - _hasNext = null; - } -} diff --git a/src/HotChocolate/Core/src/Execution/Processing/ResultHelper.cs b/src/HotChocolate/Core/src/Execution/Processing/ResultHelper.cs deleted file mode 100644 index 3801e74c658..00000000000 --- a/src/HotChocolate/Core/src/Execution/Processing/ResultHelper.cs +++ /dev/null @@ -1,286 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; -using HotChocolate.Execution.Properties; -using HotChocolate.Language; - -namespace HotChocolate.Execution.Processing; - -internal sealed partial class ResultHelper : IResultHelper -{ - private readonly object _syncMap = new(); - private readonly object _syncMapList = new(); - private readonly object _syncList = new(); - private readonly object _syncErrors = new(); - private readonly object _syncExtensions = new(); - private readonly List _errors = new(); - private readonly HashSet _fieldErrors = new(); - private readonly List _nonNullViolations = new(); - private readonly ResultPool _resultPool; - private readonly Dictionary _extensions = new(); - private readonly Dictionary _contextData = new(); - private ResultMemoryOwner _resultOwner; - private ResultMap? _data; - private Path? _path; - private string? _label; - private bool? _hasNext; - - public ResultHelper(ResultPool resultPool) - { - _resultPool = resultPool; - _resultOwner = new ResultMemoryOwner(resultPool); - } - - public IReadOnlyList Errors => _errors; - - public ResultMap RentResultMap(int capacity) - { - ResultMap? map; - - lock (_syncMap) - { - if (!_resultOwner.ResultMaps.TryPeek(out var buffer) || - !buffer.TryPop(out map)) - { - buffer = _resultPool.GetResultMap(); - map = buffer.Pop(); - _resultOwner.ResultMaps.Push(buffer); - } - } - - map.EnsureCapacity(capacity); - return map; - } - - public ResultMapList RentResultMapList() - { - ResultMapList? mapList; - - lock (_syncMapList) - { - if (!_resultOwner.ResultMapLists.TryPeek( - out var buffer) || - !buffer.TryPop(out mapList)) - { - buffer = _resultPool.GetResultMapList(); - mapList = buffer.Pop(); - _resultOwner.ResultMapLists.Push(buffer); - } - } - - return mapList; - } - - public ResultList RentResultList() - { - ResultList? list; - - lock (_syncList) - { - if (!_resultOwner.ResultLists.TryPeek(out var buffer) || - !buffer.TryPop(out list)) - { - buffer = _resultPool.GetResultList(); - list = buffer.Pop(); - _resultOwner.ResultLists.Push(buffer); - } - } - - return list; - } - - public void SetData(ResultMap data) - { - _data = data; - } - - public void SetExtension(string key, object? value) - { - lock (_syncExtensions) - { - _extensions[key] = value; - } - } - - public void SetContextData(string key, object? value) - { - lock (_syncExtensions) - { - _contextData[key] = value; - } - } - - public void SetPath(Path? path) - { - _path = path; - } - - public void SetLabel(string? label) - { - _label = label; - } - - public void SetHasNext(bool value) - { - _hasNext = value; - } - - public void AddError(IError error, FieldNode? selection = null) - { - lock (_syncErrors) - { - _errors.Add(error); - if (selection is { }) - { - _fieldErrors.Add(selection); - } - } - } - - public void AddErrors(IEnumerable errors, FieldNode? selection = null) - { - lock (_syncErrors) - { - _errors.AddRange(errors); - - if (selection is { }) - { - _fieldErrors.Add(selection); - } - } - } - - public void AddNonNullViolation(FieldNode selection, Path path, IResultMap parent) - { - _nonNullViolations.Add(new NonNullViolation(selection, path, parent)); - } - - public IQueryResult BuildResult() - { - while (_data != null && _nonNullViolations.TryPop(out var violation)) - { - var path = violation.Path; - IResultData? parent = violation.Parent; - - if (!_fieldErrors.Contains(violation.Selection)) - { - _errors.Add(ErrorHelper.NonNullOutputFieldViolation(path, violation.Selection)); - } - - while (parent != null) - { - if (parent is ResultMap map && path is NamePathSegment nameSegment) - { - var value = map.GetValue(nameSegment.Name, out var index); - - if (value.IsNullable) - { - map.SetValue(index, value.Name, value: null, isNullable: true); - break; - } - - if (index != -1) - { - map.RemoveValue(index); - } - - path = path.Parent; - parent = parent.Parent; - - if (parent is null) - { - _data = null; - _resultOwner.Dispose(); - break; - } - } - else if (parent is ResultMapList mapList && - path is IndexerPathSegment mapListIndexSegment) - { - if (mapList.IsNullable) - { - mapList[mapListIndexSegment.Index] = null; - break; - } - - path = path.Parent; - parent = parent.Parent; - } - else if (parent is ResultList list && - path is IndexerPathSegment listIndexSegment) - { - if (list.IsNullable) - { - list[listIndexSegment.Index] = null; - break; - } - - path = path.Parent; - parent = parent.Parent; - } - else - { - break; - } - } - } - - if (_data is null && _errors.Count == 0 && _hasNext is not false) - { - throw new InvalidOperationException( - Resources.ResultHelper_BuildResult_InvalidResult); - } - - var result = new QueryResult - ( - _data, - _errors.Count == 0 ? null : new List(_errors), - CreateExtensionData(_extensions), - CreateExtensionData(_contextData), - _label, - _path, - _hasNext - ); - - if (_data is not null) - { - result.RegisterForCleanup(_resultOwner); - } - - return result; - } - - private IReadOnlyDictionary? CreateExtensionData( - Dictionary data) - { - if (data.Count == 0) - { - return null; - } - - if (data.Count == 1) - { - var value = data.Single(); - return new SingleValueExtensionData(value.Key, value.Value); - } - - return ImmutableDictionary.CreateRange(data); - } - - public void DropResult() => _resultOwner.Dispose(); - - private readonly struct NonNullViolation - { - public NonNullViolation(FieldNode selection, Path path, IResultMap parent) - { - Selection = selection; - Path = path; - Parent = parent; - } - - public FieldNode Selection { get; } - public Path Path { get; } - public IResultMap Parent { get; } - } -} diff --git a/src/HotChocolate/Core/src/Execution/Processing/ResultList.cs b/src/HotChocolate/Core/src/Execution/Processing/ResultList.cs deleted file mode 100644 index 9ac302c90ab..00000000000 --- a/src/HotChocolate/Core/src/Execution/Processing/ResultList.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System.Collections.Generic; - -namespace HotChocolate.Execution.Processing; - -public sealed class ResultList - : List - , IResultList - , IHasResultDataParent -{ - public IResultData? Parent { get; set; } - - IResultData? IHasResultDataParent.Parent { get => Parent; set => Parent = value; } - - /// - /// Defines if the elements of this list are nullable. - /// - public bool IsNullable { get; set; } -} diff --git a/src/HotChocolate/Core/src/Execution/Processing/ResultListPool.cs b/src/HotChocolate/Core/src/Execution/Processing/ResultListPool.cs deleted file mode 100644 index 5dd8af58d7f..00000000000 --- a/src/HotChocolate/Core/src/Execution/Processing/ResultListPool.cs +++ /dev/null @@ -1,35 +0,0 @@ -using Microsoft.Extensions.ObjectPool; - -namespace HotChocolate.Execution.Processing; - -internal sealed class ResultListPool : DefaultObjectPool> -{ - public ResultListPool(int maximumRetained) - : base(new BufferPolicy(), maximumRetained) - { - } - - private sealed class BufferPolicy : IPooledObjectPolicy> - { - private static readonly ResultMapPolicy _policy = new(); - - public ResultObjectBuffer Create() => new(16, _policy); - - public bool Return(ResultObjectBuffer obj) - { - obj.Reset(); - return true; - } - } - - private sealed class ResultMapPolicy : IPooledObjectPolicy - { - public ResultList Create() => new(); - - public bool Return(ResultList obj) - { - obj.Clear(); - return true; - } - } -} diff --git a/src/HotChocolate/Core/src/Execution/Processing/ResultMap.cs b/src/HotChocolate/Core/src/Execution/Processing/ResultMap.cs deleted file mode 100644 index 70e4b261c0a..00000000000 --- a/src/HotChocolate/Core/src/Execution/Processing/ResultMap.cs +++ /dev/null @@ -1,182 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using HotChocolate.Utilities; - -namespace HotChocolate.Execution.Processing; - -public sealed class ResultMap - : IResultMap - , IReadOnlyDictionary - , IHasResultDataParent -{ - private ResultValue[] _buffer; - private int _capacity; - - public ResultMap() - { - _buffer = Array.Empty(); - } - - public IResultData? Parent { get; set; } - - IResultData? IHasResultDataParent.Parent { get => Parent; set => Parent = value; } - - public ResultValue this[int index] { get => _buffer[index]; } - - public int Count => _capacity; - - object? IReadOnlyDictionary.this[string key] - { - get - { - var value = GetValue(key, out var index); - - if (index == -1) - { - throw new KeyNotFoundException(key); - } - - return value.Value; - } - } - - IEnumerable IReadOnlyDictionary.Keys - { - get - { - for (var i = 0; i < _capacity; i++) - { - var value = _buffer[i]; - - if (value.IsInitialized) - { - yield return value.Name; - } - } - } - } - - IEnumerable IReadOnlyDictionary.Values - { - get - { - for (var i = 0; i < _capacity; i++) - { - var value = _buffer[i]; - - if (value.IsInitialized) - { - yield return value.Value; - } - } - } - } - - public void SetValue(int index, string name, object? value, bool isNullable = true) - { - if (index >= _capacity) - { - throw new ArgumentOutOfRangeException(nameof(index)); - } - - if (string.IsNullOrEmpty(name)) - { - throw new ArgumentNullException(nameof(name)); - } - - _buffer[index] = new ResultValue(name, value, isNullable); - } - - public ResultValue GetValue(string name, out int index) - { - if (string.IsNullOrEmpty(name)) - { - throw new ArgumentNullException(nameof(name)); - } - - var i = (IntPtr)0; - var length = _capacity; - ref var searchSpace = ref MemoryMarshal.GetReference(_buffer.AsSpan()); - - while (length > 0) - { - length--; - - if (name.EqualsOrdinal(Unsafe.Add(ref searchSpace, i).Name)) - { - index = i.ToInt32(); - return _buffer[index]; - } - - i += 1; - } - - index = -1; - return default; - } - - public void RemoveValue(int index) => _buffer[index] = default; - - public void EnsureCapacity(int capacity) - { - if (_buffer.Length < capacity) - { - var newCapacity = _buffer.Length is 0 ? 4 : _buffer.Length * 2; - - if (newCapacity < capacity) - { - newCapacity = capacity; - } - - _buffer = new ResultValue[newCapacity]; - } - - _capacity = capacity; - } - - public void Clear() => _buffer.AsSpan().Slice(0, _capacity).Clear(); - - bool IReadOnlyDictionary.ContainsKey(string key) - { - GetValue(key, out var index); - return index is not -1; - } - - bool IReadOnlyDictionary.TryGetValue(string key, out object? value) - { - value = GetValue(key, out var index).Value; - return index is not -1; - } - - public IEnumerator GetEnumerator() - { - for (var i = 0; i < _capacity; i++) - { - var value = _buffer[i]; - - if (value.IsInitialized) - { - yield return value; - } - } - } - - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - - IEnumerator> - IEnumerable>.GetEnumerator() - { - for (var i = 0; i < _capacity; i++) - { - var value = _buffer[i]; - - if (value.IsInitialized) - { - yield return new KeyValuePair(value.Name, value.Value); - } - } - } -} diff --git a/src/HotChocolate/Core/src/Execution/Processing/ResultMapList.cs b/src/HotChocolate/Core/src/Execution/Processing/ResultMapList.cs deleted file mode 100644 index f5c650b8f1b..00000000000 --- a/src/HotChocolate/Core/src/Execution/Processing/ResultMapList.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System.Collections.Generic; - -namespace HotChocolate.Execution.Processing; - -public sealed class ResultMapList - : List - , IResultMapList - , IHasResultDataParent -{ - public IResultData? Parent { get; set; } - - IResultData? IHasResultDataParent.Parent { get => Parent; set => Parent = value; } - - /// - /// Defines if the elements of this list are nullable. - /// - public bool IsNullable { get; set; } -} diff --git a/src/HotChocolate/Core/src/Execution/Processing/ResultMapListPool.cs b/src/HotChocolate/Core/src/Execution/Processing/ResultMapListPool.cs deleted file mode 100644 index c32f1bc4d38..00000000000 --- a/src/HotChocolate/Core/src/Execution/Processing/ResultMapListPool.cs +++ /dev/null @@ -1,35 +0,0 @@ -using Microsoft.Extensions.ObjectPool; - -namespace HotChocolate.Execution.Processing; - -internal sealed class ResultMapListPool : DefaultObjectPool> -{ - public ResultMapListPool(int maximumRetained) - : base(new BufferPolicy(), maximumRetained) - { - } - - private sealed class BufferPolicy : IPooledObjectPolicy> - { - private static readonly ResultMapListPolicy _policy = new(); - - public ResultObjectBuffer Create() => new(16, _policy); - - public bool Return(ResultObjectBuffer obj) - { - obj.Reset(); - return true; - } - } - - private sealed class ResultMapListPolicy : IPooledObjectPolicy - { - public ResultMapList Create() => new(); - - public bool Return(ResultMapList obj) - { - obj.Clear(); - return true; - } - } -} diff --git a/src/HotChocolate/Core/src/Execution/Processing/ResultMapPool.cs b/src/HotChocolate/Core/src/Execution/Processing/ResultMapPool.cs deleted file mode 100644 index 0208f489fc3..00000000000 --- a/src/HotChocolate/Core/src/Execution/Processing/ResultMapPool.cs +++ /dev/null @@ -1,35 +0,0 @@ -using Microsoft.Extensions.ObjectPool; - -namespace HotChocolate.Execution.Processing; - -internal sealed class ResultMapPool : DefaultObjectPool> -{ - public ResultMapPool(int maximumRetained) - : base(new BufferPolicy(), maximumRetained) - { - } - - private sealed class BufferPolicy : IPooledObjectPolicy> - { - private static readonly ResultMapPolicy _policy = new(); - - public ResultObjectBuffer Create() => new(16, _policy); - - public bool Return(ResultObjectBuffer obj) - { - obj.Reset(); - return true; - } - } - - private sealed class ResultMapPolicy : IPooledObjectPolicy - { - public ResultMap Create() => new(); - - public bool Return(ResultMap obj) - { - obj.Clear(); - return true; - } - } -} diff --git a/src/HotChocolate/Core/src/Execution/Processing/ResultMemoryOwner.cs b/src/HotChocolate/Core/src/Execution/Processing/ResultMemoryOwner.cs deleted file mode 100644 index e852de077af..00000000000 --- a/src/HotChocolate/Core/src/Execution/Processing/ResultMemoryOwner.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System.Collections.Generic; - -namespace HotChocolate.Execution.Processing; - -internal sealed class ResultMemoryOwner : IResultMemoryOwner -{ - private readonly ResultPool _resultPool; - private bool _disposed; - - public ResultMemoryOwner(ResultPool resultPool) - { - _resultPool = resultPool; - } - - public IResultMap? Data { get; set; } - - public List> ResultMaps { get; } = new(); - - public List> ResultMapLists { get; } = new(); - - public List> ResultLists { get; } = new(); - - public void Dispose() - { - if (!_disposed) - { - _resultPool.Return(ResultMaps); - _resultPool.Return(ResultMapLists); - _resultPool.Return(ResultLists); - _disposed = true; - } - } -} diff --git a/src/HotChocolate/Core/src/Execution/Processing/ResultPool.cs b/src/HotChocolate/Core/src/Execution/Processing/ResultPool.cs deleted file mode 100644 index 9e8d4239756..00000000000 --- a/src/HotChocolate/Core/src/Execution/Processing/ResultPool.cs +++ /dev/null @@ -1,60 +0,0 @@ -using System.Collections.Generic; -using Microsoft.Extensions.ObjectPool; - -namespace HotChocolate.Execution.Processing; - -internal sealed class ResultPool -{ - private readonly ObjectPool> _resultMapPool; - private readonly ObjectPool> _resultMapListPool; - private readonly ObjectPool> _resultListPool; - - public ResultPool( - ObjectPool> resultMapPool, - ObjectPool> resultMapListPool, - ObjectPool> resultListPool) - { - _resultMapPool = resultMapPool; - _resultMapListPool = resultMapListPool; - _resultListPool = resultListPool; - } - - public ResultObjectBuffer GetResultMap() - { - return _resultMapPool.Get(); - } - - public ResultObjectBuffer GetResultMapList() - { - return _resultMapListPool.Get(); - } - - public ResultObjectBuffer GetResultList() - { - return _resultListPool.Get(); - } - - public void Return(IList> buffers) - { - for (var i = 0; i < buffers.Count; i++) - { - _resultMapPool.Return(buffers[i]); - } - } - - public void Return(IList> buffers) - { - for (var i = 0; i < buffers.Count; i++) - { - _resultMapListPool.Return(buffers[i]); - } - } - - public void Return(IList> buffers) - { - for (var i = 0; i < buffers.Count; i++) - { - _resultListPool.Return(buffers[i]); - } - } -} diff --git a/src/HotChocolate/Core/src/Execution/Processing/SelectionSet.cs b/src/HotChocolate/Core/src/Execution/Processing/SelectionSet.cs index b43192ebe76..2d8c7c0e0fe 100644 --- a/src/HotChocolate/Core/src/Execution/Processing/SelectionSet.cs +++ b/src/HotChocolate/Core/src/Execution/Processing/SelectionSet.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Runtime.InteropServices; namespace HotChocolate.Execution.Processing; @@ -11,8 +12,8 @@ namespace HotChocolate.Execution.Processing; internal sealed class SelectionSet : ISelectionSet { private static readonly Fragment[] _empty = Array.Empty(); - private readonly IReadOnlyList _selections; - private readonly IReadOnlyList _fragments; + private readonly Selection[] _selections; + private readonly Fragment[] _fragments; private Flags _flags; /// @@ -25,7 +26,7 @@ internal sealed class SelectionSet : ISelectionSet /// Defines if this list needs post processing for skip and include. /// public SelectionSet( - IReadOnlyList selections, + Selection[] selections, bool isConditional) { _selections = selections; @@ -47,8 +48,8 @@ internal sealed class SelectionSet : ISelectionSet /// Defines if this list needs post processing for skip and include. /// public SelectionSet( - IReadOnlyList selections, - IReadOnlyList? fragments, + Selection[] selections, + Fragment[]? fragments, bool isConditional) { _selections = selections; @@ -65,16 +66,11 @@ internal sealed class SelectionSet : ISelectionSet /// public IReadOnlyList Fragments => _fragments; - /// - /// Gets an empty selection set. - /// - public static SelectionSet Empty { get; } = new(Array.Empty(), false); - internal void Seal() { if ((_flags & Flags.Sealed) != Flags.Sealed) { - for (var i = 0; i < _selections.Count; i++) + for (var i = 0; i < _selections.Length; i++) { _selections[i].Seal(); } @@ -83,6 +79,16 @@ internal void Seal() } } + /// + /// Returns a reference to the 0th element of the underlying selections array. + /// If the selections array is empty, returns a reference to the location where the 0th element + /// would have been stored. Such a reference may or may not be null. + /// It can be used for pinning but must never be dereferenced. + /// This is only meant for use by the execution engine. + /// + internal ref Selection GetSelectionsReference() + => ref MemoryMarshal.GetReference(_selections.AsSpan()); + [Flags] private enum Flags { diff --git a/src/HotChocolate/Core/src/Execution/Processing/SelectionVariants.cs b/src/HotChocolate/Core/src/Execution/Processing/SelectionVariants.cs index 3c99ad7c645..c8f6aeda2b6 100644 --- a/src/HotChocolate/Core/src/Execution/Processing/SelectionVariants.cs +++ b/src/HotChocolate/Core/src/Execution/Processing/SelectionVariants.cs @@ -79,8 +79,8 @@ internal bool ContainsSelectionSet(IObjectType typeContext) internal void AddSelectionSet( ObjectType typeContext, - IReadOnlyList selections, - IReadOnlyList? fragments, + Selection[] selections, + Fragment[]? fragments, bool isConditional) { if (_readOnly) diff --git a/src/HotChocolate/Core/src/Execution/Processing/SubscriptionExecutor.Subscription.cs b/src/HotChocolate/Core/src/Execution/Processing/SubscriptionExecutor.Subscription.cs index fcded2b0730..d102cb4bc93 100644 --- a/src/HotChocolate/Core/src/Execution/Processing/SubscriptionExecutor.Subscription.cs +++ b/src/HotChocolate/Core/src/Execution/Processing/SubscriptionExecutor.Subscription.cs @@ -236,7 +236,7 @@ private async ValueTask SubscribeAsync() // next we need a result map so that we can store the subscribe temporarily // while executing the subscribe pipeline. - var resultMap = operationContext.Result.RentResultMap(1); + var resultMap = operationContext.Result.RentObject(1); var rootSelection = _rootSelections.Selections[0]; // we create a temporary middleware context so that we can use the standard @@ -292,7 +292,7 @@ private async ValueTask SubscribeAsync() } finally { - operationContext.Result.DropResult(); + operationContext.Result.DiscardResult(); _operationContextPool.Return(operationContext); } } diff --git a/src/HotChocolate/Core/src/Execution/Processing/Tasks/ResolverTask.cs b/src/HotChocolate/Core/src/Execution/Processing/Tasks/ResolverTask.cs index 9ff153d1f46..7ea83131d37 100644 --- a/src/HotChocolate/Core/src/Execution/Processing/Tasks/ResolverTask.cs +++ b/src/HotChocolate/Core/src/Execution/Processing/Tasks/ResolverTask.cs @@ -54,7 +54,7 @@ public ResolverTask(ObjectPool objectPool) /// /// Gets access to the internal result map into which the task will write the result. /// - public ResultMap ResultMap { get; private set; } = default!; + public ObjectResult ParentResult { get; private set; } = default!; /// /// Gets the completed value of this task. @@ -95,21 +95,20 @@ private void CompleteValue(bool success, CancellationToken cancellationToken) // we will only try to complete the resolver value if there are no known errors. if (success) { - if (ValueCompletion.TryComplete( + completedValue = ValueCompletion.Complete( _operationContext, _resolverContext, + _taskBuffer, _resolverContext.Selection, _resolverContext.Path, _selection.Type, _resolverContext.ResponseName, _resolverContext.ResponseIndex, - _resolverContext.Result, - _taskBuffer, - out completedValue) && - _selection.TypeKind is not TypeKind.Scalar and not TypeKind.Enum && - completedValue is IHasResultDataParent result) + _resolverContext.Result); + + if (completedValue is ResultData result) { - result.Parent = _resolverContext.ResultMap; + result.Parent = _resolverContext.ParentResult; } } } @@ -135,6 +134,12 @@ private void CompleteValue(bool success, CancellationToken cancellationToken) CompletedValue = completedValue; var isNonNullType = _selection.Type.Kind is TypeKind.NonNull; + _resolverContext.ParentResult.SetValueUnsafe( + _resolverContext.ResponseIndex, + _resolverContext.ResponseName, + completedValue, + !isNonNullType); + if (completedValue is null && isNonNullType) { // if we detect a non-null violation we will stash it for later. @@ -143,17 +148,9 @@ private void CompleteValue(bool success, CancellationToken cancellationToken) _operationContext.Result.AddNonNullViolation( _resolverContext.Selection.SyntaxNode, _resolverContext.Path, - _resolverContext.ResultMap); + _resolverContext.ParentResult); _taskBuffer.Clear(); } - else - { - _resolverContext.ResultMap.SetValue( - _resolverContext.ResponseIndex, - _resolverContext.ResponseName, - completedValue, - !isNonNullType); - } } /// @@ -162,7 +159,7 @@ private void CompleteValue(bool success, CancellationToken cancellationToken) public void Initialize( IOperationContext operationContext, ISelection selection, - ResultMap resultMap, + ObjectResult parentResult, int responseIndex, object? parent, Path path, @@ -173,12 +170,12 @@ private void CompleteValue(bool success, CancellationToken cancellationToken) _resolverContext.Initialize( operationContext, selection, - resultMap, + parentResult, responseIndex, parent, path, scopedContextData); - ResultMap = resultMap; + ParentResult = parentResult; } /// @@ -191,7 +188,7 @@ internal bool Reset() _operationContext = default!; _selection = default!; _resolverContext.Clean(); - ResultMap = default!; + ParentResult = default!; CompletedValue = null; Status = ExecutionTaskStatus.WaitingToRun; IsSerial = false; diff --git a/src/HotChocolate/Core/src/Execution/Processing/Tasks/ResolverTaskFactory.cs b/src/HotChocolate/Core/src/Execution/Processing/Tasks/ResolverTaskFactory.cs index 8abc2fbd982..57e189121ad 100644 --- a/src/HotChocolate/Core/src/Execution/Processing/Tasks/ResolverTaskFactory.cs +++ b/src/HotChocolate/Core/src/Execution/Processing/Tasks/ResolverTaskFactory.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; +using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using HotChocolate.Types; @@ -12,7 +13,7 @@ internal static class ResolverTaskFactory { private static List? _pooled = new(); - public static ResultMap EnqueueResolverTasks( + public static ObjectResult EnqueueResolverTasks( IOperationContext operationContext, ISelectionSet selectionSet, object? parent, @@ -20,8 +21,8 @@ internal static class ResolverTaskFactory IImmutableDictionary scopedContext) { var responseIndex = 0; - var selections = selectionSet.Selections; - var resultMap = operationContext.Result.RentResultMap(selections.Count); + var selectionsCount = selectionSet.Selections.Count; + var parentResult = operationContext.Result.RentObject(selectionsCount); var scheduler = operationContext.Scheduler; var includeFlags = operationContext.IncludeFlags; var final = !selectionSet.IsConditional; @@ -31,9 +32,12 @@ internal static class ResolverTaskFactory try { - for (var i = 0; i < selections.Count; i++) + ref var selectionSpace = ref ((SelectionSet)selectionSet).GetSelectionsReference(); + + for (var i = 0; i < selectionsCount; i++) { - var selection = selections[i]; + ref var selection = ref Unsafe.Add(ref selectionSpace, i); + if (final || selection.IsIncluded(includeFlags)) { bufferedTasks.Add(CreateResolverTask( @@ -42,7 +46,7 @@ internal static class ResolverTaskFactory parent, responseIndex++, operationContext.PathFactory.Append(path, selection.ResponseName), - resultMap, + parentResult, scopedContext)); } } @@ -58,19 +62,22 @@ internal static class ResolverTaskFactory scheduler.Register(bufferedTasks); } - TryHandleDeferredFragments( - operationContext, - selectionSet, - scopedContext, - path, - parent); + if (selectionSet.Fragments.Count > 0) + { + TryHandleDeferredFragments( + operationContext, + selectionSet, + scopedContext, + path, + parent); + } - return resultMap; + return parentResult; } finally { bufferedTasks.Clear(); - Interlocked.Exchange(ref _pooled, bufferedTasks); + Interlocked.Exchange(ref _pooled!, bufferedTasks); } } @@ -83,8 +90,7 @@ internal static class ResolverTaskFactory IAsyncEnumerator value, IImmutableDictionary scopedContext) { - var resultMap = operationContext.Result.RentResultMap(1); - + var parentResult = operationContext.Result.RentObject(1); var bufferedTasks = Interlocked.Exchange(ref _pooled, null) ?? new(); Debug.Assert(bufferedTasks.Count == 0, "The buffer must be clean."); @@ -94,7 +100,7 @@ internal static class ResolverTaskFactory parent, 0, path, - resultMap, + parentResult, scopedContext); try @@ -106,7 +112,7 @@ internal static class ResolverTaskFactory selection.Type.ElementType(), operationContext.PathFactory.Append(path, index), 0, - resultMap, + parentResult, value.Current, bufferedTasks); } @@ -119,62 +125,71 @@ internal static class ResolverTaskFactory return resolverTask; } - public static ResultMap EnqueueOrInlineResolverTasks( + public static ObjectResult EnqueueOrInlineResolverTasks( IOperationContext operationContext, MiddlewareContext resolverContext, Path path, - ObjectType resultType, - object result, + ObjectType parentType, + object parent, ISelectionSet selectionSet, List bufferedTasks) { var responseIndex = 0; - var selections = selectionSet.Selections; - var resultMap = operationContext.Result.RentResultMap(selections.Count); + var selectionsCount = selectionSet.Selections.Count; + var parentResult = operationContext.Result.RentObject(selectionsCount); + var pathFactory = operationContext.PathFactory; var includeFlags = operationContext.IncludeFlags; var final = !selectionSet.IsConditional; - for (var i = 0; i < selections.Count; i++) + ref var selectionSpace = ref ((SelectionSet)selectionSet).GetSelectionsReference(); + + for (var i = 0; i < selectionsCount; i++) { - var selection = selections[i]; + ref var selection = ref Unsafe.Add(ref selectionSpace, i); if (final || selection.IsIncluded(includeFlags)) { + var selectionPath = pathFactory.Append(path, selection.ResponseName); + if (selection.Strategy is SelectionExecutionStrategy.Pure) { ResolveAndCompleteInline( operationContext, resolverContext, selection, - operationContext.PathFactory.Append(path, selection.ResponseName), + selectionPath, responseIndex++, - resultType, - result, - resultMap, + parentType, + parent, + parentResult, bufferedTasks); } else { - bufferedTasks.Add(CreateResolverTask( - operationContext, - resolverContext, - selection, - operationContext.PathFactory.Append(path, selection.ResponseName), - responseIndex++, - result, - resultMap)); + bufferedTasks.Add( + CreateResolverTask( + operationContext, + resolverContext, + selection, + selectionPath, + responseIndex++, + parent, + parentResult)); } } } - TryHandleDeferredFragments( - operationContext, - selectionSet, - resolverContext.ScopedContextData, - path, - result); + if (selectionSet.Fragments.Count > 0) + { + TryHandleDeferredFragments( + operationContext, + selectionSet, + resolverContext.ScopedContextData, + path, + parent); + } - return resultMap; + return parentResult; } private static void ResolveAndCompleteInline( @@ -185,44 +200,34 @@ internal static class ResolverTaskFactory int responseIndex, ObjectType parentType, object parent, - ResultMap resultMap, + ObjectResult parentResult, List bufferedTasks) { - var committed = false; + var executedSuccessfully = false; object? resolverResult = null; try { - if (TryExecute(out resolverResult)) + // we first try to create a context for our pure resolver. + // this should actually only fail if we are unable to coerce + // the field arguments. + if (resolverContext.TryCreatePureContext( + selection, path, parentType, parent, + out var childContext)) { - committed = true; - - CompleteInline( - operationContext, - resolverContext, - selection, - selection.Type, - path, - responseIndex, - resultMap, - resolverResult, - bufferedTasks); + // if we have a pure context we can execute out pure resolver. + resolverResult = selection.PureResolver!(childContext); + executedSuccessfully = true; } } catch (OperationCanceledException) { // If we run into this exception the request was aborted. // In this case we do nothing and just return. + return; } catch (Exception ex) { - if (operationContext.RequestAborted.IsCancellationRequested) - { - // if cancellation is request we do no longer report errors to the - // operation context. - return; - } - ValueCompletion.ReportError( operationContext, resolverContext, @@ -231,41 +236,32 @@ internal static class ResolverTaskFactory ex); } - if (!committed) + if (executedSuccessfully) { - CommitValue( + // if we were able to execute the resolver we will try to complete the + // resolver result inline and commit the value to the result.. + CompleteInline( operationContext, + resolverContext, selection, + selection.Type, path, responseIndex, - resultMap, - resolverResult); + parentResult, + resolverResult, + bufferedTasks); } - - bool TryExecute(out object? result) + else { - try - { - if (resolverContext.TryCreatePureContext( - selection, path, parentType, parent, - out var childContext)) - { - result = selection.PureResolver!(childContext); - return true; - } - } - catch (Exception ex) - { - ValueCompletion.ReportError( - operationContext, - resolverContext, - selection, - path, - ex); - } - - result = null; - return false; + // if we were not able to execute the resolver we will commit the null value + // of the resolver to the object result which could trigger a non-null propagation. + CommitValue( + operationContext, + selection, + path, + responseIndex, + parentResult, + resolverResult); } } @@ -273,10 +269,10 @@ bool TryExecute(out object? result) IOperationContext operationContext, MiddlewareContext resolverContext, ISelection selection, - IType elementType, + IType selectionType, Path path, int responseIndex, - ResultMap resultMap, + ObjectResult resultMap, object? value, List bufferedTasks) { @@ -284,19 +280,18 @@ bool TryExecute(out object? result) try { - if (ValueCompletion.TryComplete( + completedValue = ValueCompletion.Complete( operationContext, resolverContext, + bufferedTasks, selection, path, - elementType, + selectionType, selection.ResponseName, responseIndex, - value, - bufferedTasks, - out completedValue) && - elementType.Kind is not TypeKind.Scalar and not TypeKind.Enum && - completedValue is IHasResultDataParent result) + value); + + if (completedValue is ResultData result) { result.Parent = resultMap; } @@ -309,13 +304,6 @@ bool TryExecute(out object? result) } catch (Exception ex) { - if (operationContext.RequestAborted.IsCancellationRequested) - { - // if cancellation is request we do no longer report errors to the - // operation context. - return; - } - ValueCompletion.ReportError( operationContext, resolverContext, @@ -338,11 +326,17 @@ bool TryExecute(out object? result) ISelection selection, Path path, int responseIndex, - ResultMap resultMap, + ObjectResult parentResult, object? completedValue) { var isNonNullType = selection.Type.Kind is TypeKind.NonNull; + parentResult.SetValueUnsafe( + responseIndex, + selection.ResponseName, + completedValue, + !isNonNullType); + if (completedValue is null && isNonNullType) { // if we detect a non-null violation we will stash it for later. @@ -350,15 +344,7 @@ bool TryExecute(out object? result) operationContext.Result.AddNonNullViolation( selection.SyntaxNode, path, - resultMap); - } - else - { - resultMap.SetValue( - responseIndex, - selection.ResponseName, - completedValue, - !isNonNullType); + parentResult); } } @@ -369,14 +355,14 @@ bool TryExecute(out object? result) Path path, int responseIndex, object parent, - ResultMap resultMap) + ObjectResult parentResult) { var task = operationContext.ResolverTasks.Get(); task.Initialize( operationContext, selection, - resultMap, + parentResult, responseIndex, parent, path, @@ -391,7 +377,7 @@ bool TryExecute(out object? result) object? parent, int responseIndex, Path path, - ResultMap resultMap, + ObjectResult result, IImmutableDictionary scopedContext) { var task = operationContext.ResolverTasks.Get(); @@ -399,7 +385,7 @@ bool TryExecute(out object? result) task.Initialize( operationContext, selection, - resultMap, + result, responseIndex, parent, path, @@ -415,24 +401,21 @@ bool TryExecute(out object? result) Path path, object? parent) { - if (selectionSet.Fragments.Count > 0) - { - var fragments = selectionSet.Fragments; - var includeFlags = operationContext.IncludeFlags; + var fragments = selectionSet.Fragments; + var includeFlags = operationContext.IncludeFlags; - for (var i = 0; i < fragments.Count; i++) + for (var i = 0; i < fragments.Count; i++) + { + var fragment = fragments[i]; + if (!fragment.IsConditional || fragment.IsIncluded(includeFlags)) { - var fragment = fragments[i]; - if (!fragment.IsConditional || fragment.IsIncluded(includeFlags)) - { - operationContext.Scheduler.DeferredWork.Register( - new DeferredFragment( - fragment, - fragment.GetLabel(operationContext.Variables), - path, - parent, - scopedContext)); - } + operationContext.Scheduler.DeferredWork.Register( + new DeferredFragment( + fragment, + fragment.GetLabel(operationContext.Variables), + path, + parent, + scopedContext)); } } } diff --git a/src/HotChocolate/Core/src/Execution/Processing/ValueCompletion.Leaf.cs b/src/HotChocolate/Core/src/Execution/Processing/ValueCompletion.Leaf.cs index b7f2e807077..78c764d45ce 100644 --- a/src/HotChocolate/Core/src/Execution/Processing/ValueCompletion.Leaf.cs +++ b/src/HotChocolate/Core/src/Execution/Processing/ValueCompletion.Leaf.cs @@ -7,14 +7,13 @@ namespace HotChocolate.Execution.Processing; internal static partial class ValueCompletion { - private static bool TryCompleteLeafValue( + private static object? CompleteLeafValue( IOperationContext operationContext, MiddlewareContext resolverContext, ISelection selection, Path path, IType fieldType, - object? result, - out object? completedResult) + object? result) { try { @@ -27,8 +26,7 @@ internal static partial class ValueCompletion result = c; } - completedResult = leafType.Serialize(result); - return true; + return leafType.Serialize(result); } catch (SerializationException ex) { @@ -51,7 +49,6 @@ internal static partial class ValueCompletion path)); } - completedResult = null; - return true; + return null; } } diff --git a/src/HotChocolate/Core/src/Execution/Processing/ValueCompletion.List.cs b/src/HotChocolate/Core/src/Execution/Processing/ValueCompletion.List.cs index 85c07039a0e..ecc57d74271 100644 --- a/src/HotChocolate/Core/src/Execution/Processing/ValueCompletion.List.cs +++ b/src/HotChocolate/Core/src/Execution/Processing/ValueCompletion.List.cs @@ -1,6 +1,8 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using HotChocolate.Execution.Processing.Tasks; using HotChocolate.Types; using static HotChocolate.Execution.ErrorHelper; @@ -9,268 +11,119 @@ namespace HotChocolate.Execution.Processing; internal static partial class ValueCompletion { - private static bool TryCompleteListValue( + private static object? CompleteListValue( IOperationContext operationContext, MiddlewareContext resolverContext, + List tasks, ISelection selection, Path path, IType fieldType, string responseName, int responseIndex, - object? result, - List bufferedTasks, - out object? completedValue) + object? result) { var elementType = fieldType.InnerType(); - - if (elementType.Kind is TypeKind.NonNull) - { - elementType = elementType.InnerType(); - } - - if (elementType.Kind is TypeKind.Object or TypeKind.Interface or TypeKind.Union) - { - return TryCompleteCompositeListValue( - operationContext, - resolverContext, - selection, - path, - fieldType, - responseName, - responseIndex, - result, - bufferedTasks, - out completedValue); - } - - return TryCompleteOtherListValue( - operationContext, - resolverContext, - selection, - path, - fieldType, - responseName, - responseIndex, - result, - bufferedTasks, - out completedValue); - } - - private static bool TryCompleteCompositeListValue( - IOperationContext operationContext, - MiddlewareContext resolverContext, - ISelection selection, - Path path, - IType fieldType, - string responseName, - int responseIndex, - object? result, - List bufferedTasks, - out object? completedResult) - { - var resultList = operationContext.Result.RentResultMapList(); - var elementType = fieldType.InnerType(); - resultList.IsNullable = elementType.Kind is not TypeKind.NonNull; + var isLeafType = elementType.IsLeafType(); if (result is Array array) { + var resultList = operationContext.Result.RentList(array.Length); + resultList.IsNullable = elementType.Kind is not TypeKind.NonNull; + for (var i = 0; i < array.Length; i++) { - if (!TryCompleteElement( - operationContext.PathFactory.Append(path,i), - array.GetValue(i))) + var elementResult = array.GetValue(i); + var elementPath = operationContext.PathFactory.Append(path, i); + + if (!TryCompleteElement(resultList, elementPath, elementResult)) { - completedResult = null; - return true; + return null; } } - completedResult = resultList; - return true; + return resultList; } if (result is IList list) { + var resultList = operationContext.Result.RentList(list.Count); + resultList.IsNullable = elementType.Kind is not TypeKind.NonNull; + for (var i = 0; i < list.Count; i++) { - if (!TryCompleteElement(operationContext.PathFactory.Append(path, i), list[i])) + var elementPath = operationContext.PathFactory.Append(path, i); + if (!TryCompleteElement(resultList, elementPath, list[i])) { - completedResult = null; - return true; + return null; } } - completedResult = resultList; - return true; + return resultList; } if (result is IEnumerable enumerable) { - var index = 0; + var resultList = operationContext.Result.RentList(4); + resultList.IsNullable = elementType.Kind is not TypeKind.NonNull; + var index = 0; foreach (var element in enumerable) { - if (!TryCompleteElement( - operationContext.PathFactory.Append(path, index++), element)) + if (resultList.Count == resultList.Capacity) { - completedResult = null; - return true; + resultList.Grow(); } - } - completedResult = resultList; - return true; - } - - ReportError( - operationContext, - resolverContext, - selection, - ListValueIsNotSupported(resultList.GetType(), selection.SyntaxNode, path)); - - completedResult = null; - return false; - - bool TryCompleteElement(Path elementPath, object? elementResult) - { - if (TryComplete( - operationContext, - resolverContext, - selection, - elementPath, - elementType, - responseName, - responseIndex, - elementResult, - bufferedTasks, - out var completedElement) && - completedElement is ResultMap resultMap) - { - resultMap.Parent = resultList; - resultList.Add(resultMap); - } - else if (resultList.IsNullable) - { - resultList.Add(null); - } - else - { - return false; - } - - return true; - } - } - - private static bool TryCompleteOtherListValue( - IOperationContext operationContext, - MiddlewareContext resolverContext, - ISelection selection, - Path path, - IType fieldType, - string responseName, - int responseIndex, - object? result, - List bufferedTasks, - out object? completedResult) - { - var resultList = operationContext.Result.RentResultList(); - var elementType = fieldType.InnerType(); - resultList.IsNullable = elementType.Kind is not TypeKind.NonNull; - var isElementList = elementType.IsListType(); - - if (result is Array array) - { - for (var i = 0; i < array.Length; i++) - { - if (!TryCompleteElement( - operationContext.PathFactory.Append(path, i), - array.GetValue(i))) + var elementPath = operationContext.PathFactory.Append(path, index++); + if (!TryCompleteElement(resultList, elementPath, element)) { - completedResult = null; - return true; + return null; } } - completedResult = resultList; - return true; - } - - if (result is IList list) - { - for (var i = 0; i < list.Count; i++) - { - if (!TryCompleteElement(operationContext.PathFactory.Append(path, i), list[i])) - { - completedResult = null; - return true; - } - } - - completedResult = resultList; - return true; - } - - if (result is IEnumerable enumerable) - { - var index = 0; - - foreach (var element in enumerable) - { - if (!TryCompleteElement( - operationContext.PathFactory.Append(path, index++), - element)) - { - completedResult = null; - return true; - } - } - - completedResult = resultList; - return true; + return resultList; } ReportError( operationContext, resolverContext, selection, - ListValueIsNotSupported(resultList.GetType(), selection.SyntaxNode, path)); + ListValueIsNotSupported(typeof(ListResult), selection.SyntaxNode, path)); - completedResult = null; - return false; + return null; - bool TryCompleteElement(Path elementPath, object? elementResult) + bool TryCompleteElement(ListResult resultList, Path elementPath, object? elementResult) { - if (TryComplete( + var completedElement = Complete( operationContext, resolverContext, + tasks, selection, elementPath, elementType, responseName, responseIndex, - elementResult, - bufferedTasks, - out var completedElement) && - completedElement is not null) + elementResult); + + if (completedElement is not null) { - resultList.Add(completedElement); + resultList.AddUnsafe(completedElement); - if (isElementList) + if (!isLeafType) { - ((IHasResultDataParent)completedElement).Parent = resultList; + ((ResultData)completedElement).Parent = resultList; } + + return true; } - else if (resultList.IsNullable) - { - resultList.Add(null); - } - else + + if (resultList.IsNullable) { - return false; + resultList.AddUnsafe(null); + return true; } - return true; + return false; } } } diff --git a/src/HotChocolate/Core/src/Execution/Processing/ValueCompletion.Object.cs b/src/HotChocolate/Core/src/Execution/Processing/ValueCompletion.Object.cs index d44cd252bf3..5609e778261 100644 --- a/src/HotChocolate/Core/src/Execution/Processing/ValueCompletion.Object.cs +++ b/src/HotChocolate/Core/src/Execution/Processing/ValueCompletion.Object.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using HotChocolate.Execution.Processing.Tasks; -using HotChocolate.Language; using HotChocolate.Types; using HotChocolate.Utilities; using static HotChocolate.Execution.ErrorHelper; @@ -12,15 +11,14 @@ namespace HotChocolate.Execution.Processing; internal static partial class ValueCompletion { - private static bool TryCompleteCompositeValue( + private static ObjectResult? CompleteCompositeValue( IOperationContext operationContext, MiddlewareContext resolverContext, + List tasks, ISelection selection, Path path, IType fieldType, - object result, - List bufferedTasks, - [NotNullWhen(true)] out object? completedResult) + object result) { if (TryResolveObjectType( operationContext, @@ -40,15 +38,14 @@ internal static partial class ValueCompletion result = converted; } - completedResult = EnqueueOrInlineResolverTasks( + return EnqueueOrInlineResolverTasks( operationContext, resolverContext, path, objectType, result, selectionSet, - bufferedTasks); - return true; + tasks); } ReportError( @@ -57,8 +54,7 @@ internal static partial class ValueCompletion selection, ValueCompletion_CouldNotResolveAbstractType(selection.SyntaxNode, path, result)); - completedResult = null; - return false; + return null; } private static bool TryResolveObjectType( diff --git a/src/HotChocolate/Core/src/Execution/Processing/ValueCompletion.cs b/src/HotChocolate/Core/src/Execution/Processing/ValueCompletion.cs index e2299092afc..29680b06b1d 100644 --- a/src/HotChocolate/Core/src/Execution/Processing/ValueCompletion.cs +++ b/src/HotChocolate/Core/src/Execution/Processing/ValueCompletion.cs @@ -7,80 +7,65 @@ namespace HotChocolate.Execution.Processing; internal static partial class ValueCompletion { - public static bool TryComplete( + public static object? Complete( IOperationContext operationContext, MiddlewareContext resolverContext, + List tasks, ISelection selection, Path path, IType fieldType, string responseName, int responseIndex, - object? result, - List bufferedTasks, - out object? completedResult) + object? result) { var typeKind = fieldType.Kind; if (typeKind is TypeKind.NonNull) { - return TryComplete( - operationContext, - resolverContext, - selection, - path, - fieldType.InnerType(), - responseName, - responseIndex, - result, - bufferedTasks, - out completedResult) && - completedResult is not null; + fieldType = fieldType.InnerType(); + typeKind = fieldType.Kind; } if (result is null) { - completedResult = null; - return true; + return null; } - if (typeKind is TypeKind.List) + if (typeKind is TypeKind.Scalar or TypeKind.Enum) { - return TryCompleteListValue( + return CompleteLeafValue( operationContext, resolverContext, selection, path, fieldType, - responseName, - responseIndex, - result, - bufferedTasks, - out completedResult); + result); } - if (typeKind is TypeKind.Scalar or TypeKind.Enum) + if (typeKind is TypeKind.List) { - return TryCompleteLeafValue( + return CompleteListValue( operationContext, resolverContext, + tasks, selection, path, fieldType, - result, - out completedResult); + responseName, + responseIndex, + result); } if (typeKind is TypeKind.Object or TypeKind.Interface or TypeKind.Union) { - return TryCompleteCompositeValue( + return CompleteCompositeValue( operationContext, resolverContext, + tasks, selection, path, fieldType, - result, - bufferedTasks, - out completedResult); + result); } ReportError( @@ -89,8 +74,6 @@ internal static partial class ValueCompletion selection, UnexpectedValueCompletionError(selection.SyntaxNode, path)); - completedResult = null; - return false; + return null; } - } diff --git a/src/HotChocolate/Core/src/Execution/Processing/WorkScheduler.Pooling.cs b/src/HotChocolate/Core/src/Execution/Processing/WorkScheduler.Pooling.cs index 93107a9a593..92ec266bbc8 100644 --- a/src/HotChocolate/Core/src/Execution/Processing/WorkScheduler.Pooling.cs +++ b/src/HotChocolate/Core/src/Execution/Processing/WorkScheduler.Pooling.cs @@ -19,7 +19,7 @@ internal sealed partial class WorkScheduler private IRequestContext _requestContext = default!; private IBatchDispatcher _batchDispatcher = default!; private IErrorHandler _errorHandler = default!; - private IResultHelper _result = default!; + private ResultBuilder _result = default!; private IExecutionDiagnosticEvents _diagnosticEvents = default!; private CancellationToken _ct; diff --git a/src/HotChocolate/Core/src/Execution/Serialization/JsonQueryResultFormatter.cs b/src/HotChocolate/Core/src/Execution/Serialization/JsonQueryResultFormatter.cs index 022e9d6d60d..3b2966627ad 100644 --- a/src/HotChocolate/Core/src/Execution/Serialization/JsonQueryResultFormatter.cs +++ b/src/HotChocolate/Core/src/Execution/Serialization/JsonQueryResultFormatter.cs @@ -3,11 +3,13 @@ using System.Collections; using System.Collections.Generic; using System.IO; +using System.Runtime.CompilerServices; using System.Text; using System.Text.Encodings.Web; using System.Text.Json; using System.Threading; using System.Threading.Tasks; +using HotChocolate.Execution.Processing; using HotChocolate.Utilities; using static HotChocolate.Execution.Serialization.JsonConstants; @@ -191,9 +193,9 @@ private void WriteResult(Utf8JsonWriter writer, IQueryResult result) { writer.WritePropertyName(Data); - if (data is IResultMap resultMap) + if (data is ObjectResult resultMap) { - WriteResultMap(writer, resultMap); + WriteObjectResult(writer, resultMap); } else { @@ -334,55 +336,70 @@ private static void WritePathValue(Utf8JsonWriter writer, Path path) writer.WriteEndObject(); } - private void WriteResultMap( + private void WriteDictionary( Utf8JsonWriter writer, - IResultMap resultMap) + Dictionary dict) { writer.WriteStartObject(); - for (var i = 0; i < resultMap.Count; i++) + foreach (var item in dict) + { + writer.WritePropertyName(item.Key); + WriteFieldValue(writer, item.Value); + } + + writer.WriteEndObject(); + } + + private void WriteObjectResult( + Utf8JsonWriter writer, + ObjectResult objectResult) + { + writer.WriteStartObject(); + + ref var searchSpace = ref objectResult.GetReference(); + + for(var i = 0; i < objectResult.Capacity; i++) { - var value = resultMap[i]; - if (value.IsInitialized) + var field = Unsafe.Add(ref searchSpace, i); + if (field.IsInitialized) { - writer.WritePropertyName(value.Name); - WriteFieldValue(writer, value.Value); + writer.WritePropertyName(field.Name); + WriteFieldValue(writer, field.Value); } } writer.WriteEndObject(); } - private void WriteList( +#if NET5_0_OR_GREATER + private void WriteListResult( Utf8JsonWriter writer, - IList list) + ListResult list) { writer.WriteStartArray(); + ref var searchSpace = ref list.GetReference(); + for (var i = 0; i < list.Count; i++) { - WriteFieldValue(writer, list[i]); + var element = Unsafe.Add(ref searchSpace, i); + WriteFieldValue(writer, element); } writer.WriteEndArray(); } +#endif - private void WriteResultMapList( + private void WriteList( Utf8JsonWriter writer, - IResultMapList list) + IList list) { writer.WriteStartArray(); for (var i = 0; i < list.Count; i++) { - if (list[i] is { } m) - { - WriteResultMap(writer, m); - } - else - { - WriteFieldValue(writer, null); - } + WriteFieldValue(writer, list[i]); } writer.WriteEndArray(); @@ -400,12 +417,17 @@ private static void WritePathValue(Utf8JsonWriter writer, Path path) switch (value) { - case IResultMap resultMap: - WriteResultMap(writer, resultMap); + case ObjectResult resultMap: + WriteObjectResult(writer, resultMap); break; - case IResultMapList resultMapList: - WriteResultMapList(writer, resultMapList); +#if NET5_0_OR_GREATER + case ListResult resultMapList: + WriteListResult(writer, resultMapList); + break; +#endif + case Dictionary dict: + WriteDictionary(writer, dict); break; case IReadOnlyDictionary dict: diff --git a/src/HotChocolate/Core/src/Types.OffsetPagination/CollectionSegment.cs b/src/HotChocolate/Core/src/Types.OffsetPagination/CollectionSegment.cs index 57956a6e97b..a39f2d9062f 100644 --- a/src/HotChocolate/Core/src/Types.OffsetPagination/CollectionSegment.cs +++ b/src/HotChocolate/Core/src/Types.OffsetPagination/CollectionSegment.cs @@ -29,12 +29,9 @@ public class CollectionSegment : IPage CollectionSegmentInfo info, Func> getTotalCount) { - Items = items ?? - throw new ArgumentNullException(nameof(items)); - Info = info ?? - throw new ArgumentNullException(nameof(info)); - _getTotalCount = getTotalCount ?? - throw new ArgumentNullException(nameof(getTotalCount)); + Items = items ?? throw new ArgumentNullException(nameof(items)); + Info = info ?? throw new ArgumentNullException(nameof(info)); + _getTotalCount = getTotalCount ?? throw new ArgumentNullException(nameof(getTotalCount)); } /// @@ -55,10 +52,8 @@ public class CollectionSegment : IPage int totalCount = 0) { _getTotalCount = _ => new(totalCount); - Items = items ?? - throw new ArgumentNullException(nameof(items)); - Info = info ?? - throw new ArgumentNullException(nameof(info)); + Items = items ?? throw new ArgumentNullException(nameof(items)); + Info = info ?? throw new ArgumentNullException(nameof(info)); } /// @@ -85,6 +80,6 @@ public class CollectionSegment : IPage /// /// The total count of the data set / collection. /// - public ValueTask GetTotalCountAsync(CancellationToken cancellationToken) => - _getTotalCount(cancellationToken); + public ValueTask GetTotalCountAsync(CancellationToken cancellationToken) + => _getTotalCount(cancellationToken); } diff --git a/src/HotChocolate/Core/src/Types.OffsetPagination/CollectionSegmentType~1.cs b/src/HotChocolate/Core/src/Types.OffsetPagination/CollectionSegmentType~1.cs index 5c796b0ef47..774a6f9a479 100644 --- a/src/HotChocolate/Core/src/Types.OffsetPagination/CollectionSegmentType~1.cs +++ b/src/HotChocolate/Core/src/Types.OffsetPagination/CollectionSegmentType~1.cs @@ -74,9 +74,8 @@ internal class CollectionSegmentType DefinitionBase definition, IDictionary contextData) { - context.Dependencies.Add(new( - context.TypeInspector.GetOutputTypeRef(typeof(CollectionSegmentInfoType)))); - + var typeRef = context.TypeInspector.GetOutputTypeRef(typeof(CollectionSegmentInfoType)); + context.Dependencies.Add(new(typeRef)); base.OnBeforeRegisterDependencies(context, definition, contextData); } @@ -120,8 +119,7 @@ private static IPageInfo GetPagingInfo(IPureResolverContext context) => await context.Parent().GetTotalCountAsync(context.RequestAborted); private static bool IsItemsField(ObjectFieldDefinition field) - => field.CustomSettings.Count > 0 && - field.CustomSettings[0].Equals(ContextDataKeys.Items); + => field.CustomSettings.Count > 0 && field.CustomSettings[0].Equals(ContextDataKeys.Items); internal static class Names { diff --git a/src/HotChocolate/Core/src/Types.OffsetPagination/Extensions/CursorPagingQueryableExtensions.cs b/src/HotChocolate/Core/src/Types.OffsetPagination/Extensions/CursorPagingQueryableExtensions.cs index cb8305f4519..51dcf2261af 100644 --- a/src/HotChocolate/Core/src/Types.OffsetPagination/Extensions/CursorPagingQueryableExtensions.cs +++ b/src/HotChocolate/Core/src/Types.OffsetPagination/Extensions/CursorPagingQueryableExtensions.cs @@ -115,9 +115,7 @@ public static class OffsetPagingQueryableExtensions } var skip = context.ArgumentValue(OffsetPagingArgumentNames.Skip); - var take = context.ArgumentValue(OffsetPagingArgumentNames.Take) ?? - defaultPageSize; - + var take = context.ArgumentValue(OffsetPagingArgumentNames.Take) ?? defaultPageSize; var arguments = new OffsetPagingArguments(skip, take); if (totalCount is null && context.IsTotalCountSelected()) diff --git a/src/HotChocolate/Core/test/Abstractions.Tests/Execution/ResultValueTests.cs b/src/HotChocolate/Core/test/Abstractions.Tests/Execution/ResultValueTests.cs deleted file mode 100644 index e9987378b76..00000000000 --- a/src/HotChocolate/Core/test/Abstractions.Tests/Execution/ResultValueTests.cs +++ /dev/null @@ -1,118 +0,0 @@ -using Xunit; - -namespace HotChocolate.Execution; - -public class ResultValueTests -{ - [Fact] - public void NotInitialized() - { - Assert.False(default(ResultValue).IsInitialized); - } - - [Fact] - public void Initialized() - { - Assert.True(new ResultValue("abc", null).IsInitialized); - } - - [Fact] - public void PropertiesAreSet_NonNullable() - { - var value = new ResultValue("abc", "def", false); - - Assert.Equal("abc", value.Name); - Assert.Equal("def", value.Value); - Assert.False(value.IsNullable); - } - - [Fact] - public void PropertiesAreSet_Nullable() - { - var value = new ResultValue("abc", "def", true); - - Assert.Equal("abc", value.Name); - Assert.Equal("def", value.Value); - Assert.True(value.IsNullable); - } - - [Fact] - public void Equals_True() - { - // arrange - var valueA = new ResultValue("abc", "def", true); - var valueB = new ResultValue("abc", "def", true); - - //act - var equal = valueA.Equals(valueB); - - // assert - Assert.True(equal); - } - - [Fact] - public void Object_Equals_True() - { - // arrange - var valueA = new ResultValue("abc", "def", true); - var valueB = new ResultValue("abc", "def", true); - - //act - var equal = Equals(valueA, valueB); - - // assert - Assert.True(equal); - } - - [Fact] - public void Equals_False() - { - // arrange - var valueA = new ResultValue("abc", "def", true); - var valueB = new ResultValue("abc", "def1", true); - - //act - var equal = valueA.Equals(valueB); - - // assert - Assert.False(equal); - } - - [Fact] - public void Object_Equals_False() - { - // arrange - var valueA = new ResultValue("abc", "def", true); - var valueB = new ResultValue("abc", "def1", true); - - //act - var equal = Equals(valueA, valueB); - - // assert - Assert.False(equal); - } - - [Fact] - public void GetHashCode_Equals() - { - // arrange - var valueA = new ResultValue("abc", "def", true); - var valueB = new ResultValue("abc", "def", true); - - // act - // assert - Assert.Equal(valueA.GetHashCode(), valueB.GetHashCode()); - } - - [Fact] - public void GetHashCode_Not_Equals() - { - // arrange - var valueA = new ResultValue("abc", "def", true); - var valueB = new ResultValue("abc", "def1", true); - - // act - // assert - Assert.NotEqual(valueA.GetHashCode(), valueB.GetHashCode()); - } -} \ No newline at end of file diff --git a/src/HotChocolate/Core/test/Execution.Tests/Integration/StarWarsCodeFirst/StarWarsCodeFirstTests.cs b/src/HotChocolate/Core/test/Execution.Tests/Integration/StarWarsCodeFirst/StarWarsCodeFirstTests.cs index 1a0e1f140a1..171b01505ae 100644 --- a/src/HotChocolate/Core/test/Execution.Tests/Integration/StarWarsCodeFirst/StarWarsCodeFirstTests.cs +++ b/src/HotChocolate/Core/test/Execution.Tests/Integration/StarWarsCodeFirst/StarWarsCodeFirstTests.cs @@ -108,25 +108,25 @@ public async Task GraphQLOrgAliasExample() public async Task GraphQLOrgFragmentExample() { Snapshot.FullName(); - await ExpectValid(@" - { - leftComparison: hero(episode: EMPIRE) { - ...comparisonFields - } - rightComparison: hero(episode: JEDI) { - ...comparisonFields - } + await ExpectValid( + @"{ + leftComparison: hero(episode: EMPIRE) { + ...comparisonFields } + rightComparison: hero(episode: JEDI) { + ...comparisonFields + } + } - fragment comparisonFields on Character { - name - appearsIn - friends { - nodes { - name - } + fragment comparisonFields on Character { + name + appearsIn + friends { + nodes { + name } - }") + } + }") .MatchSnapshotAsync(); } @@ -573,7 +573,7 @@ public async Task SubscribeToReview() eventResult?.MatchSnapshot(); } - + [Fact] public async Task SubscribeToReview_WithInlineFragment() { diff --git a/src/HotChocolate/Core/test/Execution.Tests/Processing/Result/ObjectFieldResultTests.cs b/src/HotChocolate/Core/test/Execution.Tests/Processing/Result/ObjectFieldResultTests.cs new file mode 100644 index 00000000000..e65313eb098 --- /dev/null +++ b/src/HotChocolate/Core/test/Execution.Tests/Processing/Result/ObjectFieldResultTests.cs @@ -0,0 +1,69 @@ +using Xunit; + +namespace HotChocolate.Execution.Processing; + +public class ObjectFieldResultTests +{ + [Fact] + public void SetNullable() + { + // arrange + var field = new ObjectFieldResult(); + + // act + field.Set("abc", "def", true); + + // assert + Assert.Equal("abc", field.Name); + Assert.Equal("def", field.Value); + Assert.True(field.IsNullable); + Assert.True(field.IsInitialized); + } + + [Fact] + public void SetNonNullable() + { + // arrange + var field = new ObjectFieldResult(); + + // act + field.Set("abc", "def", false); + + // assert + Assert.Equal("abc", field.Name); + Assert.Equal("def", field.Value); + Assert.False(field.IsNullable); + Assert.True(field.IsInitialized); + } + + [Fact] + public void NewInstance() + { + // arrange + // act + var field = new ObjectFieldResult(); + + // assert + Assert.Null(field.Name); + Assert.Null(field.Value); + Assert.True(field.IsNullable); + Assert.False(field.IsInitialized); + } + + [Fact] + public void ResetInstance() + { + // arrange + var field = new ObjectFieldResult(); + field.Set("abc", "def", false); + + // act + field.Reset(); + + // assert + Assert.Null(field.Name); + Assert.Null(field.Value); + Assert.True(field.IsNullable); + Assert.False(field.IsInitialized); + } +} diff --git a/src/HotChocolate/Core/test/Execution.Tests/Processing/Result/ObjectResultTests.cs b/src/HotChocolate/Core/test/Execution.Tests/Processing/Result/ObjectResultTests.cs new file mode 100644 index 00000000000..23a879e3719 --- /dev/null +++ b/src/HotChocolate/Core/test/Execution.Tests/Processing/Result/ObjectResultTests.cs @@ -0,0 +1,191 @@ +using System.Collections.Generic; +using Xunit; + +namespace HotChocolate.Execution.Processing +{ + public class ObjectResultTests + { + [InlineData(8)] + [InlineData(4)] + [InlineData(2)] + [Theory] + public void EnsureCapacity(int size) + { + // arrange + var resultMap = new ObjectResult(); + + // act + resultMap.EnsureCapacity(size); + + // assert + Assert.Equal(size, resultMap.Capacity); + } + + [Fact] + public void SetValue() + { + // arrange + var objectResult = new ObjectResult(); + objectResult.EnsureCapacity(1); + + // act + objectResult.SetValueUnsafe(0, "abc", "def"); + + // assert + Assert.Collection( + (IEnumerable)objectResult, + t => + { + Assert.Equal("abc", t.Name); + Assert.Equal("def", t.Value); + }); + } + + [InlineData(9)] + [InlineData(8)] + [InlineData(7)] + [InlineData(5)] + [InlineData(4)] + [InlineData(3)] + [Theory] + public void GetValue_ValueIsFound(int capacity) + { + // arrange + var objectResult = new ObjectResult(); + objectResult.EnsureCapacity(capacity); + objectResult.SetValueUnsafe(0, "abc", "def"); + objectResult.SetValueUnsafe(capacity / 2, "def", "def"); + objectResult.SetValueUnsafe(capacity - 1, "ghi", "def"); + + // act + ObjectFieldResult value = objectResult.TryGetValue("def", out var index); + + // assert + Assert.Equal("def", value?.Name); + Assert.Equal(capacity / 2, index); + } + + [InlineData(9)] + [InlineData(8)] + [InlineData(7)] + [InlineData(5)] + [InlineData(4)] + [InlineData(3)] + [Theory] + public void TryGetValue_ValueIsFound(int capacity) + { + // arrange + var objectResult = new ObjectResult(); + objectResult.EnsureCapacity(capacity); + objectResult.SetValueUnsafe(0, "abc", "def"); + objectResult.SetValueUnsafe(capacity / 2, "def", "def"); + objectResult.SetValueUnsafe(capacity - 1, "ghi", "def"); + + IReadOnlyDictionary dict = objectResult; + + // act + var found = dict.TryGetValue("def", out var value); + + // assert + Assert.True(found); + Assert.Equal("def", value); + } + + [InlineData(9)] + [InlineData(8)] + [InlineData(7)] + [InlineData(5)] + [InlineData(4)] + [InlineData(3)] + [Theory] + public void ContainsKey(int capacity) + { + // arrange + var objectResult = new ObjectResult(); + objectResult.EnsureCapacity(capacity); + objectResult.SetValueUnsafe(0, "abc", "def"); + objectResult.SetValueUnsafe(capacity / 2, "def", "def"); + objectResult.SetValueUnsafe(capacity - 1, "ghi", "def"); + + IReadOnlyDictionary dict = objectResult; + + // act + var found = dict.ContainsKey("def"); + + // assert + Assert.True(found); + } + + [Fact] + public void EnumerateResultValue() + { + // arrange + var objectResult = new ObjectResult(); + objectResult.EnsureCapacity(5); + + // act + objectResult.SetValueUnsafe(0, "abc1", "def"); + objectResult.SetValueUnsafe(2, "abc2", "def"); + objectResult.SetValueUnsafe(4, "abc3", "def"); + + // assert + Assert.Collection( + (IEnumerable)objectResult, + t => + { + Assert.Equal("abc1", t.Name); + Assert.Equal("def", t.Value); + }, + t => + { + Assert.Equal("abc2", t.Name); + Assert.Equal("def", t.Value); + }, + t => + { + Assert.Equal("abc3", t.Name); + Assert.Equal("def", t.Value); + }); + } + + [Fact] + public void EnumerateKeys() + { + // arrange + var objectResult = new ObjectResult(); + objectResult.EnsureCapacity(5); + + // act + objectResult.SetValueUnsafe(0, "abc1", "def"); + objectResult.SetValueUnsafe(2, "abc2", "def"); + objectResult.SetValueUnsafe(4, "abc3", "def"); + + // assert + Assert.Collection( + ((IReadOnlyDictionary)objectResult).Keys, + t => Assert.Equal("abc1", t), + t => Assert.Equal("abc2", t), + t => Assert.Equal("abc3", t)); + } + + [Fact] + public void EnumerateValues() + { + // arrange + var objectResult = new ObjectResult(); + objectResult.EnsureCapacity(5); + + // act + objectResult.SetValueUnsafe(0, "abc1", "def"); + objectResult.SetValueUnsafe(2, "abc2", "def"); + objectResult.SetValueUnsafe(4, "abc3", "def"); + + // assert + Assert.Collection( + ((IReadOnlyDictionary)objectResult).Values, + t => Assert.Equal("def", t), + t => Assert.Equal("def", t), + t => Assert.Equal("def", t)); + } + } +} diff --git a/src/HotChocolate/Core/test/Execution.Tests/Processing/ResultHelperTests.cs b/src/HotChocolate/Core/test/Execution.Tests/Processing/Result/ResultBuilderTests.cs similarity index 59% rename from src/HotChocolate/Core/test/Execution.Tests/Processing/ResultHelperTests.cs rename to src/HotChocolate/Core/test/Execution.Tests/Processing/Result/ResultBuilderTests.cs index e329a683416..39c69daebc4 100644 --- a/src/HotChocolate/Core/test/Execution.Tests/Processing/ResultHelperTests.cs +++ b/src/HotChocolate/Core/test/Execution.Tests/Processing/Result/ResultBuilderTests.cs @@ -3,15 +3,15 @@ namespace HotChocolate.Execution.Processing; -public class ResultHelperTests +public class ResultBuilderTests { [Fact] public void BuildResult_SimpleResultSet_SnapshotMatches() { // arrange - var helper = new ResultHelper(CreatePool()); - var map = helper.RentResultMap(1); - map.SetValue(0, "abc", "def", false); + var helper = new ResultBuilder(CreatePool()); + var map = helper.RentObject(1); + map.SetValueUnsafe(0, "abc", "def", false); helper.SetData(map); // act @@ -25,8 +25,7 @@ public void BuildResult_SimpleResultSet_SnapshotMatches() private ResultPool CreatePool() { return new ResultPool( - new ResultMapPool(16), - new ResultMapListPool(16), - new ResultListPool(16)); + new ObjectResultPool(16, 16), + new ListResultPool(16, 16)); } -} \ No newline at end of file +} diff --git a/src/HotChocolate/Core/test/Execution.Tests/Processing/Result/__snapshots__/ResultBuilderTests.BuildResult_SimpleResultSet_SnapshotMatches.snap b/src/HotChocolate/Core/test/Execution.Tests/Processing/Result/__snapshots__/ResultBuilderTests.BuildResult_SimpleResultSet_SnapshotMatches.snap new file mode 100644 index 00000000000..769cd361886 --- /dev/null +++ b/src/HotChocolate/Core/test/Execution.Tests/Processing/Result/__snapshots__/ResultBuilderTests.BuildResult_SimpleResultSet_SnapshotMatches.snap @@ -0,0 +1,5 @@ +{ + "data": { + "abc": "def" + } +} diff --git a/src/HotChocolate/Core/test/Execution.Tests/Processing/ResultMapTests.cs b/src/HotChocolate/Core/test/Execution.Tests/Processing/ResultMapTests.cs deleted file mode 100644 index 1e674f54f91..00000000000 --- a/src/HotChocolate/Core/test/Execution.Tests/Processing/ResultMapTests.cs +++ /dev/null @@ -1,190 +0,0 @@ -using System.Collections.Generic; -using Xunit; - -namespace HotChocolate.Execution.Processing; - -public class ResultMapTests -{ - [InlineData(8)] - [InlineData(4)] - [InlineData(2)] - [Theory] - public void EnsureCapacity(int size) - { - // arrange - var resultMap = new ResultMap(); - - // act - resultMap.EnsureCapacity(size); - - // assert - Assert.Equal(size, resultMap.Count); - } - - [Fact] - public void SetValue() - { - // arrange - var resultMap = new ResultMap(); - resultMap.EnsureCapacity(1); - - // act - resultMap.SetValue(0, "abc", "def"); - - // assert - Assert.Collection( - (IEnumerable)resultMap, - t => - { - Assert.Equal("abc", t.Name); - Assert.Equal("def", t.Value); - }); - } - - [InlineData(9)] - [InlineData(8)] - [InlineData(7)] - [InlineData(5)] - [InlineData(4)] - [InlineData(3)] - [Theory] - public void GetValue_ValueIsFound(int capacity) - { - // arrange - var resultMap = new ResultMap(); - resultMap.EnsureCapacity(capacity); - resultMap.SetValue(0, "abc", "def"); - resultMap.SetValue(capacity / 2, "def", "def"); - resultMap.SetValue(capacity - 1, "ghi", "def"); - - // act - var value = resultMap.GetValue("def", out var index); - - // assert - Assert.Equal("def", value.Name); - Assert.Equal(capacity / 2, index); - } - - [InlineData(9)] - [InlineData(8)] - [InlineData(7)] - [InlineData(5)] - [InlineData(4)] - [InlineData(3)] - [Theory] - public void TryGetValue_ValueIsFound(int capacity) - { - // arrange - var resultMap = new ResultMap(); - resultMap.EnsureCapacity(capacity); - resultMap.SetValue(0, "abc", "def"); - resultMap.SetValue(capacity / 2, "def", "def"); - resultMap.SetValue(capacity - 1, "ghi", "def"); - - IReadOnlyDictionary dict = resultMap; - - // act - var found = dict.TryGetValue("def", out var value); - - // assert - Assert.True(found); - Assert.Equal("def", value); - } - - [InlineData(9)] - [InlineData(8)] - [InlineData(7)] - [InlineData(5)] - [InlineData(4)] - [InlineData(3)] - [Theory] - public void ContainsKey(int capacity) - { - // arrange - var resultMap = new ResultMap(); - resultMap.EnsureCapacity(capacity); - resultMap.SetValue(0, "abc", "def"); - resultMap.SetValue(capacity / 2, "def", "def"); - resultMap.SetValue(capacity - 1, "ghi", "def"); - - IReadOnlyDictionary dict = resultMap; - - // act - var found = dict.ContainsKey("def"); - - // assert - Assert.True(found); - } - - [Fact] - public void EnumerateResultValue() - { - // arrange - var resultMap = new ResultMap(); - resultMap.EnsureCapacity(5); - - // act - resultMap.SetValue(0, "abc1", "def"); - resultMap.SetValue(2, "abc2", "def"); - resultMap.SetValue(4, "abc3", "def"); - - // assert - Assert.Collection( - (IEnumerable)resultMap, - t => - { - Assert.Equal("abc1", t.Name); - Assert.Equal("def", t.Value); - }, - t => - { - Assert.Equal("abc2", t.Name); - Assert.Equal("def", t.Value); - }, - t => - { - Assert.Equal("abc3", t.Name); - Assert.Equal("def", t.Value); - }); - } - - [Fact] - public void EnumerateKeys() - { - // arrange - var resultMap = new ResultMap(); - resultMap.EnsureCapacity(5); - - // act - resultMap.SetValue(0, "abc1", "def"); - resultMap.SetValue(2, "abc2", "def"); - resultMap.SetValue(4, "abc3", "def"); - - // assert - Assert.Collection( - ((IReadOnlyDictionary)resultMap).Keys, - t => Assert.Equal("abc1", t), - t => Assert.Equal("abc2", t), - t => Assert.Equal("abc3", t)); - } - - [Fact] - public void EnumerateValues() - { - // arrange - var resultMap = new ResultMap(); - resultMap.EnsureCapacity(5); - - // act - resultMap.SetValue(0, "abc1", "def"); - resultMap.SetValue(2, "abc2", "def"); - resultMap.SetValue(4, "abc3", "def"); - - // assert - Assert.Collection( - ((IReadOnlyDictionary)resultMap).Values, - t => Assert.Equal("def", t), - t => Assert.Equal("def", t), - t => Assert.Equal("def", t)); - } -} \ No newline at end of file diff --git a/src/HotChocolate/Core/test/Execution.Tests/Processing/__snapshots__/ResultBuilderTests.BuildResult_SimpleResultSet_SnapshotMatches.snap b/src/HotChocolate/Core/test/Execution.Tests/Processing/__snapshots__/ResultBuilderTests.BuildResult_SimpleResultSet_SnapshotMatches.snap new file mode 100644 index 00000000000..769cd361886 --- /dev/null +++ b/src/HotChocolate/Core/test/Execution.Tests/Processing/__snapshots__/ResultBuilderTests.BuildResult_SimpleResultSet_SnapshotMatches.snap @@ -0,0 +1,5 @@ +{ + "data": { + "abc": "def" + } +} diff --git a/src/HotChocolate/Core/test/Types.OffsetPagination.Tests/CustomCollectionSegmentHandlerTests.cs b/src/HotChocolate/Core/test/Types.OffsetPagination.Tests/CustomCollectionSegmentHandlerTests.cs index fb08a227b5d..18beb7db351 100644 --- a/src/HotChocolate/Core/test/Types.OffsetPagination.Tests/CustomCollectionSegmentHandlerTests.cs +++ b/src/HotChocolate/Core/test/Types.OffsetPagination.Tests/CustomCollectionSegmentHandlerTests.cs @@ -25,7 +25,7 @@ public async Task Use_Resolver_Result_If_It_Is_A_Page() // arrange Snapshot.FullName(); - IReadOnlyQueryRequest request = + var request = QueryRequestBuilder.New() .SetQuery("{ items { items } }") .Create(); @@ -52,4 +52,4 @@ public CollectionSegment GetItems(int skip, int take) } } } -} \ No newline at end of file +} diff --git a/src/HotChocolate/Core/test/Types.OffsetPagination.Tests/IntegrationTests.cs b/src/HotChocolate/Core/test/Types.OffsetPagination.Tests/IntegrationTests.cs index 1fc9f114648..9712434638c 100644 --- a/src/HotChocolate/Core/test/Types.OffsetPagination.Tests/IntegrationTests.cs +++ b/src/HotChocolate/Core/test/Types.OffsetPagination.Tests/IntegrationTests.cs @@ -19,7 +19,7 @@ public class IntegrationTests [Fact] public async Task Simple_StringList_Schema() { - IRequestExecutor executor = + var executor = await new ServiceCollection() .AddGraphQL() .AddQueryType() @@ -33,7 +33,7 @@ public async Task Simple_StringList_Schema() [Fact] public async Task Attribute_Simple_StringList_Schema() { - IRequestExecutor executor = + var executor = await new ServiceCollection() .AddGraphQL() .AddQueryType() @@ -49,7 +49,7 @@ public async Task Simple_StringList_Default_Items() { Snapshot.FullName(); - IRequestExecutor executor = + var executor = await new ServiceCollection() .AddGraphQL() .AddQueryType() @@ -76,7 +76,7 @@ public async Task No_Paging_Boundaries() { Snapshot.FullName(); - IRequestExecutor executor = + var executor = await new ServiceCollection() .AddGraphQL() .AddQueryType() @@ -104,7 +104,7 @@ public async Task MaxPageSizeReached() { Snapshot.FullName(); - IRequestExecutor executor = + var executor = await new ServiceCollection() .AddGraphQL() .AddQueryType() @@ -132,7 +132,7 @@ public async Task Attribute_Simple_StringList_Default_Items() { Snapshot.FullName(); - IRequestExecutor executor = + var executor = await new ServiceCollection() .AddGraphQL() .AddQueryType() @@ -159,7 +159,7 @@ public async Task Simple_StringList_Take_2() { Snapshot.FullName(); - IRequestExecutor executor = + var executor = await new ServiceCollection() .AddGraphQL() .AddQueryType() @@ -186,7 +186,7 @@ public async Task Attribute_Simple_StringList_Take_2() { Snapshot.FullName(); - IRequestExecutor executor = + var executor = await new ServiceCollection() .AddGraphQL() .AddQueryType() @@ -213,7 +213,7 @@ public async Task Simple_StringList_Take_2_Skip_2() { Snapshot.FullName(); - IRequestExecutor executor = + var executor = await new ServiceCollection() .AddGraphQL() .AddQueryType() @@ -240,7 +240,7 @@ public async Task Attribute_Simple_StringList_Take_2_Skip_2() { Snapshot.FullName(); - IRequestExecutor executor = + var executor = await new ServiceCollection() .AddGraphQL() .AddQueryType() @@ -267,7 +267,7 @@ public async Task Simple_StringList_Global_DefaultItem_2() { Snapshot.FullName(); - IRequestExecutor executor = + var executor = await new ServiceCollection() .AddGraphQL() .AddQueryType() @@ -295,7 +295,7 @@ public async Task Simple_StringList_Global_DefaultItem_50_Page_Larger_Than_Data_ { Snapshot.FullName(); - IRequestExecutor executor = + var executor = await new ServiceCollection() .AddGraphQL() .AddQueryType() @@ -323,7 +323,7 @@ public async Task Attribute_Simple_StringList_Global_DefaultItem_2() { Snapshot.FullName(); - IRequestExecutor executor = + var executor = await new ServiceCollection() .AddGraphQL() .AddQueryType() @@ -351,7 +351,7 @@ public async Task Schema_Type_Is_Explicitly_Specified() { Snapshot.FullName(); - IRequestExecutor executor = + var executor = await new ServiceCollection() .AddGraphQL() .AddQueryType() @@ -378,7 +378,7 @@ public async Task Attribute_Schema_Type_Is_Explicitly_Specified() { Snapshot.FullName(); - IRequestExecutor executor = + var executor = await new ServiceCollection() .AddGraphQL() .AddQueryType() @@ -405,7 +405,7 @@ public async Task Nested_List_With_Field_Settings() { Snapshot.FullName(); - IRequestExecutor executor = + var executor = await new ServiceCollection() .AddGraphQL() .AddQueryType() @@ -435,7 +435,7 @@ public async Task Executable_With_Field_Settings() { Snapshot.FullName(); - IRequestExecutor executor = + var executor = await new ServiceCollection() .AddGraphQL() .AddQueryType() @@ -465,7 +465,7 @@ public async Task Attribute_Nested_List_With_Field_Settings() { Snapshot.FullName(); - IRequestExecutor executor = + var executor = await new ServiceCollection() .AddGraphQL() .AddQueryType() @@ -495,7 +495,7 @@ public async Task Nested_List_With_Field_Settings_Skip_2() { Snapshot.FullName(); - IRequestExecutor executor = + var executor = await new ServiceCollection() .AddGraphQL() .AddQueryType() @@ -525,7 +525,7 @@ public async Task Attribute_Nested_List_With_Field_Settings_Skip_2() { Snapshot.FullName(); - IRequestExecutor executor = + var executor = await new ServiceCollection() .AddGraphQL() .AddQueryType() @@ -555,7 +555,7 @@ public async Task Interface_With_Paging_Field() { Snapshot.FullName(); - ISchema schema = + var schema = await new ServiceCollection() .AddGraphQL() .AddQueryType() @@ -583,7 +583,7 @@ public async Task Attribute_Interface_With_Paging_Field() { Snapshot.FullName(); - ISchema schema = + var schema = await new ServiceCollection() .AddGraphQL() .AddQueryType() @@ -605,7 +605,7 @@ public async Task FluentPagingTests() { Snapshot.FullName(); - IRequestExecutor executor = + var executor = await new ServiceCollection() .AddGraphQL() .AddQueryType() @@ -629,7 +629,7 @@ public async Task TotalCountWithCustomCollectionSegment() // arrange Snapshot.FullName(); - IRequestExecutor executor = await new ServiceCollection() + var executor = await new ServiceCollection() .AddGraphQL() .AddQueryType() .BuildRequestExecutorAsync(); @@ -643,7 +643,7 @@ public async Task TotalCountWithCustomCollectionSegment() } "; - IExecutionResult result = await executor.ExecuteAsync(query); + var result = await executor.ExecuteAsync(query); // assert result.ToJson().MatchSnapshot(); diff --git a/src/HotChocolate/Core/test/Types.Tests/Extensions/SchemaBuilderExtensions.Resolvers.Tests.cs b/src/HotChocolate/Core/test/Types.Tests/Extensions/SchemaBuilderExtensions.Resolvers.Tests.cs index 50959c17762..4a8fe8b7d1c 100644 --- a/src/HotChocolate/Core/test/Types.Tests/Extensions/SchemaBuilderExtensions.Resolvers.Tests.cs +++ b/src/HotChocolate/Core/test/Types.Tests/Extensions/SchemaBuilderExtensions.Resolvers.Tests.cs @@ -56,12 +56,10 @@ public async Task AddResolverContextObject_ResolveField() builder.AddDocumentFromString("type Query { foo: String }"); // act - SchemaBuilderExtensions - .AddResolver( - builder, - "Query", - "foo", - new Func(c => "bar")); + builder.AddResolver( + "Query", + "foo", + new Func(_ => "bar")); // assert await builder.Create() diff --git a/src/HotChocolate/Core/test/Types.Tests/SchemaFirstTests.cs b/src/HotChocolate/Core/test/Types.Tests/SchemaFirstTests.cs index 2c1dbeca646..823f480f783 100644 --- a/src/HotChocolate/Core/test/Types.Tests/SchemaFirstTests.cs +++ b/src/HotChocolate/Core/test/Types.Tests/SchemaFirstTests.cs @@ -1,17 +1,17 @@ +using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using ChilliCream.Testing; using HotChocolate.Execution; +using HotChocolate.Language; using HotChocolate.Tests; using HotChocolate.Types; +using HotChocolate.Types.Descriptors; +using HotChocolate.Types.Descriptors.Definitions; using Snapshooter.Xunit; using Xunit; using Snapshot = Snapshooter.Xunit.Snapshot; -using System.Collections.Generic; -using System.Linq; -using HotChocolate.Language; -using HotChocolate.Types.Descriptors; -using HotChocolate.Types.Descriptors.Definitions; namespace HotChocolate; diff --git a/src/HotChocolate/Core/test/Types.Tests/Types/Relay/RelaySchemaTests.cs b/src/HotChocolate/Core/test/Types.Tests/Types/Relay/RelaySchemaTests.cs index 47db874be95..5ac8468ca6a 100644 --- a/src/HotChocolate/Core/test/Types.Tests/Types/Relay/RelaySchemaTests.cs +++ b/src/HotChocolate/Core/test/Types.Tests/Types/Relay/RelaySchemaTests.cs @@ -132,12 +132,10 @@ public async Task Relay_ShouldReturnNonNullError_When_IdIsNull() await new ServiceCollection() .AddGraphQL() - .AddQueryType(d => d.Field("user") + .AddQueryType(d => d + .Field("user") .Type() - .Resolve(c => new User - { - Name = "TEST" - })) + .Resolve(_ => new User { Name = "TEST" })) .AddGlobalObjectIdentification() .ExecuteRequestAsync("query { user { id name } } ") .MatchSnapshotAsync();