From 0d365a62f94ae28001f281fc7a4b6c0fa56fbb2e Mon Sep 17 00:00:00 2001 From: Vijay-Nirmal Date: Sat, 2 Nov 2024 19:17:37 +0530 Subject: [PATCH 1/5] Added BRPOPLPUSH, ZREVRANGEBYLEX deprecated commands by mapping to existing commends --- libs/resources/RespCommandsDocs.json | 82 +++++++++++++++++++ libs/resources/RespCommandsInfo.json | 63 ++++++++++++++ libs/server/Resp/CmdStrings.cs | 4 + libs/server/Resp/Objects/ListCommands.cs | 21 +++++ libs/server/Resp/Objects/SortedSetCommands.cs | 47 +++++++++++ libs/server/Resp/Parser/RespCommand.cs | 10 +++ libs/server/Resp/RespServerSession.cs | 2 + .../CommandInfoUpdater/SupportedCommand.cs | 2 + .../RedirectTests/BaseCommand.cs | 47 +++++++++++ .../ClusterSlotVerificationTests.cs | 14 ++++ test/Garnet.test/Resp/ACL/RespCommandTests.cs | 30 +++++++ test/Garnet.test/RespBlockingListTests.cs | 20 +++++ test/Garnet.test/RespListTests.cs | 44 ++++++++++ website/docs/commands/api-compatibility.md | 6 +- website/docs/commands/data-structures.md | 32 ++++++++ website/docs/commands/server.md | 17 ++++ 16 files changed, 438 insertions(+), 3 deletions(-) diff --git a/libs/resources/RespCommandsDocs.json b/libs/resources/RespCommandsDocs.json index 426502ee71..22bd48aca0 100644 --- a/libs/resources/RespCommandsDocs.json +++ b/libs/resources/RespCommandsDocs.json @@ -684,6 +684,37 @@ } ] }, + { + "Command": "BRPOPLPUSH", + "Name": "BRPOPLPUSH", + "Summary": "Pops an element from a list, pushes it to another list and returns it. Block until an element is available otherwise. Deletes the list if the last element was popped.", + "Group": "List", + "Complexity": "O(1)", + "DocFlags": "Deprecated", + "ReplacedBy": "\u0060BLMOVE\u0060 with the \u0060RIGHT\u0060 and \u0060LEFT\u0060 arguments", + "Arguments": [ + { + "TypeDiscriminator": "RespCommandKeyArgument", + "Name": "SOURCE", + "DisplayText": "source", + "Type": "Key", + "KeySpecIndex": 0 + }, + { + "TypeDiscriminator": "RespCommandKeyArgument", + "Name": "DESTINATION", + "DisplayText": "destination", + "Type": "Key", + "KeySpecIndex": 1 + }, + { + "TypeDiscriminator": "RespCommandBasicArgument", + "Name": "TIMEOUT", + "DisplayText": "timeout", + "Type": "Double" + } + ] + }, { "Command": "CLIENT", "Name": "CLIENT", @@ -5872,6 +5903,57 @@ } ] }, + { + "Command": "ZREVRANGEBYLEX", + "Name": "ZREVRANGEBYLEX", + "Summary": "Returns members in a sorted set within a lexicographical range in reverse order.", + "Group": "SortedSet", + "Complexity": "O(log(N)\u002BM) with N being the number of elements in the sorted set and M the number of elements being returned. If M is constant (e.g. always asking for the first 10 elements with LIMIT), you can consider it O(log(N)).", + "DocFlags": "Deprecated", + "ReplacedBy": "\u0060ZRANGE\u0060 with the \u0060REV\u0060 and \u0060BYLEX\u0060 arguments", + "Arguments": [ + { + "TypeDiscriminator": "RespCommandKeyArgument", + "Name": "KEY", + "DisplayText": "key", + "Type": "Key", + "KeySpecIndex": 0 + }, + { + "TypeDiscriminator": "RespCommandBasicArgument", + "Name": "MAX", + "DisplayText": "max", + "Type": "String" + }, + { + "TypeDiscriminator": "RespCommandBasicArgument", + "Name": "MIN", + "DisplayText": "min", + "Type": "String" + }, + { + "TypeDiscriminator": "RespCommandContainerArgument", + "Name": "LIMIT", + "Type": "Block", + "Token": "LIMIT", + "ArgumentFlags": "Optional", + "Arguments": [ + { + "TypeDiscriminator": "RespCommandBasicArgument", + "Name": "OFFSET", + "DisplayText": "offset", + "Type": "Integer" + }, + { + "TypeDiscriminator": "RespCommandBasicArgument", + "Name": "COUNT", + "DisplayText": "count", + "Type": "Integer" + } + ] + } + ] + }, { "Command": "ZREVRANGEBYSCORE", "Name": "ZREVRANGEBYSCORE", diff --git a/libs/resources/RespCommandsInfo.json b/libs/resources/RespCommandsInfo.json index 2e7732a60e..16b934766e 100644 --- a/libs/resources/RespCommandsInfo.json +++ b/libs/resources/RespCommandsInfo.json @@ -355,6 +355,44 @@ } ] }, + { + "Command": "BRPOPLPUSH", + "Name": "BRPOPLPUSH", + "Arity": 4, + "Flags": "Blocking, DenyOom, Write", + "FirstKey": 1, + "LastKey": 2, + "Step": 1, + "AclCategories": "Blocking, List, Slow, Write", + "KeySpecifications": [ + { + "BeginSearch": { + "TypeDiscriminator": "BeginSearchIndex", + "Index": 1 + }, + "FindKeys": { + "TypeDiscriminator": "FindKeysRange", + "LastKey": 0, + "KeyStep": 1, + "Limit": 0 + }, + "Flags": "RW, Access, Delete" + }, + { + "BeginSearch": { + "TypeDiscriminator": "BeginSearchIndex", + "Index": 2 + }, + "FindKeys": { + "TypeDiscriminator": "FindKeysRange", + "LastKey": 0, + "KeyStep": 1, + "Limit": 0 + }, + "Flags": "RW, Insert" + } + ] + }, { "Command": "CLIENT", "Name": "CLIENT", @@ -4476,6 +4514,31 @@ } ] }, + { + "Command": "ZREVRANGEBYLEX", + "Name": "ZREVRANGEBYLEX", + "Arity": -4, + "Flags": "ReadOnly", + "FirstKey": 1, + "LastKey": 1, + "Step": 1, + "AclCategories": "Read, SortedSet, Slow", + "KeySpecifications": [ + { + "BeginSearch": { + "TypeDiscriminator": "BeginSearchIndex", + "Index": 1 + }, + "FindKeys": { + "TypeDiscriminator": "FindKeysRange", + "LastKey": 0, + "KeyStep": 1, + "Limit": 0 + }, + "Flags": "RO, Access" + } + ] + }, { "Command": "ZREVRANGEBYSCORE", "Name": "ZREVRANGEBYSCORE", diff --git a/libs/server/Resp/CmdStrings.cs b/libs/server/Resp/CmdStrings.cs index 4dc7121f11..0ffb4e9930 100644 --- a/libs/server/Resp/CmdStrings.cs +++ b/libs/server/Resp/CmdStrings.cs @@ -108,6 +108,10 @@ static partial class CmdStrings public static ReadOnlySpan CHANNELS => "CHANNELS"u8; public static ReadOnlySpan NUMPAT => "NUMPAT"u8; public static ReadOnlySpan NUMSUB => "NUMSUB"u8; + public static ReadOnlySpan RIGHT => "RIGHT"u8; + public static ReadOnlySpan LEFT => "LEFT"u8; + public static ReadOnlySpan BYLEX => "BYLEX"u8; + public static ReadOnlySpan REV => "REV"u8; /// /// Response strings diff --git a/libs/server/Resp/Objects/ListCommands.cs b/libs/server/Resp/Objects/ListCommands.cs index 62be648670..4a64c23126 100644 --- a/libs/server/Resp/Objects/ListCommands.cs +++ b/libs/server/Resp/Objects/ListCommands.cs @@ -380,6 +380,27 @@ private unsafe bool ListBlockingMove(RespCommand command) return true; } + /// + /// BRPOPLPUSH + /// + /// + private bool ListBlockingPopPush() + { + if (parseState.Count != 3) + { + return AbortWithWrongNumberOfArguments(nameof(RespCommand.BRPOPLPUSH)); + } + + var srcKey = parseState.GetArgSliceByRef(0); + var dstKey = parseState.GetArgSliceByRef(1); + var rightOption = ArgSlice.FromPinnedSpan(CmdStrings.RIGHT); + var leftOption = ArgSlice.FromPinnedSpan(CmdStrings.LEFT); + var timeout = parseState.GetArgSliceByRef(2); + parseState.InitializeWithArguments(srcKey, dstKey, rightOption, leftOption, timeout); + + return ListBlockingMove(RespCommand.BLMOVE); + } + /// /// LLEN key /// Gets the length of the list stored at key. diff --git a/libs/server/Resp/Objects/SortedSetCommands.cs b/libs/server/Resp/Objects/SortedSetCommands.cs index 9a5a9053b4..5a1cf2a881 100644 --- a/libs/server/Resp/Objects/SortedSetCommands.cs +++ b/libs/server/Resp/Objects/SortedSetCommands.cs @@ -193,6 +193,53 @@ private unsafe bool SortedSetRange(RespCommand command, ref TGarnetA return true; } + /// + /// ZREVRANGEBYLEX + /// + /// + /// + /// + private bool ListRangeByLength(ref TGarnetApi storageApi) + where TGarnetApi : IGarnetApi + { + if (parseState.Count is not (3 or 6)) + { + return AbortWithWrongNumberOfArguments(nameof(RespCommand.ZREVRANGEBYLEX)); + } + + + var sbKey = parseState.GetArgSliceByRef(0); + var start = parseState.GetArgSliceByRef(1); + var stop = parseState.GetArgSliceByRef(2); + var byLenOption = ArgSlice.FromPinnedSpan(CmdStrings.BYLEX); + var revOption = ArgSlice.FromPinnedSpan(CmdStrings.REV); + + // ZREVRANGEBYLEX key start stop + if (parseState.Count == 3) + { + parseState.InitializeWithArguments(sbKey, start, stop, byLenOption, revOption); + + return SortedSetRange(RespCommand.ZRANGE, ref storageApi); + } + + // ZREVRANGEBYLEX key start stop [LIMIT offset count] + var limit = parseState.GetArgSliceByRef(3); + var offset = parseState.GetArgSliceByRef(4); + var count = parseState.GetArgSliceByRef(5); + + parseState.Initialize(8); + parseState.SetArgument(0, sbKey); + parseState.SetArgument(1, start); + parseState.SetArgument(2, stop); + parseState.SetArgument(3, byLenOption); + parseState.SetArgument(4, revOption); + parseState.SetArgument(5, limit); + parseState.SetArgument(6, offset); + parseState.SetArgument(7, count); + + return SortedSetRange(RespCommand.ZRANGE, ref storageApi); + } + /// /// Returns the score of member in the sorted set at key. /// If member does not exist in the sorted set, or key does not exist, nil is returned. diff --git a/libs/server/Resp/Parser/RespCommand.cs b/libs/server/Resp/Parser/RespCommand.cs index e9c912ff5f..88eb333c5f 100644 --- a/libs/server/Resp/Parser/RespCommand.cs +++ b/libs/server/Resp/Parser/RespCommand.cs @@ -81,6 +81,7 @@ public enum RespCommand : ushort ZRANGEBYSCORE, ZRANK, ZREVRANGE, + ZREVRANGEBYLEX, ZREVRANGEBYSCORE, ZREVRANK, ZSCAN, @@ -121,6 +122,7 @@ public enum RespCommand : ushort BLPOP, BRPOP, BLMOVE, + BRPOPLPUSH, MIGRATE, MSET, MSETNX, @@ -1365,6 +1367,10 @@ private RespCommand FastParseArrayCommand(ref int count, ref ReadOnlySpan { return RespCommand.ZDIFFSTORE; } + else if (*(ulong*)(ptr + 1) == MemoryMarshal.Read("10\r\nBRPO"u8) && *(uint*)(ptr + 9) == MemoryMarshal.Read("PLPUSH\r\n"u8)) + { + return RespCommand.BRPOPLPUSH; + } break; case 11: @@ -1425,6 +1431,10 @@ private RespCommand FastParseArrayCommand(ref int count, ref ReadOnlySpan { return RespCommand.ZREMRANGEBYLEX; } + else if (*(ulong*)(ptr + 3) == MemoryMarshal.Read("\r\nZREVRA"u8) && *(ulong*)(ptr + 11) == MemoryMarshal.Read("NGEBYLEX"u8) && *(ushort*)(ptr + 19) == MemoryMarshal.Read("\r\n"u8)) + { + return RespCommand.ZREVRANGEBYLEX; + } break; case 15: diff --git a/libs/server/Resp/RespServerSession.cs b/libs/server/Resp/RespServerSession.cs index 89e921c51d..0c82ec3a08 100644 --- a/libs/server/Resp/RespServerSession.cs +++ b/libs/server/Resp/RespServerSession.cs @@ -637,6 +637,7 @@ private bool ProcessArrayCommands(RespCommand cmd, ref TGarnetApi st RespCommand.LLEN => ListLength(ref storageApi), RespCommand.LTRIM => ListTrim(ref storageApi), RespCommand.LRANGE => ListRange(ref storageApi), + RespCommand.ZREVRANGEBYLEX => ListRangeByLength(ref storageApi), RespCommand.LINDEX => ListIndex(ref storageApi), RespCommand.LINSERT => ListInsert(ref storageApi), RespCommand.LREM => ListRemove(ref storageApi), @@ -647,6 +648,7 @@ private bool ProcessArrayCommands(RespCommand cmd, ref TGarnetApi st RespCommand.BLPOP => ListBlockingPop(cmd), RespCommand.BRPOP => ListBlockingPop(cmd), RespCommand.BLMOVE => ListBlockingMove(cmd), + RespCommand.BRPOPLPUSH => ListBlockingPopPush(), // Hash Commands RespCommand.HSET => HashSet(cmd, ref storageApi), RespCommand.HMSET => HashSet(cmd, ref storageApi), diff --git a/playground/CommandInfoUpdater/SupportedCommand.cs b/playground/CommandInfoUpdater/SupportedCommand.cs index 2aaf0ad74b..94b527ed18 100644 --- a/playground/CommandInfoUpdater/SupportedCommand.cs +++ b/playground/CommandInfoUpdater/SupportedCommand.cs @@ -36,6 +36,7 @@ public class SupportedCommand new("BLPOP", RespCommand.BLPOP), new("BRPOP", RespCommand.BRPOP), new("BLMOVE", RespCommand.BLMOVE), + new("BRPOPLPUSH", RespCommand.BRPOPLPUSH), new("CLIENT", RespCommand.CLIENT, [ new("CLIENT|ID", RespCommand.CLIENT_ID), @@ -275,6 +276,7 @@ public class SupportedCommand new("ZREMRANGEBYRANK", RespCommand.ZREMRANGEBYRANK), new("ZREMRANGEBYSCORE", RespCommand.ZREMRANGEBYSCORE), new("ZREVRANGE", RespCommand.ZREVRANGE), + new("ZREVRANGEBYLEX", RespCommand.ZREVRANGEBYLEX), new("ZREVRANGEBYSCORE", RespCommand.ZREVRANGEBYSCORE), new("ZREVRANK", RespCommand.ZREVRANK), new("ZSCAN", RespCommand.ZSCAN), diff --git a/test/Garnet.test.cluster/RedirectTests/BaseCommand.cs b/test/Garnet.test.cluster/RedirectTests/BaseCommand.cs index d1fc00d071..e2b770c408 100644 --- a/test/Garnet.test.cluster/RedirectTests/BaseCommand.cs +++ b/test/Garnet.test.cluster/RedirectTests/BaseCommand.cs @@ -1396,6 +1396,35 @@ public override ArraySegment[] SetupSingleSlotRequest() } } + internal class BRPOPLPUSH : BaseCommand + { + public override bool IsArrayCommand => true; + public override bool ArrayResponse => false; + public override string Command => nameof(BRPOPLPUSH); + + public override string[] GetSingleSlotRequest() + { + var ssk = GetSingleSlotKeys; + return [ssk[0], ssk[1], "1"]; + } + + public override string[] GetCrossSlotRequest() + { + var csk = GetCrossSlotKeys; + return [csk[0], csk[1], "1"]; + } + + public override ArraySegment[] SetupSingleSlotRequest() + { + var ssk = GetSingleSlotKeys; + var setup = new ArraySegment[3]; + setup[0] = new ArraySegment(["LPUSH", ssk[1], "value1", "value2", "value3"]); + setup[1] = new ArraySegment(["LPUSH", ssk[2], "value4", "value5", "value6"]); + setup[2] = new ArraySegment(["LPUSH", ssk[3], "value7", "value8", "value9"]); + return setup; + } + } + internal class LLEN : BaseCommand { public override bool IsArrayCommand => false; @@ -1685,6 +1714,24 @@ public override string[] GetSingleSlotRequest() public override ArraySegment[] SetupSingleSlotRequest() => throw new NotImplementedException(); } + internal class ZREVRANGEBYLEX : BaseCommand + { + public override bool IsArrayCommand => false; + public override bool ArrayResponse => true; + public override string Command => nameof(ZREVRANGEBYLEX); + + public override string[] GetSingleSlotRequest() + { + var ssk = GetSingleSlotKeys; + // ZREVRANGEBYLEX x 0 -1 + return [ssk[0], "0", "-1"]; + } + + public override string[] GetCrossSlotRequest() => throw new NotImplementedException(); + + public override ArraySegment[] SetupSingleSlotRequest() => throw new NotImplementedException(); + } + internal class ZSCORE : BaseCommand { public override bool IsArrayCommand => false; diff --git a/test/Garnet.test.cluster/RedirectTests/ClusterSlotVerificationTests.cs b/test/Garnet.test.cluster/RedirectTests/ClusterSlotVerificationTests.cs index bbd539ed3d..994749b182 100644 --- a/test/Garnet.test.cluster/RedirectTests/ClusterSlotVerificationTests.cs +++ b/test/Garnet.test.cluster/RedirectTests/ClusterSlotVerificationTests.cs @@ -72,6 +72,7 @@ public class ClusterSlotVerificationTests new LMPOP(), new BLPOP(), new BLMOVE(), + new BRPOPLPUSH(), new LLEN(), new LTRIM(), new LRANGE(), @@ -94,6 +95,7 @@ public class ClusterSlotVerificationTests new ZREM(), new ZCARD(), new ZRANGE(), + new ZREVRANGEBYLEX(), new ZSCORE(), new ZMSCORE(), new ZPOPMAX(), @@ -250,6 +252,7 @@ public virtual void OneTimeTearDown() [TestCase("LMPOP")] [TestCase("BLPOP")] [TestCase("BLMOVE")] + [TestCase("BRPOPLPUSH")] [TestCase("LLEN")] [TestCase("LTRIM")] [TestCase("LRANGE")] @@ -272,6 +275,7 @@ public virtual void OneTimeTearDown() [TestCase("ZREM")] [TestCase("ZCARD")] [TestCase("ZRANGE")] + [TestCase("ZREVRANGEBYLEX")] [TestCase("ZSCORE")] [TestCase("ZMSCORE")] [TestCase("ZPOPMAX")] @@ -388,6 +392,7 @@ void GarnetClientSessionClusterDown(BaseCommand command) [TestCase("LMPOP")] [TestCase("BLPOP")] [TestCase("BLMOVE")] + [TestCase("BRPOPLPUSH")] [TestCase("LLEN")] [TestCase("LTRIM")] [TestCase("LRANGE")] @@ -410,6 +415,7 @@ void GarnetClientSessionClusterDown(BaseCommand command) [TestCase("ZREM")] [TestCase("ZCARD")] [TestCase("ZRANGE")] + [TestCase("ZREVRANGEBYLEX")] [TestCase("ZSCORE")] [TestCase("ZMSCORE")] [TestCase("ZPOPMAX")] @@ -536,6 +542,7 @@ void GarnetClientSessionOK(BaseCommand command) [TestCase("LMPOP")] [TestCase("BLPOP")] [TestCase("BLMOVE")] + [TestCase("BRPOPLPUSH")] [TestCase("LLEN")] [TestCase("LTRIM")] [TestCase("LRANGE")] @@ -558,6 +565,7 @@ void GarnetClientSessionOK(BaseCommand command) [TestCase("ZREM")] [TestCase("ZCARD")] [TestCase("ZRANGE")] + [TestCase("ZREVRANGEBYLEX")] [TestCase("ZSCORE")] [TestCase("ZMSCORE")] [TestCase("ZPOPMAX")] @@ -676,6 +684,7 @@ void GarnetClientSessionCrossslotTest(BaseCommand command) [TestCase("LMPOP")] [TestCase("BLPOP")] [TestCase("BLMOVE")] + [TestCase("BRPOPLPUSH")] [TestCase("LLEN")] [TestCase("LTRIM")] [TestCase("LRANGE")] @@ -698,6 +707,7 @@ void GarnetClientSessionCrossslotTest(BaseCommand command) [TestCase("ZREM")] [TestCase("ZCARD")] [TestCase("ZRANGE")] + [TestCase("ZREVRANGEBYLEX")] [TestCase("ZSCORE")] [TestCase("ZMSCORE")] [TestCase("ZPOPMAX")] @@ -823,6 +833,7 @@ void GarnetClientSessionMOVEDTest(BaseCommand command) [TestCase("LMPOP")] [TestCase("BLPOP")] [TestCase("BLMOVE")] + [TestCase("BRPOPLPUSH")] [TestCase("LLEN")] [TestCase("LTRIM")] [TestCase("LRANGE")] @@ -845,6 +856,7 @@ void GarnetClientSessionMOVEDTest(BaseCommand command) [TestCase("ZREM")] [TestCase("ZCARD")] [TestCase("ZRANGE")] + [TestCase("ZREVRANGEBYLEX")] [TestCase("ZSCORE")] [TestCase("ZMSCORE")] [TestCase("ZPOPMAX")] @@ -987,6 +999,7 @@ void GarnetClientSessionASKTest(BaseCommand command) [TestCase("LMPOP")] [TestCase("BLPOP")] [TestCase("BLMOVE")] + [TestCase("BRPOPLPUSH")] [TestCase("LLEN")] [TestCase("LTRIM")] [TestCase("LRANGE")] @@ -1009,6 +1022,7 @@ void GarnetClientSessionASKTest(BaseCommand command) [TestCase("ZREM")] [TestCase("ZCARD")] [TestCase("ZRANGE")] + [TestCase("ZREVRANGEBYLEX")] [TestCase("ZSCORE")] [TestCase("ZMSCORE")] [TestCase("ZPOPMAX")] diff --git a/test/Garnet.test/Resp/ACL/RespCommandTests.cs b/test/Garnet.test/Resp/ACL/RespCommandTests.cs index 99f4616dc8..eb13069dff 100644 --- a/test/Garnet.test/Resp/ACL/RespCommandTests.cs +++ b/test/Garnet.test/Resp/ACL/RespCommandTests.cs @@ -3625,6 +3625,21 @@ static async Task DoBLMoveAsync(GarnetClient client) } } + [Test] + public async Task BRPopLPushACLsAsync() + { + await CheckCommandsAsync( + "BRPOPLPUSH", + [DoBRPopLPushAsync] + ); + + static async Task DoBRPopLPushAsync(GarnetClient client) + { + string val = await client.ExecuteForStringResultAsync("BRPOPLPUSH", ["foo", "bar", "1"]); + ClassicAssert.IsNull(val); + } + } + [Test] public async Task BLPopACLsAsync() { @@ -5689,6 +5704,21 @@ static async Task DoZRangeAsync(GarnetClient client) } } + [Test] + public async Task ZRevRangeByLexACLsAsync() + { + await CheckCommandsAsync( + "ZREVRANGEBYLEX", + [DoZRevRangeByLexAsync] + ); + + static async Task DoZRevRangeByLexAsync(GarnetClient client) + { + string[] val = await client.ExecuteForStringArrayResultAsync("ZREVRANGEBYLEX", ["key", "10", "20"]); + ClassicAssert.AreEqual(0, val.Length); + } + } + [Test] public async Task ZRangeByScoreACLsAsync() { diff --git a/test/Garnet.test/RespBlockingListTests.cs b/test/Garnet.test/RespBlockingListTests.cs index aee646e9a9..b0a281da61 100644 --- a/test/Garnet.test/RespBlockingListTests.cs +++ b/test/Garnet.test/RespBlockingListTests.cs @@ -231,5 +231,25 @@ public void BlockingClientEventsTests(string blockingCmd) ClassicAssert.IsTrue(blockingTask.IsCompletedSuccessfully); ClassicAssert.IsTrue(releasingTask.IsCompletedSuccessfully); } + + [Test] + public void BasicBlockingListPopPushTest() + { + var srcKey1 = "mykey_src"; + var dstKey1 = "mykey_dst"; + var value1 = "myval"; + + using var lightClientRequest = TestUtils.CreateRequest(); + + var response = lightClientRequest.SendCommand($"LPUSH {srcKey1} {value1}"); + var expectedResponse = ":1\r\n"; + var actualValue = Encoding.ASCII.GetString(response).Substring(0, expectedResponse.Length); + ClassicAssert.AreEqual(expectedResponse, actualValue); + + response = lightClientRequest.SendCommand($"BRPOPLPUSH {srcKey1} {dstKey1} 10"); + expectedResponse = $"${value1.Length}\r\n{value1}\r\n"; + actualValue = Encoding.ASCII.GetString(response).Substring(0, expectedResponse.Length); + ClassicAssert.AreEqual(expectedResponse, actualValue); + } } } \ No newline at end of file diff --git a/test/Garnet.test/RespListTests.cs b/test/Garnet.test/RespListTests.cs index fbf140c13d..539e21658f 100644 --- a/test/Garnet.test/RespListTests.cs +++ b/test/Garnet.test/RespListTests.cs @@ -1591,5 +1591,49 @@ public void LPOSWithInvalidOptions(string items, string find, string expectedInd } #endregion + + #region ZREVRANGEBYLEX + + [Test] + [TestCase("(a", "(a", new string[] { })] + public void CanDoZRevRangeByLex(string max, string min, string[] expected, int offset = 0, int count = -1) + { + using var redis = ConnectionMultiplexer.Connect(TestUtils.GetConfig()); + var db = redis.GetDatabase(0); + + var key = "myzset"; + db.SortedSetAdd(key, "a", 0); + db.SortedSetAdd(key, "b", 0); + db.SortedSetAdd(key, "c", 0); + db.SortedSetAdd(key, "d", 0); + db.SortedSetAdd(key, "e", 0); + db.SortedSetAdd(key, "f", 0); + db.SortedSetAdd(key, "g", 0); + + var result = (string[])db.Execute("ZREVRANGEBYLEX", key, max, min, "LIMIT", offset, count); + CollectionAssert.AreEqual(expected, result); + } + + [Test] + [TestCase("(a", "(a", new string[] { })] + public void CanDoZRevRangeByLexWithoutLimit(string min, string max, string[] expected) + { + using var redis = ConnectionMultiplexer.Connect(TestUtils.GetConfig()); + var db = redis.GetDatabase(0); + + var key = "myzset"; + db.SortedSetAdd(key, "a", 0); + db.SortedSetAdd(key, "b", 0); + db.SortedSetAdd(key, "c", 0); + db.SortedSetAdd(key, "d", 0); + db.SortedSetAdd(key, "e", 0);` + db.SortedSetAdd(key, "f", 0); + db.SortedSetAdd(key, "g", 0); + + var result = (string[])db.Execute("ZREVRANGEBYLEX", key, max, min); + ClassicAssert.AreEqual(expected, result); + } + + #endregion } } \ No newline at end of file diff --git a/website/docs/commands/api-compatibility.md b/website/docs/commands/api-compatibility.md index 89e8ad773a..9ba77278d4 100644 --- a/website/docs/commands/api-compatibility.md +++ b/website/docs/commands/api-compatibility.md @@ -212,7 +212,7 @@ Note that this list is subject to change as we continue to expand our API comman | | BLMPOP | ➖ | | | | [BLPOP](data-structures.md#blpop) | ➕ | | | | [BRPOP](data-structures.md#brpop) | ➕ | | -| | BRPOPLPUSH | ➖ | (Deprecated) | +| | [BRPOPLPUSH](data-structures.md#brpoplpush) | ➕ | (Deprecated) | | | [LINDEX](data-structures.md#lindex) | ➕ | | | | [LINSERT](data-structures.md#linsert) | ➕ | | | | [LLEN](data-structures.md#llen) | ➕ | | @@ -290,7 +290,7 @@ Note that this list is subject to change as we continue to expand our API comman | | ROLE | ➖ | | | | [SAVE](checkpoint.md#save) | ➕ | | | | SHUTDOWN | ➖ | | -| | SLAVEOF | ➖ | (Deprecated) | +| | [SLAVEOF](server.md#slaveof) | ➕ | (Deprecated) | | | SWAPDB | ➖ | | | | SYNC | ➖ | | | | [TIME](server.md#time) | ➕ | | @@ -346,7 +346,7 @@ Note that this list is subject to change as we continue to expand our API comman | | [ZREMRANGEBYRANK](data-structures.md#zremrangebyrank) | ➕ | | | | [ZREMRANGEBYSCORE](data-structures.md#zremrangebyscore) | ➕ | | | | [ZREVRANGE](data-structures.md#zrevrange) | ➕ | (Deprecated) | -| | ZREVRANGEBYLEX | ➖ | (Deprecated) | +| | [ZREVRANGEBYLEX](data-structures.md#zrevrangebylex) | ➕ | (Deprecated) | | | [ZREVRANGEBYSCORE](data-structures.md#zrevrangebyscore) | ➕ | (Deprecated) | | | [ZREVRANK](data-structures.md#zrevrank) | ➕ | | | | [ZSCAN](data-structures.md#zscan) | ➕ | | diff --git a/website/docs/commands/data-structures.md b/website/docs/commands/data-structures.md index bf9a634fd8..1d3e51bc1d 100644 --- a/website/docs/commands/data-structures.md +++ b/website/docs/commands/data-structures.md @@ -232,6 +232,22 @@ BLMOVE is the blocking variant of [LMOVE](#lmove-lmove). When source contains el --- +### BRPOPLPUSH + +#### Syntax + +```bash +BRPOPLPUSH source destination timeout +``` + +The BRPOPLPUSH command removes the last element from the list stored at source, and pushes the element to the list stored at destination. It then returns the element to the caller. + +#### Resp Reply + +Bulk string reply: the element being popped and pushed. + +--- + ### BLPOP #### Syntax @@ -962,6 +978,22 @@ The meaning of min and max are the same of the [ZRANGEBYLEX](#zrangebylex) comma --- +### ZREVRANGEBYLEX + +#### Syntax + +```bash +ZREVRANGEBYLEX key max min [LIMIT offset count] +``` + +The ZREVRANGEBYLEX command returns a range of members in a sorted set, by lexicographical order, ordered from higher to lower strings. + +#### Resp Reply + +Array reply: list of elements in the specified range. + +--- + ### ZREMRANGEBYSCORE #### Syntax diff --git a/website/docs/commands/server.md b/website/docs/commands/server.md index 8983a4a664..f7a9a92c8b 100644 --- a/website/docs/commands/server.md +++ b/website/docs/commands/server.md @@ -211,6 +211,7 @@ One of the following: * Null reply: if the key does not exist. --- + ### REPLICAOF #### Syntax @@ -227,6 +228,22 @@ Simple string reply: OK. --- +### SLAVEOF + +#### Syntax + +```bash +SLAVEOF +``` + +The SLAVEOF command can change the replication settings of a slave on the fly. + +#### Resp Reply + +Simple string reply: OK. + +--- + ### TIME #### Syntax From 2fea26b6ce66c36eee8f44a63697c9f1ec3b8439 Mon Sep 17 00:00:00 2001 From: Vijay-Nirmal Date: Sat, 2 Nov 2024 19:33:40 +0530 Subject: [PATCH 2/5] Fixed build issue --- test/Garnet.test/RespListTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Garnet.test/RespListTests.cs b/test/Garnet.test/RespListTests.cs index 539e21658f..2dbd8d1631 100644 --- a/test/Garnet.test/RespListTests.cs +++ b/test/Garnet.test/RespListTests.cs @@ -1626,7 +1626,7 @@ public void CanDoZRevRangeByLexWithoutLimit(string min, string max, string[] exp db.SortedSetAdd(key, "b", 0); db.SortedSetAdd(key, "c", 0); db.SortedSetAdd(key, "d", 0); - db.SortedSetAdd(key, "e", 0);` + db.SortedSetAdd(key, "e", 0); db.SortedSetAdd(key, "f", 0); db.SortedSetAdd(key, "g", 0); From 9aefc75b70822fa02f930cb2a4fda9cb84aac0be Mon Sep 17 00:00:00 2001 From: Vijay-Nirmal Date: Sat, 23 Nov 2024 22:57:46 +0530 Subject: [PATCH 3/5] Review comment fixes --- .../Objects/SortedSet/SortedSetObject.cs | 2 + .../Objects/SortedSet/SortedSetObjectImpl.cs | 4 + libs/server/Resp/Objects/ListCommands.cs | 82 +++++++++++-------- libs/server/Resp/Objects/SortedSetCommands.cs | 48 +---------- libs/server/Resp/RespServerSession.cs | 4 +- libs/server/Transaction/TxnKeyManager.cs | 2 + test/Garnet.test/RespBlockingListTests.cs | 52 ++++++++++++ test/Garnet.test/RespListTests.cs | 44 ---------- test/Garnet.test/RespSortedSetTests.cs | 40 +++++++++ 9 files changed, 150 insertions(+), 128 deletions(-) diff --git a/libs/server/Objects/SortedSet/SortedSetObject.cs b/libs/server/Objects/SortedSet/SortedSetObject.cs index c8a6f3aaad..4042441287 100644 --- a/libs/server/Objects/SortedSet/SortedSetObject.cs +++ b/libs/server/Objects/SortedSet/SortedSetObject.cs @@ -35,6 +35,7 @@ public enum SortedSetOperation : byte GEOSEARCH, GEOSEARCHSTORE, ZREVRANGE, + ZREVRANGEBYLEX, ZREVRANGEBYSCORE, ZREVRANK, ZREMRANGEBYLEX, @@ -280,6 +281,7 @@ public override unsafe bool Operate(ref ObjectInput input, ref SpanByteAndMemory case SortedSetOperation.ZREVRANGE: SortedSetRange(ref input, ref output); break; + case SortedSetOperation.ZREVRANGEBYLEX: case SortedSetOperation.ZREVRANGEBYSCORE: SortedSetRange(ref input, ref output); break; diff --git a/libs/server/Objects/SortedSet/SortedSetObjectImpl.cs b/libs/server/Objects/SortedSet/SortedSetObjectImpl.cs index 522e4b027b..13ab47eeae 100644 --- a/libs/server/Objects/SortedSet/SortedSetObjectImpl.cs +++ b/libs/server/Objects/SortedSet/SortedSetObjectImpl.cs @@ -445,6 +445,10 @@ private void SortedSetRange(ref ObjectInput input, ref SpanByteAndMemory output) options.ByScore = true; options.Reverse = true; break; + case SortedSetOperation.ZREVRANGEBYLEX: + options.ByLex = true; + options.Reverse = true; + break; } if (count > 2) diff --git a/libs/server/Resp/Objects/ListCommands.cs b/libs/server/Resp/Objects/ListCommands.cs index 4a64c23126..d1c489fe6a 100644 --- a/libs/server/Resp/Objects/ListCommands.cs +++ b/libs/server/Resp/Objects/ListCommands.cs @@ -3,6 +3,7 @@ using System; using System.Text; +using System.Threading; using Garnet.common; using Tsavorite.core; @@ -324,21 +325,60 @@ private bool ListBlockingPop(RespCommand command) return true; } - private unsafe bool ListBlockingMove(RespCommand command) + private unsafe bool ListBlockingMove() { if (parseState.Count != 5) { - return AbortWithWrongNumberOfArguments(command.ToString()); + return AbortWithWrongNumberOfArguments(nameof(RespCommand.BLMOVE)); } - var cmdArgs = new ArgSlice[] { default, default, default }; + var srcKey = parseState.GetArgSliceByRef(0); + var dstKey = parseState.GetArgSliceByRef(1); + var srcDir = parseState.GetArgSliceByRef(2); + var dstDir = parseState.GetArgSliceByRef(3); + + if (!parseState.TryGetDouble(4, out var timeout)) + { + while (!RespWriteUtils.WriteError(CmdStrings.RESP_ERR_TIMEOUT_NOT_VALID_FLOAT, ref dcurr, dend)) + SendAndReset(); + return true; + } + + return ListBlockingMove(srcKey, dstKey, srcDir, dstDir, timeout); + } + + /// + /// BRPOPLPUSH + /// + /// + private bool ListBlockingPopPush() + { + if (parseState.Count != 3) + { + return AbortWithWrongNumberOfArguments(nameof(RespCommand.BRPOPLPUSH)); + } var srcKey = parseState.GetArgSliceByRef(0); + var dstKey = parseState.GetArgSliceByRef(1); + var rightOption = ArgSlice.FromPinnedSpan(CmdStrings.RIGHT); + var leftOption = ArgSlice.FromPinnedSpan(CmdStrings.LEFT); + + if (!parseState.TryGetDouble(2, out var timeout)) + { + while (!RespWriteUtils.WriteError(CmdStrings.RESP_ERR_TIMEOUT_NOT_VALID_FLOAT, ref dcurr, dend)) + SendAndReset(); + return true; + } + + return ListBlockingMove(srcKey, dstKey, rightOption, leftOption, timeout); + } + + private bool ListBlockingMove(ArgSlice srcKey, ArgSlice dstKey, ArgSlice srcDir, ArgSlice dstDir, double timeout) + { + var cmdArgs = new ArgSlice[] { default, default, default }; // Read destination key - cmdArgs[0] = parseState.GetArgSliceByRef(1); - var srcDir = parseState.GetArgSliceByRef(2); - var dstDir = parseState.GetArgSliceByRef(3); + cmdArgs[0] = dstKey; var sourceDirection = GetOperationDirection(srcDir); var destinationDirection = GetOperationDirection(dstDir); @@ -353,17 +393,10 @@ private unsafe bool ListBlockingMove(RespCommand command) cmdArgs[1] = new ArgSlice(pSrcDir, 1); cmdArgs[2] = new ArgSlice(pDstDir, 1); - if (!parseState.TryGetDouble(4, out var timeout)) - { - while (!RespWriteUtils.WriteError(CmdStrings.RESP_ERR_TIMEOUT_NOT_VALID_FLOAT, ref dcurr, dend)) - SendAndReset(); - return true; - } - if (storeWrapper.itemBroker == null) throw new GarnetException("Object store is disabled"); - var result = storeWrapper.itemBroker.MoveCollectionItemAsync(command, srcKey.ToArray(), this, timeout, + var result = storeWrapper.itemBroker.MoveCollectionItemAsync(RespCommand.BLMOVE, srcKey.ToArray(), this, timeout, cmdArgs).Result; if (!result.Found) @@ -380,27 +413,6 @@ private unsafe bool ListBlockingMove(RespCommand command) return true; } - /// - /// BRPOPLPUSH - /// - /// - private bool ListBlockingPopPush() - { - if (parseState.Count != 3) - { - return AbortWithWrongNumberOfArguments(nameof(RespCommand.BRPOPLPUSH)); - } - - var srcKey = parseState.GetArgSliceByRef(0); - var dstKey = parseState.GetArgSliceByRef(1); - var rightOption = ArgSlice.FromPinnedSpan(CmdStrings.RIGHT); - var leftOption = ArgSlice.FromPinnedSpan(CmdStrings.LEFT); - var timeout = parseState.GetArgSliceByRef(2); - parseState.InitializeWithArguments(srcKey, dstKey, rightOption, leftOption, timeout); - - return ListBlockingMove(RespCommand.BLMOVE); - } - /// /// LLEN key /// Gets the length of the list stored at key. diff --git a/libs/server/Resp/Objects/SortedSetCommands.cs b/libs/server/Resp/Objects/SortedSetCommands.cs index 5a1cf2a881..09fc435351 100644 --- a/libs/server/Resp/Objects/SortedSetCommands.cs +++ b/libs/server/Resp/Objects/SortedSetCommands.cs @@ -164,6 +164,7 @@ private unsafe bool SortedSetRange(RespCommand command, ref TGarnetA RespCommand.ZRANGE => SortedSetOperation.ZRANGE, RespCommand.ZREVRANGE => SortedSetOperation.ZREVRANGE, RespCommand.ZRANGEBYSCORE => SortedSetOperation.ZRANGEBYSCORE, + RespCommand.ZREVRANGEBYLEX => SortedSetOperation.ZREVRANGEBYLEX, RespCommand.ZREVRANGEBYSCORE => SortedSetOperation.ZREVRANGEBYSCORE, _ => throw new Exception($"Unexpected {nameof(SortedSetOperation)}: {command}") }; @@ -193,53 +194,6 @@ private unsafe bool SortedSetRange(RespCommand command, ref TGarnetA return true; } - /// - /// ZREVRANGEBYLEX - /// - /// - /// - /// - private bool ListRangeByLength(ref TGarnetApi storageApi) - where TGarnetApi : IGarnetApi - { - if (parseState.Count is not (3 or 6)) - { - return AbortWithWrongNumberOfArguments(nameof(RespCommand.ZREVRANGEBYLEX)); - } - - - var sbKey = parseState.GetArgSliceByRef(0); - var start = parseState.GetArgSliceByRef(1); - var stop = parseState.GetArgSliceByRef(2); - var byLenOption = ArgSlice.FromPinnedSpan(CmdStrings.BYLEX); - var revOption = ArgSlice.FromPinnedSpan(CmdStrings.REV); - - // ZREVRANGEBYLEX key start stop - if (parseState.Count == 3) - { - parseState.InitializeWithArguments(sbKey, start, stop, byLenOption, revOption); - - return SortedSetRange(RespCommand.ZRANGE, ref storageApi); - } - - // ZREVRANGEBYLEX key start stop [LIMIT offset count] - var limit = parseState.GetArgSliceByRef(3); - var offset = parseState.GetArgSliceByRef(4); - var count = parseState.GetArgSliceByRef(5); - - parseState.Initialize(8); - parseState.SetArgument(0, sbKey); - parseState.SetArgument(1, start); - parseState.SetArgument(2, stop); - parseState.SetArgument(3, byLenOption); - parseState.SetArgument(4, revOption); - parseState.SetArgument(5, limit); - parseState.SetArgument(6, offset); - parseState.SetArgument(7, count); - - return SortedSetRange(RespCommand.ZRANGE, ref storageApi); - } - /// /// Returns the score of member in the sorted set at key. /// If member does not exist in the sorted set, or key does not exist, nil is returned. diff --git a/libs/server/Resp/RespServerSession.cs b/libs/server/Resp/RespServerSession.cs index dc022293a4..0cd3fee153 100644 --- a/libs/server/Resp/RespServerSession.cs +++ b/libs/server/Resp/RespServerSession.cs @@ -609,6 +609,7 @@ private bool ProcessArrayCommands(RespCommand cmd, ref TGarnetApi st RespCommand.ZDIFF => SortedSetDifference(ref storageApi), RespCommand.ZDIFFSTORE => SortedSetDifferenceStore(ref storageApi), RespCommand.ZREVRANGE => SortedSetRange(cmd, ref storageApi), + RespCommand.ZREVRANGEBYLEX => SortedSetRange(cmd, ref storageApi), RespCommand.ZREVRANGEBYSCORE => SortedSetRange(cmd, ref storageApi), RespCommand.ZSCAN => ObjectScan(GarnetObjectType.SortedSet, ref storageApi), //SortedSet for Geo Commands @@ -640,7 +641,6 @@ private bool ProcessArrayCommands(RespCommand cmd, ref TGarnetApi st RespCommand.LLEN => ListLength(ref storageApi), RespCommand.LTRIM => ListTrim(ref storageApi), RespCommand.LRANGE => ListRange(ref storageApi), - RespCommand.ZREVRANGEBYLEX => ListRangeByLength(ref storageApi), RespCommand.LINDEX => ListIndex(ref storageApi), RespCommand.LINSERT => ListInsert(ref storageApi), RespCommand.LREM => ListRemove(ref storageApi), @@ -650,7 +650,7 @@ private bool ProcessArrayCommands(RespCommand cmd, ref TGarnetApi st RespCommand.LSET => ListSet(ref storageApi), RespCommand.BLPOP => ListBlockingPop(cmd), RespCommand.BRPOP => ListBlockingPop(cmd), - RespCommand.BLMOVE => ListBlockingMove(cmd), + RespCommand.BLMOVE => ListBlockingMove(), RespCommand.BRPOPLPUSH => ListBlockingPopPush(), // Hash Commands RespCommand.HSET => HashSet(cmd, ref storageApi), diff --git a/libs/server/Transaction/TxnKeyManager.cs b/libs/server/Transaction/TxnKeyManager.cs index 54f6cff259..39b262b0a7 100644 --- a/libs/server/Transaction/TxnKeyManager.cs +++ b/libs/server/Transaction/TxnKeyManager.cs @@ -105,6 +105,7 @@ internal int GetKeys(RespCommand command, int inputCount, out ReadOnlySpan RespCommand.GEOPOS => SortedSetObjectKeys(SortedSetOperation.GEOPOS, inputCount), RespCommand.GEOSEARCH => SortedSetObjectKeys(SortedSetOperation.GEOSEARCH, inputCount), RespCommand.ZREVRANGE => SortedSetObjectKeys(SortedSetOperation.ZREVRANGE, inputCount), + RespCommand.ZREVRANGEBYLEX => SortedSetObjectKeys(SortedSetOperation.ZREVRANGEBYLEX, inputCount), RespCommand.ZREVRANGEBYSCORE => SortedSetObjectKeys(SortedSetOperation.ZREVRANGEBYSCORE, inputCount), RespCommand.LINDEX => ListObjectKeys((byte)ListOperation.LINDEX), RespCommand.LINSERT => ListObjectKeys((byte)ListOperation.LINSERT), @@ -219,6 +220,7 @@ private int SortedSetObjectKeys(SortedSetOperation command, int inputCount) SortedSetOperation.GEOPOS => SingleKey(1, true, LockType.Shared), SortedSetOperation.GEOSEARCH => SingleKey(1, true, LockType.Shared), SortedSetOperation.ZREVRANGE => SingleKey(1, true, LockType.Shared), + SortedSetOperation.ZREVRANGEBYLEX => SingleKey(1, true, LockType.Shared), SortedSetOperation.ZREVRANGEBYSCORE => SingleKey(1, true, LockType.Shared), _ => -1 }; diff --git a/test/Garnet.test/RespBlockingListTests.cs b/test/Garnet.test/RespBlockingListTests.cs index b0a281da61..ca2eee001e 100644 --- a/test/Garnet.test/RespBlockingListTests.cs +++ b/test/Garnet.test/RespBlockingListTests.cs @@ -238,6 +238,9 @@ public void BasicBlockingListPopPushTest() var srcKey1 = "mykey_src"; var dstKey1 = "mykey_dst"; var value1 = "myval"; + var srcKey2 = "mykey2_src"; + var dstKey2 = "mykey2_dst"; + var value2 = "myval2"; using var lightClientRequest = TestUtils.CreateRequest(); @@ -250,6 +253,55 @@ public void BasicBlockingListPopPushTest() expectedResponse = $"${value1.Length}\r\n{value1}\r\n"; actualValue = Encoding.ASCII.GetString(response).Substring(0, expectedResponse.Length); ClassicAssert.AreEqual(expectedResponse, actualValue); + + response = lightClientRequest.SendCommand($"LRANGE {srcKey1} 0 -1"); + expectedResponse = $"*0\r\n"; + actualValue = Encoding.ASCII.GetString(response).Substring(0, expectedResponse.Length); + ClassicAssert.AreEqual(expectedResponse, actualValue); + + response = lightClientRequest.SendCommand($"LRANGE {dstKey1} 0 -1", 2); + expectedResponse = $"*1\r\n${value1.Length}\r\n{value1}\r\n"; + actualValue = Encoding.ASCII.GetString(response).Substring(0, expectedResponse.Length); + ClassicAssert.AreEqual(expectedResponse, actualValue); + + var blockingTask = taskFactory.StartNew(() => + { + using var lcr = TestUtils.CreateRequest(); + var btResponse = lcr.SendCommand($"BRPOPLPUSH {srcKey2} {dstKey2} 0"); + var btExpectedResponse = $"${value2.Length}\r\n{value2}\r\n"; + var btActualValue = Encoding.ASCII.GetString(btResponse).Substring(0, btExpectedResponse.Length); + ClassicAssert.AreEqual(btExpectedResponse, btActualValue); + }); + + var releasingTask = taskFactory.StartNew(() => + { + using var lcr = TestUtils.CreateRequest(); + Task.Delay(TimeSpan.FromSeconds(2)).Wait(); + return lcr.SendCommand($"LPUSH {srcKey2} {value2}"); + }); + + var timeout = TimeSpan.FromSeconds(5); + try + { + Task.WaitAll([blockingTask, releasingTask], timeout); + } + catch (AggregateException) + { + Assert.Fail(); + } + + ClassicAssert.IsTrue(blockingTask.IsCompletedSuccessfully); + ClassicAssert.IsTrue(releasingTask.IsCompletedSuccessfully); + + response = lightClientRequest.SendCommand($"LRANGE {srcKey2} 0 -1"); + expectedResponse = $"*0\r\n"; + actualValue = Encoding.ASCII.GetString(response).Substring(0, expectedResponse.Length); + ClassicAssert.AreEqual(expectedResponse, actualValue); + + response = lightClientRequest.SendCommand($"LRANGE {dstKey2} 0 -1", 2); + expectedResponse = $"*1\r\n${value2.Length}\r\n{value2}\r\n"; + actualValue = Encoding.ASCII.GetString(response).Substring(0, expectedResponse.Length); + ClassicAssert.AreEqual(expectedResponse, actualValue); } } } \ No newline at end of file diff --git a/test/Garnet.test/RespListTests.cs b/test/Garnet.test/RespListTests.cs index 2dbd8d1631..fbf140c13d 100644 --- a/test/Garnet.test/RespListTests.cs +++ b/test/Garnet.test/RespListTests.cs @@ -1591,49 +1591,5 @@ public void LPOSWithInvalidOptions(string items, string find, string expectedInd } #endregion - - #region ZREVRANGEBYLEX - - [Test] - [TestCase("(a", "(a", new string[] { })] - public void CanDoZRevRangeByLex(string max, string min, string[] expected, int offset = 0, int count = -1) - { - using var redis = ConnectionMultiplexer.Connect(TestUtils.GetConfig()); - var db = redis.GetDatabase(0); - - var key = "myzset"; - db.SortedSetAdd(key, "a", 0); - db.SortedSetAdd(key, "b", 0); - db.SortedSetAdd(key, "c", 0); - db.SortedSetAdd(key, "d", 0); - db.SortedSetAdd(key, "e", 0); - db.SortedSetAdd(key, "f", 0); - db.SortedSetAdd(key, "g", 0); - - var result = (string[])db.Execute("ZREVRANGEBYLEX", key, max, min, "LIMIT", offset, count); - CollectionAssert.AreEqual(expected, result); - } - - [Test] - [TestCase("(a", "(a", new string[] { })] - public void CanDoZRevRangeByLexWithoutLimit(string min, string max, string[] expected) - { - using var redis = ConnectionMultiplexer.Connect(TestUtils.GetConfig()); - var db = redis.GetDatabase(0); - - var key = "myzset"; - db.SortedSetAdd(key, "a", 0); - db.SortedSetAdd(key, "b", 0); - db.SortedSetAdd(key, "c", 0); - db.SortedSetAdd(key, "d", 0); - db.SortedSetAdd(key, "e", 0); - db.SortedSetAdd(key, "f", 0); - db.SortedSetAdd(key, "g", 0); - - var result = (string[])db.Execute("ZREVRANGEBYLEX", key, max, min); - ClassicAssert.AreEqual(expected, result); - } - - #endregion } } \ No newline at end of file diff --git a/test/Garnet.test/RespSortedSetTests.cs b/test/Garnet.test/RespSortedSetTests.cs index 52889b6f66..cb70206712 100644 --- a/test/Garnet.test/RespSortedSetTests.cs +++ b/test/Garnet.test/RespSortedSetTests.cs @@ -1086,6 +1086,46 @@ public void CheckSortedSetDifferenceStoreWithNoMatchSE() ClassicAssert.AreEqual(0, actualMembers.Length); } + [Test] + [TestCase("(a", "(a", new string[] { })] + public void CanDoZRevRangeByLex(string max, string min, string[] expected, int offset = 0, int count = -1) + { + using var redis = ConnectionMultiplexer.Connect(TestUtils.GetConfig()); + var db = redis.GetDatabase(0); + + var key = "myzset"; + db.SortedSetAdd(key, "a", 0); + db.SortedSetAdd(key, "b", 0); + db.SortedSetAdd(key, "c", 0); + db.SortedSetAdd(key, "d", 0); + db.SortedSetAdd(key, "e", 0); + db.SortedSetAdd(key, "f", 0); + db.SortedSetAdd(key, "g", 0); + + var result = (string[])db.Execute("ZREVRANGEBYLEX", key, max, min, "LIMIT", offset, count); + CollectionAssert.AreEqual(expected, result); + } + + [Test] + [TestCase("(a", "(a", new string[] { })] + public void CanDoZRevRangeByLexWithoutLimit(string min, string max, string[] expected) + { + using var redis = ConnectionMultiplexer.Connect(TestUtils.GetConfig()); + var db = redis.GetDatabase(0); + + var key = "myzset"; + db.SortedSetAdd(key, "a", 0); + db.SortedSetAdd(key, "b", 0); + db.SortedSetAdd(key, "c", 0); + db.SortedSetAdd(key, "d", 0); + db.SortedSetAdd(key, "e", 0); + db.SortedSetAdd(key, "f", 0); + db.SortedSetAdd(key, "g", 0); + + var result = (string[])db.Execute("ZREVRANGEBYLEX", key, max, min); + ClassicAssert.AreEqual(expected, result); + } + #endregion #region LightClientTests From fe6af90c1d4a987d20d7d7940db99b66bed1502c Mon Sep 17 00:00:00 2001 From: Vijay Nirmal Date: Sat, 30 Nov 2024 10:53:23 +0530 Subject: [PATCH 4/5] Add link in doc for SUBSTR --- website/docs/commands/api-compatibility.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/commands/api-compatibility.md b/website/docs/commands/api-compatibility.md index 9de4584006..6ba44e2bc6 100644 --- a/website/docs/commands/api-compatibility.md +++ b/website/docs/commands/api-compatibility.md @@ -397,7 +397,7 @@ Note that this list is subject to change as we continue to expand our API comman | | [SETNX](raw-string.md#setnx) | ➕ | | | | [SETRANGE](raw-string.md#setrange) | ➕ | | | | [STRLEN](raw-string.md#strlen) | ➕ | | -| | SUBSTR | ➖ | (Deprecated) | +| | [SUBSTR](raw-string.md#substr) | ➖ | (Deprecated) | | **TRANSACTIONS** | [DISCARD](transactions.md#discard) | ➕ | | | | [EXEC](transactions.md#exec) | ➕ | | | | [MULTI](transactions.md#multi) | ➕ | | From 6a4d3cdeab0cb5140b600ee16a300918f3964d04 Mon Sep 17 00:00:00 2001 From: Tal Zaccai Date: Wed, 4 Dec 2024 12:24:58 -0800 Subject: [PATCH 5/5] removing unnecessary using --- libs/server/Resp/Objects/ListCommands.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/libs/server/Resp/Objects/ListCommands.cs b/libs/server/Resp/Objects/ListCommands.cs index d1c489fe6a..9163d852d4 100644 --- a/libs/server/Resp/Objects/ListCommands.cs +++ b/libs/server/Resp/Objects/ListCommands.cs @@ -3,7 +3,6 @@ using System; using System.Text; -using System.Threading; using Garnet.common; using Tsavorite.core;