From 66fce8c1a13d25befe55094ab3cd48adde206c4f Mon Sep 17 00:00:00 2001 From: slorello89 Date: Thu, 1 Sep 2022 16:30:40 -0400 Subject: [PATCH 1/5] adding JSON commands --- src/NRedisStack/Auxiliary.cs | 14 + src/NRedisStack/Json/IJsonCommands.cs | 232 ++++++++++++++ src/NRedisStack/Json/JsonCommands.cs | 308 +++++++++++++++++-- src/NRedisStack/Json/JsonType.cs | 13 + src/NRedisStack/ResponseParser.cs | 15 + tests/NRedisStack.Tests/Json/JsonTests.cs | 358 +++++++++++++++++++++- 6 files changed, 919 insertions(+), 21 deletions(-) create mode 100644 src/NRedisStack/Json/IJsonCommands.cs create mode 100644 src/NRedisStack/Json/JsonType.cs diff --git a/src/NRedisStack/Auxiliary.cs b/src/NRedisStack/Auxiliary.cs index 474489ce..29b40fea 100644 --- a/src/NRedisStack/Auxiliary.cs +++ b/src/NRedisStack/Auxiliary.cs @@ -10,5 +10,19 @@ public static List MergeArgs(RedisKey key, params RedisValue[] items) foreach (var item in items) args.Add(item); return args; } + + public static object[] AssembleNonNullArguments(params object?[] arguments) + { + var args = new List(); + foreach (var arg in arguments) + { + if (arg != null) + { + args.Add(arg); + } + } + + return args.ToArray(); + } } } \ No newline at end of file diff --git a/src/NRedisStack/Json/IJsonCommands.cs b/src/NRedisStack/Json/IJsonCommands.cs new file mode 100644 index 00000000..c7dd9174 --- /dev/null +++ b/src/NRedisStack/Json/IJsonCommands.cs @@ -0,0 +1,232 @@ +using StackExchange.Redis; + +namespace NRedisStack; + +public interface IJsonCommands +{ + /// + /// Appends the provided items to the array at the provided path. + /// + /// The key to append to + /// The path to append to + /// the values to append + /// The new array sizes for the appended paths + /// + public long?[] ArrayAppend(RedisKey key, string? path, params object[] values); + + /// + /// Finds the index of the provided item within the provided range + /// + /// The key to look up. + /// The json path. + /// The value to find the index of. + /// The starting index within the array. Inclusive. + /// The ending index within the array. Exclusive + /// The index of the value for each array the path resolved to. + /// + public long?[] ArrayIndex(RedisKey key, string? path, object value, long? start = null, long? stop = null); + /// + /// Inserts the provided items at the provided index within a json array. + /// + /// The key to insert into. + /// The path of the array(s) within the key to insert into. + /// The index to insert at. + /// The values to insert + /// The new size of each array the item was inserted into. + /// + public long?[] ArrayInsert(RedisKey key, string path, long index, params object[] values); + + /// + /// Gets the length of the arrays resolved by the provided path. + /// + /// The key of the json object. + /// The path to the array(s) + /// The length of each array resolved by the json path. + /// + public long?[] ArrayLength(RedisKey key, string? path = null); + + /// + /// Pops an item from the array(s) at the provided index. Or the last element if no index is provided. + /// + /// The json key to use. + /// The path of the array(s). + /// The index to pop from + /// The items popped from the array + /// + public RedisResult[] ArrayPop(RedisKey key, string? path = null, long? index = null); + + /// + /// Trims the array(s) at the provided path, leaving the range between the specified indexes (inclusive). + /// + /// The key to trim from. + /// The path of the array(s) within the json object to trim. + /// the starting index to retain. + /// The ending index to retain. + /// The new length of the array(s) after they're trimmed. + /// + public long?[] ArrayTrim(RedisKey key, string path, long start, long stop); + + /// + /// Clear's container values(arrays/objects), and sets numeric values to 0. + /// + /// The key to clear. + /// The path to clear. + /// number of values cleared + /// + public long Clear(RedisKey key, string? path = null); + + /// + /// Deletes a json value. + /// + /// The key to delete from. + /// The path to delete. + /// number of path's deleted + /// + public long Del(RedisKey key, string? path = null); + + /// + /// Deletes a json value. + /// + /// The key to delete from. + /// The path to delete. + /// number of path's deleted + /// + public long Forget(RedisKey key, string? path = null); + + /// + /// Gets the value stored at the key and path in redis. + /// + /// The key to retrieve. + /// the indentation string for nested levels + /// sets the string that's printed at the end of each line + /// sets the string that's put between a key and a value + /// the path to get. + /// The requested Items + /// + public RedisResult Get(RedisKey key, RedisValue? indent = null, RedisValue? newLine = null, RedisValue? space = null, RedisValue? path = null); + + /// + /// Generically gets an Item stored in Redis. + /// + /// The key to retrieve + /// The path to retrieve + /// The type retrieved + /// The object requested + /// + public T? Get(RedisKey key, string path = "$"); + + /// + /// retrieves a group of items stored in redis, appropriate if the path will resolve to multiple records. + /// + /// The key to pull from. + /// The path to pull. + /// The type. + /// An enumerable of the requested tyep + /// + public IEnumerable GetEnumerable(RedisKey key, string path = "$"); + + /// + /// Gets the provided path from multiple keys + /// + /// The keys to retrieve from. + /// The path to retrieve + /// An array of RedisResults with the requested data. + /// + public RedisResult[] MGet(RedisKey[] keys, string path); + + /// + /// Increments the fields at the provided path by the provided number. + /// + /// The key. + /// The path to increment. + /// The value to increment by. + /// The new values after being incremented, or null if the path resolved a non-numeric. + /// + public double?[] NumIncrby(RedisKey key, string path, double value); + + /// + /// Gets the keys of the object at the provided path. + /// + /// the key of the json object. + /// The path of the object(s) + /// the keys of the resolved object(s) + /// + public IEnumerable> ObjectKeys(RedisKey key, string? path = null); + + /// + /// returns the number of keys in the object(s) at the provided path. + /// + /// The key of the json object. + /// The path of the object(s) to resolve. + /// The length of the object(s) keyspace. + /// + public long?[] ObjectLength(RedisKey key, string? path = null); + + /// + /// Gets the key in RESP(Redis Serialization Protocol) form. + /// + /// The key to get. + /// Path within the key to get. + /// the resultant resp + /// + public RedisResult[] Resp(RedisKey key, string? path = null); + + /// + /// Set's the key/path to the provided value. + /// + /// The key. + /// The path to set within the key. + /// The value to set. + /// When to set the value. + /// The disposition of the command + /// + public bool Set(RedisKey key, RedisValue path, object obj, When when = When.Always); + + /// + /// Set's the key/path to the provided value. + /// + /// The key. + /// The path to set within the key. + /// The value to set. + /// When to set the value. + /// The disposition of the command + /// + public bool Set(RedisKey key, RedisValue path, RedisValue json, When when = When.Always); + + /// + /// Appends the provided string to the string(s) at the provided path. + /// + /// The key to append to. + /// The path of the string(s) to append to. + /// The value to append. + /// The new length of the string(s) appended to, those lengths will be null if the path did not resolve ot a string. + /// + public long?[] StringAppend(RedisKey key, string? path, string value); + + /// + /// Check's the length of the string(s) at the provided path. + /// + /// The key of the json object. + /// The path of the string(s) within the json object. + /// The length of the string(s) appended to, those lengths will be null if the path did not resolve ot a string. + /// + public long?[] StringLength(RedisKey key, string? path = null); + + /// + /// Toggles the boolean value(s) at the provided path. + /// + /// The key of the json object. + /// The path of the value(s) to toggle. + /// the new value(s). Which will be null if the path did not resolve to a boolean. + /// + public bool?[] Toggle(RedisKey key, string? path = null); + + /// + /// Gets the type(s) of the item(s) at the provided json path. + /// + /// The key of the JSON object. + /// The path to resolve. + /// An array of types. + /// + public JsonType[] Type(RedisKey key, string? path = null); +} \ No newline at end of file diff --git a/src/NRedisStack/Json/JsonCommands.cs b/src/NRedisStack/Json/JsonCommands.cs index 7782408d..d5a1a72a 100644 --- a/src/NRedisStack/Json/JsonCommands.cs +++ b/src/NRedisStack/Json/JsonCommands.cs @@ -1,47 +1,237 @@ using NRedisStack.Literals; using StackExchange.Redis; using System.Text.Json; -using System.Text.Json.Serialization; +using System.Text.Json.Nodes; +using static NRedisStack.Auxiliary; namespace NRedisStack; -public class JsonCommands +public class JsonCommands : IJsonCommands { IDatabase _db; public JsonCommands(IDatabase db) { _db = db; } - private readonly JsonSerializerOptions Options = new() + + /// + public RedisResult[] Resp(RedisKey key, string? path = null) { - DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, - }; - public RedisResult Set(RedisKey key, RedisValue path, object obj, When when = When.Always) + RedisResult result; + if (string.IsNullOrEmpty(path)) + { + result = _db.Execute(JSON.RESP, key); + } + else + { + result = _db.Execute(JSON.RESP, key, path); + } + + if (result.IsNull) + { + return Array.Empty(); + } + + return (RedisResult[])result!; + } + + /// + public bool Set(RedisKey key, RedisValue path, object obj, When when = When.Always) { string json = JsonSerializer.Serialize(obj); return Set(key, path, json, when); } - public RedisResult Set(RedisKey key, RedisValue path, RedisValue json, When when = When.Always) + /// + public bool Set(RedisKey key, RedisValue path, RedisValue json, When when = When.Always) { - switch (when) + var result = when switch + { + When.Exists => _db.Execute(JSON.SET, key, path, json, "XX"), + When.NotExists => _db.Execute(JSON.SET, key, path, json, "NX"), + _ => _db.Execute(JSON.SET, key, path, json) + }; + + if (result.IsNull || result.ToString() != "OK") { - case When.Exists: - return _db.Execute(JSON.SET, key, path, json, "XX"); - case When.NotExists: - return _db.Execute(JSON.SET, key, path, json, "NX"); - default: - return _db.Execute(JSON.SET, key, path, json); + return false; } + + return true; } - public RedisResult Get(RedisKey key, - RedisValue? indent = null, - RedisValue? newLine = null, - RedisValue? space = null, - RedisValue? path = null) + /// + public long?[] StringAppend(RedisKey key, string? path, string value) { + RedisResult result; + if (path == null) + { + result = _db.Execute( JSON.STRAPPEND, key, JsonSerializer.Serialize(value)); + } + else + { + result = _db.Execute( JSON.STRAPPEND, key, path, JsonSerializer.Serialize(value)); + } + + return result.ToNullableLongArray(); + } + /// + public long?[] StringLength(RedisKey key, string? path = null) + { + RedisResult result; + if (path != null) + { + result = _db.Execute(JSON.STRLEN, key, path); + } + else + { + result = _db.Execute(JSON.STRLEN, key); + } + + return result.ToNullableLongArray(); + } + + /// + public bool?[] Toggle(RedisKey key, string? path = null) + { + RedisResult result; + if (path != null) + { + result = _db.Execute(JSON.TOGGLE, key, path); + } + else + { + result = _db.Execute(JSON.TOGGLE, key, "$"); + } + + if (result.IsNull) + { + return Array.Empty(); + } + + if (result.Type == ResultType.Integer) + { + return new bool?[] {(long)result == 1}; + } + + return ((RedisResult[])result!).Select(x => (bool?)((long)x == 1)).ToArray(); + } + + /// + public JsonType[] Type(RedisKey key, string? path = null) + { + RedisResult result; + if (path == null) + { + result = _db.Execute(JSON.TYPE, key); + } + else + { + result = _db.Execute(JSON.TYPE, key, path); + } + + if (result.Type == ResultType.MultiBulk) + { + return ((RedisResult[])result!).Select(x => Enum.Parse(x.ToString()!.ToUpper())).ToArray(); + } + + if (result.Type == ResultType.BulkString) + { + return new [] {Enum.Parse(result.ToString()!.ToUpper())}; + } + + return Array.Empty(); + + } + + /// + public long?[] ArrayAppend(RedisKey key, string? path, params object[] values) + { + var args = new List{key}; + if (path != null) + { + args.Add(path); + } + + args.AddRange(values.Select(x=>JsonSerializer.Serialize(x))); + + var result = _db.Execute(JSON.ARRAPPEND, args.ToArray()); + return result.ToNullableLongArray(); + } + + /// + public long?[] ArrayIndex(RedisKey key, string? path, object value, long? start = null, long? stop = null) + { + var args = AssembleNonNullArguments(key, path, JsonSerializer.Serialize(value), start, stop); + var result = _db.Execute(JSON.ARRINDEX, args); + return result.ToNullableLongArray(); + } + + /// + public long?[] ArrayInsert(RedisKey key, string path, long index, params object[] values) + { + var args = new List { key, path, index }; + foreach (var val in values) + { + args.Add(JsonSerializer.Serialize(val)); + } + + var result = _db.Execute(JSON.ARRINSERT, args); + return result.ToNullableLongArray(); + } + + /// + public long?[] ArrayLength(RedisKey key, string? path = null) + { + var args = AssembleNonNullArguments(key, path); + var result = _db.Execute(JSON.ARRLEN, args); + return result.ToNullableLongArray(); + } + + /// + public RedisResult[] ArrayPop(RedisKey key, string? path = null, long? index = null) + { + var args = AssembleNonNullArguments(key, path, index); + var res = _db.Execute(JSON.ARRPOP, args)!; + + if (res.Type == ResultType.MultiBulk) + { + return (RedisResult[])res!; + } + + if (res.Type == ResultType.BulkString) + { + return new [] { res }; + } + + return Array.Empty(); + } + + /// + public long?[] ArrayTrim(RedisKey key, string path, long start, long stop) => + _db.Execute(JSON.ARRTRIM, key, path, start, stop).ToNullableLongArray(); + + /// + public long Clear(RedisKey key, string? path = null) + { + var args = AssembleNonNullArguments(key, path); + return (long)_db.Execute(JSON.CLEAR, args); + } + + /// + public long Del(RedisKey key, string? path = null) + { + var args = AssembleNonNullArguments(key, path); + return (long)_db.Execute(JSON.DEL, args); + } + + /// + public long Forget(RedisKey key, string? path = null) => Del(key, path); + + /// + public RedisResult Get(RedisKey key, RedisValue? indent = null, RedisValue? newLine = null, RedisValue? space = null, RedisValue? path = null) + { List args = new List(){key}; if (indent != null) @@ -69,4 +259,84 @@ public RedisResult Get(RedisKey key, return _db.Execute(JSON.GET, args); } + + /// + public T? Get(RedisKey key, string path = "$") + { + var res = _db.Execute(JSON.GET, key, path); + if (res.Type == ResultType.BulkString) + { + var arr = JsonSerializer.Deserialize(res.ToString()!); + if(arr?.Count > 0 ) + { + return JsonSerializer.Deserialize(JsonSerializer.Serialize(arr[0])); + } + } + + return default; + } + + /// + public IEnumerable GetEnumerable(RedisKey key, string path = "$") + { + var res = _db.Execute(JSON.GET, key, path); + return JsonSerializer.Deserialize>(res.ToString()); + } + + /// + public RedisResult[] MGet(RedisKey[] keys, string path) + { + var args = new List(); + foreach (var key in keys) + { + args.Add(key); + } + + args.Add(path); + return (RedisResult[])_db.Execute(JSON.MGET, args)!; + } + + /// + public double?[] NumIncrby(RedisKey key, string path, double value) + { + var res = _db.Execute(JSON.NUMINCRBY, key, path, value); + return JsonSerializer.Deserialize(res.ToString()); + } + + /// + public IEnumerable> ObjectKeys(RedisKey key, string? path = null) + { + var sets = new List>(); + var args = AssembleNonNullArguments(key, path); + var res = (RedisResult[])_db.Execute(JSON.OBJKEYS, args)!; + if (res.All(x=>x.Type!=ResultType.MultiBulk)) + { + var keys = res.Select(x => x.ToString()!); + sets.Add(keys.ToHashSet()); + return sets; + } + + foreach (var result in res) + { + var set = new HashSet(); + if (result.Type == ResultType.MultiBulk) + { + var resultArr = (RedisResult[])result!; + foreach (var item in resultArr) + { + set.Add(item.ToString()!); + } + } + sets.Add(set); + } + + return sets; + } + + /// + public long?[] ObjectLength(RedisKey key, string? path = null) + { + var args = AssembleNonNullArguments(key, path); + return _db.Execute(JSON.OBJLEN, args).ToNullableLongArray(); + } } \ No newline at end of file diff --git a/src/NRedisStack/Json/JsonType.cs b/src/NRedisStack/Json/JsonType.cs new file mode 100644 index 00000000..6e5ac114 --- /dev/null +++ b/src/NRedisStack/Json/JsonType.cs @@ -0,0 +1,13 @@ +namespace NRedisStack; + +public enum JsonType +{ + UNKNOWN = 0, + NULL = 1, + BOOLEAN = 2, + INTEGER = 3, + NUMBER = 4, + STRING = 5, + ARRAY = 6, + OBJECT = 7 +} \ No newline at end of file diff --git a/src/NRedisStack/ResponseParser.cs b/src/NRedisStack/ResponseParser.cs index 6cf0c475..22916106 100644 --- a/src/NRedisStack/ResponseParser.cs +++ b/src/NRedisStack/ResponseParser.cs @@ -531,5 +531,20 @@ public static IReadOnlyList ToStringArray(this RedisResult result) Array.ForEach(redisResults, str => list.Add((string)str)); return list; } + + public static long?[] ToNullableLongArray(this RedisResult result) + { + if (result.IsNull) + { + return Array.Empty(); + } + + if (result.Type == ResultType.Integer) + { + return new[] { (long?)result }; + } + + return ((RedisResult[])result!).Select(x=>(long?)x).ToArray(); + } } } \ No newline at end of file diff --git a/tests/NRedisStack.Tests/Json/JsonTests.cs b/tests/NRedisStack.Tests/Json/JsonTests.cs index 33b0eaf7..069241e9 100644 --- a/tests/NRedisStack.Tests/Json/JsonTests.cs +++ b/tests/NRedisStack.Tests/Json/JsonTests.cs @@ -1,3 +1,4 @@ +using System.Diagnostics; using Xunit; using StackExchange.Redis; using Moq; @@ -9,12 +10,12 @@ namespace NRedisStack.Tests; public class JsonTests : AbstractNRedisStackTest, IDisposable { Mock _mock = new Mock(); - private readonly string key = "JSON_TESTS"; + private readonly string _testName = "JSON_TESTS"; public JsonTests(RedisFixture redisFixture) : base(redisFixture) { } public void Dispose() { - redisFixture.Redis.GetDatabase().KeyDelete(key); + redisFixture.Redis.GetDatabase().KeyDelete(_testName); } // [Fact] @@ -29,6 +30,7 @@ public void Dispose() public void TestJsonSetNotExist() { var obj = new Person { Name = "Shachar", Age = 23 }; + _mock.Setup(x => x.Execute(It.IsAny(), It.IsAny())).Returns((RedisResult.Create(new RedisValue("OK")))); _mock.Object.JSON().Set("Person:Shachar", "$", obj, When.NotExists); _mock.Verify(x => x.Execute("JSON.SET", "Person:Shachar", "$", "{\"Name\":\"Shachar\",\"Age\":23}", "NX")); } @@ -102,6 +104,358 @@ public void TestModulePrefixs1() // ... conn.Dispose(); } + } + + [Fact] + public void TestResp() + { + //arrange + var conn = redisFixture.Redis; + var db = conn.GetDatabase(); + IJsonCommands commands = new JsonCommands(db); + var keys = CreateKeyNames(1); + + var key = keys[0]; + commands.Set(key, "$", new { name = "Steve", age = 33 }); + + //act + var respResult = commands.Resp(key); + + //assert + var i = 0; + Assert.Equal("{", respResult[i++]!.ToString()); + Assert.Equal("name", respResult[i++]!.ToString()); + Assert.Equal("Steve", respResult[i++]!.ToString()); + Assert.Equal("age", respResult[i++]!.ToString()); + Assert.Equal(33, (long)respResult[i]!); + conn.GetDatabase().KeyDelete(key); + } + + [Fact] + public void TestStringAppend() + { + //arrange + var conn = redisFixture.Redis; + var db = conn.GetDatabase(); + IJsonCommands commands = new JsonCommands(db); + var keys = CreateKeyNames(2); + + var key = keys[0]; + commands.Set(key, "$", new { name = "Steve", sibling = new {name = "christopher"}, age = 33}); + var simpleStringKey = keys[1]; + commands.Set(simpleStringKey, "$", "\"foo\""); + + //act + var nullResult = commands.StringAppend(key, "$.age", " Lorello"); + var keyResult = commands.StringAppend(key, "$..name", " Lorello"); + var simpleKeyResult = commands.StringAppend(simpleStringKey, null, "bar"); + + //assert + var i = 0; + Assert.Equal(2, keyResult.Length); + Assert.Equal(13, keyResult[i++]); + Assert.Equal(19, keyResult[i++]); + Assert.Null(nullResult[0]); + Assert.Equal(6, simpleKeyResult[0]); + } + + [Fact] + public void StringLength() + { + //arrange + var conn = redisFixture.Redis; + var db = conn.GetDatabase(); + IJsonCommands commands = new JsonCommands(db); + var keys = CreateKeyNames(2); + var key = keys[0]; + var simpleStringKey = keys[1]; + + commands.Set(key, "$", new { name = "Steve", sibling = new {name = "christopher"}, age = 33}); + commands.Set(simpleStringKey, "$", "\"foo\""); + + var normalResult = commands.StringLength(key, "$..name"); + var nullResult = commands.StringLength(key, "$.age"); + var simpleResult = commands.StringLength(simpleStringKey); + + var i = 0; + Assert.Equal(5, normalResult[i++]); + Assert.Equal(11, normalResult[i]); + Assert.Null(nullResult[0]); + Assert.Equal(3,simpleResult[0]); + } + + [Fact] + public void Toggle() + { + //arrange + var conn = redisFixture.Redis; + var db = conn.GetDatabase(); + IJsonCommands commands = new JsonCommands(db); + var keys = CreateKeyNames(2); + var key = keys[0]; + var simpleKey = keys[1]; + + commands.Set(key, "$", new { @bool = true, other = new {@bool = false}, age = 33}); + commands.Set(simpleKey, "$", true); + + var result = commands.Toggle(key, "$..bool"); + var simpleResult = commands.Toggle(simpleKey); + + Assert.False(result[0]); + Assert.True(result[1]); + Assert.False(simpleResult[0]); + } + + [Fact] + public void Type() + { + //arrange + var conn = redisFixture.Redis; + var db = conn.GetDatabase(); + IJsonCommands commands = new JsonCommands(db); + var keys = CreateKeyNames(2); + var key = keys[0]; + var simpleKey = keys[1]; + commands.Set(key, "$", new { name = "Steve", sibling = new {name = "christopher"}, age = 33, aDouble = 3.5}); + commands.Set(simpleKey, "$", "true"); + + var result = commands.Type(key, "$..name"); + Assert.Equal(JsonType.STRING, result[0]); + Assert.Equal(JsonType.STRING, result[1]); + result = commands.Type(key, "$..age"); + Assert.Equal(JsonType.INTEGER, result[0]); + result = commands.Type(key, "$..aDouble"); + Assert.Equal(JsonType.NUMBER, result[0]); + result = commands.Type(simpleKey); + Assert.Equal(JsonType.BOOLEAN, result[0]); + } + + [Fact] + public void ArrayAppend() + { + var conn = redisFixture.Redis; + var db = conn.GetDatabase(); + IJsonCommands commands = new JsonCommands(db); + var keys = CreateKeyNames(2); + var key = keys[0]; + var complexKey = keys[1]; + + commands.Set(key, "$", new { name = "Elizabeth", nickNames = new[] { "Beth" } }); + commands.Set(complexKey, "$", new { name = "foo", people = new[] { new { name = "steve" } } }); + var result = commands.ArrayAppend(key, "$.nickNames", "Elle", "Liz","Betty"); + Assert.Equal(4, result[0]); + result = commands.ArrayAppend(complexKey, "$.people", new { name = "bob" }); + Assert.Equal(2, result[0]); + } + + [Fact] + public void ArrayIndex() + { + var conn = redisFixture.Redis; + var db = conn.GetDatabase(); + IJsonCommands commands = new JsonCommands(db); + var keys = CreateKeyNames(1); + var key = keys[0]; + commands.Set(key, "$", new { name = "Elizabeth", nicknames = new[] { "Beth", "Betty", "Liz" }, sibling = new {name="Johnathan", nicknames = new [] {"Jon", "Johnny"}} }); + var res = commands.ArrayIndex(key, "$..nicknames", "Betty", 0,5); + Assert.Equal(1,res[0]); + Assert.Equal(-1,res[1]); + } + + [Fact] + public void ArrayInsert() + { + IJsonCommands commands = new JsonCommands(redisFixture.Redis.GetDatabase()); + var keys = CreateKeyNames(2); + var key = keys[0]; + var simpleKey = keys[1]; + + commands.Set(key, "$", new { name = "Alice", nicknames = new[] { "Al", "Ali", "Ally" } }); + commands.Set(simpleKey, "$", new[] { "Al", "Ali", "Ally" }); + + var result = commands.ArrayInsert(key, $"$.nicknames", 1, "Lys"); + Assert.Equal(4,result[0]); + result = commands.ArrayInsert(simpleKey, "$", 1, "Lys"); + Assert.Equal(4, result[0]); + } + + [Fact] + public void ArrayLength() + { + IJsonCommands commands = new JsonCommands(redisFixture.Redis.GetDatabase()); + var keys = CreateKeyNames(2); + var key = keys[0]; + var simpleKey = keys[1]; + commands.Set(key, "$", new { name = "Alice", nicknames = new[] { "Al", "Ali", "Ally" } }); + commands.Set(simpleKey, "$", new[] { "Al", "Ali", "Ally" }); + var result = commands.ArrayLength(key, $"$.nicknames"); + Assert.Equal(3, result[0]); + result = commands.ArrayLength(simpleKey); + Assert.Equal(3, result[0]); + } + + [Fact] + public void ArrayPop() + { + IJsonCommands commands = new JsonCommands(redisFixture.Redis.GetDatabase()); + var keys = CreateKeyNames(2); + var key = keys[0]; + var simpleKey = keys[1]; + commands.Set(key, "$", new { name = "Alice", nicknames = new[] { "Al", "Ali", "Ally" } }); + commands.Set(simpleKey, "$", new[] { "Al", "Ali", "Ally" }); + + var result = commands.ArrayPop(key, "$.nicknames", 1); + Assert.Equal("\"Ali\"", result[0].ToString()); + result = commands.ArrayPop(key, "$.nicknames"); + Assert.Equal("\"Ally\"", result[0].ToString()); + result = commands.ArrayPop(simpleKey); + Assert.Equal("\"Ally\"", result[0].ToString()); + } + + [Fact] + public void ArrayTrim() + { + IJsonCommands commands = new JsonCommands(redisFixture.Redis.GetDatabase()); + var keys = CreateKeyNames(2); + var key = keys[0]; + var simpleKey = keys[1]; + commands.Set(key, "$", new { name = "Alice", nicknames = new[] { "Al", "Ali", "Ally" } }); + commands.Set(simpleKey, "$", new[] { "Al", "Ali", "Ally" }); + + var result = commands.ArrayTrim(key, "$.nicknames", 0, 0); + Assert.Equal(1,result[0]); + result = commands.ArrayTrim(simpleKey, "$", 0, 1); + Assert.Equal(2,result[0]); + } + + [Fact] + public void Clear() + { + IJsonCommands commands = new JsonCommands(redisFixture.Redis.GetDatabase()); + var keys = CreateKeyNames(2); + var key = keys[0]; + var simpleKey = keys[1]; + commands.Set(key, "$", new { name = "Alice", nicknames = new[] { "Al", "Ali", "Ally" } }); + commands.Set(simpleKey, "$", new[] { "Al", "Ali", "Ally" }); + + var result = commands.Clear(key, "$.nicknames"); + Assert.Equal(1,result); + result = commands.Clear(simpleKey); + Assert.Equal(1,result); + } + + [Fact] + public void Del() + { + IJsonCommands commands = new JsonCommands(redisFixture.Redis.GetDatabase()); + var keys = CreateKeyNames(2); + var key = keys[0]; + var simpleKey = keys[1]; + commands.Set(key, "$", new { name = "Alice", nicknames = new[] { "Al", "Ali", "Ally" } }); + commands.Set(simpleKey, "$", new[] { "Al", "Ali", "Ally" }); + + var result = commands.Del(key, "$.nicknames"); + Assert.Equal(1,result); + result = commands.Del(simpleKey); + Assert.Equal(1,result); + } + + [Fact] + public void Forget() + { + IJsonCommands commands = new JsonCommands(redisFixture.Redis.GetDatabase()); + var keys = CreateKeyNames(2); + var key = keys[0]; + var simpleKey = keys[1]; + commands.Set(key, "$", new { name = "Alice", nicknames = new[] { "Al", "Ali", "Ally" } }); + commands.Set(simpleKey, "$", new[] { "Al", "Ali", "Ally" }); + + var result = commands.Forget(key, "$.nicknames"); + Assert.Equal(1,result); + result = commands.Forget(simpleKey); + Assert.Equal(1,result); + } + + [Fact] + public void Get() + { + IJsonCommands commands = new JsonCommands(redisFixture.Redis.GetDatabase()); + var keys = CreateKeyNames(2); + var key = keys[0]; + var complexKey = keys[1]; + commands.Set(key, "$", new Person(){Age = 35, Name = "Alice"}); + commands.Set(complexKey, "$", new {a=new Person(){Age = 35, Name = "Alice"}, b = new {a = new Person(){Age = 35, Name = "Alice"}}}); + var result = commands.Get(key); + Assert.Equal("Alice", result!.Name); + Assert.Equal(35, result.Age); + var people = commands.GetEnumerable(complexKey, "$..a").ToArray(); + Assert.Equal(2, people.Length); + Assert.Equal("Alice", people[0]!.Name); + Assert.Equal(35, people[0]!.Age); + Assert.Equal("Alice", people[1]!.Name); + Assert.Equal(35, people[1]!.Age); + } + + [Fact] + public void MGet() + { + IJsonCommands commands = new JsonCommands(redisFixture.Redis.GetDatabase()); + var keys = CreateKeyNames(2); + var key1 = keys[0]; + var key2 = keys[1]; + commands.Set(key1, "$", new { a = "hello" }); + commands.Set(key2, "$", new { a = "world" }); + var result = commands.MGet(keys.Select(x => new RedisKey(x)).ToArray(), "$.a"); + + Assert.Equal("[\"hello\"]", result[0].ToString()); + Assert.Equal("[\"world\"]", result[1].ToString()); + } + + [Fact] + public void NumIncrby() + { + IJsonCommands commands = new JsonCommands(redisFixture.Redis.GetDatabase()); + var keys = CreateKeyNames(1); + var key = keys[0]; + commands.Set(key, "$", new { age = 33, a = new { age = 34 }, b = new {age = "cat"} }); + var result = commands.NumIncrby(key, "$..age", 2); + Assert.Equal(35, result[0]); + Assert.Equal(36, result[1]); + Assert.Null(result[2]); + } + + [Fact] + public void ObjectKeys() + { + IJsonCommands commands = new JsonCommands(redisFixture.Redis.GetDatabase()); + var keys = CreateKeyNames(3); + var key = keys[0]; + commands.Set(key, "$", new { a = 5, b = 10, c = "hello", d = new { a = new { a = 6, b = "hello" }, b = 7 } }); + var result = commands.ObjectKeys(key).ToArray(); + Assert.Contains("a", result[0]); + Assert.Contains("b", result[0]); + Assert.Contains("c", result[0]); + Assert.Contains("d", result[0]); + result = commands.ObjectKeys(key, "$..a").ToArray(); + Assert.Empty(result[0]); + Assert.Contains("a", result[1]); + Assert.Contains("b", result[1]); + } + + [Fact] + public void ObjectLength() + { + IJsonCommands commands = new JsonCommands(redisFixture.Redis.GetDatabase()); + var keys = CreateKeyNames(3); + var key = keys[0]; + commands.Set(key, "$", new { a = 5, b = 10, c = "hello", d = new { a = new { a = 6, b = "hello" }, b = 7 } }); + var result = commands.ObjectLength(key); + Assert.Equal(4, result[0]); + result = commands.ObjectLength(key, $"$..a"); + Assert.Null(result[0]); + Assert.Equal(2, result[1]); + Assert.Null(result[2]); + } } \ No newline at end of file From 6269fd6920d8882e87ffe87e2a4c082922fba8e3 Mon Sep 17 00:00:00 2001 From: shacharPash Date: Sun, 4 Sep 2022 12:01:55 +0300 Subject: [PATCH 2/5] Change commands names --- src/NRedisStack/Json/IJsonCommands.cs | 58 ++++++++++---------- src/NRedisStack/Json/JsonCommands.cs | 42 +++++++-------- tests/NRedisStack.Tests/Json/JsonTests.cs | 65 +++++++++++------------ 3 files changed, 82 insertions(+), 83 deletions(-) diff --git a/src/NRedisStack/Json/IJsonCommands.cs b/src/NRedisStack/Json/IJsonCommands.cs index c7dd9174..e2574fd6 100644 --- a/src/NRedisStack/Json/IJsonCommands.cs +++ b/src/NRedisStack/Json/IJsonCommands.cs @@ -12,8 +12,8 @@ public interface IJsonCommands /// the values to append /// The new array sizes for the appended paths /// - public long?[] ArrayAppend(RedisKey key, string? path, params object[] values); - + public long?[] ArrAppend(RedisKey key, string? path, params object[] values); + /// /// Finds the index of the provided item within the provided range /// @@ -24,7 +24,7 @@ public interface IJsonCommands /// The ending index within the array. Exclusive /// The index of the value for each array the path resolved to. /// - public long?[] ArrayIndex(RedisKey key, string? path, object value, long? start = null, long? stop = null); + public long?[] ArrIndex(RedisKey key, string? path, object value, long? start = null, long? stop = null); /// /// Inserts the provided items at the provided index within a json array. /// @@ -34,8 +34,8 @@ public interface IJsonCommands /// The values to insert /// The new size of each array the item was inserted into. /// - public long?[] ArrayInsert(RedisKey key, string path, long index, params object[] values); - + public long?[] ArrInsert(RedisKey key, string path, long index, params object[] values); + /// /// Gets the length of the arrays resolved by the provided path. /// @@ -43,8 +43,8 @@ public interface IJsonCommands /// The path to the array(s) /// The length of each array resolved by the json path. /// - public long?[] ArrayLength(RedisKey key, string? path = null); - + public long?[] ArrLen(RedisKey key, string? path = null); + /// /// Pops an item from the array(s) at the provided index. Or the last element if no index is provided. /// @@ -53,8 +53,8 @@ public interface IJsonCommands /// The index to pop from /// The items popped from the array /// - public RedisResult[] ArrayPop(RedisKey key, string? path = null, long? index = null); - + public RedisResult[] ArrPop(RedisKey key, string? path = null, long? index = null); + /// /// Trims the array(s) at the provided path, leaving the range between the specified indexes (inclusive). /// @@ -64,8 +64,8 @@ public interface IJsonCommands /// The ending index to retain. /// The new length of the array(s) after they're trimmed. /// - public long?[] ArrayTrim(RedisKey key, string path, long start, long stop); - + public long?[] ArrTrim(RedisKey key, string path, long start, long stop); + /// /// Clear's container values(arrays/objects), and sets numeric values to 0. /// @@ -74,7 +74,7 @@ public interface IJsonCommands /// number of values cleared /// public long Clear(RedisKey key, string? path = null); - + /// /// Deletes a json value. /// @@ -83,7 +83,7 @@ public interface IJsonCommands /// number of path's deleted /// public long Del(RedisKey key, string? path = null); - + /// /// Deletes a json value. /// @@ -92,7 +92,7 @@ public interface IJsonCommands /// number of path's deleted /// public long Forget(RedisKey key, string? path = null); - + /// /// Gets the value stored at the key and path in redis. /// @@ -104,7 +104,7 @@ public interface IJsonCommands /// The requested Items /// public RedisResult Get(RedisKey key, RedisValue? indent = null, RedisValue? newLine = null, RedisValue? space = null, RedisValue? path = null); - + /// /// Generically gets an Item stored in Redis. /// @@ -114,7 +114,7 @@ public interface IJsonCommands /// The object requested /// public T? Get(RedisKey key, string path = "$"); - + /// /// retrieves a group of items stored in redis, appropriate if the path will resolve to multiple records. /// @@ -124,7 +124,7 @@ public interface IJsonCommands /// An enumerable of the requested tyep /// public IEnumerable GetEnumerable(RedisKey key, string path = "$"); - + /// /// Gets the provided path from multiple keys /// @@ -133,7 +133,7 @@ public interface IJsonCommands /// An array of RedisResults with the requested data. /// public RedisResult[] MGet(RedisKey[] keys, string path); - + /// /// Increments the fields at the provided path by the provided number. /// @@ -143,7 +143,7 @@ public interface IJsonCommands /// The new values after being incremented, or null if the path resolved a non-numeric. /// public double?[] NumIncrby(RedisKey key, string path, double value); - + /// /// Gets the keys of the object at the provided path. /// @@ -151,7 +151,7 @@ public interface IJsonCommands /// The path of the object(s) /// the keys of the resolved object(s) /// - public IEnumerable> ObjectKeys(RedisKey key, string? path = null); + public IEnumerable> ObjKeys(RedisKey key, string? path = null); /// /// returns the number of keys in the object(s) at the provided path. @@ -160,7 +160,7 @@ public interface IJsonCommands /// The path of the object(s) to resolve. /// The length of the object(s) keyspace. /// - public long?[] ObjectLength(RedisKey key, string? path = null); + public long?[] ObjLen(RedisKey key, string? path = null); /// /// Gets the key in RESP(Redis Serialization Protocol) form. @@ -170,7 +170,7 @@ public interface IJsonCommands /// the resultant resp /// public RedisResult[] Resp(RedisKey key, string? path = null); - + /// /// Set's the key/path to the provided value. /// @@ -181,7 +181,7 @@ public interface IJsonCommands /// The disposition of the command /// public bool Set(RedisKey key, RedisValue path, object obj, When when = When.Always); - + /// /// Set's the key/path to the provided value. /// @@ -192,7 +192,7 @@ public interface IJsonCommands /// The disposition of the command /// public bool Set(RedisKey key, RedisValue path, RedisValue json, When when = When.Always); - + /// /// Appends the provided string to the string(s) at the provided path. /// @@ -201,8 +201,8 @@ public interface IJsonCommands /// The value to append. /// The new length of the string(s) appended to, those lengths will be null if the path did not resolve ot a string. /// - public long?[] StringAppend(RedisKey key, string? path, string value); - + public long?[] StrAppend(RedisKey key, string? path, string value); + /// /// Check's the length of the string(s) at the provided path. /// @@ -210,8 +210,8 @@ public interface IJsonCommands /// The path of the string(s) within the json object. /// The length of the string(s) appended to, those lengths will be null if the path did not resolve ot a string. /// - public long?[] StringLength(RedisKey key, string? path = null); - + public long?[] StrLen(RedisKey key, string? path = null); + /// /// Toggles the boolean value(s) at the provided path. /// @@ -220,7 +220,7 @@ public interface IJsonCommands /// the new value(s). Which will be null if the path did not resolve to a boolean. /// public bool?[] Toggle(RedisKey key, string? path = null); - + /// /// Gets the type(s) of the item(s) at the provided json path. /// diff --git a/src/NRedisStack/Json/JsonCommands.cs b/src/NRedisStack/Json/JsonCommands.cs index d5a1a72a..47dd2d41 100644 --- a/src/NRedisStack/Json/JsonCommands.cs +++ b/src/NRedisStack/Json/JsonCommands.cs @@ -2,7 +2,7 @@ using StackExchange.Redis; using System.Text.Json; using System.Text.Json.Nodes; -using static NRedisStack.Auxiliary; +using static NRedisStack.Auxiliary; namespace NRedisStack; @@ -13,14 +13,14 @@ public JsonCommands(IDatabase db) { _db = db; } - + /// public RedisResult[] Resp(RedisKey key, string? path = null) { RedisResult result; if (string.IsNullOrEmpty(path)) { - result = _db.Execute(JSON.RESP, key); + result = _db.Execute(JSON.RESP, key); } else { @@ -61,7 +61,7 @@ public bool Set(RedisKey key, RedisValue path, RedisValue json, When when = When } /// - public long?[] StringAppend(RedisKey key, string? path, string value) + public long?[] StrAppend(RedisKey key, string? path, string value) { RedisResult result; if (path == null) @@ -77,12 +77,12 @@ public bool Set(RedisKey key, RedisValue path, RedisValue json, When when = When } /// - public long?[] StringLength(RedisKey key, string? path = null) + public long?[] StrLen(RedisKey key, string? path = null) { RedisResult result; if (path != null) { - result = _db.Execute(JSON.STRLEN, key, path); + result = _db.Execute(JSON.STRLEN, key, path); } else { @@ -98,7 +98,7 @@ public bool Set(RedisKey key, RedisValue path, RedisValue json, When when = When RedisResult result; if (path != null) { - result = _db.Execute(JSON.TOGGLE, key, path); + result = _db.Execute(JSON.TOGGLE, key, path); } else { @@ -140,28 +140,28 @@ public JsonType[] Type(RedisKey key, string? path = null) { return new [] {Enum.Parse(result.ToString()!.ToUpper())}; } - + return Array.Empty(); - + } /// - public long?[] ArrayAppend(RedisKey key, string? path, params object[] values) + public long?[] ArrAppend(RedisKey key, string? path, params object[] values) { var args = new List{key}; if (path != null) { args.Add(path); } - + args.AddRange(values.Select(x=>JsonSerializer.Serialize(x))); var result = _db.Execute(JSON.ARRAPPEND, args.ToArray()); return result.ToNullableLongArray(); } - + /// - public long?[] ArrayIndex(RedisKey key, string? path, object value, long? start = null, long? stop = null) + public long?[] ArrIndex(RedisKey key, string? path, object value, long? start = null, long? stop = null) { var args = AssembleNonNullArguments(key, path, JsonSerializer.Serialize(value), start, stop); var result = _db.Execute(JSON.ARRINDEX, args); @@ -169,20 +169,20 @@ public JsonType[] Type(RedisKey key, string? path = null) } /// - public long?[] ArrayInsert(RedisKey key, string path, long index, params object[] values) + public long?[] ArrInsert(RedisKey key, string path, long index, params object[] values) { var args = new List { key, path, index }; foreach (var val in values) { args.Add(JsonSerializer.Serialize(val)); } - + var result = _db.Execute(JSON.ARRINSERT, args); return result.ToNullableLongArray(); } /// - public long?[] ArrayLength(RedisKey key, string? path = null) + public long?[] ArrLen(RedisKey key, string? path = null) { var args = AssembleNonNullArguments(key, path); var result = _db.Execute(JSON.ARRLEN, args); @@ -190,7 +190,7 @@ public JsonType[] Type(RedisKey key, string? path = null) } /// - public RedisResult[] ArrayPop(RedisKey key, string? path = null, long? index = null) + public RedisResult[] ArrPop(RedisKey key, string? path = null, long? index = null) { var args = AssembleNonNullArguments(key, path, index); var res = _db.Execute(JSON.ARRPOP, args)!; @@ -209,7 +209,7 @@ public RedisResult[] ArrayPop(RedisKey key, string? path = null, long? index = n } /// - public long?[] ArrayTrim(RedisKey key, string path, long start, long stop) => + public long?[] ArrTrim(RedisKey key, string path, long start, long stop) => _db.Execute(JSON.ARRTRIM, key, path, start, stop).ToNullableLongArray(); /// @@ -272,7 +272,7 @@ public RedisResult Get(RedisKey key, RedisValue? indent = null, RedisValue? newL return JsonSerializer.Deserialize(JsonSerializer.Serialize(arr[0])); } } - + return default; } @@ -304,7 +304,7 @@ public RedisResult[] MGet(RedisKey[] keys, string path) } /// - public IEnumerable> ObjectKeys(RedisKey key, string? path = null) + public IEnumerable> ObjKeys(RedisKey key, string? path = null) { var sets = new List>(); var args = AssembleNonNullArguments(key, path); @@ -334,7 +334,7 @@ public IEnumerable> ObjectKeys(RedisKey key, string? path = null } /// - public long?[] ObjectLength(RedisKey key, string? path = null) + public long?[] ObjLen(RedisKey key, string? path = null) { var args = AssembleNonNullArguments(key, path); return _db.Execute(JSON.OBJLEN, args).ToNullableLongArray(); diff --git a/tests/NRedisStack.Tests/Json/JsonTests.cs b/tests/NRedisStack.Tests/Json/JsonTests.cs index 069241e9..cf1e8b2f 100644 --- a/tests/NRedisStack.Tests/Json/JsonTests.cs +++ b/tests/NRedisStack.Tests/Json/JsonTests.cs @@ -71,7 +71,6 @@ public void TestJsonSetNotExist() // Assert.Equal(result.ToString(), expected); // } - [Fact] public void TestModulePrefixs() { @@ -117,10 +116,10 @@ public void TestResp() var key = keys[0]; commands.Set(key, "$", new { name = "Steve", age = 33 }); - + //act var respResult = commands.Resp(key); - + //assert var i = 0; Assert.Equal("{", respResult[i++]!.ToString()); @@ -144,12 +143,12 @@ public void TestStringAppend() commands.Set(key, "$", new { name = "Steve", sibling = new {name = "christopher"}, age = 33}); var simpleStringKey = keys[1]; commands.Set(simpleStringKey, "$", "\"foo\""); - + //act - var nullResult = commands.StringAppend(key, "$.age", " Lorello"); - var keyResult = commands.StringAppend(key, "$..name", " Lorello"); - var simpleKeyResult = commands.StringAppend(simpleStringKey, null, "bar"); - + var nullResult = commands.StrAppend(key, "$.age", " Lorello"); + var keyResult = commands.StrAppend(key, "$..name", " Lorello"); + var simpleKeyResult = commands.StrAppend(simpleStringKey, null, "bar"); + //assert var i = 0; Assert.Equal(2, keyResult.Length); @@ -169,13 +168,13 @@ public void StringLength() var keys = CreateKeyNames(2); var key = keys[0]; var simpleStringKey = keys[1]; - + commands.Set(key, "$", new { name = "Steve", sibling = new {name = "christopher"}, age = 33}); commands.Set(simpleStringKey, "$", "\"foo\""); - var normalResult = commands.StringLength(key, "$..name"); - var nullResult = commands.StringLength(key, "$.age"); - var simpleResult = commands.StringLength(simpleStringKey); + var normalResult = commands.StrLen(key, "$..name"); + var nullResult = commands.StrLen(key, "$.age"); + var simpleResult = commands.StrLen(simpleStringKey); var i = 0; Assert.Equal(5, normalResult[i++]); @@ -200,7 +199,7 @@ public void Toggle() var result = commands.Toggle(key, "$..bool"); var simpleResult = commands.Toggle(simpleKey); - + Assert.False(result[0]); Assert.True(result[1]); Assert.False(simpleResult[0]); @@ -239,12 +238,12 @@ public void ArrayAppend() var keys = CreateKeyNames(2); var key = keys[0]; var complexKey = keys[1]; - + commands.Set(key, "$", new { name = "Elizabeth", nickNames = new[] { "Beth" } }); commands.Set(complexKey, "$", new { name = "foo", people = new[] { new { name = "steve" } } }); - var result = commands.ArrayAppend(key, "$.nickNames", "Elle", "Liz","Betty"); + var result = commands.ArrAppend(key, "$.nickNames", "Elle", "Liz","Betty"); Assert.Equal(4, result[0]); - result = commands.ArrayAppend(complexKey, "$.people", new { name = "bob" }); + result = commands.ArrAppend(complexKey, "$.people", new { name = "bob" }); Assert.Equal(2, result[0]); } @@ -257,7 +256,7 @@ public void ArrayIndex() var keys = CreateKeyNames(1); var key = keys[0]; commands.Set(key, "$", new { name = "Elizabeth", nicknames = new[] { "Beth", "Betty", "Liz" }, sibling = new {name="Johnathan", nicknames = new [] {"Jon", "Johnny"}} }); - var res = commands.ArrayIndex(key, "$..nicknames", "Betty", 0,5); + var res = commands.ArrIndex(key, "$..nicknames", "Betty", 0,5); Assert.Equal(1,res[0]); Assert.Equal(-1,res[1]); } @@ -273,9 +272,9 @@ public void ArrayInsert() commands.Set(key, "$", new { name = "Alice", nicknames = new[] { "Al", "Ali", "Ally" } }); commands.Set(simpleKey, "$", new[] { "Al", "Ali", "Ally" }); - var result = commands.ArrayInsert(key, $"$.nicknames", 1, "Lys"); + var result = commands.ArrInsert(key, $"$.nicknames", 1, "Lys"); Assert.Equal(4,result[0]); - result = commands.ArrayInsert(simpleKey, "$", 1, "Lys"); + result = commands.ArrInsert(simpleKey, "$", 1, "Lys"); Assert.Equal(4, result[0]); } @@ -289,9 +288,9 @@ public void ArrayLength() commands.Set(key, "$", new { name = "Alice", nicknames = new[] { "Al", "Ali", "Ally" } }); commands.Set(simpleKey, "$", new[] { "Al", "Ali", "Ally" }); - var result = commands.ArrayLength(key, $"$.nicknames"); + var result = commands.ArrLen(key, $"$.nicknames"); Assert.Equal(3, result[0]); - result = commands.ArrayLength(simpleKey); + result = commands.ArrLen(simpleKey); Assert.Equal(3, result[0]); } @@ -305,11 +304,11 @@ public void ArrayPop() commands.Set(key, "$", new { name = "Alice", nicknames = new[] { "Al", "Ali", "Ally" } }); commands.Set(simpleKey, "$", new[] { "Al", "Ali", "Ally" }); - var result = commands.ArrayPop(key, "$.nicknames", 1); + var result = commands.ArrPop(key, "$.nicknames", 1); Assert.Equal("\"Ali\"", result[0].ToString()); - result = commands.ArrayPop(key, "$.nicknames"); + result = commands.ArrPop(key, "$.nicknames"); Assert.Equal("\"Ally\"", result[0].ToString()); - result = commands.ArrayPop(simpleKey); + result = commands.ArrPop(simpleKey); Assert.Equal("\"Ally\"", result[0].ToString()); } @@ -323,9 +322,9 @@ public void ArrayTrim() commands.Set(key, "$", new { name = "Alice", nicknames = new[] { "Al", "Ali", "Ally" } }); commands.Set(simpleKey, "$", new[] { "Al", "Ali", "Ally" }); - var result = commands.ArrayTrim(key, "$.nicknames", 0, 0); + var result = commands.ArrTrim(key, "$.nicknames", 0, 0); Assert.Equal(1,result[0]); - result = commands.ArrayTrim(simpleKey, "$", 0, 1); + result = commands.ArrTrim(simpleKey, "$", 0, 1); Assert.Equal(2,result[0]); } @@ -360,7 +359,7 @@ public void Del() result = commands.Del(simpleKey); Assert.Equal(1,result); } - + [Fact] public void Forget() { @@ -407,7 +406,7 @@ public void MGet() commands.Set(key1, "$", new { a = "hello" }); commands.Set(key2, "$", new { a = "world" }); var result = commands.MGet(keys.Select(x => new RedisKey(x)).ToArray(), "$.a"); - + Assert.Equal("[\"hello\"]", result[0].ToString()); Assert.Equal("[\"world\"]", result[1].ToString()); } @@ -432,12 +431,12 @@ public void ObjectKeys() var keys = CreateKeyNames(3); var key = keys[0]; commands.Set(key, "$", new { a = 5, b = 10, c = "hello", d = new { a = new { a = 6, b = "hello" }, b = 7 } }); - var result = commands.ObjectKeys(key).ToArray(); + var result = commands.ObjKeys(key).ToArray(); Assert.Contains("a", result[0]); Assert.Contains("b", result[0]); Assert.Contains("c", result[0]); Assert.Contains("d", result[0]); - result = commands.ObjectKeys(key, "$..a").ToArray(); + result = commands.ObjKeys(key, "$..a").ToArray(); Assert.Empty(result[0]); Assert.Contains("a", result[1]); Assert.Contains("b", result[1]); @@ -450,12 +449,12 @@ public void ObjectLength() var keys = CreateKeyNames(3); var key = keys[0]; commands.Set(key, "$", new { a = 5, b = 10, c = "hello", d = new { a = new { a = 6, b = "hello" }, b = 7 } }); - var result = commands.ObjectLength(key); + var result = commands.ObjLen(key); Assert.Equal(4, result[0]); - result = commands.ObjectLength(key, $"$..a"); + result = commands.ObjLen(key, $"$..a"); Assert.Null(result[0]); Assert.Equal(2, result[1]); Assert.Null(result[2]); - + } } \ No newline at end of file From b4d387acd5b6200257731746a46f966c09f6c000 Mon Sep 17 00:00:00 2001 From: shacharPash Date: Sun, 4 Sep 2022 16:23:54 +0300 Subject: [PATCH 3/5] some fixes --- src/NRedisStack/Bloom/BloomCommands.cs | 6 ++-- src/NRedisStack/Json/IJsonCommands.cs | 7 ++-- src/NRedisStack/Json/JsonCommands.cs | 41 ++++++++++++++--------- tests/NRedisStack.Tests/Json/JsonTests.cs | 6 ++-- 4 files changed, 36 insertions(+), 24 deletions(-) diff --git a/src/NRedisStack/Bloom/BloomCommands.cs b/src/NRedisStack/Bloom/BloomCommands.cs index dfc96deb..d0765adc 100644 --- a/src/NRedisStack/Bloom/BloomCommands.cs +++ b/src/NRedisStack/Bloom/BloomCommands.cs @@ -111,7 +111,7 @@ public bool[] Insert(RedisKey key, RedisValue[] items, int? capacity = null, throw new ArgumentOutOfRangeException(nameof(items)); var args = BloomAux.BuildInsertArgs(key, items, capacity, error, expansion, nocreate, nonscaling); - + return _db.Execute(BF.INSERT, args).ToBooleanArray(); } @@ -335,7 +335,7 @@ public async Task ReserveAsync(RedisKey key, double errorRate, long capaci /// Iterator value; either 0 or the iterator from a previous invocation of this command. /// Tuple of iterator and data. /// - public Tuple ScanDump(RedisKey key, long iterator) + public Tuple ScanDump(RedisKey key, long iterator) { return _db.Execute(BF.SCANDUMP, key, iterator).ToScanDumpTuple(); } @@ -347,7 +347,7 @@ public Tuple ScanDump(RedisKey key, long iterator) /// Iterator value; either 0 or the iterator from a previous invocation of this command. /// Tuple of iterator and data. /// - public async Task> ScanDumpAsync(RedisKey key, long iterator) + public async Task> ScanDumpAsync(RedisKey key, long iterator) { var result = await _db.ExecuteAsync(BF.SCANDUMP, key, iterator); return result.ToScanDumpTuple(); diff --git a/src/NRedisStack/Json/IJsonCommands.cs b/src/NRedisStack/Json/IJsonCommands.cs index e2574fd6..0e339d34 100644 --- a/src/NRedisStack/Json/IJsonCommands.cs +++ b/src/NRedisStack/Json/IJsonCommands.cs @@ -12,7 +12,7 @@ public interface IJsonCommands /// the values to append /// The new array sizes for the appended paths /// - public long?[] ArrAppend(RedisKey key, string? path, params object[] values); + public long?[] ArrAppend(RedisKey key, string? path = null, params object[] values); /// /// Finds the index of the provided item within the provided range @@ -24,7 +24,8 @@ public interface IJsonCommands /// The ending index within the array. Exclusive /// The index of the value for each array the path resolved to. /// - public long?[] ArrIndex(RedisKey key, string? path, object value, long? start = null, long? stop = null); + public long?[] ArrIndex(RedisKey key, string path, object value, long? start = null, long? stop = null); + /// /// Inserts the provided items at the provided index within a json array. /// @@ -201,7 +202,7 @@ public interface IJsonCommands /// The value to append. /// The new length of the string(s) appended to, those lengths will be null if the path did not resolve ot a string. /// - public long?[] StrAppend(RedisKey key, string? path, string value); + public long?[] StrAppend(RedisKey key, string value, string? path = null); /// /// Check's the length of the string(s) at the provided path. diff --git a/src/NRedisStack/Json/JsonCommands.cs b/src/NRedisStack/Json/JsonCommands.cs index 47dd2d41..acd5b057 100644 --- a/src/NRedisStack/Json/JsonCommands.cs +++ b/src/NRedisStack/Json/JsonCommands.cs @@ -61,16 +61,16 @@ public bool Set(RedisKey key, RedisValue path, RedisValue json, When when = When } /// - public long?[] StrAppend(RedisKey key, string? path, string value) + public long?[] StrAppend(RedisKey key, string value, string? path = null) { RedisResult result; if (path == null) { - result = _db.Execute( JSON.STRAPPEND, key, JsonSerializer.Serialize(value)); + result = _db.Execute(JSON.STRAPPEND, key, JsonSerializer.Serialize(value)); } else { - result = _db.Execute( JSON.STRAPPEND, key, path, JsonSerializer.Serialize(value)); + result = _db.Execute(JSON.STRAPPEND, key, path, JsonSerializer.Serialize(value)); } return result.ToNullableLongArray(); @@ -112,7 +112,7 @@ public bool Set(RedisKey key, RedisValue path, RedisValue json, When when = When if (result.Type == ResultType.Integer) { - return new bool?[] {(long)result == 1}; + return new bool?[] { (long)result == 1 }; } return ((RedisResult[])result!).Select(x => (bool?)((long)x == 1)).ToArray(); @@ -138,7 +138,7 @@ public JsonType[] Type(RedisKey key, string? path = null) if (result.Type == ResultType.BulkString) { - return new [] {Enum.Parse(result.ToString()!.ToUpper())}; + return new[] { Enum.Parse(result.ToString()!.ToUpper()) }; } return Array.Empty(); @@ -146,24 +146,30 @@ public JsonType[] Type(RedisKey key, string? path = null) } /// - public long?[] ArrAppend(RedisKey key, string? path, params object[] values) + public long?[] ArrAppend(RedisKey key, string? path = null, params object[] values) { - var args = new List{key}; + if (values.Length < 1) + throw new ArgumentOutOfRangeException(nameof(values)); + + var args = new List { key }; if (path != null) { args.Add(path); } - args.AddRange(values.Select(x=>JsonSerializer.Serialize(x))); + args.AddRange(values.Select(x => JsonSerializer.Serialize(x))); var result = _db.Execute(JSON.ARRAPPEND, args.ToArray()); return result.ToNullableLongArray(); } /// - public long?[] ArrIndex(RedisKey key, string? path, object value, long? start = null, long? stop = null) + public long?[] ArrIndex(RedisKey key, string path, object value, long? start = null, long? stop = null) { - var args = AssembleNonNullArguments(key, path, JsonSerializer.Serialize(value), start, stop); + if (start == null && stop != null) + throw new ArgumentException("stop cannot be defined without start"); + + var args = AssembleNonNullArguments(key, path, JsonSerializer.Serialize(value), start, stop); var result = _db.Execute(JSON.ARRINDEX, args); return result.ToNullableLongArray(); } @@ -171,6 +177,8 @@ public JsonType[] Type(RedisKey key, string? path = null) /// public long?[] ArrInsert(RedisKey key, string path, long index, params object[] values) { + if (values.Length < 1) + throw new ArgumentOutOfRangeException(nameof(values)); var args = new List { key, path, index }; foreach (var val in values) { @@ -179,7 +187,7 @@ public JsonType[] Type(RedisKey key, string? path = null) var result = _db.Execute(JSON.ARRINSERT, args); return result.ToNullableLongArray(); - } + } /// public long?[] ArrLen(RedisKey key, string? path = null) @@ -192,6 +200,9 @@ public JsonType[] Type(RedisKey key, string? path = null) /// public RedisResult[] ArrPop(RedisKey key, string? path = null, long? index = null) { + if (path == null && index != null) + throw new ArgumentException("index cannot be defined without path"); + var args = AssembleNonNullArguments(key, path, index); var res = _db.Execute(JSON.ARRPOP, args)!; @@ -202,7 +213,7 @@ public RedisResult[] ArrPop(RedisKey key, string? path = null, long? index = nul if (res.Type == ResultType.BulkString) { - return new [] { res }; + return new[] { res }; } return Array.Empty(); @@ -232,7 +243,7 @@ public long Del(RedisKey key, string? path = null) /// public RedisResult Get(RedisKey key, RedisValue? indent = null, RedisValue? newLine = null, RedisValue? space = null, RedisValue? path = null) { - List args = new List(){key}; + List args = new List() { key }; if (indent != null) { @@ -267,7 +278,7 @@ public RedisResult Get(RedisKey key, RedisValue? indent = null, RedisValue? newL if (res.Type == ResultType.BulkString) { var arr = JsonSerializer.Deserialize(res.ToString()!); - if(arr?.Count > 0 ) + if (arr?.Count > 0) { return JsonSerializer.Deserialize(JsonSerializer.Serialize(arr[0])); } @@ -309,7 +320,7 @@ public IEnumerable> ObjKeys(RedisKey key, string? path = null) var sets = new List>(); var args = AssembleNonNullArguments(key, path); var res = (RedisResult[])_db.Execute(JSON.OBJKEYS, args)!; - if (res.All(x=>x.Type!=ResultType.MultiBulk)) + if (res.All(x => x.Type != ResultType.MultiBulk)) { var keys = res.Select(x => x.ToString()!); sets.Add(keys.ToHashSet()); diff --git a/tests/NRedisStack.Tests/Json/JsonTests.cs b/tests/NRedisStack.Tests/Json/JsonTests.cs index cf1e8b2f..f56079a6 100644 --- a/tests/NRedisStack.Tests/Json/JsonTests.cs +++ b/tests/NRedisStack.Tests/Json/JsonTests.cs @@ -145,9 +145,9 @@ public void TestStringAppend() commands.Set(simpleStringKey, "$", "\"foo\""); //act - var nullResult = commands.StrAppend(key, "$.age", " Lorello"); - var keyResult = commands.StrAppend(key, "$..name", " Lorello"); - var simpleKeyResult = commands.StrAppend(simpleStringKey, null, "bar"); + var nullResult = commands.StrAppend(key, " Lorello", "$.age"); + var keyResult = commands.StrAppend(key, " Lorello", "$..name"); + var simpleKeyResult = commands.StrAppend(simpleStringKey, "bar"); //assert var i = 0; From 1b6d63d8858331cf096680ca562f4179a1ca9567 Mon Sep 17 00:00:00 2001 From: slorello89 Date: Tue, 6 Sep 2022 08:57:50 -0400 Subject: [PATCH 4/5] adding missing commands, removing visibility modifiers in interface --- src/NRedisStack/Json/IJsonCommands.cs | 63 +++++++++++++++-------- src/NRedisStack/Json/JsonCommands.cs | 40 ++++++++++++++ src/NRedisStack/Json/Literals/Commands.cs | 2 +- tests/NRedisStack.Tests/Json/JsonTests.cs | 40 ++++++++++++++ 4 files changed, 122 insertions(+), 23 deletions(-) diff --git a/src/NRedisStack/Json/IJsonCommands.cs b/src/NRedisStack/Json/IJsonCommands.cs index 0e339d34..7fd56ee9 100644 --- a/src/NRedisStack/Json/IJsonCommands.cs +++ b/src/NRedisStack/Json/IJsonCommands.cs @@ -12,7 +12,7 @@ public interface IJsonCommands /// the values to append /// The new array sizes for the appended paths /// - public long?[] ArrAppend(RedisKey key, string? path = null, params object[] values); + long?[] ArrAppend(RedisKey key, string? path = null, params object[] values); /// /// Finds the index of the provided item within the provided range @@ -24,7 +24,7 @@ public interface IJsonCommands /// The ending index within the array. Exclusive /// The index of the value for each array the path resolved to. /// - public long?[] ArrIndex(RedisKey key, string path, object value, long? start = null, long? stop = null); + long?[] ArrIndex(RedisKey key, string path, object value, long? start = null, long? stop = null); /// /// Inserts the provided items at the provided index within a json array. @@ -35,7 +35,7 @@ public interface IJsonCommands /// The values to insert /// The new size of each array the item was inserted into. /// - public long?[] ArrInsert(RedisKey key, string path, long index, params object[] values); + long?[] ArrInsert(RedisKey key, string path, long index, params object[] values); /// /// Gets the length of the arrays resolved by the provided path. @@ -44,7 +44,7 @@ public interface IJsonCommands /// The path to the array(s) /// The length of each array resolved by the json path. /// - public long?[] ArrLen(RedisKey key, string? path = null); + long?[] ArrLen(RedisKey key, string? path = null); /// /// Pops an item from the array(s) at the provided index. Or the last element if no index is provided. @@ -54,7 +54,7 @@ public interface IJsonCommands /// The index to pop from /// The items popped from the array /// - public RedisResult[] ArrPop(RedisKey key, string? path = null, long? index = null); + RedisResult[] ArrPop(RedisKey key, string? path = null, long? index = null); /// /// Trims the array(s) at the provided path, leaving the range between the specified indexes (inclusive). @@ -65,7 +65,7 @@ public interface IJsonCommands /// The ending index to retain. /// The new length of the array(s) after they're trimmed. /// - public long?[] ArrTrim(RedisKey key, string path, long start, long stop); + long?[] ArrTrim(RedisKey key, string path, long start, long stop); /// /// Clear's container values(arrays/objects), and sets numeric values to 0. @@ -74,7 +74,7 @@ public interface IJsonCommands /// The path to clear. /// number of values cleared /// - public long Clear(RedisKey key, string? path = null); + long Clear(RedisKey key, string? path = null); /// /// Deletes a json value. @@ -83,7 +83,7 @@ public interface IJsonCommands /// The path to delete. /// number of path's deleted /// - public long Del(RedisKey key, string? path = null); + long Del(RedisKey key, string? path = null); /// /// Deletes a json value. @@ -92,7 +92,7 @@ public interface IJsonCommands /// The path to delete. /// number of path's deleted /// - public long Forget(RedisKey key, string? path = null); + long Forget(RedisKey key, string? path = null); /// /// Gets the value stored at the key and path in redis. @@ -104,7 +104,18 @@ public interface IJsonCommands /// the path to get. /// The requested Items /// - public RedisResult Get(RedisKey key, RedisValue? indent = null, RedisValue? newLine = null, RedisValue? space = null, RedisValue? path = null); + RedisResult Get(RedisKey key, RedisValue? indent = null, RedisValue? newLine = null, RedisValue? space = null, RedisValue? path = null); + + /// + /// Gets the values stored at the provided paths in redis. + /// + /// The key to pull from. + /// The paths within the key to pull. + /// the indentation string for nested levels + /// sets the string that's printed at the end of each line + /// sets the string that's put between a key and a value + /// + RedisResult Get(RedisKey key, string[] paths, RedisValue? indent = null, RedisValue? newLine = null, RedisValue? space = null); /// /// Generically gets an Item stored in Redis. @@ -114,7 +125,7 @@ public interface IJsonCommands /// The type retrieved /// The object requested /// - public T? Get(RedisKey key, string path = "$"); + T? Get(RedisKey key, string path = "$"); /// /// retrieves a group of items stored in redis, appropriate if the path will resolve to multiple records. @@ -124,7 +135,7 @@ public interface IJsonCommands /// The type. /// An enumerable of the requested tyep /// - public IEnumerable GetEnumerable(RedisKey key, string path = "$"); + IEnumerable GetEnumerable(RedisKey key, string path = "$"); /// /// Gets the provided path from multiple keys @@ -133,7 +144,7 @@ public interface IJsonCommands /// The path to retrieve /// An array of RedisResults with the requested data. /// - public RedisResult[] MGet(RedisKey[] keys, string path); + RedisResult[] MGet(RedisKey[] keys, string path); /// /// Increments the fields at the provided path by the provided number. @@ -143,7 +154,7 @@ public interface IJsonCommands /// The value to increment by. /// The new values after being incremented, or null if the path resolved a non-numeric. /// - public double?[] NumIncrby(RedisKey key, string path, double value); + double?[] NumIncrby(RedisKey key, string path, double value); /// /// Gets the keys of the object at the provided path. @@ -152,7 +163,7 @@ public interface IJsonCommands /// The path of the object(s) /// the keys of the resolved object(s) /// - public IEnumerable> ObjKeys(RedisKey key, string? path = null); + IEnumerable> ObjKeys(RedisKey key, string? path = null); /// /// returns the number of keys in the object(s) at the provided path. @@ -161,7 +172,7 @@ public interface IJsonCommands /// The path of the object(s) to resolve. /// The length of the object(s) keyspace. /// - public long?[] ObjLen(RedisKey key, string? path = null); + long?[] ObjLen(RedisKey key, string? path = null); /// /// Gets the key in RESP(Redis Serialization Protocol) form. @@ -170,7 +181,7 @@ public interface IJsonCommands /// Path within the key to get. /// the resultant resp /// - public RedisResult[] Resp(RedisKey key, string? path = null); + RedisResult[] Resp(RedisKey key, string? path = null); /// /// Set's the key/path to the provided value. @@ -181,7 +192,7 @@ public interface IJsonCommands /// When to set the value. /// The disposition of the command /// - public bool Set(RedisKey key, RedisValue path, object obj, When when = When.Always); + bool Set(RedisKey key, RedisValue path, object obj, When when = When.Always); /// /// Set's the key/path to the provided value. @@ -192,7 +203,7 @@ public interface IJsonCommands /// When to set the value. /// The disposition of the command /// - public bool Set(RedisKey key, RedisValue path, RedisValue json, When when = When.Always); + bool Set(RedisKey key, RedisValue path, RedisValue json, When when = When.Always); /// /// Appends the provided string to the string(s) at the provided path. @@ -202,7 +213,7 @@ public interface IJsonCommands /// The value to append. /// The new length of the string(s) appended to, those lengths will be null if the path did not resolve ot a string. /// - public long?[] StrAppend(RedisKey key, string value, string? path = null); + long?[] StrAppend(RedisKey key, string value, string? path = null); /// /// Check's the length of the string(s) at the provided path. @@ -220,7 +231,7 @@ public interface IJsonCommands /// The path of the value(s) to toggle. /// the new value(s). Which will be null if the path did not resolve to a boolean. /// - public bool?[] Toggle(RedisKey key, string? path = null); + bool?[] Toggle(RedisKey key, string? path = null); /// /// Gets the type(s) of the item(s) at the provided json path. @@ -229,5 +240,13 @@ public interface IJsonCommands /// The path to resolve. /// An array of types. /// - public JsonType[] Type(RedisKey key, string? path = null); + JsonType[] Type(RedisKey key, string? path = null); + + /// + /// Report a value's memory usage in bytes. path defaults to root if not provided. + /// + /// The object's key + /// The path within the object. + /// the value's size in bytes. + long DebugMemory(string key, string? path = null); } \ No newline at end of file diff --git a/src/NRedisStack/Json/JsonCommands.cs b/src/NRedisStack/Json/JsonCommands.cs index acd5b057..c789e2b2 100644 --- a/src/NRedisStack/Json/JsonCommands.cs +++ b/src/NRedisStack/Json/JsonCommands.cs @@ -145,6 +145,15 @@ public JsonType[] Type(RedisKey key, string? path = null) } + public long DebugMemory(string key, string? path = null) + { + if (path != null) + { + return (long)_db.Execute(JSON.DEBUG, JSON.MEMORY, key, path); + } + return (long)_db.Execute(JSON.DEBUG, JSON.MEMORY, key); + } + /// public long?[] ArrAppend(RedisKey key, string? path = null, params object[] values) { @@ -271,6 +280,37 @@ public RedisResult Get(RedisKey key, RedisValue? indent = null, RedisValue? newL return _db.Execute(JSON.GET, args); } + /// + public RedisResult Get(RedisKey key, string[] paths, RedisValue? indent = null, RedisValue? newLine = null, RedisValue? space = null) + { + List args = new List() { key }; + + foreach (var path in paths) + { + args.Add(path); + } + + if (indent != null) + { + args.Add(JsonArgs.INDENT); + args.Add(indent); + } + + if (newLine != null) + { + args.Add(JsonArgs.NEWLINE); + args.Add(newLine); + } + + if (space != null) + { + args.Add(JsonArgs.SPACE); + args.Add(space); + } + + return _db.Execute(JSON.GET, args); + } + /// public T? Get(RedisKey key, string path = "$") { diff --git a/src/NRedisStack/Json/Literals/Commands.cs b/src/NRedisStack/Json/Literals/Commands.cs index 159a4c81..c455e6ea 100644 --- a/src/NRedisStack/Json/Literals/Commands.cs +++ b/src/NRedisStack/Json/Literals/Commands.cs @@ -11,10 +11,10 @@ internal class JSON public const string CLEAR = "JSON.CLEAR"; public const string DEBUG = "JSON.DEBUG"; public const string DEBUG_HELP = "JSON.DEBUG HELP"; - public const string DEBUG_MEMORY = "JSON.DEBUG MEMORY"; public const string DEL = "JSON.DEL"; public const string FORGET = "JSON.FORGET"; public const string GET = "JSON.GET"; + public const string MEMORY = "MEMORY"; public const string MGET = "JSON.MGET"; public const string NUMINCRBY = "JSON.NUMINCRBY"; public const string NUMMULTBY = "JSON.NUMMULTBY"; diff --git a/tests/NRedisStack.Tests/Json/JsonTests.cs b/tests/NRedisStack.Tests/Json/JsonTests.cs index f56079a6..d8bd961b 100644 --- a/tests/NRedisStack.Tests/Json/JsonTests.cs +++ b/tests/NRedisStack.Tests/Json/JsonTests.cs @@ -1,4 +1,6 @@ using System.Diagnostics; +using System.Text.Json; +using System.Text.Json.Nodes; using Xunit; using StackExchange.Redis; using Moq; @@ -457,4 +459,42 @@ public void ObjectLength() Assert.Null(result[2]); } + + [Fact] + public void TestMultiPathGet() + { + IJsonCommands commands = new JsonCommands(redisFixture.Redis.GetDatabase()); + var keys = CreateKeyNames(1); + var key = keys[0]; + commands.Set(key, "$", new { a = "hello", b = new { a = "world" } }); + var res = commands.Get(key, new[] { "$..a", "$.b" }).ToString(); + var obj = JsonSerializer.Deserialize(res); + Assert.True(obj.ContainsKey("$..a")); + Assert.True(obj.ContainsKey("$.b")); + if (obj["$..a"] is JsonArray arr) + { + Assert.Equal("hello", arr[0]!.ToString()); + Assert.Equal("world", arr[1]!.ToString()); + } + else + { + Assert.True(false, "$..a was not a json array"); + } + + Assert.True(obj["$.b"]![0]!["a"]!.ToString() == "world"); + } + + [Fact] + public void Memory() + { + IJsonCommands commands = new JsonCommands(redisFixture.Redis.GetDatabase()); + var keys = CreateKeyNames(1); + var key = keys[0]; + + commands.Set(key, "$", new {a="hello", b=new {a="world"}}); + var res = commands.DebugMemory(key); + Assert.Equal(45, res); + res = commands.DebugMemory("non-existent key"); + Assert.Equal(0,res); + } } \ No newline at end of file From dea262ff0e861b160bcc65b6489ee3ac541ae636 Mon Sep 17 00:00:00 2001 From: slorello89 Date: Tue, 6 Sep 2022 14:54:21 -0400 Subject: [PATCH 5/5] async breakout --- src/NRedisStack/Json/IJsonCommands.cs | 246 +++++++++++++ src/NRedisStack/Json/JsonCommands.cs | 362 +++++++++++++++++-- src/NRedisStack/ResponseParser.cs | 33 ++ tests/NRedisStack.Tests/Json/JsonTests.cs | 419 +++++++++++++++++++--- 4 files changed, 991 insertions(+), 69 deletions(-) diff --git a/src/NRedisStack/Json/IJsonCommands.cs b/src/NRedisStack/Json/IJsonCommands.cs index 7fd56ee9..f8d5cd70 100644 --- a/src/NRedisStack/Json/IJsonCommands.cs +++ b/src/NRedisStack/Json/IJsonCommands.cs @@ -249,4 +249,250 @@ public interface IJsonCommands /// The path within the object. /// the value's size in bytes. long DebugMemory(string key, string? path = null); + + /// + /// Appends the provided items to the array at the provided path. + /// + /// The key to append to + /// The path to append to + /// the values to append + /// The new array sizes for the appended paths + /// + Task ArrAppendAsync(RedisKey key, string? path = null, params object[] values); + + /// + /// Finds the index of the provided item within the provided range + /// + /// The key to look up. + /// The json path. + /// The value to find the index of. + /// The starting index within the array. Inclusive. + /// The ending index within the array. Exclusive + /// The index of the value for each array the path resolved to. + /// + Task ArrIndexAsync(RedisKey key, string path, object value, long? start = null, long? stop = null); + + /// + /// Inserts the provided items at the provided index within a json array. + /// + /// The key to insert into. + /// The path of the array(s) within the key to insert into. + /// The index to insert at. + /// The values to insert + /// The new size of each array the item was inserted into. + /// + Task ArrInsertAsync(RedisKey key, string path, long index, params object[] values); + + /// + /// Gets the length of the arrays resolved by the provided path. + /// + /// The key of the json object. + /// The path to the array(s) + /// The length of each array resolved by the json path. + /// + Task ArrLenAsync(RedisKey key, string? path = null); + + /// + /// Pops an item from the array(s) at the provided index. Or the last element if no index is provided. + /// + /// The json key to use. + /// The path of the array(s). + /// The index to pop from + /// The items popped from the array + /// + Task ArrPopAsync(RedisKey key, string? path = null, long? index = null); + + /// + /// Trims the array(s) at the provided path, leaving the range between the specified indexes (inclusive). + /// + /// The key to trim from. + /// The path of the array(s) within the json object to trim. + /// the starting index to retain. + /// The ending index to retain. + /// The new length of the array(s) after they're trimmed. + /// + Task ArrTrimAsync(RedisKey key, string path, long start, long stop); + + /// + /// Clear's container values(arrays/objects), and sets numeric values to 0. + /// + /// The key to clear. + /// The path to clear. + /// number of values cleared + /// + Task ClearAsync(RedisKey key, string? path = null); + + /// + /// Deletes a json value. + /// + /// The key to delete from. + /// The path to delete. + /// number of path's deleted + /// + Task DelAsync(RedisKey key, string? path = null); + + /// + /// Deletes a json value. + /// + /// The key to delete from. + /// The path to delete. + /// number of path's deleted + /// + Task ForgetAsync(RedisKey key, string? path = null); + + /// + /// Gets the value stored at the key and path in redis. + /// + /// The key to retrieve. + /// the indentation string for nested levels + /// sets the string that's printed at the end of each line + /// sets the string that's put between a key and a value + /// the path to get. + /// The requested Items + /// + Task GetAsync(RedisKey key, RedisValue? indent = null, RedisValue? newLine = null, RedisValue? space = null, RedisValue? path = null); + + /// + /// Gets the values stored at the provided paths in redis. + /// + /// The key to pull from. + /// The paths within the key to pull. + /// the indentation string for nested levels + /// sets the string that's printed at the end of each line + /// sets the string that's put between a key and a value + /// + Task GetAsync(RedisKey key, string[] paths, RedisValue? indent = null, RedisValue? newLine = null, RedisValue? space = null); + + /// + /// Generically gets an Item stored in Redis. + /// + /// The key to retrieve + /// The path to retrieve + /// The type retrieved + /// The object requested + /// + Task GetAsync(RedisKey key, string path = "$"); + + /// + /// retrieves a group of items stored in redis, appropriate if the path will resolve to multiple records. + /// + /// The key to pull from. + /// The path to pull. + /// The type. + /// An enumerable of the requested tyep + /// + Task> GetEnumerableAsync(RedisKey key, string path = "$"); + + /// + /// Gets the provided path from multiple keys + /// + /// The keys to retrieve from. + /// The path to retrieve + /// An array of RedisResults with the requested data. + /// + Task MGetAsync(RedisKey[] keys, string path); + + /// + /// Increments the fields at the provided path by the provided number. + /// + /// The key. + /// The path to increment. + /// The value to increment by. + /// The new values after being incremented, or null if the path resolved a non-numeric. + /// + Task NumIncrbyAsync(RedisKey key, string path, double value); + + /// + /// Gets the keys of the object at the provided path. + /// + /// the key of the json object. + /// The path of the object(s) + /// the keys of the resolved object(s) + /// + Task>> ObjKeysAsync(RedisKey key, string? path = null); + + /// + /// returns the number of keys in the object(s) at the provided path. + /// + /// The key of the json object. + /// The path of the object(s) to resolve. + /// The length of the object(s) keyspace. + /// + Task ObjLenAsync(RedisKey key, string? path = null); + + /// + /// Gets the key in RESP(Redis Serialization Protocol) form. + /// + /// The key to get. + /// Path within the key to get. + /// the resultant resp + /// + Task RespAsync(RedisKey key, string? path = null); + + /// + /// Set's the key/path to the provided value. + /// + /// The key. + /// The path to set within the key. + /// The value to set. + /// When to set the value. + /// The disposition of the command + /// + Task SetAsync(RedisKey key, RedisValue path, object obj, When when = When.Always); + + /// + /// Set's the key/path to the provided value. + /// + /// The key. + /// The path to set within the key. + /// The value to set. + /// When to set the value. + /// The disposition of the command + /// + Task SetAsync(RedisKey key, RedisValue path, RedisValue json, When when = When.Always); + + /// + /// Appends the provided string to the string(s) at the provided path. + /// + /// The key to append to. + /// The path of the string(s) to append to. + /// The value to append. + /// The new length of the string(s) appended to, those lengths will be null if the path did not resolve ot a string. + /// + Task StrAppendAsync(RedisKey key, string value, string? path = null); + + /// + /// Check's the length of the string(s) at the provided path. + /// + /// The key of the json object. + /// The path of the string(s) within the json object. + /// The length of the string(s) appended to, those lengths will be null if the path did not resolve ot a string. + /// + Task StrLenAsync(RedisKey key, string? path = null); + + /// + /// Toggles the boolean value(s) at the provided path. + /// + /// The key of the json object. + /// The path of the value(s) to toggle. + /// the new value(s). Which will be null if the path did not resolve to a boolean. + /// + Task ToggleAsync(RedisKey key, string? path = null); + + /// + /// Gets the type(s) of the item(s) at the provided json path. + /// + /// The key of the JSON object. + /// The path to resolve. + /// An array of types. + /// + Task TypeAsync(RedisKey key, string? path = null); + + /// + /// Report a value's memory usage in bytes. path defaults to root if not provided. + /// + /// The object's key + /// The path within the object. + /// the value's size in bytes. + Task DebugMemoryAsync(string key, string? path = null); } \ No newline at end of file diff --git a/src/NRedisStack/Json/JsonCommands.cs b/src/NRedisStack/Json/JsonCommands.cs index c789e2b2..78fa6627 100644 --- a/src/NRedisStack/Json/JsonCommands.cs +++ b/src/NRedisStack/Json/JsonCommands.cs @@ -154,6 +154,343 @@ public long DebugMemory(string key, string? path = null) return (long)_db.Execute(JSON.DEBUG, JSON.MEMORY, key); } + public async Task ArrAppendAsync(RedisKey key, string? path = null, params object[] values) + { + if (values.Length < 1) + throw new ArgumentOutOfRangeException(nameof(values)); + + var args = new List { key }; + if (path != null) + { + args.Add(path); + } + + args.AddRange(values.Select(x => JsonSerializer.Serialize(x))); + + var result = await _db.ExecuteAsync(JSON.ARRAPPEND, args.ToArray()); + return result.ToNullableLongArray(); + } + + public async Task ArrIndexAsync(RedisKey key, string path, object value, long? start = null, long? stop = null) + { + if (start == null && stop != null) + throw new ArgumentException("stop cannot be defined without start"); + + var args = AssembleNonNullArguments(key, path, JsonSerializer.Serialize(value), start, stop); + var result = await _db.ExecuteAsync(JSON.ARRINDEX, args); + return result.ToNullableLongArray(); + } + + public async Task ArrInsertAsync(RedisKey key, string path, long index, params object[] values) + { + if (values.Length < 1) + throw new ArgumentOutOfRangeException(nameof(values)); + var args = new List { key, path, index }; + foreach (var val in values) + { + args.Add(JsonSerializer.Serialize(val)); + } + + var result = await _db.ExecuteAsync(JSON.ARRINSERT, args); + return result.ToNullableLongArray(); + } + + public async Task ArrLenAsync(RedisKey key, string? path = null) + { + var args = AssembleNonNullArguments(key, path); + var result = await _db.ExecuteAsync(JSON.ARRLEN, args); + return result.ToNullableLongArray(); + } + + public async Task ArrPopAsync(RedisKey key, string? path = null, long? index = null) + { + if (path == null && index != null) + throw new ArgumentException("index cannot be defined without path"); + + var args = AssembleNonNullArguments(key, path, index); + var res = await _db.ExecuteAsync(JSON.ARRPOP, args)!; + + if (res.Type == ResultType.MultiBulk) + { + return (RedisResult[])res!; + } + + if (res.Type == ResultType.BulkString) + { + return new[] { res }; + } + + return Array.Empty(); + } + + public async Task ArrTrimAsync(RedisKey key, string path, long start, long stop) => + (await _db.ExecuteAsync(JSON.ARRTRIM, key, path, start, stop)).ToNullableLongArray(); + + public async Task ClearAsync(RedisKey key, string? path = null) + { + var args = AssembleNonNullArguments(key, path); + return (long)await _db.ExecuteAsync(JSON.CLEAR, args); + } + + public async Task DelAsync(RedisKey key, string? path = null) + { + var args = AssembleNonNullArguments(key, path); + return (long)await _db.ExecuteAsync(JSON.DEL, args); + } + + public Task ForgetAsync(RedisKey key, string? path = null) => DelAsync(key, path); + + public Task GetAsync(RedisKey key, RedisValue? indent = null, RedisValue? newLine = null, RedisValue? space = null, + RedisValue? path = null) + { + List args = new List() { key }; + + if (indent != null) + { + args.Add(JsonArgs.INDENT); + args.Add(indent); + } + + if (newLine != null) + { + args.Add(JsonArgs.NEWLINE); + args.Add(newLine); + } + + if (space != null) + { + args.Add(JsonArgs.SPACE); + args.Add(space); + } + + if (path != null) + { + args.Add(path); + } + + return _db.ExecuteAsync(JSON.GET, args); + } + + public Task GetAsync(RedisKey key, string[] paths, RedisValue? indent = null, RedisValue? newLine = null, + RedisValue? space = null) + { + List args = new List() { key }; + + foreach (var path in paths) + { + args.Add(path); + } + + if (indent != null) + { + args.Add(JsonArgs.INDENT); + args.Add(indent); + } + + if (newLine != null) + { + args.Add(JsonArgs.NEWLINE); + args.Add(newLine); + } + + if (space != null) + { + args.Add(JsonArgs.SPACE); + args.Add(space); + } + + return _db.ExecuteAsync(JSON.GET, args); + } + + public async Task GetAsync(RedisKey key, string path = "$") + { + var res = await _db.ExecuteAsync(JSON.GET, key, path); + if (res.Type == ResultType.BulkString) + { + var arr = JsonSerializer.Deserialize(res.ToString()!); + if (arr?.Count > 0) + { + return JsonSerializer.Deserialize(JsonSerializer.Serialize(arr[0])); + } + } + + return default; + } + + public Task> GetEnumerableAsync(RedisKey key, string path = "$") + { + throw new NotImplementedException(); + } + + public async Task MGetAsync(RedisKey[] keys, string path) + { + var args = new List(); + foreach (var key in keys) + { + args.Add(key); + } + + args.Add(path); + var res = await _db.ExecuteAsync(JSON.MGET, args); + if (res.IsNull) + { + return Array.Empty(); + } + return (RedisResult[])res!; + } + + public async Task NumIncrbyAsync(RedisKey key, string path, double value) + { + var res = await _db.ExecuteAsync(JSON.NUMINCRBY, key, path, value); + return JsonSerializer.Deserialize(res.ToString()); + } + + public async Task>> ObjKeysAsync(RedisKey key, string? path = null) + { + var args = AssembleNonNullArguments(key, path); + return (await _db.ExecuteAsync(JSON.OBJKEYS, args)).ToHashSets(); + } + + public async Task ObjLenAsync(RedisKey key, string? path = null) + { + var args = AssembleNonNullArguments(key, path); + return (await _db.ExecuteAsync(JSON.OBJLEN, args)).ToNullableLongArray(); + } + + public async Task RespAsync(RedisKey key, string? path = null) + { + RedisResult result; + if (string.IsNullOrEmpty(path)) + { + result = await _db.ExecuteAsync(JSON.RESP, key); + } + else + { + result = await _db.ExecuteAsync(JSON.RESP, key, path); + } + + if (result.IsNull) + { + return Array.Empty(); + } + + return (RedisResult[])result!; + } + + public Task SetAsync(RedisKey key, RedisValue path, object obj, When when = When.Always) + { + string json = JsonSerializer.Serialize(obj); + return SetAsync(key, path, json, when); + } + + public async Task SetAsync(RedisKey key, RedisValue path, RedisValue json, When when = When.Always) + { + var t = when switch + { + When.Exists => _db.ExecuteAsync(JSON.SET, key, path, json, "XX"), + When.NotExists => _db.ExecuteAsync(JSON.SET, key, path, json, "NX"), + _ => _db.ExecuteAsync(JSON.SET, key, path, json) + }; + + var result = await t; + + if (result.IsNull || result.ToString() != "OK") + { + return false; + } + + return true; + } + + public async Task StrAppendAsync(RedisKey key, string value, string? path = null) + { + RedisResult result; + if (path == null) + { + result = await _db.ExecuteAsync(JSON.STRAPPEND, key, JsonSerializer.Serialize(value)); + } + else + { + result = await _db.ExecuteAsync(JSON.STRAPPEND, key, path, JsonSerializer.Serialize(value)); + } + + return result.ToNullableLongArray(); + } + + public async Task StrLenAsync(RedisKey key, string? path = null) + { + RedisResult result; + if (path != null) + { + result = await _db.ExecuteAsync(JSON.STRLEN, key, path); + } + else + { + result = await _db.ExecuteAsync(JSON.STRLEN, key); + } + + return result.ToNullableLongArray(); + } + + public async Task ToggleAsync(RedisKey key, string? path = null) + { + RedisResult result; + if (path != null) + { + result = await _db.ExecuteAsync(JSON.TOGGLE, key, path); + } + else + { + result = await _db.ExecuteAsync(JSON.TOGGLE, key, "$"); + } + + if (result.IsNull) + { + return Array.Empty(); + } + + if (result.Type == ResultType.Integer) + { + return new bool?[] { (long)result == 1 }; + } + + return ((RedisResult[])result!).Select(x => (bool?)((long)x == 1)).ToArray(); + } + + public async Task TypeAsync(RedisKey key, string? path = null) + { + RedisResult result; + if (path == null) + { + result = await _db.ExecuteAsync(JSON.TYPE, key); + } + else + { + result = await _db.ExecuteAsync(JSON.TYPE, key, path); + } + + if (result.Type == ResultType.MultiBulk) + { + return ((RedisResult[])result!).Select(x => Enum.Parse(x.ToString()!.ToUpper())).ToArray(); + } + + if (result.Type == ResultType.BulkString) + { + return new[] { Enum.Parse(result.ToString()!.ToUpper()) }; + } + + return Array.Empty(); + } + + public async Task DebugMemoryAsync(string key, string? path = null) + { + if (path != null) + { + return (long)await _db.ExecuteAsync(JSON.DEBUG, JSON.MEMORY, key, path); + } + return (long)await _db.ExecuteAsync(JSON.DEBUG, JSON.MEMORY, key); + } + /// public long?[] ArrAppend(RedisKey key, string? path = null, params object[] values) { @@ -357,31 +694,8 @@ public RedisResult[] MGet(RedisKey[] keys, string path) /// public IEnumerable> ObjKeys(RedisKey key, string? path = null) { - var sets = new List>(); var args = AssembleNonNullArguments(key, path); - var res = (RedisResult[])_db.Execute(JSON.OBJKEYS, args)!; - if (res.All(x => x.Type != ResultType.MultiBulk)) - { - var keys = res.Select(x => x.ToString()!); - sets.Add(keys.ToHashSet()); - return sets; - } - - foreach (var result in res) - { - var set = new HashSet(); - if (result.Type == ResultType.MultiBulk) - { - var resultArr = (RedisResult[])result!; - foreach (var item in resultArr) - { - set.Add(item.ToString()!); - } - } - sets.Add(set); - } - - return sets; + return _db.Execute(JSON.OBJKEYS, args).ToHashSets(); } /// diff --git a/src/NRedisStack/ResponseParser.cs b/src/NRedisStack/ResponseParser.cs index 22916106..5e9f87d4 100644 --- a/src/NRedisStack/ResponseParser.cs +++ b/src/NRedisStack/ResponseParser.cs @@ -546,5 +546,38 @@ public static IReadOnlyList ToStringArray(this RedisResult result) return ((RedisResult[])result!).Select(x=>(long?)x).ToArray(); } + + public static IEnumerable> ToHashSets(this RedisResult result) + { + if (result.IsNull) + { + return Array.Empty>(); + } + + var res = (RedisResult[])result!; + var sets = new List>(); + if (res.All(x => x.Type != ResultType.MultiBulk)) + { + var keys = res.Select(x => x.ToString()!); + sets.Add(keys.ToHashSet()); + return sets; + } + + foreach (var arr in res) + { + var set = new HashSet(); + if (arr.Type == ResultType.MultiBulk) + { + var resultArr = (RedisResult[])arr!; + foreach (var item in resultArr) + { + set.Add(item.ToString()!); + } + } + sets.Add(set); + } + + return sets; + } } } \ No newline at end of file diff --git a/tests/NRedisStack.Tests/Json/JsonTests.cs b/tests/NRedisStack.Tests/Json/JsonTests.cs index d8bd961b..b35fb9ab 100644 --- a/tests/NRedisStack.Tests/Json/JsonTests.cs +++ b/tests/NRedisStack.Tests/Json/JsonTests.cs @@ -20,14 +20,6 @@ public void Dispose() redisFixture.Redis.GetDatabase().KeyDelete(_testName); } - // [Fact] - // public void TestJsonSet() - // { - // var obj = new Person { Name = "Shachar", Age = 23 }; - // _mock.Object.JSON().Set("Person:Shachar", "$", obj, When.Exists); - // _mock.Verify(x => x.Execute("JSON.SET", "Person:Shachar", "$", "{\"Name\":\"Shachar\",\"Age\":23}", "XX")); - // } - [Fact] public void TestJsonSetNotExist() { @@ -37,42 +29,6 @@ public void TestJsonSetNotExist() _mock.Verify(x => x.Execute("JSON.SET", "Person:Shachar", "$", "{\"Name\":\"Shachar\",\"Age\":23}", "NX")); } - //TODO: understand why this 2 tests are not pass what we do - //"dotnet test" but they pass when we do "dotnet test --filter ..." - // [Fact] - // public void TestSimpleJsonGet() - // { - // var obj = new Person { Name = "Shachar", Age = 23 }; - // IDatabase db = redisFixture.Redis.GetDatabase(); - // db.Execute("FLUSHALL"); - // var cf = db.JSON(); - - // json.Set(key, "$", obj); - // string expected = "{\"Name\":\"Shachar\",\"Age\":23}"; - // var result = json.Get(key).ToString(); - // if(result == null) - // throw new ArgumentNullException(nameof(result)); - - // Assert.Equal(result, expected); - // } - - // [Fact] - // public void TestJsonGet() - // { - // var obj = new Person { Name = "Shachar", Age = 23 }; - // IDatabase db = redisFixture.Redis.GetDatabase(); - // db.Execute("FLUSHALL"); - // var cf = db.JSON(); - - // json.Set(key, "$", obj); - - // var expected = "[222111\"Shachar\"222]"; - // var result = json.Get(key, "111", "222", "333", "$.Name"); - // // if(result == null) - // // throw new ArgumentNullException(nameof(result)); - // Assert.Equal(result.ToString(), expected); - // } - [Fact] public void TestModulePrefixs() { @@ -131,6 +87,31 @@ public void TestResp() Assert.Equal(33, (long)respResult[i]!); conn.GetDatabase().KeyDelete(key); } + + [Fact] + public async Task TestRespAsync() + { + //arrange + var conn = redisFixture.Redis; + var db = conn.GetDatabase(); + IJsonCommands commands = new JsonCommands(db); + var keys = CreateKeyNames(1); + + var key = keys[0]; + await commands.SetAsync(key, "$", new { name = "Steve", age = 33 }); + + //act + var respResult = await commands.RespAsync(key); + + //assert + var i = 0; + Assert.Equal("{", respResult[i++]!.ToString()); + Assert.Equal("name", respResult[i++]!.ToString()); + Assert.Equal("Steve", respResult[i++]!.ToString()); + Assert.Equal("age", respResult[i++]!.ToString()); + Assert.Equal(33, (long)respResult[i]!); + conn.GetDatabase().KeyDelete(key); + } [Fact] public void TestStringAppend() @@ -155,7 +136,35 @@ public void TestStringAppend() var i = 0; Assert.Equal(2, keyResult.Length); Assert.Equal(13, keyResult[i++]); - Assert.Equal(19, keyResult[i++]); + Assert.Equal(19, keyResult[i]); + Assert.Null(nullResult[0]); + Assert.Equal(6, simpleKeyResult[0]); + } + + [Fact] + public async Task TestStringAppendAsync() + { + //arrange + var conn = redisFixture.Redis; + var db = conn.GetDatabase(); + IJsonCommands commands = new JsonCommands(db); + var keys = CreateKeyNames(2); + + var key = keys[0]; + await commands.SetAsync(key, "$", new { name = "Steve", sibling = new {name = "christopher"}, age = 33}); + var simpleStringKey = keys[1]; + await commands.SetAsync(simpleStringKey, "$", "\"foo\""); + + //act + var nullResult = await commands.StrAppendAsync(key, " Lorello", "$.age"); + var keyResult = await commands.StrAppendAsync(key, " Lorello", "$..name"); + var simpleKeyResult = await commands.StrAppendAsync(simpleStringKey, "bar"); + + //assert + var i = 0; + Assert.Equal(2, keyResult.Length); + Assert.Equal(13, keyResult[i++]); + Assert.Equal(19, keyResult[i]); Assert.Null(nullResult[0]); Assert.Equal(6, simpleKeyResult[0]); } @@ -184,6 +193,31 @@ public void StringLength() Assert.Null(nullResult[0]); Assert.Equal(3,simpleResult[0]); } + + [Fact] + public async Task StringLengthAsync() + { + //arrange + var conn = redisFixture.Redis; + var db = conn.GetDatabase(); + IJsonCommands commands = new JsonCommands(db); + var keys = CreateKeyNames(2); + var key = keys[0]; + var simpleStringKey = keys[1]; + + await commands.SetAsync(key, "$", new { name = "Steve", sibling = new {name = "christopher"}, age = 33}); + await commands.SetAsync(simpleStringKey, "$", "\"foo\""); + + var normalResult = await commands.StrLenAsync(key, "$..name"); + var nullResult = await commands.StrLenAsync(key, "$.age"); + var simpleResult = await commands.StrLenAsync(simpleStringKey); + + var i = 0; + Assert.Equal(5, normalResult[i++]); + Assert.Equal(11, normalResult[i]); + Assert.Null(nullResult[0]); + Assert.Equal(3,simpleResult[0]); + } [Fact] public void Toggle() @@ -206,6 +240,28 @@ public void Toggle() Assert.True(result[1]); Assert.False(simpleResult[0]); } + + [Fact] + public async Task ToggleAsync() + { + //arrange + var conn = redisFixture.Redis; + var db = conn.GetDatabase(); + IJsonCommands commands = new JsonCommands(db); + var keys = CreateKeyNames(2); + var key = keys[0]; + var simpleKey = keys[1]; + + await commands.SetAsync(key, "$", new { @bool = true, other = new {@bool = false}, age = 33}); + await commands.SetAsync(simpleKey, "$", true); + + var result = await commands.ToggleAsync(key, "$..bool"); + var simpleResult = await commands.ToggleAsync(simpleKey); + + Assert.False(result[0]); + Assert.True(result[1]); + Assert.False(simpleResult[0]); + } [Fact] public void Type() @@ -230,6 +286,30 @@ public void Type() result = commands.Type(simpleKey); Assert.Equal(JsonType.BOOLEAN, result[0]); } + + [Fact] + public async Task TypeAsync() + { + //arrange + var conn = redisFixture.Redis; + var db = conn.GetDatabase(); + IJsonCommands commands = new JsonCommands(db); + var keys = CreateKeyNames(2); + var key = keys[0]; + var simpleKey = keys[1]; + await commands.SetAsync(key, "$", new { name = "Steve", sibling = new {name = "christopher"}, age = 33, aDouble = 3.5}); + await commands.SetAsync(simpleKey, "$", "true"); + + var result = await commands.TypeAsync(key, "$..name"); + Assert.Equal(JsonType.STRING, result[0]); + Assert.Equal(JsonType.STRING, result[1]); + result = await commands.TypeAsync(key, "$..age"); + Assert.Equal(JsonType.INTEGER, result[0]); + result = await commands.TypeAsync(key, "$..aDouble"); + Assert.Equal(JsonType.NUMBER, result[0]); + result = await commands.TypeAsync(simpleKey); + Assert.Equal(JsonType.BOOLEAN, result[0]); + } [Fact] public void ArrayAppend() @@ -248,6 +328,24 @@ public void ArrayAppend() result = commands.ArrAppend(complexKey, "$.people", new { name = "bob" }); Assert.Equal(2, result[0]); } + + [Fact] + public async Task ArrayAppendAsync() + { + var conn = redisFixture.Redis; + var db = conn.GetDatabase(); + IJsonCommands commands = new JsonCommands(db); + var keys = CreateKeyNames(2); + var key = keys[0]; + var complexKey = keys[1]; + + await commands.SetAsync(key, "$", new { name = "Elizabeth", nickNames = new[] { "Beth" } }); + await commands.SetAsync(complexKey, "$", new { name = "foo", people = new[] { new { name = "steve" } } }); + var result = await commands.ArrAppendAsync(key, "$.nickNames", "Elle", "Liz","Betty"); + Assert.Equal(4, result[0]); + result = await commands.ArrAppendAsync(complexKey, "$.people", new { name = "bob" }); + Assert.Equal(2, result[0]); + } [Fact] public void ArrayIndex() @@ -262,6 +360,20 @@ public void ArrayIndex() Assert.Equal(1,res[0]); Assert.Equal(-1,res[1]); } + + [Fact] + public async Task ArrayIndexAsync() + { + var conn = redisFixture.Redis; + var db = conn.GetDatabase(); + IJsonCommands commands = new JsonCommands(db); + var keys = CreateKeyNames(1); + var key = keys[0]; + await commands.SetAsync(key, "$", new { name = "Elizabeth", nicknames = new[] { "Beth", "Betty", "Liz" }, sibling = new {name="Johnathan", nicknames = new [] {"Jon", "Johnny"}} }); + var res = await commands.ArrIndexAsync(key, "$..nicknames", "Betty", 0,5); + Assert.Equal(1,res[0]); + Assert.Equal(-1,res[1]); + } [Fact] public void ArrayInsert() @@ -279,6 +391,23 @@ public void ArrayInsert() result = commands.ArrInsert(simpleKey, "$", 1, "Lys"); Assert.Equal(4, result[0]); } + + [Fact] + public async Task ArrayInsertAsync() + { + IJsonCommands commands = new JsonCommands(redisFixture.Redis.GetDatabase()); + var keys = CreateKeyNames(2); + var key = keys[0]; + var simpleKey = keys[1]; + + await commands.SetAsync(key, "$", new { name = "Alice", nicknames = new[] { "Al", "Ali", "Ally" } }); + await commands.SetAsync(simpleKey, "$", new[] { "Al", "Ali", "Ally" }); + + var result = await commands.ArrInsertAsync(key, $"$.nicknames", 1, "Lys"); + Assert.Equal(4,result[0]); + result = await commands.ArrInsertAsync(simpleKey, "$", 1, "Lys"); + Assert.Equal(4, result[0]); + } [Fact] public void ArrayLength() @@ -295,6 +424,22 @@ public void ArrayLength() result = commands.ArrLen(simpleKey); Assert.Equal(3, result[0]); } + + [Fact] + public async Task ArrayLengthAsync() + { + IJsonCommands commands = new JsonCommands(redisFixture.Redis.GetDatabase()); + var keys = CreateKeyNames(2); + var key = keys[0]; + var simpleKey = keys[1]; + await commands.SetAsync(key, "$", new { name = "Alice", nicknames = new[] { "Al", "Ali", "Ally" } }); + await commands.SetAsync(simpleKey, "$", new[] { "Al", "Ali", "Ally" }); + + var result = await commands.ArrLenAsync(key, $"$.nicknames"); + Assert.Equal(3, result[0]); + result = await commands.ArrLenAsync(simpleKey); + Assert.Equal(3, result[0]); + } [Fact] public void ArrayPop() @@ -329,6 +474,22 @@ public void ArrayTrim() result = commands.ArrTrim(simpleKey, "$", 0, 1); Assert.Equal(2,result[0]); } + + [Fact] + public async Task ArrayTrimAsync() + { + IJsonCommands commands = new JsonCommands(redisFixture.Redis.GetDatabase()); + var keys = CreateKeyNames(2); + var key = keys[0]; + var simpleKey = keys[1]; + await commands.SetAsync(key, "$", new { name = "Alice", nicknames = new[] { "Al", "Ali", "Ally" } }); + await commands.SetAsync(simpleKey, "$", new[] { "Al", "Ali", "Ally" }); + + var result = await commands.ArrTrimAsync(key, "$.nicknames", 0, 0); + Assert.Equal(1,result[0]); + result = await commands.ArrTrimAsync(simpleKey, "$", 0, 1); + Assert.Equal(2,result[0]); + } [Fact] public void Clear() @@ -345,6 +506,22 @@ public void Clear() result = commands.Clear(simpleKey); Assert.Equal(1,result); } + + [Fact] + public async Task ClearAsync() + { + IJsonCommands commands = new JsonCommands(redisFixture.Redis.GetDatabase()); + var keys = CreateKeyNames(2); + var key = keys[0]; + var simpleKey = keys[1]; + await commands.SetAsync(key, "$", new { name = "Alice", nicknames = new[] { "Al", "Ali", "Ally" } }); + await commands.SetAsync(simpleKey, "$", new[] { "Al", "Ali", "Ally" }); + + var result = await commands.ClearAsync(key, "$.nicknames"); + Assert.Equal(1,result); + result = await commands.ClearAsync(simpleKey); + Assert.Equal(1,result); + } [Fact] public void Del() @@ -361,6 +538,22 @@ public void Del() result = commands.Del(simpleKey); Assert.Equal(1,result); } + + [Fact] + public async Task DelAsync() + { + IJsonCommands commands = new JsonCommands(redisFixture.Redis.GetDatabase()); + var keys = CreateKeyNames(2); + var key = keys[0]; + var simpleKey = keys[1]; + await commands.SetAsync(key, "$", new { name = "Alice", nicknames = new[] { "Al", "Ali", "Ally" } }); + await commands.SetAsync(simpleKey, "$", new[] { "Al", "Ali", "Ally" }); + + var result = await commands.DelAsync(key, "$.nicknames"); + Assert.Equal(1,result); + result = await commands.DelAsync(simpleKey); + Assert.Equal(1,result); + } [Fact] public void Forget() @@ -377,6 +570,22 @@ public void Forget() result = commands.Forget(simpleKey); Assert.Equal(1,result); } + + [Fact] + public async Task ForgetAsync() + { + IJsonCommands commands = new JsonCommands(redisFixture.Redis.GetDatabase()); + var keys = CreateKeyNames(2); + var key = keys[0]; + var simpleKey = keys[1]; + await commands.SetAsync(key, "$", new { name = "Alice", nicknames = new[] { "Al", "Ali", "Ally" } }); + await commands.SetAsync(simpleKey, "$", new[] { "Al", "Ali", "Ally" }); + + var result = await commands.ForgetAsync(key, "$.nicknames"); + Assert.Equal(1,result); + result = await commands.ForgetAsync(simpleKey); + Assert.Equal(1,result); + } [Fact] public void Get() @@ -397,6 +606,26 @@ public void Get() Assert.Equal("Alice", people[1]!.Name); Assert.Equal(35, people[1]!.Age); } + + [Fact] + public async Task GetAsync() + { + IJsonCommands commands = new JsonCommands(redisFixture.Redis.GetDatabase()); + var keys = CreateKeyNames(2); + var key = keys[0]; + var complexKey = keys[1]; + await commands.SetAsync(key, "$", new Person(){Age = 35, Name = "Alice"}); + await commands.SetAsync(complexKey, "$", new {a=new Person(){Age = 35, Name = "Alice"}, b = new {a = new Person(){Age = 35, Name = "Alice"}}}); + var result = await commands.GetAsync(key); + Assert.Equal("Alice", result!.Name); + Assert.Equal(35, result.Age); + var people = commands.GetEnumerable(complexKey, "$..a").ToArray(); + Assert.Equal(2, people.Length); + Assert.Equal("Alice", people[0]!.Name); + Assert.Equal(35, people[0]!.Age); + Assert.Equal("Alice", people[1]!.Name); + Assert.Equal(35, people[1]!.Age); + } [Fact] public void MGet() @@ -412,6 +641,21 @@ public void MGet() Assert.Equal("[\"hello\"]", result[0].ToString()); Assert.Equal("[\"world\"]", result[1].ToString()); } + + [Fact] + public async Task MGetAsync() + { + IJsonCommands commands = new JsonCommands(redisFixture.Redis.GetDatabase()); + var keys = CreateKeyNames(2); + var key1 = keys[0]; + var key2 = keys[1]; + await commands.SetAsync(key1, "$", new { a = "hello" }); + await commands.SetAsync(key2, "$", new { a = "world" }); + var result = await commands.MGetAsync(keys.Select(x => new RedisKey(x)).ToArray(), "$.a"); + + Assert.Equal("[\"hello\"]", result[0].ToString()); + Assert.Equal("[\"world\"]", result[1].ToString()); + } [Fact] public void NumIncrby() @@ -425,6 +669,19 @@ public void NumIncrby() Assert.Equal(36, result[1]); Assert.Null(result[2]); } + + [Fact] + public async Task NumIncrbyAsync() + { + IJsonCommands commands = new JsonCommands(redisFixture.Redis.GetDatabase()); + var keys = CreateKeyNames(1); + var key = keys[0]; + await commands.SetAsync(key, "$", new { age = 33, a = new { age = 34 }, b = new {age = "cat"} }); + var result = await commands.NumIncrbyAsync(key, "$..age", 2); + Assert.Equal(35, result[0]); + Assert.Equal(36, result[1]); + Assert.Null(result[2]); + } [Fact] public void ObjectKeys() @@ -443,6 +700,24 @@ public void ObjectKeys() Assert.Contains("a", result[1]); Assert.Contains("b", result[1]); } + + [Fact] + public async Task ObjectKeysAsync() + { + IJsonCommands commands = new JsonCommands(redisFixture.Redis.GetDatabase()); + var keys = CreateKeyNames(3); + var key = keys[0]; + await commands.SetAsync(key, "$", new { a = 5, b = 10, c = "hello", d = new { a = new { a = 6, b = "hello" }, b = 7 } }); + var result = (await commands.ObjKeysAsync(key)).ToArray(); + Assert.Contains("a", result[0]); + Assert.Contains("b", result[0]); + Assert.Contains("c", result[0]); + Assert.Contains("d", result[0]); + result = (await commands.ObjKeysAsync(key, "$..a")).ToArray(); + Assert.Empty(result[0]); + Assert.Contains("a", result[1]); + Assert.Contains("b", result[1]); + } [Fact] public void ObjectLength() @@ -459,6 +734,22 @@ public void ObjectLength() Assert.Null(result[2]); } + + [Fact] + public async Task ObjectLengthAsync() + { + IJsonCommands commands = new JsonCommands(redisFixture.Redis.GetDatabase()); + var keys = CreateKeyNames(3); + var key = keys[0]; + await commands.SetAsync(key, "$", new { a = 5, b = 10, c = "hello", d = new { a = new { a = 6, b = "hello" }, b = 7 } }); + var result = await commands.ObjLenAsync(key); + Assert.Equal(4, result[0]); + result = await commands.ObjLenAsync(key, $"$..a"); + Assert.Null(result[0]); + Assert.Equal(2, result[1]); + Assert.Null(result[2]); + + } [Fact] public void TestMultiPathGet() @@ -483,6 +774,30 @@ public void TestMultiPathGet() Assert.True(obj["$.b"]![0]!["a"]!.ToString() == "world"); } + + [Fact] + public async Task TestMultiPathGetAsync() + { + IJsonCommands commands = new JsonCommands(redisFixture.Redis.GetDatabase()); + var keys = CreateKeyNames(1); + var key = keys[0]; + await commands.SetAsync(key, "$", new { a = "hello", b = new { a = "world" } }); + var res = (await commands.GetAsync(key, new[] { "$..a", "$.b" })).ToString(); + var obj = JsonSerializer.Deserialize(res); + Assert.True(obj.ContainsKey("$..a")); + Assert.True(obj.ContainsKey("$.b")); + if (obj["$..a"] is JsonArray arr) + { + Assert.Equal("hello", arr[0]!.ToString()); + Assert.Equal("world", arr[1]!.ToString()); + } + else + { + Assert.True(false, "$..a was not a json array"); + } + + Assert.True(obj["$.b"]![0]!["a"]!.ToString() == "world"); + } [Fact] public void Memory() @@ -497,4 +812,18 @@ public void Memory() res = commands.DebugMemory("non-existent key"); Assert.Equal(0,res); } + + [Fact] + public async Task MemoryAsync() + { + IJsonCommands commands = new JsonCommands(redisFixture.Redis.GetDatabase()); + var keys = CreateKeyNames(1); + var key = keys[0]; + + await commands.SetAsync(key, "$", new {a="hello", b=new {a="world"}}); + var res = await commands.DebugMemoryAsync(key); + Assert.Equal(45, res); + res = await commands.DebugMemoryAsync("non-existent key"); + Assert.Equal(0,res); + } } \ No newline at end of file