Skip to content

Commit 4daf881

Browse files
groupby_reduce (#48)
Support GROUPBY <label> REDUCE <reducer> #42
1 parent 9b17c1f commit 4daf881

File tree

12 files changed

+357
-21
lines changed

12 files changed

+357
-21
lines changed

NRedisTimeSeries.Example/MRangeExample.cs

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ public static void BasicMRangeExample()
3737
/// Example for basic usage of RedisTimeSeries RANGE command with "-" and "+" as range boundreis, a filter and the COUNT parameter.
3838
/// NRedisTimeSeris MRange is expecting two TimeStamps objects as the range boundries.
3939
/// In this case, the strings are implicitly casted into TimeStamp objects.
40-
/// The TimeSeriesMRange command returns an IReadOnlyList<(string key, IReadOnlyList<TimeSeriesLabel> labels, IReadOnlyList<TimeSeriesTuple> values)>collection.
40+
/// The TimeSeriesMRange command returns an IReadOnlyList (collection) of (string key, IReadOnlyList(TimeSeriesLabel) labels, IReadOnlyList(TimeSeriesTuple) values).
4141
/// </summary>
4242
public static void CountMRangeExample()
4343
{
@@ -58,7 +58,7 @@ public static void CountMRangeExample()
5858
/// Example for basic usage of RedisTimeSeries RANGE command with "-" and "+" as range boundreis, a filter and MIN aggregation.
5959
/// NRedisTimeSeris MRange is expecting two TimeStamps objects as the range boundries.
6060
/// In this case, the strings are implicitly casted into TimeStamp objects.
61-
/// The TimeSeriesMRange command returns an IReadOnlyList<(string key, IReadOnlyList<TimeSeriesLabel> labels, IReadOnlyList<TimeSeriesTuple> values)>collection.
61+
/// The TimeSeriesMRange command returns an IReadOnlyList (collection) of (string key, IReadOnlyList(TimeSeriesLabel) labels, IReadOnlyList(TimeSeriesTuple) values).
6262
/// </summary>
6363
public static void MRangeAggregationExample()
6464
{
@@ -79,7 +79,7 @@ public static void MRangeAggregationExample()
7979
/// Example for basic usage of RedisTimeSeries RANGE command with "-" and "+" as range boundreis, a filter and WITHLABELS flag.
8080
/// NRedisTimeSeris MRange is expecting two TimeStamps objects as the range boundries.
8181
/// In this case, the strings are implicitly casted into TimeStamp objects.
82-
/// The TimeSeriesMRange command returns an IReadOnlyList<(string key, IReadOnlyList<TimeSeriesLabel> labels, IReadOnlyList<TimeSeriesTuple> values)>collection.
82+
/// The TimeSeriesMRange command returns an IReadOnlyList (collection) of (string key, IReadOnlyList(TimeSeriesLabel) labels, IReadOnlyList(TimeSeriesTuple) values).
8383
/// </summary>
8484
public static void MRangeWithLabelsExample()
8585
{
@@ -96,5 +96,27 @@ public static void MRangeWithLabelsExample()
9696
}
9797
redis.Close();
9898
}
99+
100+
/// <summary>
101+
/// Example for basic usage of RedisTimeSeries RANGE command with "-" and "+" as range boundreis, a filter and a Groupby concept.
102+
/// NRedisTimeSeris MRange is expecting two TimeStamps objects as the range boundries.
103+
/// In this case, the strings are implicitly casted into TimeStamp objects.
104+
/// The TimeSeriesMRange command returns an IReadOnlyList (collection) of (string key, IReadOnlyList(TimeSeriesLabel) labels, IReadOnlyList(TimeSeriesTuple) values).
105+
/// </summary>
106+
public static void MRangeWithGroupbyExample()
107+
{
108+
ConnectionMultiplexer redis = ConnectionMultiplexer.Connect("localhost");
109+
IDatabase db = redis.GetDatabase();
110+
var filter = new List<string> { "MRANGEkey=MRANGEvalue" };
111+
var results = db.TimeSeriesMRange("-", "+", filter, withLabels: true, groupbyTuple: ("labelName", TsReduce.Max));
112+
// Values extraction example.
113+
foreach (var result in results)
114+
{
115+
string group = result.key;
116+
IReadOnlyList<TimeSeriesLabel> labels = result.labels;
117+
IReadOnlyList<TimeSeriesTuple> values = result.values;
118+
}
119+
redis.Close();
120+
}
99121
}
100122
}

