Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,14 @@ public static async ValueTask<Page<T>> ToPageAsync<T>(
}

var pageIndex = CreateIndex(arguments, cursor, totalCount);
return CreatePage(builder.ToImmutable(), arguments, keys, fetchCount, pageIndex, requestedCount, totalCount);
return CreateValueCursorPage(
Page<T>.ToEntries(builder.ToImmutable()),
arguments,
keys,
fetchCount,
pageIndex,
requestedCount,
totalCount);
}

/// <summary>
Expand Down Expand Up @@ -296,11 +303,12 @@ public static ValueTask<Dictionary<TKey, Page<TValue>>> ToBatchPageAsync<TKey, T
PagingArguments arguments,
CancellationToken cancellationToken = default)
where TKey : notnull
=> ToBatchPageAsync(
=> ToBatchPageAsync<TKey, TValue, TValue>(
source,
keySelector,
t => t,
null,
arguments,
includeTotalCount: arguments.IncludeTotalCount,
cancellationToken);

/// <summary>
Expand Down Expand Up @@ -338,10 +346,10 @@ public static ValueTask<Dictionary<TKey, Page<TValue>>> ToBatchPageAsync<TKey, T
bool includeTotalCount,
CancellationToken cancellationToken = default)
where TKey : notnull
=> ToBatchPageAsync(
=> ToBatchPageAsync<TKey, TValue, TValue>(
source,
keySelector,
t => t,
null,
arguments,
includeTotalCount: includeTotalCount,
cancellationToken);
Expand All @@ -368,10 +376,10 @@ public static ValueTask<Dictionary<TKey, Page<TValue>>> ToBatchPageAsync<TKey, T
/// The type of the parent key.
/// </typeparam>
/// <typeparam name="TValue">
/// The type of the items in the queryable.
/// The type of the value selected from the items in the queryable.
/// </typeparam>
/// <typeparam name="TElement">
/// The type of the items in the queryable.
/// The type of the source elements from which keys and values are projected.
/// </typeparam>
/// <returns></returns>
/// <exception cref="ArgumentException">
Expand Down Expand Up @@ -420,7 +428,7 @@ public static ValueTask<Dictionary<TKey, Page<TValue>>> ToBatchPageAsync<TKey, T
/// The type of the items in the queryable.
/// </typeparam>
/// <typeparam name="TElement">
/// The type of the items in the queryable.
/// The type of the source elements from which keys and values are projected.
/// </typeparam>
/// <returns></returns>
/// <exception cref="ArgumentException">
Expand All @@ -429,7 +437,7 @@ public static ValueTask<Dictionary<TKey, Page<TValue>>> ToBatchPageAsync<TKey, T
public static async ValueTask<Dictionary<TKey, Page<TValue>>> ToBatchPageAsync<TKey, TValue, TElement>(
this IQueryable<TElement> source,
Expression<Func<TElement, TKey>> keySelector,
Func<TElement, TValue> valueSelector,
Func<TElement, TValue>? valueSelector,
PagingArguments arguments,
bool includeTotalCount,
CancellationToken cancellationToken = default)
Comment thread
tobias-tengler marked this conversation as resolved.
Expand Down Expand Up @@ -464,6 +472,13 @@ public static async ValueTask<Dictionary<TKey, Page<TValue>>> ToBatchPageAsync<T
nameof(arguments));
}

if (valueSelector is null && !typeof(TValue).IsAssignableFrom(typeof(TElement)))
{
throw new ArgumentException(
"If no value selector is provided, the source element type must be assignable to the value type.",
nameof(valueSelector));
}

if (arguments.EnableRelativeCursors
&& string.IsNullOrEmpty(arguments.After)
&& string.IsNullOrEmpty(arguments.Before))
Expand Down Expand Up @@ -516,34 +531,73 @@ public static async ValueTask<Dictionary<TKey, Page<TValue>>> ToBatchPageAsync<T
}

var itemCount = requestedCount > item.Items.Count ? item.Items.Count : requestedCount;
var builder = ImmutableArray.CreateBuilder<TValue>(itemCount);
var totalCount = counts?.GetValueOrDefault(item.Key) ?? batchExpression.Cursor?.TotalCount;
var pageIndex = CreateIndex(arguments, batchExpression.Cursor, totalCount);

if (batchExpression.IsBackward)
if (valueSelector is not null)
{
for (var i = itemCount - 1; i >= 0; i--)
var entryBuilder = ImmutableArray.CreateBuilder<PageEntry<TValue>>(itemCount);
var elementBuilder = ImmutableArray.CreateBuilder<TElement>(itemCount);

if (batchExpression.IsBackward)
{
builder.Add(valueSelector(item.Items[i]));
for (var i = itemCount - 1; i >= 0; i--)
{
var element = item.Items[i];
entryBuilder.Add(new PageEntry<TValue>(valueSelector(element), entryBuilder.Count));
elementBuilder.Add(element);
}
}
else
{
for (var i = 0; i < itemCount; i++)
{
var element = item.Items[i];
entryBuilder.Add(new PageEntry<TValue>(valueSelector(element), entryBuilder.Count));
elementBuilder.Add(element);
}
}

var page = CreateElementCursorPage(
entryBuilder.ToImmutable(),
elementBuilder.ToImmutable(),
arguments,
keys,
item.Items.Count,
pageIndex,
requestedCount,
totalCount);
map.Add(item.Key, page);
}
else
{
for (var i = 0; i < itemCount; i++)
var entryBuilder = ImmutableArray.CreateBuilder<PageEntry<TValue>>(itemCount);

if (batchExpression.IsBackward)
{
builder.Add(valueSelector(item.Items[i]));
for (var i = itemCount - 1; i >= 0; i--)
{
entryBuilder.Add(new PageEntry<TValue>((TValue)(object)item.Items[i]!, entryBuilder.Count));
}
}
else
{
for (var i = 0; i < itemCount; i++)
{
entryBuilder.Add(new PageEntry<TValue>((TValue)(object)item.Items[i]!, entryBuilder.Count));
}
}
}

