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
6 changes: 6 additions & 0 deletions src/NRedisStack/PublicAPI/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
NRedisStack.Search.Aggregation.Row.Enumerator
NRedisStack.Search.Aggregation.Row.Enumerator.Current.get -> System.Collections.Generic.KeyValuePair<string!, StackExchange.Redis.RedisValue>
NRedisStack.Search.Aggregation.Row.Enumerator.Enumerator() -> void
NRedisStack.Search.Aggregation.Row.Enumerator.MoveNext() -> bool
NRedisStack.Search.Aggregation.Row.FieldCount() -> int
NRedisStack.Search.Aggregation.Row.GetEnumerator() -> NRedisStack.Search.Aggregation.Row.Enumerator
NRedisStack.Search.Parameters
static NRedisStack.Search.Parameters.From<T>(T obj) -> System.Collections.Generic.IReadOnlyDictionary<string!, object!>!
[NRS001]abstract NRedisStack.Search.VectorData.AsRedisValue() -> StackExchange.Redis.RedisValue
Expand Down
67 changes: 65 additions & 2 deletions src/NRedisStack/Search/Row.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
using StackExchange.Redis;
using System.Collections;
using StackExchange.Redis;

namespace NRedisStack.Search.Aggregation;

public readonly struct Row
public readonly struct Row : IEnumerable<KeyValuePair<string, RedisValue>>
{
private readonly Dictionary<string, object> _fields;

internal Row(Dictionary<string, object> fields)
{
// note: only RedisValue fields are expected and exposed, due to how AggregationResult is constructed
_fields = fields;
}

Expand All @@ -18,4 +20,65 @@ internal Row(Dictionary<string, object> fields)
public string? GetString(string key) => _fields.TryGetValue(key, out var result) ? result.ToString() : default;
public long GetLong(string key) => _fields.TryGetValue(key, out var result) ? (long)(RedisValue)result : default;
public double GetDouble(string key) => _fields.TryGetValue(key, out var result) ? (double)(RedisValue)result : default;

/// <summary>
/// Gets the number of fields in this row.
/// </summary>
public int FieldCount()
{
// only include RedisValue fields, since nested aggregates are not supported via this API
var count = 0;
foreach (var field in _fields)
{
if (field.Value is RedisValue)
{
count++;
}
}
return count;
}

/// <summary>
/// Access the fields as a sequence of key/value pairs.
/// </summary>
public Enumerator GetEnumerator() => new(in this);

public struct Enumerator : IEnumerator<KeyValuePair<string, RedisValue>>
{
private Dictionary<string, object>.Enumerator _enumerator;
internal Enumerator(in Row row) => _enumerator = row._fields?.GetEnumerator() ?? default;

/// <inheritdoc/>
public bool MoveNext()
{
while (_enumerator.MoveNext())
{
if (_enumerator.Current.Value is RedisValue)
{
return true;
}
}

return false;
}

/// <inheritdoc/>
public KeyValuePair<string, RedisValue> Current
{
get
{
var pair = _enumerator.Current;
return pair.Value is RedisValue value ? new(pair.Key, value) : default;
}
}

void IEnumerator.Reset() => throw new NotSupportedException();

object IEnumerator.Current => Current;

void IDisposable.Dispose() => _enumerator.Dispose();
}

IEnumerator<KeyValuePair<string, RedisValue>> IEnumerable<KeyValuePair<string, RedisValue>>.GetEnumerator() => GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
57 changes: 57 additions & 0 deletions tests/NRedisStack.Tests/Search/AggregationResultUnitTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
using NRedisStack.Search.Aggregation;
using StackExchange.Redis;
using Xunit;

namespace NRedisStack.Tests.Search;

public class AggregationResultUnitTests
{
[Fact]
public void CanIterateRow()
{
Row row = new(new()
{
// the fields must all be RedisValue internally, else ignored
{ "name", (RedisValue)"John" },
{ "age", (RedisValue)30 },
{ "ignored", new RedisValue[] { "a", "b", "c" } },
{ "city", (RedisValue)"New York" },
});

// test via Row
Assert.Equal(3, row.FieldCount());
Assert.Equal("John", row.GetString("name"));
Assert.Equal(30, row.GetLong("age"));
Assert.Equal("New York", row.GetString("city"));

// test via foreach
int count = 0;
foreach (var field in row)
{
switch (field.Key)
{
case "name":
Assert.Equal("John", (string?)field.Value);
break;
case "age":
Assert.Equal(30, (int)field.Value);
break;
case "city":
Assert.Equal("New York", (string?)field.Value);
break;
default:
Assert.Fail($"Unexpected field: {field.Key}");
break;
}
count++;
}
Assert.Equal(3, count);

// test via Select
var isolated = row.Select(x => (x.Key, x.Value)).ToArray();
Assert.Equal(3, isolated.Length);
Assert.Contains(("name", (RedisValue)"John"), isolated);
Assert.Contains(("age", (RedisValue)30), isolated);
Assert.Contains(("city", (RedisValue)"New York"), isolated);
}
}
Loading