Repository navigation
Expand file tree
/
Copy pathAggregationResult.cs
More file actions
162 lines (146 loc) · 6.63 KB
/
Copy pathAggregationResult.cs
File metadata and controls
162 lines (146 loc) · 6.63 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
using System.Diagnostics;
using NRedisStack.Search.Aggregation;
using StackExchange.Redis;
namespace NRedisStack.Search;
public class AggregationResult
{
// internal subclass for WITHCURSOR calls, which need to be issued to the same connection
internal sealed class WithCursorAggregationResult : AggregationResult
{
internal WithCursorAggregationResult(string indexName, RedisResult result, long cursorId, IServer? server,
int? database) : base(result, cursorId)
{
IndexName = indexName;
Server = server;
Database = database;
}
public string IndexName { get; }
public IServer? Server { get; }
public int? Database { get; }
}
public long TotalResults { get; }
private readonly Dictionary<string, object>[] _results;
private Dictionary<string, RedisValue>[]? _resultsAsRedisValues;
public long CursorId { get; }
/// <summary>
/// Warnings reported by the server for this query (for example when a query times out under the
/// <c>return</c>/<c>return-strict</c> on-timeout policy and partial results are returned).
/// Only populated on RESP3; on RESP2 FT.AGGREGATE does not carry warnings and this array is empty.
/// </summary>
public string[] Warnings { get; } = [];
internal AggregationResult(RedisResult result, long cursorId = -1)
{
var arr = (RedisResult[])result!;
Dictionary<string, object>[]? results = null;
if (result.Resp3Type is ResultType.Map)
{
results = ResponseParser.ParseSearchResultsMap(result, ParseRecordFromMap, out long totalResults);
// TotalResults = totalResults; // we ignore this in RESP3 mode for consistent API behaviour
Warnings = ResponseParser.ParseWarnings(result);
}
else
{
// this statement below is not true as explained in the document https://redis.io/docs/latest/commands/ft.aggregate/#return
// // the first element is always the number of results
// TotalResults = (long)arr[0];
results = new Dictionary<string, object>[arr.Length - 1];
for (int i = 1; i < arr.Length; i++)
{
var raw = (RedisResult[])arr[i]!;
var cur = new Dictionary<string, object>();
for (int j = 0; j < raw.Length;)
{
var key = (string)raw[j++]!;
var val = raw[j++];
cur.Add(key, ParseFieldValue(val));
}
results[i - 1] = cur;
}
}
TotalResults = results.Length;
CursorId = cursorId;
_results = results ?? []; // if we didn't get results, make an empty array
static object ParseFieldValue(RedisResult val) => val.Resp2Type switch
{
ResultType.Array => ConvertMultiBulkToObject((RedisResult[])val!),
_ => (RedisValue)val
};
static Dictionary<string, object> ParseRecordFromMap(string[] attributes, RedisResult[] map)
{
var record = new Dictionary<string, object>();
for (int i = 0; i + 1 < map.Length; i += 2)
{
var key = (string)map[i]!;
var val = map[i + 1];
switch (key)
{
case "values" when val.Resp3Type is ResultType.Array:
var values = (RedisResult[])val!;
for (int j = 0; j < values.Length && j < attributes.Length; j++)
{
record.Add(attributes[j], ParseFieldValue(values[j]));
}
break;
case "extra_attributes" when val.Resp3Type is ResultType.Map:
var extraAttributes = (RedisResult[])val!;
for (int j = 0; j + 1 < extraAttributes.Length; j += 2)
{
record.Add((string)extraAttributes[j]!, ParseFieldValue(extraAttributes[j + 1]));
}
break;
}
}
return record;
}
}
/// <summary>
/// takes a Redis multi-bulk array represented by a RedisResult[] and recursively processes its elements.
/// For each element in the array, it checks if it's another multi-bulk array, and if so, it recursively calls itself.
/// If the element is not a multi-bulk array, it's added directly to a List<object>.
/// The method returns a nested list structure, reflecting the hierarchy of the original multi-bulk array,
/// with each element either being a direct value or a nested list.
/// </summary>
/// <param name="multiBulkArray"></param>
/// <returns>object</returns>
private static object ConvertMultiBulkToObject(IEnumerable<RedisResult> multiBulkArray)
{
return multiBulkArray.Select(item => item.Resp2Type == ResultType.Array
? ConvertMultiBulkToObject((RedisResult[])item!)
: (RedisValue)item)
.ToList();
}
/// <summary>
/// Gets the results as a read-only list of dictionaries with string keys and RedisValue values.
/// </summary>
/// <remarks>
/// This method is deprecated and will be removed in future versions.
/// Please use <see cref="GetRow"/> instead.
/// </remarks>
[Obsolete("This method is deprecated and will be removed in future versions. Please use 'GetRow' instead.")]
public IReadOnlyList<Dictionary<string, RedisValue>> GetResults()
{
return GetResultsAsRedisValues();
}
/// <summary>
/// Gets the aggregation result at the specified index.
/// </summary>
/// <param name="index">The zero-based index of the aggregation result to retrieve.</param>
/// <returns>
/// A dictionary containing the aggregation result as Redis values if the index is within bounds;
/// otherwise, <c>null</c>.
/// </returns>
[Obsolete("This method is deprecated and will be removed in future versions. Please use 'GetRow' instead.")]
public Dictionary<string, RedisValue>? this[int index]
=> index >= GetResultsAsRedisValues().Length ? null : GetResultsAsRedisValues()[index];
public Row GetRow(int index)
{
return index >= _results.Length ? default : new Row(_results[index]);
}
private Dictionary<string, RedisValue>[] GetResultsAsRedisValues()
{
return _resultsAsRedisValues ??= _results.Select(dict => dict.ToDictionary(
kvp => kvp.Key,
kvp => kvp.Value is RedisValue value ? value : RedisValue.Null
)).ToArray();
}
}