NRedisTimeSeries.Example/MRangeExampleAsync.cs

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ internal class MRangeAsyncExample
1616
/// Example for basic usage of RedisTimeSeries RANGE command with "-" and "+" as range boundreis and a filter.
1717
/// NRedisTimeSeris MRange is expecting two TimeStamps objects as the range boundries.
1818
/// In this case, the strings are implicitly casted into TimeStamp objects.
19-
/// The TimeSeriesMRange command returns an IReadOnlyList<(string key, IReadOnlyList<TimeSeriesLabel> labels, IReadOnlyList<TimeSeriesTuple> values)>collection.
19+
/// The TimeSeriesMRange command returns an IReadOnlyList (collection) of (string key, IReadOnlyList(TimeSeriesLabel) labels, IReadOnlyList(TimeSeriesTuple) values).
2020
/// </summary>
2121
public static async Task BasicMRangeAsyncExample()
2222
{
@@ -37,7 +37,7 @@ public static async Task BasicMRangeAsyncExample()
3737
/// Example for basic usage of RedisTimeSeries RANGE command with "-" and "+" as range boundreis, a filter and the COUNT parameter.
3838
/// NRedisTimeSeris MRange is expecting two TimeStamps objects as the range boundries.
3939
/// In this case, the strings are implicitly casted into TimeStamp objects.
40-
/// The TimeSeriesMRange command returns an IReadOnlyList<(string key, IReadOnlyList<TimeSeriesLabel> labels, IReadOnlyList<TimeSeriesTuple> values)>collection.
40+
/// The TimeSeriesMRange command returns an IReadOnlyList (collection) of (string key, IReadOnlyList(TimeSeriesLabel) labels, IReadOnlyList(TimeSeriesTuple) values).
4141
/// </summary>
4242
public static async Task CountMRangeAsyncExample()
4343
{
@@ -58,7 +58,7 @@ public static async Task CountMRangeAsyncExample()
5858
/// Example for basic usage of RedisTimeSeries RANGE command with "-" and "+" as range boundreis, a filter and MIN aggregation.
5959
/// NRedisTimeSeris MRange is expecting two TimeStamps objects as the range boundries.
6060
/// In this case, the strings are implicitly casted into TimeStamp objects.
61-
/// The TimeSeriesMRange command returns an IReadOnlyList<(string key, IReadOnlyList<TimeSeriesLabel> labels, IReadOnlyList<TimeSeriesTuple> values)>collection.
61+
/// The TimeSeriesMRange command returns an IReadOnlyList (collection) of (string key, IReadOnlyList(TimeSeriesLabel) labels, IReadOnlyList(TimeSeriesTuple) values).
6262
/// </summary>
6363
public static async Task MRangeAggregationAsyncExample()
6464
{
@@ -79,7 +79,7 @@ public static async Task MRangeAggregationAsyncExample()
7979
/// Example for basic usage of RedisTimeSeries RANGE command with "-" and "+" as range boundreis, a filter and WITHLABELS flag.
8080
/// NRedisTimeSeris MRange is expecting two TimeStamps objects as the range boundries.
8181
/// In this case, the strings are implicitly casted into TimeStamp objects.
82-
/// The TimeSeriesMRange command returns an IReadOnlyList<(string key, IReadOnlyList<TimeSeriesLabel> labels, IReadOnlyList<TimeSeriesTuple> values)>collection.
82+
/// The TimeSeriesMRange command returns an IReadOnlyList (collection) of (string key, IReadOnlyList(TimeSeriesLabel) labels, IReadOnlyList(TimeSeriesTuple) values).
8383
/// </summary>
8484
public static async Task MRangeWithLabelsAsyncExample()
8585
{
@@ -96,5 +96,27 @@ public static async Task MRangeWithLabelsAsyncExample()
9696
}
9797
redis.Close();
9898
}
99+
100+
/// <summary>
101+
/// Example for basic usage of RedisTimeSeries RANGE command with "-" and "+" as range boundreis, a filter and a Groupby concept.
102+
/// NRedisTimeSeris MRange is expecting two TimeStamps objects as the range boundries.
103+
/// In this case, the strings are implicitly casted into TimeStamp objects.
104+
/// The TimeSeriesMRange command returns an IReadOnlyList (collection) of (string key, IReadOnlyList(TimeSeriesLabel) labels, IReadOnlyList(TimeSeriesTuple) values).
105+
/// </summary>
106+
public static async Task MRangeWithGroupbyAsyncExample()
107+
{
108+
ConnectionMultiplexer redis = ConnectionMultiplexer.Connect("localhost");
109+
IDatabase db = redis.GetDatabase();
110+
var filter = new List<string> { "MRANGEkey=MRANGEvalue" };
111+
var results = await db.TimeSeriesMRangeAsync("-", "+", filter, withLabels: true, groupbyTuple: ("labelName", TsReduce.Max));
112+
// Values extraction example.
113+
foreach (var result in results)
114+
{
115+
string group = result.key;
116+
IReadOnlyList<TimeSeriesLabel> labels = result.labels;
117+
IReadOnlyList<TimeSeriesTuple> values = result.values;
118+
}
119+
redis.Close();
120+
}
99121
}
100122
}

