Skip to content

Commit

Permalink
Added Helper to convert a ISelection to an Expression. (#7531)
Browse files Browse the repository at this point in the history
(cherry picked from commit 7270474)
  • Loading branch information
michaelstaib committed Sep 29, 2024
1 parent 0c9789a commit 9a1f448
Show file tree
Hide file tree
Showing 2 changed files with 79 additions and 55 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
using System.Buffers.Text;
using System.Linq.Expressions;
using System.Text;
using System.Runtime.CompilerServices;
using HotChocolate.Execution.Projections;

// ReSharper disable once CheckNamespace
namespace HotChocolate.Execution.Processing;

/// <summary>
/// Provides extension methods to work with selections.
/// </summary>
public static class HotChocolateExecutionSelectionExtensions
{
private static readonly SelectionExpressionBuilder _builder = new();

/// <summary>
/// Creates a selector expression from a GraphQL selection.
/// </summary>
/// <param name="selection">
/// The selection that shall be converted into a selector expression.
/// </param>
/// <typeparam name="TValue">
/// The type of the value that is returned by the <see cref="ISelection"/>.
/// </typeparam>
/// <returns>
/// Returns a selector expression that can be used for data projections.
/// </returns>
public static Expression<Func<TValue, TValue>> ToSelectorExpression<TValue>(
this ISelection selection)
=> GetOrCreateExpression<TValue>(selection);

private static Expression<Func<TValue, TValue>> GetOrCreateExpression<TValue>(
ISelection selection)
{
return selection.DeclaringOperation.GetOrAddState(
CreateExpressionKey(selection.Id),
static (_, ctx) => ctx._builder.BuildExpression<TValue>(ctx.selection),
(_builder, selection));
}

private static string CreateExpressionKey(int key)
{
var keyPrefix = GetKeyPrefix();
var requiredBufferSize = EstimateIntLength(key) + keyPrefix.Length;
Span<byte> span = stackalloc byte[requiredBufferSize];
keyPrefix.CopyTo(span);
Utf8Formatter.TryFormat(key, span.Slice(keyPrefix.Length), out var written, 'D');
return Encoding.UTF8.GetString(span.Slice(0, written + keyPrefix.Length));
}

private static ReadOnlySpan<byte> GetKeyPrefix()
=> "hc-dataloader-expr-"u8;

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int EstimateIntLength(int value)
{
if (value == 0)
{
// to print 0 we need still 1 digit
return 1;
}

// if the number is negative we need one more digit for the sign
var length = (value < 0) ? 1 : 0;

// we add the number of digits the number has to the length of the number.
length += (int)Math.Floor(Math.Log10(Math.Abs(value)) + 1);

return length;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,9 @@
#nullable enable

using System.Buffers;
using System.Buffers.Text;
using System.Diagnostics.CodeAnalysis;
using System.Linq.Expressions;
using System.Runtime.CompilerServices;
using System.Text;
using HotChocolate.Execution;
using HotChocolate.Execution.Processing;
using HotChocolate.Execution.Projections;
using HotChocolate.Pagination;
using HotChocolate.Types;
using HotChocolate.Types.Descriptors.Definitions;
Expand All @@ -26,8 +21,6 @@ namespace GreenDonut.Projections;
#endif
public static class HotChocolateExecutionDataLoaderExtensions
{
private static readonly SelectionExpressionBuilder _builder = new();

/// <summary>
/// Selects the fields that where selected in the GraphQL selection tree.
/// </summary>
Expand All @@ -51,7 +44,7 @@ public static ISelectionDataLoader<TKey, TValue> Select<TKey, TValue>(
ISelection selection)
where TKey : notnull
{
var expression = GetOrCreateExpression<TKey, TValue>(selection);
var expression = selection.ToSelectorExpression<TValue>();
return dataLoader.Select(expression);
}

Expand Down Expand Up @@ -86,8 +79,8 @@ public static IPagingDataLoader<TKey, Page<TValue>> Select<TKey, TValue>(
var count = GetConnectionSelections(selection, buffer);
for (var i = 0; i < count; i++)
{
var expression = GetOrCreateExpression<TKey, TValue>(buffer[i]);
HotChocolatePaginationBatchingDataLoaderExtensions.Select(dataLoader, expression);
var expression = buffer[i].ToSelectorExpression<TValue>();
dataLoader.Select(expression);
}
ArrayPool<ISelection>.Shared.Return(buffer);
}
Expand All @@ -97,15 +90,15 @@ public static IPagingDataLoader<TKey, Page<TValue>> Select<TKey, TValue>(
var count = GetCollectionSelections(selection, buffer);
for (var i = 0; i < count; i++)
{
var expression = GetOrCreateExpression<TKey, TValue>(buffer[i]);
HotChocolatePaginationBatchingDataLoaderExtensions.Select(dataLoader, expression);
var expression = buffer[i].ToSelectorExpression<TValue>();
dataLoader.Select(expression);
}
ArrayPool<ISelection>.Shared.Return(buffer);
}
else
{
var expression = GetOrCreateExpression<TKey, TValue>(selection);
HotChocolatePaginationBatchingDataLoaderExtensions.Select(dataLoader, expression);
var expression = selection.ToSelectorExpression<TValue>();
dataLoader.Select(expression);
}

return dataLoader;
Expand Down Expand Up @@ -172,46 +165,5 @@ private static int GetCollectionSelections(ISelection selection, Span<ISelection

return count;
}

private static Expression<Func<TValue, TValue>> GetOrCreateExpression<TKey, TValue>(
ISelection selection)
where TKey : notnull
{
return selection.DeclaringOperation.GetOrAddState(
CreateExpressionKey(selection.Id),
static (_, ctx) => ctx._builder.BuildExpression<TValue>(ctx.selection),
(_builder, selection));
}

private static string CreateExpressionKey(int key)
{
var keyPrefix = GetKeyPrefix();
var requiredBufferSize = EstimateIntLength(key) + keyPrefix.Length;
Span<byte> span = stackalloc byte[requiredBufferSize];
keyPrefix.CopyTo(span);
Utf8Formatter.TryFormat(key, span.Slice(keyPrefix.Length), out var written, 'D');
return Encoding.UTF8.GetString(span.Slice(0, written + keyPrefix.Length));
}

private static ReadOnlySpan<byte> GetKeyPrefix()
=> "hc-dataloader-expr-"u8;

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int EstimateIntLength(int value)
{
if (value == 0)
{
// to print 0 we need still 1 digit
return 1;
}

// if the number is negative we need one more digit for the sign
var length = (value < 0) ? 1 : 0;

// we add the number of digits the number has to the length of the number.
length += (int)Math.Floor(Math.Log10(Math.Abs(value)) + 1);

return length;
}
}
#endif

0 comments on commit 9a1f448

Please sign in to comment.