var totalCount = counts?.GetValueOrDefault(item.Key) ?? batchExpression.Cursor?.TotalCount;
var pageIndex = CreateIndex(arguments, batchExpression.Cursor, totalCount);
var page = CreatePage(
builder.ToImmutable(),
arguments,
keys,
item.Items.Count,
pageIndex,
requestedCount,
totalCount);
map.Add(item.Key, page);
var page = CreateValueCursorPage(
entryBuilder.ToImmutable(),
arguments,
keys,
item.Items.Count,
pageIndex,
requestedCount,
totalCount);
map.Add(item.Key, page);
}
}

return map;
Expand Down Expand Up @@ -604,14 +658,74 @@ private class CountResult<TKey>
public int Count { get; set; }
}

private static Page<T> CreatePage<T>(
ImmutableArray<T> items,
private static Page<T> CreateValueCursorPage<T>(
ImmutableArray<PageEntry<T>> entries,
PagingArguments arguments,
CursorKey[] keys,
int fetchCount,
int? index,
int? requestedPageSize,
int? totalCount)
{
var (hasNext, hasPrevious) = CreatePageFlags(arguments, fetchCount);

if (arguments.EnableRelativeCursors && totalCount is not null && requestedPageSize is not null)
{
return new ValueCursorPage<T>(
entries,
hasNext,
hasPrevious,
entry => CursorFormatter.Format(entry.Node, keys, new CursorPageInfo(entry.Offset, entry.PageIndex, entry.TotalCount)),
index ?? 1,
requestedPageSize.Value,
totalCount.Value);
}

return new ValueCursorPage<T>(
entries,
hasNext,
hasPrevious,
item => CursorFormatter.Format(item, keys),
totalCount);
}

private static Page<TValue> CreateElementCursorPage<TElement, TValue>(
ImmutableArray<PageEntry<TValue>> entries,
ImmutableArray<TElement> elements,
PagingArguments arguments,
CursorKey[] keys,
int fetchCount,
int? index,
int? requestedPageSize,
int? totalCount)
{
var (hasNext, hasPrevious) = CreatePageFlags(arguments, fetchCount);

if (arguments.EnableRelativeCursors && totalCount is not null && requestedPageSize is not null)
{
return new ElementCursorPage<TElement, TValue>(
entries,
elements,
hasNext,
hasPrevious,
entry => CursorFormatter.Format(entry.Node, keys, new CursorPageInfo(entry.Offset, entry.PageIndex, entry.TotalCount)),
index ?? 1,
requestedPageSize.Value,
totalCount.Value);
}

return new ElementCursorPage<TElement, TValue>(
entries,
elements,
hasNext,
hasPrevious,
item => CursorFormatter.Format(item, keys),
totalCount);
}

private static (bool HasNext, bool HasPrevious) CreatePageFlags(
PagingArguments arguments,
int fetchCount)
{
var hasPrevious = false;
var hasNext = false;
Expand Down Expand Up @@ -644,24 +758,7 @@ private static Page<T> CreatePage<T>(
hasNext = true;
}

if (arguments.EnableRelativeCursors && totalCount is not null && requestedPageSize is not null)
{
return new Page<T>(
items,
hasNext,
hasPrevious,
(item, o, p, c) => CursorFormatter.Format(item, keys, new CursorPageInfo(o, p, c)),
index ?? 1,
requestedPageSize.Value,
totalCount.Value);
}

return new Page<T>(
items,
hasNext,
hasPrevious,
item => CursorFormatter.Format(item, keys),
totalCount);
return (hasNext, hasPrevious);
}

private static int? CreateIndex(PagingArguments arguments, Cursor? cursor, int? totalCount)
Expand Down
53 changes: 53 additions & 0 deletions src/GreenDonut/src/GreenDonut.Data.Primitives/EdgeEntry.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
namespace GreenDonut.Data;

/// <summary>
/// Bundles a node with the positional metadata needed for cursor generation.
/// </summary>
/// <typeparam name="T">
/// The type of the node.
/// </typeparam>
public readonly struct EdgeEntry<T>
{
/// <summary>
/// Initializes a new instance of the <see cref="EdgeEntry{T}"/> struct.
/// </summary>
/// <param name="node">
/// The node (item from the page).
/// </param>
/// <param name="offset">
/// The offset relative to the current cursor position.
/// </param>
/// <param name="pageIndex">
/// The page index.
/// </param>
/// <param name="totalCount">
/// The total count of items in the dataset.
/// </param>
public EdgeEntry(T node, int offset, int pageIndex, int totalCount)
{
Node = node;
Offset = offset;
PageIndex = pageIndex;
TotalCount = totalCount;
}

/// <summary>
/// Gets the node (the item from the page).
/// </summary>
public T Node { get; }

/// <summary>
/// Gets the offset relative to the current cursor position.
/// </summary>
public int Offset { get; }

/// <summary>
/// Gets the page index.
/// </summary>
public int PageIndex { get; }

/// <summary>
/// Gets the total count of items in the dataset.
/// </summary>
public int TotalCount { get; }
}
Loading
Loading