NRedisTimeSeries.Test/TestAPI/TestMRange.cs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,53 @@ public void TestMissingTimeBucket()
174174
var tuples = CreateData(db, 50);
175175
var ex = Assert.Throws<ArgumentException>(() => db.TimeSeriesMRange("-", "+", new List<string> { "key=MissingTimeBucket" }, aggregation: TsAggregation.Avg));
176176
Assert.Equal("RANGE Aggregation should have timeBucket value", ex.Message);
177+
}
178+
179+
[Fact]
180+
public void TestMRangeGroupby()
181+
{
182+
IDatabase db = redisFixture.Redis.GetDatabase();
183+
for(int i = 0; i < keys.Length; i++)
184+
{
185+
var label1 = new TimeSeriesLabel("key", "MRangeGroupby");
186+
var label2 = new TimeSeriesLabel("group", i.ToString());
187+
db.TimeSeriesCreate(keys[i], labels: new List<TimeSeriesLabel> { label1, label2 });
188+
}
177189

190+
var tuples = CreateData(db, 50);
191+
var results = db.TimeSeriesMRange("-", "+", new List<string> { "key=MRangeGroupby" }, withLabels: true, groupbyTuple: ("group", TsReduce.Min));
192+
Assert.Equal(keys.Length, results.Count);
193+
for (int i = 0; i < results.Count; i++)
194+
{
195+
Assert.Equal("group=" + i, results[i].key);
196+
Assert.Equal(new TimeSeriesLabel("group", i.ToString()), results[i].labels[0]);
197+
Assert.Equal(new TimeSeriesLabel("__reducer__", "min"), results[i].labels[1]);
198+
Assert.Equal(new TimeSeriesLabel("__source__", keys[i]), results[i].labels[2]);
199+
Assert.Equal(tuples, results[i].values);
200+
}
201+
}
202+
203+
[Fact]
204+
public void TestMRangeReduce()
205+
{
206+
IDatabase db = redisFixture.Redis.GetDatabase();
207+
foreach(var key in keys)
208+
{
209+
var label = new TimeSeriesLabel("key", "MRangeReduce");
210+
db.TimeSeriesCreate(key, labels: new List<TimeSeriesLabel> { label });
211+
}
212+
213+
var tuples = CreateData(db, 50);
214+
var results = db.TimeSeriesMRange("-", "+", new List<string> { "key=MRangeReduce" }, withLabels: true, groupbyTuple: ("key", TsReduce.Sum));
215+
Assert.Equal(1, results.Count);
216+
Assert.Equal("key=MRangeReduce", results[0].key);
217+
Assert.Equal(new TimeSeriesLabel("key", "MRangeReduce"), results[0].labels[0]);
218+
Assert.Equal(new TimeSeriesLabel("__reducer__", "sum"), results[0].labels[1]);
219+
Assert.Equal(new TimeSeriesLabel("__source__", string.Join(",", keys)), results[0].labels[2]);
220+
for(int i = 0; i < results[0].values.Count; i++)
221+
{
222+
Assert.Equal(tuples[i].Val * 2, results[0].values[i].Val);
223+
}
178224
}
179225
}
180226
}

