Skip to content

Commit

Permalink
Added @defer to the GraphQL core. (#2359)
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelstaib committed Sep 24, 2020
1 parent 0b42aef commit 91d6801
Show file tree
Hide file tree
Showing 604 changed files with 2,661 additions and 556 deletions.
@@ -1,11 +1,8 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.ObjectPool;
using BenchmarkDotNet.Attributes;
using HotChocolate.Execution.Utilities;
using HotChocolate.Language;
using HotChocolate.StarWars;

Expand Down
@@ -1,5 +1,5 @@
using BenchmarkDotNet.Attributes;
using HotChocolate.Execution.Utilities;
using HotChocolate.Execution.Processing;
using HotChocolate.Language;
using HotChocolate.StarWars;

Expand Down
@@ -1,6 +1,7 @@
using BenchmarkDotNet.Attributes;
using System.Collections.Generic;
using System.Buffers;
using System.Collections.Generic;
using BenchmarkDotNet.Attributes;
using HotChocolate.Execution.Processing;

namespace HotChocolate.Execution.Benchmarks
{
Expand Down
@@ -1,9 +1,9 @@
using System;
using BenchmarkDotNet.Attributes;
using HotChocolate.Execution.Utilities;
using HotChocolate.Language;
using HotChocolate.StarWars;
using System.Collections.Generic;
using HotChocolate.Execution.Processing;

namespace HotChocolate.Execution.Benchmarks
{
Expand Down
145 changes: 145 additions & 0 deletions src/HotChocolate/Core/src/Abstractions/Execution/DeferredResult.cs
@@ -0,0 +1,145 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using HotChocolate.Properties;

#nullable enable

namespace HotChocolate.Execution.Processing
{
public sealed class DeferredResult
: IExecutionResult
, IResponseStream
{
private readonly IQueryResult _initialResult;
private readonly IAsyncEnumerable<IQueryResult> _deferredResults;
private readonly IDisposable? _session;
private bool _isRead;
private bool _disposed;

public DeferredResult(
IQueryResult initialResult,
IAsyncEnumerable<IQueryResult> deferredResults,
IReadOnlyDictionary<string, object?>? extensions = null,
IReadOnlyDictionary<string, object?>? contextData = null,
IDisposable? session = null)
{
_initialResult = initialResult ??
throw new ArgumentNullException(nameof(initialResult));
_deferredResults = deferredResults ??
throw new ArgumentNullException(nameof(deferredResults));
Extensions = extensions;
ContextData = contextData;
_session = session;
}

public DeferredResult(DeferredResult result, IDisposable session)
{
if (result is null)
{
throw new ArgumentNullException(nameof(result));
}

if (session is null)
{
throw new ArgumentNullException(nameof(session));
}

_initialResult = result._initialResult;
_deferredResults = result._deferredResults;
Extensions = result.Extensions;
ContextData = result.ContextData;
_session = result._session is not null
? new CombinedDispose(result._session, session)
: session;
}

public IReadOnlyList<IError>? Errors => null;

public IReadOnlyDictionary<string, object?>? Extensions { get; }

public IReadOnlyDictionary<string, object?>? ContextData { get; }

public IAsyncEnumerable<IQueryResult> ReadResultsAsync()
{
if (_isRead)
{
throw new InvalidOperationException(
AbstractionResources.SubscriptionResult_ReadOnlyOnce);
}

if (_disposed)
{
throw new ObjectDisposedException(nameof(SubscriptionResult));
}

_isRead = true;
return new EnumerateResults(_initialResult, _deferredResults, this);
}

public ValueTask DisposeAsync()
{
if (!_disposed)
{
_session?.Dispose();
_disposed = true;
}
return default;
}

private class EnumerateResults : IAsyncEnumerable<IQueryResult>
{
private readonly IQueryResult _initialResult;
private readonly IAsyncEnumerable<IQueryResult> _deferredResults;
private readonly IAsyncDisposable _session;

public EnumerateResults(
IQueryResult initialResult,
IAsyncEnumerable<IQueryResult> deferredResults,
IAsyncDisposable session)
{
_initialResult = initialResult;
_deferredResults = deferredResults;
_session = session;
}

public async IAsyncEnumerator<IQueryResult> GetAsyncEnumerator(
CancellationToken cancellationToken = default)
{
yield return _initialResult;

await foreach (IQueryResult deferredResult in
_deferredResults.WithCancellation(cancellationToken))
{
yield return deferredResult;
}

await _session.DisposeAsync();
}
}

internal class CombinedDispose : IDisposable
{
private readonly IDisposable _a;
private readonly IDisposable _b;
private bool _disposed;

public CombinedDispose(IDisposable a, IDisposable b)
{
_a = a;
_b = b;
}

public void Dispose()
{
if (!_disposed)
{
_a.Dispose();
_b.Dispose();
_disposed = true;
}
}
}
}
}
30 changes: 30 additions & 0 deletions src/HotChocolate/Core/src/Abstractions/Execution/IQueryResult.cs
Expand Up @@ -5,12 +5,42 @@

namespace HotChocolate.Execution
{
/// <summary>
/// Represents a query result object.
/// </summary>
public interface IQueryResult
: IExecutionResult
, IDisposable
{
/// <summary>
/// A string that was passed to the label argument of the @defer or @stream
/// directive that corresponds to this results.
/// </summary>
/// <value></value>
string? Label { get; }

/// <summary>
/// A path to the insertion point that informs the client how to patch a
/// subsequent delta payload into the original payload.
/// </summary>
/// <value></value>
Path? Path { get; }

/// <summary>
/// The data that is being delivered.
/// </summary>
/// <value></value>
IReadOnlyDictionary<string, object?>? Data { get; }

/// <summary>
/// A boolean that is present and <c>true</c> when there are more payloads
/// that will be sent for this operation. The last payload in a multi payload response
/// should return HasNext: <c>false</c>.
/// HasNext is null for single-payload responses to preserve backwards compatibility.
/// </summary>
/// <value></value>
bool? HasNext { get; }

IReadOnlyDictionary<string, object?> ToDictionary();
}
}
Expand Up @@ -27,6 +27,12 @@ public interface IQueryResultBuilder

IQueryResultBuilder ClearContextData();

IQueryResultBuilder SetLabel(string? label);

IQueryResultBuilder SetPath(Path? path);

IQueryResultBuilder SetHasNext(bool? hasNext);

IQueryResult Create();
}
}
29 changes: 28 additions & 1 deletion src/HotChocolate/Core/src/Abstractions/Execution/QueryResult.cs
Expand Up @@ -5,17 +5,26 @@

namespace HotChocolate.Execution
{
public sealed class QueryResult
/// <summary>
/// Represents a query result object.
/// </summary>
public sealed class QueryResult
: IQueryResult
, IReadOnlyQueryResult
{
private readonly IDisposable? _disposable;

/// <summary>
/// Initializes a new <see cref="QueryResult"/>.
/// </summary>
public QueryResult(
IReadOnlyDictionary<string, object?>? data,
IReadOnlyList<IError>? errors,
IReadOnlyDictionary<string, object?>? extension = null,
IReadOnlyDictionary<string, object?>? contextData = null,
string? label = null,
Path? path = null,
bool? hasNext = null,
IDisposable? resultMemoryOwner = null)
{
if (data is null && errors is null)
Expand All @@ -29,22 +38,40 @@ public sealed class QueryResult
Errors = errors;
Extensions = extension;
ContextData = contextData;
Label = label;
Path = path;
HasNext = hasNext;
_disposable = resultMemoryOwner;
}

/// <inheritdoc />
public string? Label { get; }

/// <inheritdoc />
public Path? Path { get; }

/// <inheritdoc />
public IReadOnlyDictionary<string, object?>? Data { get; }

/// <inheritdoc />
public IReadOnlyList<IError>? Errors { get; }

/// <inheritdoc />
public IReadOnlyDictionary<string, object?>? Extensions { get; }

/// <inheritdoc />
public IReadOnlyDictionary<string, object?>? ContextData { get; }

/// <inheritdoc />
public bool? HasNext { get; }

/// <inheritdoc />
public IReadOnlyDictionary<string, object?> ToDictionary()
{
return QueryResultHelper.ToDictionary(this);
}

/// <inheritdoc />
public void Dispose()
{
if (Data is IDisposable disposable)
Expand Down
Expand Up @@ -11,6 +11,9 @@ public class QueryResultBuilder : IQueryResultBuilder
private List<IError>? _errors;
private ExtensionData? _extensionData;
private ExtensionData? _contextData;
private string? _label;
private Path? _path;
private bool? _hasNext;
private IDisposable? _disposable;

public IQueryResultBuilder SetData(
Expand Down Expand Up @@ -132,13 +135,34 @@ public IQueryResultBuilder ClearContextData()
return this;
}

public IQueryResultBuilder SetLabel(string? label)
{
_label = label;
return this;
}

public IQueryResultBuilder SetPath(Path? path)
{
_path = path;
return this;
}

public IQueryResultBuilder SetHasNext(bool? hasNext)
{
_hasNext = hasNext;
return this;
}

public IQueryResult Create()
{
return new QueryResult(
_data,
_errors is { } && _errors.Count > 0 ? _errors : null,
_extensionData is { } && _extensionData.Count > 0 ? _extensionData : null,
_contextData is { } && _contextData.Count > 0 ? _contextData : null,
_label,
_path,
_hasNext,
_disposable);
}

Expand Down

0 comments on commit 91d6801

Please sign in to comment.