Skip to content

Commit 0c883f3

Browse files
authored
Add CMS Commands (#5)
* Add CMS Commands * add CMS Tests * Add FLUSHALL Command to all tests
1 parent fb45f0d commit 0c883f3

File tree

11 files changed

+453
-34
lines changed

11 files changed

+453
-34
lines changed

src/NRedisStack.Core/Bloom/BloomCommands.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ public bool Exists(RedisKey key, RedisValue item)
6565
/// <returns>An array of booleans. Each element is either true or false depending on whether the
6666
/// corresponding input element was newly added to the filter or may have previously existed.</returns>
6767
/// <remarks><seealso href="https://redis.io/commands/bf.insert"/></remarks>
68-
public bool[] Insert(RedisKey key, RedisValue[] items, int? capacity = null,
68+
public bool[] Insert(RedisKey key, RedisValue[] items, int? capacity = null, //TODO: create enother function that get one item, because right now if the user want to insert one item he needs to insert this as RedisValue[]
6969
double? error = null, int? expansion = null,
7070
bool nocreate = false, bool nonscaling = false)
7171
{
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
using NRedisStack.Core.CountMinSketch.DataTypes;
2+
using NRedisStack.Core.Literals;
3+
using StackExchange.Redis;
4+
namespace NRedisStack.Core
5+
{
6+
7+
public class CmsCommands
8+
{
9+
IDatabase _db;
10+
public CmsCommands(IDatabase db)
11+
{
12+
_db = db;
13+
}
14+
15+
/// <summary>
16+
/// Increases the count of item by increment.
17+
/// </summary>
18+
/// <param name="key">The name of the sketch.</param>
19+
/// <param name="item">The item which counter is to be increased.</param>
20+
/// <param name="increment">Amount by which the item counter is to be increased.</param>
21+
/// <returns>Count of each item after increment.</returns>
22+
/// <remarks><seealso href="https://redis.io/commands/cms.incrby"/></remarks>
23+
public long IncrBy(RedisKey key, RedisValue item, long increment)
24+
{
25+
return ResponseParser.ToLong(_db.Execute(CMS.INCRBY, key, item, increment));
26+
}
27+
28+
/// <summary>
29+
/// Increases the count of item by increment.
30+
/// </summary>
31+
/// <param name="key">The name of the sketch.</param>
32+
/// <param name="itemIncrements">Tuple of The items which counter is to be increased
33+
/// and the Amount by which the item counter is to be increased.</param>
34+
/// <returns>Count of each item after increment.</returns>
35+
/// <remarks><seealso href="https://redis.io/commands/cms.incrby"/></remarks>
36+
public long[]? IncrBy(RedisKey key, Tuple<RedisValue, long>[] itemIncrements)
37+
{
38+
if (itemIncrements.Length < 1)
39+
throw new ArgumentException(nameof(itemIncrements));
40+
41+
List<object> args = new List<object> { key };
42+
foreach (var pair in itemIncrements)
43+
{
44+
args.Add(pair.Item1);
45+
args.Add(pair.Item2);
46+
}
47+
return ResponseParser.ToLongArray(_db.Execute(CMS.INCRBY, args));
48+
}
49+
50+
/// <summary>
51+
/// Return information about a sketch.
52+
/// </summary>
53+
/// <param name="key">Name of the key to return information about.</param>
54+
/// <returns>Information of the sketch.</returns>
55+
/// <remarks><seealso href="https://redis.io/commands/cms.info"/></remarks>
56+
public CmsInformation? Info(RedisKey key)
57+
{
58+
var info = _db.Execute(CMS.INFO, key);
59+
return ResponseParser.ToCmsInfo(info);
60+
}
61+
62+
//TODO: functions that returns OK cannot return false, they return OK or ERROR. fix this (the Redis functions that can return also false - will return 1 for true ans 0 for false)
63+
/// <summary>
64+
/// Initializes a Count-Min Sketch to dimensions specified by user.
65+
/// </summary>
66+
/// <param name="key">TThe name of the sketch.</param>
67+
/// <param name="width">Number of counters in each array. Reduces the error size.</param>
68+
/// <param name="depth">Number of counter-arrays. Reduces the probability for an error
69+
/// of a certain size (percentage of total count).</param>
70+
/// <returns><see langword="true"/> if if executed correctly, Error otherwise.</returns>
71+
/// <remarks><seealso href="https://redis.io/commands/cms.initbydim"/></remarks>
72+
public bool InitByDim(RedisKey key, long width, long depth)
73+
{
74+
return ResponseParser.ParseOKtoBoolean(_db.Execute(CMS.INITBYDIM, key, width, depth));
75+
}
76+
77+
/// <summary>
78+
/// Initializes a Count-Min Sketch to accommodate requested tolerances.
79+
/// </summary>
80+
/// <param name="key">The name of the sketch.</param>
81+
/// <param name="error">Estimate size of error.</param>
82+
/// <param name="probability">The desired probability for inflated count.</param>
83+
/// <returns><see langword="true"/> if if executed correctly, Error otherwise.</returns>
84+
/// <remarks><seealso href="https://redis.io/commands/cms.initbyprob"/></remarks>
85+
public bool InitByProb(RedisKey key, double error, double probability)
86+
{
87+
return ResponseParser.ParseOKtoBoolean(_db.Execute(CMS.INITBYPROB, key, error, probability));
88+
}
89+
90+
/// <summary>
91+
/// Merges several sketches into one sketch.
92+
/// </summary>
93+
/// <param name="destination">The name of destination sketch. Must be initialized</param>
94+
/// <param name="numKeys">Number of sketches to be merged.</param>
95+
/// <param name="source">Names of source sketches to be merged.</param>
96+
/// <param name="weight">Multiple of each sketch. Default = 1.</param>
97+
/// <returns><see langword="true"/> if if executed correctly, Error otherwise.</returns>
98+
/// <remarks><seealso href="https://redis.io/commands/cms.merge"/></remarks>
99+
public bool Merge(RedisValue destination, long numKeys, RedisValue[] source, long[]? weight = null)
100+
{
101+
if (source.Length < 1)
102+
throw new ArgumentNullException(nameof(source));
103+
104+
List<object> args = new List<object> { destination, numKeys };
105+
106+
foreach (var s in source) args.Add(s);
107+
108+
if (weight != null && weight.Length >= 1)
109+
{
110+
args.Add(CmsArgs.WEIGHTS);
111+
foreach (var w in weight) args.Add(w);
112+
}
113+
114+
return ResponseParser.ParseOKtoBoolean(_db.Execute(CMS.MERGE, args));
115+
}
116+
117+
/// <summary>
118+
/// Returns the count for one or more items in a sketch.
119+
/// </summary>
120+
/// <param name="key">The name of the sketch</param>
121+
/// <param name="items">One or more items for which to return the count.</param>
122+
/// <returns>Array with a min-count of each of the items in the sketch</returns>
123+
/// <remarks><seealso href="https://redis.io/commands/cms.merge"/></remarks>
124+
public long[]? Query(RedisKey key, RedisValue[] items) //TODO: Create second version of this function using params for items input
125+
{
126+
if (items.Length < 1)
127+
throw new ArgumentNullException(nameof(items));
128+
129+
List<object> args = new List<object> { key };
130+
foreach (var item in items) args.Add(item);
131+
132+
return ResponseParser.ToLongArray(_db.Execute(CMS.QUERY, args));
133+
}
134+
}
135+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
namespace NRedisStack.Core.CountMinSketch.DataTypes
2+
{
3+
/// <summary>
4+
/// This class represents the response for CMS.INFO command.
5+
/// This object has Read-only properties and cannot be generated outside a CMS.INFO response.
6+
/// </summary>
7+
public class CmsInformation
8+
{
9+
public long Width { get; private set; }
10+
public long Depth { get; private set; }
11+
public long Count { get; private set; }
12+
13+
14+
internal CmsInformation(long width, long depth, long count)
15+
{
16+
Width = width;
17+
Depth = depth;
18+
Count = count;
19+
}
20+
}
21+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
namespace NRedisStack.Core.Literals
2+
{
3+
internal class CmsArgs
4+
{
5+
public static string WEIGHTS => "WEIGHTS";
6+
// public static string CAPACITY => "CAPACITY";
7+
// public static string EXPANSION => "EXPANSION";
8+
// public static string NOCREATE => "NOCREATE";
9+
// public static string ITEMS => "ITEMS";
10+
// public static string BUCKETSIZE => "BUCKETSIZE";
11+
// public static string MAXITERATIONS => "MAXITERATIONS";
12+
}
13+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
namespace NRedisStack.Core.Literals
2+
{
3+
internal class CMS
4+
{
5+
public static string INITBYDIM => "CMS.INITBYDIM";
6+
public static string INITBYPROB => "CMS.INITBYPROB";
7+
public static string INCRBY => "CMS.INCRBY";
8+
public static string QUERY => "CMS.QUERY";
9+
public static string MERGE => "CMS.MERGE";
10+
public static string INFO => "CMS.INFO";
11+
}
12+
}

src/NRedisStack.Core/ModulPrefixes.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ public static class ModulPrefixes
1010
static bool cuckooCreated = false;
1111
static CuckooCommands cuckooCommands;
1212

13+
static bool cmsCreated = false;
14+
static CmsCommands cmsCommands;
15+
1316
static bool searchCreated = false;
1417
static SearchCommands searchCommands;
1518

@@ -41,6 +44,17 @@ static public CuckooCommands CF(this IDatabase db)
4144
return cuckooCommands;
4245
}
4346

47+
static public CmsCommands CMS(this IDatabase db)
48+
{
49+
if (!cmsCreated)
50+
{
51+
cmsCommands = new CmsCommands(db);
52+
cmsCreated = true;
53+
}
54+
55+
return cmsCommands;
56+
}
57+
4458
static public SearchCommands FT(this IDatabase db)
4559
{
4660
if (!searchCreated)

src/NRedisStack.Core/ResponseParser.cs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using StackExchange.Redis;
77
using NRedisStack.Core.Bloom.DataTypes;
88
using NRedisStack.Core.CuckooFilter.DataTypes;
9+
using NRedisStack.Core.CountMinSketch.DataTypes;
910

1011
namespace NRedisStack.Core
1112
{
@@ -41,6 +42,12 @@ public static long ToLong(RedisResult result)
4142
return (long)result;
4243
}
4344

45+
public static long[]? ToLongArray(RedisResult result)
46+
{
47+
if (result.Type == ResultType.None) return null;
48+
return (long[])result;
49+
}
50+
4451
public static TimeStamp ToTimeStamp(RedisResult result)
4552
{
4653
if (result.Type == ResultType.None) return null;
@@ -206,6 +213,7 @@ public static IReadOnlyList<TimeSeriesRule> ToRuleArray(RedisResult result)
206213

207214
return new BloomInformation(capacity, size, numberOfFilters, numberOfItemsInserted, expansionRate);
208215
}
216+
209217
public static CuckooInformation? ToCuckooInfo(RedisResult result) //TODO: Think about a different implementation, because if the output of BF.INFO changes or even just the names of the labels then the parsing will not work
210218
{
211219
long size, numberOfBuckets, numberOfFilter, numberOfItemsInserted,
@@ -256,6 +264,37 @@ public static IReadOnlyList<TimeSeriesRule> ToRuleArray(RedisResult result)
256264
numberOfItemsDeleted, bucketSize, expansionRate, maxIteration);
257265
}
258266

267+
public static CmsInformation? ToCmsInfo(RedisResult result) //TODO: Think about a different implementation, because if the output of CMS.INFO changes or even just the names of the labels then the parsing will not work
268+
{
269+
long width, depth, count;
270+
271+
width = depth = count = -1;
272+
273+
RedisResult[]? redisResults = (RedisResult[]?)result;
274+
275+
if (redisResults == null) return null;
276+
277+
for (int i = 0; i < redisResults.Length; ++i)
278+
{
279+
string? label = redisResults[i++].ToString();
280+
281+
switch (label)
282+
{
283+
case "width":
284+
width = (long)redisResults[i];
285+
break;
286+
case "depth":
287+
depth = (long)redisResults[i];
288+
break;
289+
case "count":
290+
count = (long)redisResults[i];
291+
break;
292+
}
293+
}
294+
295+
return new CmsInformation(width, depth, count);
296+
}
297+
259298
public static TimeSeriesInformation ToTimeSeriesInfo(RedisResult result)
260299
{
261300
long totalSamples = -1, memoryUsage = -1, retentionTime = -1, chunkSize = -1, chunkCount = -1;

0 commit comments

Comments
 (0)