NRedisTimeSeries.Test/TestAPI/TestMRangeAsync.cs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,5 +177,54 @@ await db.TimeSeriesMRangeAsync("-", "+",
177177
});
178178
Assert.Equal("RANGE Aggregation should have timeBucket value", ex.Message);
179179
}
180+
181+
[Fact]
182+
public async Task TestMRangeGroupby()
183+
{
184+
var keys = CreateKeyNames(2);
185+
var db = redisFixture.Redis.GetDatabase();
186+
for(int i = 0; i < keys.Length; i++)
187+
{
188+
var label1 = new TimeSeriesLabel(keys[0], "value");
189+
var label2 = new TimeSeriesLabel("group", i.ToString());
190+
await db.TimeSeriesCreateAsync(keys[i], labels: new List<TimeSeriesLabel> { label1, label2 });
191+
}
192+
193+
var tuples = await CreateData(db, keys, 50);
194+
var results = await db.TimeSeriesMRangeAsync("-", "+", new List<string> { $"{keys[0]}=value" }, withLabels: true, groupbyTuple: ("group", TsReduce.Min));
195+
Assert.Equal(keys.Length, results.Count);
196+
for (int i = 0; i < results.Count; i++)
197+
{
198+
Assert.Equal("group=" + i, results[i].key);
199+
Assert.Equal(new TimeSeriesLabel("group", i.ToString()), results[i].labels[0]);
200+
Assert.Equal(new TimeSeriesLabel("__reducer__", "min"), results[i].labels[1]);
201+
Assert.Equal(new TimeSeriesLabel("__source__", keys[i]), results[i].labels[2]);
202+
Assert.Equal(tuples, results[i].values);
203+
}
204+
}
205+
206+
[Fact]
207+
public async Task TestMRangeReduce()
208+
{
209+
var keys = CreateKeyNames(2);
210+
var db = redisFixture.Redis.GetDatabase();
211+
foreach(var key in keys)
212+
{
213+
var label = new TimeSeriesLabel(keys[0], "value");
214+
await db.TimeSeriesCreateAsync(key, labels: new List<TimeSeriesLabel> { label });
215+
}
216+
217+
var tuples = await CreateData(db, keys, 50);
218+
var results = await db.TimeSeriesMRangeAsync("-", "+", new List<string> { $"{keys[0]}=value" }, withLabels: true, groupbyTuple: (keys[0], TsReduce.Sum));
219+
Assert.Equal(1, results.Count);
220+
Assert.Equal($"{keys[0]}=value", results[0].key);
221+
Assert.Equal(new TimeSeriesLabel(keys[0], "value"), results[0].labels[0]);
222+
Assert.Equal(new TimeSeriesLabel("__reducer__", "sum"), results[0].labels[1]);
223+
Assert.Equal(new TimeSeriesLabel("__source__", string.Join(",", keys)), results[0].labels[2]);
224+
for(int i = 0; i < results[0].values.Count; i++)
225+
{
226+
Assert.Equal(tuples[i].Val * 2, results[0].values[i].Val);
227+
}
228+
}
180229
}
181230
}

NRedisTimeSeries.Test/TestAPI/TestMRevRange.cs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,56 @@ public void TestMissingTimeBucket()
171171
var tuples = CreateData(db, keys, 50);
172172
var ex = Assert.Throws<ArgumentException>(() => db.TimeSeriesMRevRange("-", "+", new List<string> { "key=MissingTimeBucket" }, aggregation: TsAggregation.Avg));
173173
Assert.Equal("RANGE Aggregation should have timeBucket value", ex.Message);
174+
}
174175

176+
[Fact]
177+
public void TestMRevRangeGroupby()
178+
{
179+
var keys = CreateKeyNames(2);
180+
var db = redisFixture.Redis.GetDatabase();
181+
for(int i = 0; i < keys.Length; i++)
182+
{
183+
var label1 = new TimeSeriesLabel(keys[0], "value");
184+
var label2 = new TimeSeriesLabel("group", i.ToString());
185+
db.TimeSeriesCreate(keys[i], labels: new List<TimeSeriesLabel> { label1, label2 });
186+
}
187+
188+
var tuples = CreateData(db, keys, 50);
189+
var results = db.TimeSeriesMRevRange("-", "+", new List<string> { $"{keys[0]}=value" }, withLabels: true, groupbyTuple: ("group", TsReduce.Min));
190+
Assert.Equal(keys.Length, results.Count);
191+
for (var i = 0; i < results.Count; i++)
192+
{
193+
Assert.Equal("group=" + i, results[i].key);
194+
Assert.Equal(new TimeSeriesLabel("group", i.ToString()), results[i].labels[0]);
195+
Assert.Equal(new TimeSeriesLabel("__reducer__", "min"), results[i].labels[1]);
196+
Assert.Equal(new TimeSeriesLabel("__source__", keys[i]), results[i].labels[2]);
197+
Assert.Equal(ReverseData(tuples), results[i].values);
198+
}
175199
}
200+
201+
[Fact]
202+
public void TestMRevRangeReduce()
203+
{
204+
var keys = CreateKeyNames(2);
205+
var db = redisFixture.Redis.GetDatabase();
206+
foreach(var key in keys)
207+
{
208+
var label = new TimeSeriesLabel(keys[0], "value");
209+
db.TimeSeriesCreate(key, labels: new List<TimeSeriesLabel> { label });
210+
}
211+
212+
var tuples = CreateData(db, keys, 50);
213+
var results = db.TimeSeriesMRevRange("-", "+", new List<string> { $"{keys[0]}=value" }, withLabels: true, groupbyTuple: (keys[0], TsReduce.Sum));
214+
Assert.Equal(1, results.Count);
215+
Assert.Equal($"{keys[0]}=value", results[0].key);
216+
Assert.Equal(new TimeSeriesLabel(keys[0], "value"), results[0].labels[0]);
217+
Assert.Equal(new TimeSeriesLabel("__reducer__", "sum"), results[0].labels[1]);
218+
Assert.Equal(new TimeSeriesLabel("__source__", string.Join(",", keys)), results[0].labels[2]);
219+
tuples = ReverseData(tuples);
220+
for(int i = 0; i < results[0].values.Count; i++)
221+
{
222+
Assert.Equal(tuples[i].Val * 2, results[0].values[i].Val);
223+
}
224+
}
176225
}
177226
}

NRedisTimeSeries.Test/TestAPI/TestMRevRangeAsync.cs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,5 +177,55 @@ await db.TimeSeriesMRevRangeAsync("-", "+",
177177
});
178178
Assert.Equal("RANGE Aggregation should have timeBucket value", ex.Message);
179179
}
180+
181+
[Fact]
182+
public async Task TestMRevRangeGroupby()
183+
{
184+
var keys = CreateKeyNames(2);
185+
var db = redisFixture.Redis.GetDatabase();
186+
for(int i = 0; i < keys.Length; i++)
187+
{
188+
var label1 = new TimeSeriesLabel(keys[0], "value");
189+
var label2 = new TimeSeriesLabel("group", i.ToString());
190+
await db.TimeSeriesCreateAsync(keys[i], labels: new List<TimeSeriesLabel> { label1, label2 });
191+
}
192+
193+
var tuples = await CreateData(db, keys, 50);
194+
var results = await db.TimeSeriesMRevRangeAsync("-", "+", new List<string> { $"{keys[0]}=value" }, withLabels: true, groupbyTuple: ("group", TsReduce.Min));
195+
Assert.Equal(keys.Length, results.Count);
196+
for (var i = 0; i < results.Count; i++)
197+
{
198+
Assert.Equal("group=" + i, results[i].key);
199+
Assert.Equal(new TimeSeriesLabel("group", i.ToString()), results[i].labels[0]);
200+
Assert.Equal(new TimeSeriesLabel("__reducer__", "min"), results[i].labels[1]);
201+
Assert.Equal(new TimeSeriesLabel("__source__", keys[i]), results[i].labels[2]);
202+
Assert.Equal(ReverseData(tuples), results[i].values);
203+
}
204+
}
205+
206+
[Fact]
207+
public async Task TestMRevRangeReduce()
208+
{
209+
var keys = CreateKeyNames(2);
210+
var db = redisFixture.Redis.GetDatabase();
211+
foreach(var key in keys)
212+
{
213+
var label = new TimeSeriesLabel(keys[0], "value");
214+
await db.TimeSeriesCreateAsync(key, labels: new List<TimeSeriesLabel> { label });
215+
}
216+
217+
var tuples = await CreateData(db, keys, 50);
218+
var results = await db.TimeSeriesMRevRangeAsync("-", "+", new List<string> { $"{keys[0]}=value" }, withLabels: true, groupbyTuple: (keys[0], TsReduce.Sum));
219+
Assert.Equal(1, results.Count);
220+
Assert.Equal($"{keys[0]}=value", results[0].key);
221+
Assert.Equal(new TimeSeriesLabel(keys[0], "value"), results[0].labels[0]);
222+
Assert.Equal(new TimeSeriesLabel("__reducer__", "sum"), results[0].labels[1]);
223+
Assert.Equal(new TimeSeriesLabel("__source__", string.Join(",", keys)), results[0].labels[2]);
224+
tuples = ReverseData(tuples);
225+
for(int i = 0; i < results[0].values.Count; i++)
226+
{
227+
Assert.Equal(tuples[i].Val * 2, results[0].values[i].Val);
228+
}
229+
}
180230
}
181231
}

NRedisTimeSeries/Commands/CommandArgs.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,7 @@ internal class CommandArgs
1313
public static string CHUNK_SIZE => "CHUNK_SIZE";
1414
public static string DUPLICATE_POLICY => "DUPLICATE_POLICY";
1515
public static string ON_DUPLICATE => "ON_DUPLICATE";
16+
public static string GROPUBY => "GROUPBY";
17+
public static string REDUCE => "REDUCE";
1618
}
1719
}

0 commit comments

Comments
 (0)